Add graph to stats page; update Rules
authorDavid Llewellyn-Jones <david@flypig.co.uk>
Sat, 21 Jul 2018 16:53:38 +0000 (17:53 +0100)
committerDavid Llewellyn-Jones <david@flypig.co.uk>
Sat, 21 Jul 2018 16:53:38 +0000 (17:53 +0100)
The graph is a placeholder and doesn't show anything useful just
yet.

harbour-pedalo.pro
qml/components/InfoRow.qml
qml/pages/Stats.qml
qml/pages/TheRules.qml
src/graph.cpp [new file with mode: 0644]
src/graph.h [new file with mode: 0644]
src/harbour-pedalo.cpp
src/status.cpp
src/status.h

index 219e8b9..a6e2abe 100644 (file)
@@ -32,7 +32,8 @@ SOURCES += src/harbour-pedalo.cpp \
     src/journeymodel.cpp \
     src/status.cpp \
     src/settings.cpp \
     src/journeymodel.cpp \
     src/status.cpp \
     src/settings.cpp \
-    src/imageprovider.cpp
+    src/imageprovider.cpp \
+    src/graph.cpp
 
 DISTFILES += qml/harbour-pedalo.qml \
     qml/cover/CoverPage.qml \
 
 DISTFILES += qml/harbour-pedalo.qml \
     qml/cover/CoverPage.qml \
@@ -73,4 +74,5 @@ HEADERS += \
     src/status.h \
     src/settings.h \
     src/harbour-pedalo.h \
     src/status.h \
     src/settings.h \
     src/harbour-pedalo.h \
-    src/imageprovider.h
+    src/imageprovider.h \
+    src/graph.h
index 8a2ec5a..cd7b6c6 100644 (file)
@@ -26,7 +26,7 @@ Item {
         anchors {
             left: parent.left
             right: parent.right
         anchors {
             left: parent.left
             right: parent.right
-            rightMargin: (width - midLine) + Theme.paddingSmall
+            rightMargin: (parent.width - midLine) + Theme.paddingSmall
             leftMargin: detailItem.leftMargin
         }
         horizontalAlignment: Text.AlignLeft
             leftMargin: detailItem.leftMargin
         }
         horizontalAlignment: Text.AlignLeft
index 39ccc79..ad2dcae 100644 (file)
@@ -1,5 +1,6 @@
 import QtQuick 2.0
 import Sailfish.Silica 1.0
 import QtQuick 2.0
 import Sailfish.Silica 1.0
+import harbour.pedalo.graph 1.0
 import "../components"
 
 Page {
 import "../components"
 
 Page {
@@ -70,5 +71,39 @@ Page {
                 horizontalAlignment: Text.AlignRight
             }
         }
                 horizontalAlignment: Text.AlignRight
             }
         }
+
+        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
+                }
+            }
+        }
     }
 }
     }
 }
index 8a5cdc2..ffd80e0 100644 (file)
@@ -51,7 +51,7 @@ To keep track of these numbers in a sensible way:
 The master rule is that, whatever approach you use, be consistent and apply the same rules in the same way for people you overtake as you do for people who overtake you.
 <br/>
 <br/>
 The master rule is that, whatever approach you use, be consistent and apply the same rules in the same way for people you overtake as you do for people who overtake you.
 <br/>
 <br/>
-Stay safe and cycle considerately!
+Finally, street cycle can be a dangerous activivity. Don't use The Rules as an excuse to justify aggressive overtaking. Stay safe and cycle considerately!
 "
             }
         }
 "
             }
         }
diff --git a/src/graph.cpp b/src/graph.cpp
new file mode 100644 (file)
index 0000000..338ab5e
--- /dev/null
@@ -0,0 +1,267 @@
+#include <QBrush>
+#include <QPainter>
+
+#include "journey.h"
+#include "graph.h"
+
+Graph::Graph(QQuickItem *parent)
+    : QQuickPaintedItem(parent)
+    , primary(QColor("#ff0000"))
+    , secondary(QColor("#00ff00"))
+    , highlight(QColor("#0000ff"))
+    , axisThickness(0.02)
+    , bars(7)
+    , gap(0.1)
+    , minmodel(0.0f)
+    , maxmodel(1.0f)
+    , miny(0.0f)
+    , maxy(1.0f)
+    , stepy(0.1f)
+    , unitsy("%")
+    , fontsize(24.0)
+    , animate(1.0)
+{
+    model.clear();
+    labelsx.clear();
+    labelsy.clear();
+}
+
+void Graph::paint(QPainter *painter) {
+    QLocale locale;
+    float barwidth;
+    float axiswidth;
+    int count;
+    int barfullwidth;
+    float labelygap;
+    float labelxgap;
+    int labels;
+    float labelheight;
+
+    QRectF size = contentsBoundingRect();
+
+    bars = model.length();
+    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);
+
+    QBrush axiscolour(primary);
+    QBrush barcolour(secondary);
+
+    painter->setBrush(axiscolour);
+    painter->setPen(Qt::NoPen);
+    painter->setRenderHint(QPainter::Antialiasing);
+    painter->setOpacity(1.0);
+
+    const QPointF points[6] = {
+        QPointF(labelxgap, (labelheight / 2.0)),
+        QPointF(labelxgap, size.height() - labelygap),
+        QPointF(size.width(), size.height() - labelygap),
+        QPointF(size.width(), size.height() - labelygap - axiswidth),
+        QPointF(labelxgap + axiswidth, size.height() - labelygap - axiswidth),
+        QPointF(labelxgap + axiswidth, (labelheight / 2.0))
+    };
+    painter->drawPolygon(points, 6);
+
+    if (bars > 0) {
+        painter->setBrush(barcolour);
+
+        count = 0;
+        for (QList<float>::const_iterator iter = model.constBegin(); (count < bars) && (iter != model.constEnd()); iter++) {
+            float barheight = ((*iter) - miny) / (maxy - miny);
+            barheight = qBound(0.0f, barheight, 1.0f) * (size.height() - labelygap - axiswidth - (labelheight / 2.0));
+            barheight *= animate;
+            painter->drawRect(labelxgap + axiswidth + (barfullwidth * count) + (barfullwidth * gap / 2.0), size.height() - labelygap - barheight - axiswidth, barwidth, barheight);
+
+            count++;
+        }
+    }
+
+    QFont font = painter->font();
+    font.setPixelSize(fontsize);
+    painter->setFont(font);
+    painter->setPen(primary);
+    painter->setBrush(Qt::NoBrush);
+
+    if ((bars > 0) && (labelsx.length() == bars)) {
+        for (count = 0; count < bars; count++) {
+            QRectF rect(labelxgap + axiswidth + (barfullwidth * count), size.height() - labelygap + 8.0, barfullwidth, 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++) {
+            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);
+        }
+    }
+    else {
+        float labelvalue = miny;
+        for (count = 0; count < labels; 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);
+            //painter->drawRect(rect);
+            labelvalue += stepy;
+        }
+
+    }
+}
+
+QList<float> Graph::getModel() const {
+    return model;
+}
+
+void Graph::setModel(QList<float> 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;
+            }
+        }
+    }
+}
+
+QStringList Graph::getLabelsx() const {
+    return labelsx;
+}
+
+void Graph::setLabelsx(QStringList value) {
+    labelsx = value;
+    emit labelsxChanged();
+}
+
+QStringList Graph::getLabelsy() const {
+    return labelsy;
+}
+
+void Graph::setLabelsy(QStringList value) {
+    labelsy = value;
+    emit labelsyChanged();
+}
+
+QColor Graph::getPrimary() const {
+    return this->primary;
+}
+
+void Graph::setPrimary(QColor value) {
+    if (primary != value) {
+        primary = value;
+        emit primaryChanged(value);
+    }
+}
+
+QColor Graph::getSecondary() const {
+    return this->secondary;
+}
+
+void Graph::setSecondary(QColor value) {
+    if (secondary != value) {
+        secondary = value;
+        emit secondaryChanged(value);
+    }
+}
+
+QColor Graph::getHighlight() const {
+    return this->highlight;
+}
+
+void Graph::setHighlight(QColor value) {
+    if (highlight != value) {
+        highlight = value;
+        emit highlightChanged(value);
+    }
+}
+
+float Graph::getGap() const {
+    return gap;
+}
+
+void Graph::setGap(float value) {
+    if (gap != value) {
+        gap = value;
+        emit gapChanged(value);
+    }
+}
+
+float Graph::getMiny() const {
+    return miny;
+}
+
+void Graph::setMiny(float value) {
+    if (miny != value) {
+        miny = value;
+        emit minyChanged(value);
+    }
+}
+
+float Graph::getMaxy() const {
+    return maxy;
+}
+
+void Graph::setMaxy(float value) {
+    if (maxy != value) {
+        maxy = value;
+        emit maxyChanged(value);
+    }
+}
+
+float Graph::getStepy() const {
+    return stepy;
+}
+
+void Graph::setStepy(float value) {
+    if (stepy != value) {
+        stepy = value;
+        emit stepyChanged(value);
+    }
+}
+
+QString Graph::getUnitsy() const {
+    return unitsy;
+}
+
+void Graph::setUnitsy(QString value) {
+    if (unitsy != value) {
+        unitsy = value;
+        emit unitsyChanged();
+    }
+}
+
+float Graph::getFontsize() const {
+    return fontsize;
+}
+
+void Graph::setFontsize(float value) {
+    if (fontsize != value) {
+        fontsize = value;
+        emit fontsizeChanged(value);
+    }
+}
+
+float Graph::getAnimate() const {
+    return animate;
+}
+
+void Graph::setAnimate(float value) {
+    if (animate != value) {
+        animate = value;
+        emit animateChanged(value);
+        update();
+    }
+}
+
diff --git a/src/graph.h b/src/graph.h
new file mode 100644 (file)
index 0000000..269f37d
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef TEXTBALLOON_H
+#define TEXTBALLOON_H
+
+#include <QObject>
+#include <QQuickPaintedItem>
+
+class Graph : public QQuickPaintedItem
+{
+    Q_OBJECT
+    Q_PROPERTY(QList<float> model READ getModel WRITE setModel NOTIFY modelChanged)
+    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)
+    Q_PROPERTY(QColor secondary READ getSecondary WRITE setSecondary NOTIFY secondaryChanged)
+    Q_PROPERTY(QColor highlight READ getHighlight WRITE setHighlight NOTIFY highlightChanged)
+
+    Q_PROPERTY(float gap READ getGap WRITE setGap NOTIFY gapChanged)
+    Q_PROPERTY(float miny READ getMiny WRITE setMiny NOTIFY minyChanged)
+    Q_PROPERTY(float maxy READ getMaxy WRITE setMaxy NOTIFY maxyChanged)
+    Q_PROPERTY(float stepy READ getStepy WRITE setStepy NOTIFY stepyChanged)
+    Q_PROPERTY(QString unitsy READ getUnitsy WRITE setUnitsy NOTIFY unitsyChanged)
+    Q_PROPERTY(float fontsize READ getFontsize WRITE setFontsize NOTIFY fontsizeChanged)
+
+    Q_PROPERTY(float animate READ getAnimate WRITE setAnimate NOTIFY animateChanged)
+
+public:
+        Graph(QQuickItem *parent = 0);
+        void paint(QPainter *painter);
+
+        QList<float> getModel() const;
+        void setModel(QList<float> value);
+        QStringList getLabelsx() const;
+        void setLabelsx(QStringList value);
+        QStringList getLabelsy() const;
+        void setLabelsy(QStringList value);
+        QColor getPrimary() const;
+        void setPrimary(QColor value);
+        QColor getSecondary() const;
+        void setSecondary(QColor value);
+        QColor getHighlight() const;
+        void setHighlight(QColor value);
+        float getGap() const;
+        void setGap(float value);
+        float getMiny() const;
+        void setMiny(float value);
+        float getMaxy() const;
+        void setMaxy(float value);
+        float getStepy() const;
+        void setStepy(float value);
+        QString getUnitsy() const;
+        void setUnitsy(QString value);
+        float getFontsize() const;
+        void setFontsize(float value);
+        float getAnimate() const;
+        void setAnimate(float value);
+
+    private:
+        QList<float> model;
+        QStringList labelsx;
+        QStringList labelsy;
+        QColor primary;
+        QColor secondary;
+        QColor highlight;
+        float axisThickness;
+        int bars;
+        float gap;
+        float minmodel;
+        float maxmodel;
+        float miny;
+        float maxy;
+        float stepy;
+        QString unitsy;
+        float fontsize;
+        float animate;
+
+    signals:
+        void modelChanged();
+        void labelsxChanged();
+        void labelsyChanged();
+        void primaryChanged(QColor primary);
+        void secondaryChanged(QColor secondary);
+        void highlightChanged(QColor highlight);
+        void gapChanged(float gap);
+        void minyChanged(float miny);
+        void maxyChanged(float maxy);
+        void stepyChanged(float maxy);
+        void unitsyChanged();
+        void fontsizeChanged(float fontsize);
+        void animateChanged(float animate);
+};
+
+#endif // TEXTBALLOON_H
index e8f123d..729edae 100644 (file)
@@ -10,6 +10,7 @@
 #include "status.h"
 #include "settings.h"
 #include "imageprovider.h"
 #include "status.h"
 #include "settings.h"
 #include "imageprovider.h"
