diff --git a/libnymea-app-core/models/logsmodelng.cpp b/libnymea-app-core/models/logsmodelng.cpp index d1a28c27..eccfafba 100644 --- a/libnymea-app-core/models/logsmodelng.cpp +++ b/libnymea-app-core/models/logsmodelng.cpp @@ -7,8 +7,7 @@ #include "types/logentry.h" #include "logmanager.h" -LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent), - m_lineSeries(new QtCharts::QLineSeries(this)) +LogsModelNg::LogsModelNg(QObject *parent) : QAbstractListModel(parent) { } @@ -134,14 +133,44 @@ void LogsModelNg::setEndTime(const QDateTime &endTime) } } -QtCharts::QLineSeries *LogsModelNg::lineSeries() const +QtCharts::QXYSeries *LogsModelNg::graphSeries() const { - return m_lineSeries; + return m_graphSeries; } -void LogsModelNg::setLineSeries(QtCharts::QLineSeries *lineSeries) +void LogsModelNg::setGraphSeries(QtCharts::QXYSeries *graphSeries) { - m_lineSeries = lineSeries; + m_graphSeries = graphSeries; +} + +QDateTime LogsModelNg::viewStartTime() const +{ + return m_viewStartTime; +} + +void LogsModelNg::setViewStartTime(const QDateTime &viewStartTime) +{ + if (m_viewStartTime != viewStartTime) { + m_viewStartTime = viewStartTime; + emit viewStartTimeChanged(); + if (m_list.count() == 0 || m_list.last()->timestamp() > m_viewStartTime) { + if (canFetchMore()) { + fetchMore(); + } + } + } +} + +QVariant LogsModelNg::minValue() const +{ + qDebug() << "returning min value" << m_minValue; + return m_minValue; +} + +QVariant LogsModelNg::maxValue() const +{ + qDebug() << "returning max value" << m_maxValue; + return m_maxValue; } void LogsModelNg::logsReply(const QVariantMap &data) @@ -179,13 +208,37 @@ void LogsModelNg::logsReply(const QVariantMap &data) } beginInsertRows(QModelIndex(), offset, offset + newBlock.count() - 1); + QVariant newMin = m_minValue; + QVariant newMax = m_maxValue; for (int i = 0; i < newBlock.count(); i++) { - m_list.insert(offset + i, newBlock.at(i)); - qDebug() << "Adding line series point:" << i << newBlock.at(i)->timestamp().toSecsSinceEpoch() << newBlock.at(i)->value().toReal(); - m_lineSeries->insert(offset + i, QPointF(newBlock.at(i)->timestamp().toSecsSinceEpoch(), newBlock.at(i)->value().toReal())); + LogEntry *entry = newBlock.at(i); + m_list.insert(offset + i, entry); + qDebug() << "Adding line series point:" << i << entry->timestamp().toSecsSinceEpoch() << entry->value().toReal(); + if (m_graphSeries) { + m_graphSeries->insert(offset + i, QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); + } + if (!newMin.isValid() || newMin > entry->value()) { + newMin = entry->value().toReal(); + } + if (!newMax.isValid() || newMax < entry->value()) { + newMax = entry->value().toReal(); + } } endInsertRows(); emit countChanged(); + qDebug() << "min" << m_minValue << "max" << m_maxValue << "newMin" << newMin << "newMax" << newMax; + if (m_minValue != newMin) { + m_minValue = newMin; + emit minValueChanged(); + } + if (m_maxValue != newMax) { + m_maxValue = newMax; + emit maxValueChanged(); + } + + if (m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) { + fetchMore(); + } } void LogsModelNg::fetchMore(const QModelIndex &parent) @@ -225,8 +278,8 @@ void LogsModelNg::fetchMore(const QModelIndex &parent) if (!m_startTime.isNull() && !m_endTime.isNull()) { QVariantList timeFilters; QVariantMap timeFilter; - timeFilter.insert("startDate", m_currentFetchStartTime.toSecsSinceEpoch()); - timeFilter.insert("endDate", m_currentFetchEndTime.toSecsSinceEpoch()); + timeFilter.insert("startDate", m_startTime.toSecsSinceEpoch()); + timeFilter.insert("endDate", m_endTime.toSecsSinceEpoch()); timeFilters.append(timeFilter); params.insert("timeFilters", timeFilters); } @@ -271,6 +324,9 @@ void LogsModelNg::newLogEntryReceived(const QVariantMap &data) QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); LogEntry *entry = new LogEntry(timeStamp, value, deviceId, typeId, loggingSource, loggingEventType, this); m_list.prepend(entry); + if (m_graphSeries) { + m_graphSeries->insert(0, QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal())); + } endInsertRows(); emit countChanged(); diff --git a/libnymea-app-core/models/logsmodelng.h b/libnymea-app-core/models/logsmodelng.h index ad0910af..120ea6ce 100644 --- a/libnymea-app-core/models/logsmodelng.h +++ b/libnymea-app-core/models/logsmodelng.h @@ -20,8 +20,11 @@ class LogsModelNg : public QAbstractListModel Q_PROPERTY(QStringList typeIds READ typeIds WRITE setTypeIds NOTIFY typeIdsChanged) Q_PROPERTY(QDateTime startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) Q_PROPERTY(QDateTime endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) + Q_PROPERTY(QVariant minValue READ minValue NOTIFY minValueChanged) + Q_PROPERTY(QVariant maxValue READ maxValue NOTIFY maxValueChanged) - Q_PROPERTY(QtCharts::QLineSeries *lineSeries READ lineSeries WRITE setLineSeries NOTIFY lineSeriesChanged) + Q_PROPERTY(QtCharts::QXYSeries *graphSeries READ graphSeries WRITE setGraphSeries NOTIFY graphSeriesChanged) + Q_PROPERTY(QDateTime viewStartTime READ viewStartTime WRITE setViewStartTime NOTIFY viewStartTimeChanged) public: enum Roles { @@ -59,8 +62,14 @@ public: QDateTime endTime() const; void setEndTime(const QDateTime &endTime); - QtCharts::QLineSeries *lineSeries() const; - void setLineSeries(QtCharts::QLineSeries *lineSeries); + QtCharts::QXYSeries *graphSeries() const; + void setGraphSeries(QtCharts::QXYSeries *lineSeries); + + QDateTime viewStartTime() const; + void setViewStartTime(const QDateTime &viewStartTime); + + QVariant minValue() const; + QVariant maxValue() const; protected: virtual void fetchMore(const QModelIndex &parent = QModelIndex()) override; @@ -75,7 +84,10 @@ signals: void startTimeChanged(); void endTimeChanged(); void engineChanged(); - void lineSeriesChanged(); + void graphSeriesChanged(); + void viewStartTimeChanged(); + void minValueChanged(); + void maxValueChanged(); private slots: void newLogEntryReceived(const QVariantMap &data); @@ -91,12 +103,13 @@ private: QStringList m_typeIds; QDateTime m_startTime; QDateTime m_endTime; - QDateTime m_currentFetchStartTime; - QDateTime m_currentFetchEndTime; int m_blockSize = 100; bool m_canFetchMore = true; + QDateTime m_viewStartTime; + QVariant m_minValue; + QVariant m_maxValue; - QtCharts::QLineSeries *m_lineSeries = nullptr; + QtCharts::QXYSeries *m_graphSeries = nullptr; QList > m_fetchedPeriods; }; diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 0c1fbb22..337433fe 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -148,5 +148,7 @@ ui/images/view-collapse.svg ui/images/view-expand.svg ui/images/weather-app-symbolic.svg + ui/images/zoom-out.svg + ui/images/zoom-in.svg diff --git a/nymea-app/ui/devicepages/StateLogPage.qml b/nymea-app/ui/devicepages/StateLogPage.qml index 0547c8ba..3da7fb25 100644 --- a/nymea-app/ui/devicepages/StateLogPage.qml +++ b/nymea-app/ui/devicepages/StateLogPage.qml @@ -1,5 +1,6 @@ -import QtQuick 2.5 -import QtQuick.Controls 2.1 +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 import QtQuick.Layouts 1.1 import Nymea 1.0 import "../components" @@ -44,7 +45,8 @@ Page { deviceId: root.device.id typeIds: [root.stateType.id] live: true - lineSeries: lineSeries1 + graphSeries: lineSeries1 + viewStartTime: xAxis.min } ColumnLayout { @@ -163,7 +165,7 @@ Page { engine: _engine // Live doesn't work yet with ValueLogsProxyModel - // live: true + // live: true } } } @@ -173,57 +175,146 @@ Page { width: swipeView.width height: swipeView.height - ChartView { - id: chartView + ColumnLayout { anchors.fill: parent - ValueAxis { - id: yAxis - min: 0 - max: 50 + RowLayout { + Layout.alignment: Qt.AlignRight + HeaderButton { + imageSource: "../images/zoom-in.svg" + onClicked: { + var diff = xAxis.max.getTime() - xAxis.min.getTime() + var newTime = new Date(xAxis.min.getTime() + (diff / 4)) + xAxis.min = newTime; + } + } + HeaderButton { + imageSource: "../images/zoom-out.svg" + onClicked: { + var diff = xAxis.max.getTime() - xAxis.min.getTime() + var newTime = new Date(xAxis.min.getTime() - (diff / 4)) + xAxis.min = newTime; + } + } } - ValueAxis { - id: xAxis - min: Math.floor(startTime.getTime() / 1000) - max: Math.floor(endTime.getTime() / 1000) + ChartView { + id: chartView + Layout.fillWidth: true + Layout.fillHeight: true + margins.top: 0 + margins.bottom: 0 + margins.left: 0 + margins.right: 0 + backgroundColor: Material.background + animationDuration: 300 + animationOptions: ChartView.SeriesAnimations - property date startTime: { - var date = new Date(); - date.setHours(date.getHours() - 6); - return date; + ValueAxis { + id: yAxis + min: logsModelNg.minValue + max: logsModelNg.maxValue + labelsFont.pixelSize: app.smallFont + tickCount: chartView.height / 40 } - property date endTime: new Date() - Component.onCompleted: print("creating axis:", startTime, endTime) - } + DateTimeAxis { + id: xAxis + gridVisible: false + tickCount: chartView.width / 70 + labelsFont.pixelSize: app.smallFont + format: { + var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000 + if (timeDiff < 60) { // one minute + return "mm:ss" + } + if (timeDiff < 60 * 60) { // one hour + return "hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 2) { // two day + return "hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 7) { // one week + return "ddd hh:mm" + } + if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month + return "dd.MM." + } + return "MMM yy" + } - LineSeries { - id: lineSeries1 - axisX: xAxis - axisY: yAxis - } + min: { + var date = new Date(); + date.setHours(date.getHours() - 6); + return date; + } + max: new Date() + } - MouseArea { + AreaSeries { + axisX: xAxis + axisY: yAxis + name: root.stateType.displayName + borderColor: app.accentColor + borderWidth: 4 + upperSeries: LineSeries { + id: lineSeries1 + width: 4 + } + color: Qt.rgba(app.accentColor.r, app.accentColor.g, app.accentColor.b, .3) + } + + MouseArea { anchors.fill: parent property int lastX: 0 property int lastY: 0 + + function scrollRightLimited(dx) { + chartView.animationOptions = ChartView.NoAnimation + var now = new Date() + // if we're already at the limit, don't even start scrolling + if (dx < 0 || xAxis.max < now) { + chartView.scrollRight(dx) + } + // figure out if we scrolled too far + var overshoot = xAxis.max.getTime() - now.getTime() + print("overshoot is:", overshoot, "oldMax", xAxis.max, "newMax", now, "oldMin", xAxis.min, "newMin", new Date(xAxis.min.getTime() - overshoot)) + if (overshoot > 0) { + var range = xAxis.max - xAxis.min + xAxis.max = now + xAxis.min = new Date(xAxis.max.getTime() - range) + } + chartView.animationOptions = ChartView.SeriesAnimations + } + + function zoomInLimited(dy) { + chartView.animationOptions = ChartView.NoAnimation + var oldMax = xAxis.max; + chartView.scrollRight(dy); + var timeDiff = xAxis.max.getTime() - oldMax.getTime() + xAxis.min = new Date(xAxis.min.getTime() - timeDiff * 2) + chartView.animationOptions = ChartView.SeriesAnimations + } + onPressed: { lastX = mouse.x lastY = mouse.y } + onWheel: { + scrollRightLimited(-wheel.pixelDelta.x) +// zoomInLimited(wheel.pixelDelta.y) + } + onPositionChanged: { if (lastX !== mouse.x) { - chartView.scrollRight(lastX - mouse.x) + scrollRightLimited(lastX - mouseX) lastX = mouse.x } - if (lastY !== mouse.y) { - chartView.scrollDown(lastY - mouse.y) - lastY = mouse.y - } } } + } } + } } } diff --git a/nymea-app/ui/images/zoom-in.svg b/nymea-app/ui/images/zoom-in.svg new file mode 100644 index 00000000..57e89dc4 --- /dev/null +++ b/nymea-app/ui/images/zoom-in.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/zoom-out.svg b/nymea-app/ui/images/zoom-out.svg new file mode 100644 index 00000000..38a04c3e --- /dev/null +++ b/nymea-app/ui/images/zoom-out.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + +