From 54ab3ebfadf8cb258561a4641249da8c6469dc0f Mon Sep 17 00:00:00 2001 From: David Llewellyn-Jones Date: Mon, 23 Jul 2018 16:18:21 +0100 Subject: [PATCH 1/1] Add graph animation; support for line graphs There aren't any line graphs yet, but there will be in the future! --- qml/pages/Stats.qml | 62 +++++----------------- src/graph.cpp | 95 ++++++++++++++++++++++------------ src/graph.h | 18 ++++--- src/journeymodel.h | 2 +- src/stats.cpp | 16 ++++-- src/stats.h | 9 ++-- src/statshourcongestion.cpp | 4 +- src/statshourjourneys.cpp | 4 +- src/statsmodel.cpp | 13 +++-- src/statsmodel.h | 6 ++- src/statsweekdayave.cpp | 4 +- src/statsweekdaycongestion.cpp | 4 +- 12 files changed, 127 insertions(+), 110 deletions(-) diff --git a/qml/pages/Stats.qml b/qml/pages/Stats.qml index 8e13f97..235ca1c 100644 --- a/qml/pages/Stats.qml +++ b/qml/pages/Stats.qml @@ -8,6 +8,7 @@ Page { // The effective value will be restricted by ApplicationWindow.allowedOrientations allowedOrientations: Orientation.All + property int showingindex: 0 SilicaFlickable { id: statsView @@ -89,10 +90,10 @@ Page { } model: statsmodel - delegate: Rectangle { + delegate: Item { + id: delegateItem width: graphsView.itemWidth height: graphsView.height - color: "transparent" SectionHeader { id: sectionHeaderItem @@ -105,66 +106,29 @@ Page { anchors.top: sectionHeaderItem.bottom height: (isPortrait ? (statsPage.height / 2.0) - Theme.paddingLarge : statsPage.height - Theme.paddingLarge - headerItem.height) - sectionHeaderItem.height anchors.left: parent.left - model: values + bardata: barvalues + //linedata: barvalues labelsx: labels //labelsy: ["0%", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"] unitsy: units primary: Theme.primaryColor secondary: Theme.highlightColor - highlight: Theme.highlightColor + highlight: Theme.secondaryColor miny: minval maxy: maxval stepy: step gap: 0.1 fontsize: Theme.fontSizeExtraSmall - /* - PropertyAnimation on animate { - id: animx - duration: 2000 - easing.type: Easing.InOutExpo - from: 0.0 - to: 1.0 - } - */ - } - - } - } + animate: (graphsView.currentItem === delegateItem) ? 1.0 : 0.0 - /* - Column { - id: graphsColumn - spacing: Theme.paddingLarge - width: isPortrait ? parent.width : parent.width * 0.5 - y: (isPortrait ? (statsPage.height / 2.0) : statsColumn.y) - anchors.left: isPortrait ? statsColumn.left : statsColumn.right - anchors.leftMargin: Theme.horizontalPageMargin - - - Graph { - id: graph - width: parent.width - 2 * Theme.horizontalPageMargin - height: isPortrait ? (statsPage.height / 2.0) - Theme.paddingLarge : statsPage.height - Theme.paddingLarge - headerItem.height - model: currentStatus.getGraphData() - labelsx: ["M", "T", "W", "Th", "F", "S", "Su", "A", "B", "C"] - //labelsy: ["0%", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"] - unitsy: "%" - primary: Theme.primaryColor - secondary: Theme.highlightColor - highlight: Theme.highlightColor - miny: 0.0 - maxy: 1.0 - stepy: 0.1 - gap: 0.1 - fontsize: Theme.fontSizeExtraSmall - PropertyAnimation on animate { - duration: 2000 - easing.type: Easing.InOutExpo - from: 0.0 - to: 1.0 + Behavior on animate { + NumberAnimation { + easing.type: Easing.OutExpo + duration: 2000 + } + } } } } - */ } } diff --git a/src/graph.cpp b/src/graph.cpp index 338ab5e..d4a60d2 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -11,9 +11,8 @@ Graph::Graph(QQuickItem *parent) , highlight(QColor("#0000ff")) , axisThickness(0.02) , bars(7) + , points(7) , gap(0.1) - , minmodel(0.0f) - , maxmodel(1.0f) , miny(0.0f) , maxy(1.0f) , stepy(0.1f) @@ -21,7 +20,8 @@ Graph::Graph(QQuickItem *parent) , fontsize(24.0) , animate(1.0) { - model.clear(); + bardata.clear(); + linedata.clear(); labelsx.clear(); labelsy.clear(); } @@ -32,31 +32,46 @@ void Graph::paint(QPainter *painter) { float axiswidth; int count; int barfullwidth; + int pointfullwidth; + int labelfullwidth; float labelygap; float labelxgap; - int labels; + int labelsycount; + int labelsxcount; float labelheight; + float steps; QRectF size = contentsBoundingRect(); - bars = model.length(); + bars = bardata.length(); + points = linedata.length(); + labelsxcount = labelsx.length(); + + steps = labelsx.length(); + if (steps == 0) { + steps = bars; + } + labelygap = size.height() * 0.1l; labelxgap = size.width() * 0.11; axiswidth = qMin(size.width() * axisThickness, size.height() * axisThickness); barfullwidth = ((size.width() - labelxgap - axiswidth) / bars); barwidth = barfullwidth * (1.0 - gap); - labels = labelsy.length() > 0 ? labelsy.length() : 1 + (maxy - miny) / stepy; - labelheight = (size.height() - labelygap - axiswidth) / (labels - 0.5); + pointfullwidth = ((size.width() - labelxgap - axiswidth) / points); + labelsycount = labelsy.length() > 0 ? labelsy.length() : 1 + (maxy - miny) / stepy; + labelheight = (size.height() - labelygap - axiswidth) / (labelsycount - 0.5); + labelfullwidth = ((size.width() - labelxgap - axiswidth) / labelsxcount); QBrush axiscolour(primary); QBrush barcolour(secondary); + QBrush linecolour(highlight); painter->setBrush(axiscolour); painter->setPen(Qt::NoPen); painter->setRenderHint(QPainter::Antialiasing); painter->setOpacity(1.0); - const QPointF points[6] = { + const QPointF axis[6] = { QPointF(labelxgap, (labelheight / 2.0)), QPointF(labelxgap, size.height() - labelygap), QPointF(size.width(), size.height() - labelygap), @@ -64,13 +79,13 @@ void Graph::paint(QPainter *painter) { QPointF(labelxgap + axiswidth, size.height() - labelygap - axiswidth), QPointF(labelxgap + axiswidth, (labelheight / 2.0)) }; - painter->drawPolygon(points, 6); + painter->drawPolygon(axis, 6); if (bars > 0) { painter->setBrush(barcolour); count = 0; - for (QList::const_iterator iter = model.constBegin(); (count < bars) && (iter != model.constEnd()); iter++) { + for (QList::const_iterator iter = bardata.constBegin(); (count < bars) && (iter != bardata.constEnd()); iter++) { float barheight = ((*iter) - miny) / (maxy - miny); barheight = qBound(0.0f, barheight, 1.0f) * (size.height() - labelygap - axiswidth - (labelheight / 2.0)); barheight *= animate; @@ -80,22 +95,42 @@ void Graph::paint(QPainter *painter) { } } + if (points > 0) { + QPen pen = QPen(linecolour, 8.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + + QPointF point[points]; + count = 0; + for (QList::const_iterator iter = linedata.constBegin(); (count < points) && (iter != linedata.constEnd()); iter++) { + float lineheight = ((*iter) - miny) / (maxy - miny); + lineheight = qBound(0.0f, lineheight, 1.0f) * (size.height() - labelygap - axiswidth - (labelheight / 2.0)); + point[count] = QPointF(labelxgap + axiswidth + (pointfullwidth * count) + (pointfullwidth / 2.0), size.height() - labelygap - lineheight - axiswidth); + count++; + } + painter->setClipRect(0, 0, labelxgap + axiswidth + (pointfullwidth / 2.0) - 8.0 + (size.width() - labelxgap - axiswidth - pointfullwidth + 16.0) * animate, size.height(), Qt::ReplaceClip); + painter->setClipping(true); + painter->drawPolyline(point, points); + } + + painter->setClipping(false); + QFont font = painter->font(); font.setPixelSize(fontsize); painter->setFont(font); painter->setPen(primary); painter->setBrush(Qt::NoBrush); - if ((bars > 0) && (labelsx.length() == bars)) { + if (labelsxcount > 0) { for (count = 0; count < bars; count++) { - QRectF rect(labelxgap + axiswidth + (barfullwidth * count), size.height() - labelygap + 8.0, barfullwidth, labelygap - 8.0); + QRectF rect(labelxgap + axiswidth + (labelfullwidth * count), size.height() - labelygap + 8.0, labelfullwidth, labelygap - 8.0); painter->drawText(rect, Qt::AlignHCenter | Qt::NoClip | Qt::TextSingleLine, labelsx[count]); //painter->drawRect(rect); } } if (labelsy.length() > 0) { - for (count = 0; count < labels; count++) { + for (count = 0; count < labelsycount; count++) { QRectF rect(0, size.height() - (labelheight * (count + 0.5)) - labelygap, labelxgap - 8.0, labelheight); painter->drawText(rect, Qt::AlignVCenter | Qt::AlignRight | Qt::NoClip | Qt::TextSingleLine, labelsy[count]); //painter->drawRect(rect); @@ -103,7 +138,7 @@ void Graph::paint(QPainter *painter) { } else { float labelvalue = miny; - for (count = 0; count < labels; count++) { + for (count = 0; count < labelsycount; count++) { QString labeltext = unitsy == "%" ? locale.toString(labelvalue * 100, 'f', 0) + "%" : locale.toString(labelvalue, 'g', 2) + unitsy; QRectF rect(0, size.height() - (labelheight * (count + 0.5)) - labelygap, labelxgap - 8.0, labelheight); painter->drawText(rect, Qt::AlignVCenter | Qt::AlignRight | Qt::NoClip | Qt::TextSingleLine, labeltext); @@ -114,26 +149,22 @@ void Graph::paint(QPainter *painter) { } } -QList Graph::getModel() const { - return model; +QList Graph::getBarData() const { + return bardata; } -void Graph::setModel(QList value) { - model = value; - emit modelChanged(); - - if (model.length() > 0) { - minmodel = model[0]; - maxmodel = minmodel; - foreach (int y, model) { - if (y < minmodel) { - minmodel = y; - } - if (y > maxmodel) { - maxmodel = y; - } - } - } +void Graph::setBarData(QList value) { + bardata = value; + emit barDataChanged(); +} + +QList Graph::getLineData() const { + return linedata; +} + +void Graph::setLineData(QList value) { + linedata = value; + emit lineDataChanged(); } QStringList Graph::getLabelsx() const { diff --git a/src/graph.h b/src/graph.h index 269f37d..704eb0c 100644 --- a/src/graph.h +++ b/src/graph.h @@ -7,7 +7,8 @@ class Graph : public QQuickPaintedItem { Q_OBJECT - Q_PROPERTY(QList model READ getModel WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(QList bardata READ getBarData WRITE setBarData NOTIFY barDataChanged) + Q_PROPERTY(QList linedata READ getLineData WRITE setLineData NOTIFY lineDataChanged) Q_PROPERTY(QStringList labelsx READ getLabelsx WRITE setLabelsx NOTIFY labelsxChanged) Q_PROPERTY(QStringList labelsy READ getLabelsy WRITE setLabelsy NOTIFY labelsyChanged) Q_PROPERTY(QColor primary READ getPrimary WRITE setPrimary NOTIFY primaryChanged) @@ -27,8 +28,10 @@ public: Graph(QQuickItem *parent = 0); void paint(QPainter *painter); - QList getModel() const; - void setModel(QList value); + QList getBarData() const; + void setBarData(QList value); + QList getLineData() const; + void setLineData(QList value); QStringList getLabelsx() const; void setLabelsx(QStringList value); QStringList getLabelsy() const; @@ -55,7 +58,8 @@ public: void setAnimate(float value); private: - QList model; + QList bardata; + QList linedata; QStringList labelsx; QStringList labelsy; QColor primary; @@ -63,9 +67,8 @@ public: QColor highlight; float axisThickness; int bars; + int points; float gap; - float minmodel; - float maxmodel; float miny; float maxy; float stepy; @@ -74,7 +77,8 @@ public: float animate; signals: - void modelChanged(); + void barDataChanged(); + void lineDataChanged(); void labelsxChanged(); void labelsyChanged(); void primaryChanged(QColor primary); diff --git a/src/journeymodel.h b/src/journeymodel.h index ccb4ffc..639c6d4 100644 --- a/src/journeymodel.h +++ b/src/journeymodel.h @@ -15,7 +15,7 @@ public: StartRole = Qt::UserRole + 1, DurationRole, OvertookRole, - OvertakenByRole + OvertakenByRole, }; QHash roleNames() const; diff --git a/src/stats.cpp b/src/stats.cpp index 1a80ab2..d8b92a9 100644 --- a/src/stats.cpp +++ b/src/stats.cpp @@ -22,8 +22,12 @@ QStringList Stats::getLabels() const { return labels; } -QList Stats::getValues() const { - return values; +QList Stats::getBarValues() const { + return barvalues; +} + +QList Stats::getLineValues() const { + return linevalues; } QString Stats::getUnits() const { @@ -50,8 +54,12 @@ void Stats::setLabels(QStringList &value) { labels = value; } -void Stats::setValues(QList &value) { - values = value; +void Stats::setBarValues(QList &value) { + barvalues = value; +} + +void Stats::setLineValues(QList &value) { + linevalues = value; } void Stats::setUnits(QString &value) { diff --git a/src/stats.h b/src/stats.h index 73accfd..e156787 100644 --- a/src/stats.h +++ b/src/stats.h @@ -12,7 +12,8 @@ public: virtual QString getTitle() const; virtual QStringList getLabels() const; - virtual QList getValues() const; + virtual QList getBarValues() const; + virtual QList getLineValues() const; virtual QString getUnits() const; virtual float getMinVal() const; virtual float getMaxVal() const; @@ -20,7 +21,8 @@ public: virtual void setTitle(QString &value); virtual void setLabels(QStringList &value); - virtual void setValues(QList &value); + virtual void setBarValues(QList &value); + virtual void setLineValues(QList &value); virtual void setUnits(QString &value); virtual void setMinVal(float value); virtual void setMaxVal(float value); @@ -29,7 +31,8 @@ public: protected: QString title; QStringList labels; - QList values; + QList barvalues; + QList linevalues; QString units; float minval; float maxval; diff --git a/src/statshourcongestion.cpp b/src/statshourcongestion.cpp index a98230e..1d049e7 100644 --- a/src/statshourcongestion.cpp +++ b/src/statshourcongestion.cpp @@ -23,7 +23,7 @@ void StatsHourCongestion::update() { int pos; qDebug() << "Calculating values"; - values.clear(); + barvalues.clear(); for (pos = 0; pos < 24; pos++) { passed[pos] = 0.0; @@ -58,7 +58,7 @@ void StatsHourCongestion::update() { if (result > maxval) { maxval = result; } - values << result; + barvalues << result; } step = (maxval > 5.0) ? qRound(maxval / 5.0) : (maxval / 5.0); diff --git a/src/statshourjourneys.cpp b/src/statshourjourneys.cpp index f8616cb..270d436 100644 --- a/src/statshourjourneys.cpp +++ b/src/statshourjourneys.cpp @@ -23,7 +23,7 @@ void StatsHourJourneys::update() { int pos; qDebug() << "Calculating values"; - values.clear(); + barvalues.clear(); for (pos = 0; pos < 24; pos++) { minsperhour[pos] = 0u; @@ -53,7 +53,7 @@ void StatsHourJourneys::update() { if (totalmins > 0) { result = ((double)minsperhour[pos] / (double)totalmins); } - values << result; + barvalues << result; } qDebug() << "Calculated values"; diff --git a/src/statsmodel.cpp b/src/statsmodel.cpp index b6a2ef8..5bc8155 100644 --- a/src/statsmodel.cpp +++ b/src/statsmodel.cpp @@ -1,8 +1,11 @@ #include "statsmodel.h" -StatsModel::StatsModel(QObject *parent) : QAbstractListModel(parent) { +StatsModel::StatsModel(QObject *parent) : QAbstractListModel(parent), + visibleIndex(-1) +{ roles[TitleRole] = "title"; - roles[ValuesRole] = "values"; + roles[BarValuesRole] = "barvalues"; + roles[LineValuesRole] = "linevalues"; roles[LabelsRole] = "labels"; roles[UnitsRole] = "units"; roles[MinValRole] = "minval"; @@ -30,8 +33,10 @@ QVariant StatsModel::data(const QModelIndex & index, int role) const { const Stats *stat = stats[index.row()]; if (role == TitleRole) return stat->getTitle(); - else if (role == ValuesRole) - return QVariant::fromValue>(stat->getValues()); + else if (role == BarValuesRole) + return QVariant::fromValue>(stat->getBarValues()); + else if (role == LineValuesRole) + return QVariant::fromValue>(stat->getLineValues()); else if (role == LabelsRole) return stat->getLabels(); else if (role == UnitsRole) diff --git a/src/statsmodel.h b/src/statsmodel.h index 662cf79..3a2b41b 100644 --- a/src/statsmodel.h +++ b/src/statsmodel.h @@ -12,12 +12,13 @@ class StatsModel : public QAbstractListModel public: enum StatsRoles { TitleRole = Qt::UserRole + 1, - ValuesRole, + BarValuesRole, + LineValuesRole, LabelsRole, UnitsRole, MinValRole, MaxValRole, - StepRole + StepRole, }; QHash roleNames() const; @@ -37,6 +38,7 @@ public: private: QHash roles; QList stats; + int visibleIndex; }; #endif // STATSMODEL_H diff --git a/src/statsweekdayave.cpp b/src/statsweekdayave.cpp index 527cb1d..c0bbd2b 100644 --- a/src/statsweekdayave.cpp +++ b/src/statsweekdayave.cpp @@ -16,7 +16,7 @@ void StatsWeekdayAve::update() { int pos; qDebug() << "Calculating values"; - values.clear(); + barvalues.clear(); for (pos = 0; pos < 7; pos++) { duration[pos] = 0.0; @@ -41,7 +41,7 @@ void StatsWeekdayAve::update() { if (result > maxval) { maxval = result; } - values << result; + barvalues << result; } step = maxval > 5.0 ? qRound(maxval / 5.0) : (maxval / 5.0); diff --git a/src/statsweekdaycongestion.cpp b/src/statsweekdaycongestion.cpp index c4842b3..3e19192 100644 --- a/src/statsweekdaycongestion.cpp +++ b/src/statsweekdaycongestion.cpp @@ -16,7 +16,7 @@ void StatsWeekdayCongestion::update() { int pos; qDebug() << "Calculating values"; - values.clear(); + barvalues.clear(); for (pos = 0; pos < 7; pos++) { passed[pos] = 0u; @@ -42,7 +42,7 @@ void StatsWeekdayCongestion::update() { if (result > maxval) { maxval = result; } - values << result; + barvalues << result; } step = maxval > 5.0 ? qRound(maxval / 5.0) : (maxval / 5.0); -- 2.25.1