+#include "graph.h"
 
 #include "harbour-pedalo.h"
 
 
 #include "harbour-pedalo.h"
 
@@ -34,6 +35,8 @@ int main(int argc, char *argv[])
 
     Settings::instantiate();
     qmlRegisterSingletonType<Settings>("harbour.pedalo.settings", 1, 0, "Settings", Settings::provider);
 
     Settings::instantiate();
     qmlRegisterSingletonType<Settings>("harbour.pedalo.settings", 1, 0, "Settings", Settings::provider);
+    qmlRegisterType<JourneyModel>("harbour.pedalo.journeymodel", 1, 0, "JourneyModel");
+    qmlRegisterType<Graph>("harbour.pedalo.graph", 1, 0, "Graph");
 
     JourneyModel journeys;
     Status currentStatus(journeys);
 
     JourneyModel journeys;
     Status currentStatus(journeys);
index 4a46cdf..aeff901 100644 (file)
@@ -109,7 +109,6 @@ QString Status::getFormattedTime(quint64 seconds, int min, int max) {
         portion = (unit == max - 1) ? remaining : remaining % base[unit];
         portions << portion;
         remaining /= base[unit];
         portion = (unit == max - 1) ? remaining : remaining % base[unit];
         portions << portion;
         remaining /= base[unit];
-        qDebug() << plural[unit] << ": " << portion;
     }
 
     formatted = "";
     }
 
     formatted = "";
@@ -131,3 +130,8 @@ QString Status::getFormattedTime(quint64 seconds, int min, int max) {
     return formatted;
 }
 
     return formatted;
 }
 
+QList<float> Status::getGraphData() {
+    static QList<float> data({0.1, 0.2, 0.3, 0.4, 1.0, 0.8, 0.7, 0.9, 0.5, 1.0});
+
+    return data;
+}
index 8b6b665..58db726 100644 (file)
@@ -27,6 +27,7 @@ public:
 
     Q_INVOKABLE static QString getFormattedTime(quint64 seconds, int min, int max);
 
 
     Q_INVOKABLE static QString getFormattedTime(quint64 seconds, int min, int max);
 
+    Q_INVOKABLE QList<float> getGraphData();
 signals:
     void cyclingChanged(bool cycling);
     void startTimeChanged(quint64 startTime);
 signals:
     void cyclingChanged(bool cycling);
     void startTimeChanged(quint64 startTime);