more work on qtcharts

This commit is contained in:
Michael Zanetti 2018-10-26 12:11:25 +02:00
parent 37c4ed0261
commit 65d983d5e3
5 changed files with 385 additions and 229 deletions

View File

@ -213,9 +213,39 @@ void LogsModelNg::logsReply(const QVariantMap &data)
for (int i = 0; i < newBlock.count(); i++) {
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()));
Device *dev = m_engine->deviceManager()->devices()->getDevice(entry->deviceId());
if (dev && dev->deviceClass()->stateTypes()->getStateType(entry->typeId())->type() == "Bool") {
// We don't want bools painting triangles, add a toggle point to keep lines straight
if (i > 0) {
LogEntry *newerEntry = newBlock.at(i - 1);
if (newerEntry->value().toBool() != entry->value().toBool()) {
qDebug() << "Adding bool line series point:" << (newerEntry->timestamp().addSecs(-1)) << newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0) << "(correction)";
m_graphSeries->append(QPointF(newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
}
}
if (m_graphSeries->count() == 0) {
qDebug() << "Adding bool line series point:" << QDateTime::currentDateTime() << QDateTime::currentDateTime().toMSecsSinceEpoch() - 1 << (entry->value().toBool() ? 1 : 0) << "(beginning)";
m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), 1));// entry->value().toBool() ? 1 : 0));
m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
}
qDebug() << "Adding bool line series point:" << entry->timestamp() << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0);
m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
} else {
// if (i > 0) {
// LogEntry *newerEntry = newBlock.at(i - 1);
// if (newerEntry->value() != entry->value()) {
// qDebug() << "Adding line series point:" << (offset + i) << newerEntry->timestamp().toMSecsSinceEpoch() - 1 << (entry->value().toReal()) << "(correction)";
// m_graphSeries->append(QPointF(newerEntry->timestamp().toMSecsSinceEpoch() - 1, entry->value().toReal()));
// }
// }
if (m_graphSeries->count() == 0) {
m_graphSeries->insert(0, QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toReal()));
}
qDebug() << "Adding line series point:" << (offset + i) << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toReal());
m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal()));
}
}
if (!newMin.isValid() || newMin > entry->value()) {
newMin = entry->value().toReal();
@ -236,7 +266,7 @@ void LogsModelNg::logsReply(const QVariantMap &data)
emit maxValueChanged();
}
if (m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) {
if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) {
fetchMore();
}
}
@ -246,8 +276,8 @@ void LogsModelNg::fetchMore(const QModelIndex &parent)
Q_UNUSED(parent)
qDebug() << "fetchMore called";
if (!m_engine->jsonRpcClient()) {
qWarning() << "Cannot update. JsonRpcClient not set";
if (!m_engine) {
qWarning() << "Cannot update. Engine not set";
return;
}
if (m_busy) {
@ -294,8 +324,8 @@ void LogsModelNg::fetchMore(const QModelIndex &parent)
bool LogsModelNg::canFetchMore(const QModelIndex &parent) const
{
Q_UNUSED(parent)
qDebug() << "canFetchMore" << m_canFetchMore;
return m_canFetchMore;
qDebug() << "canFetchMore" << (m_engine && m_canFetchMore);
return m_engine && m_canFetchMore;
}
void LogsModelNg::newLogEntryReceived(const QVariantMap &data)

View File

@ -127,5 +127,7 @@
<file>translations/nymea-app-de_DE.qm</file>
<file>translations/nymea-app-en_US.qm</file>
<file>../LICENSE</file>
<file>ui/customviews/GenericTypeGraphPre110.qml</file>
<file>ui/customviews/GenericTypeGraph.qml</file>
</qresource>
</RCC>

View File

@ -0,0 +1,252 @@
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"
import "../customviews"
import QtCharts 2.2
Item {
id: root
property var device: null
property var stateType: null
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0
readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null
LogsModelNg {
id: logsModelNg
engine: _engine
deviceId: root.device.id
typeIds: [root.stateType.id]
live: true
graphSeries: lineSeries1
viewStartTime: xAxis.min
}
LogsModelNg {
id: connectedLogsModel
engine: root.hasConnectable ? _engine : null // don't even try to poll if we don't have a connectable interface
deviceId: root.device.id
typeIds: [root.connectedStateType.id]
live: true
graphSeries: connectedLineSeries
viewStartTime: xAxis.min
}
ColumnLayout {
anchors.fill: parent
spacing: 0
RowLayout {
Layout.alignment: Qt.AlignHCenter
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;
}
}
Label {
Layout.preferredWidth: 100
horizontalAlignment: Text.AlignHCenter
text: {
var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000;
if (timeDiff < 60) {
return qsTr("%1 seconds").arg(Math.round(timeDiff));
}
timeDiff = timeDiff / 60
if (timeDiff < 60) {
return qsTr("%1 minutes").arg(Math.round(timeDiff));
}
timeDiff = timeDiff / 60
if (timeDiff < 48) {
return qsTr("%1 hours").arg(Math.round(timeDiff));
}
timeDiff = timeDiff / 24;
if (timeDiff < 14) {
return qsTr("%1 days").arg(Math.round(timeDiff));
}
timeDiff = timeDiff / 7
if (timeDiff < 5) {
return qsTr("%1 weeks").arg(Math.round(timeDiff));
}
timeDiff * timeDiff * 7 / 30
if (timeDiff < 24) {
return qsTr("%1 months").arg(Math.round(timeDiff));
}
timeDiff = timeDiff * 30 / 356
return qsTr("%1 years").arg(Math.round(timeDiff))
}
}
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;
}
}
}
ChartView {
id: chartView
Layout.fillWidth: true
Layout.fillHeight: true
margins.top: 0
margins.bottom: 0
margins.left: 0
margins.right: 0
backgroundColor: Material.background
legend.labelColor: app.foregroundColor
animationDuration: 300
animationOptions: ChartView.SeriesAnimations
ValueAxis {
id: yAxis
min: logsModelNg.minValue
max: logsModelNg.maxValue
labelsFont.pixelSize: app.smallFont
labelsColor: app.foregroundColor
tickCount: chartView.height / 40
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2)
gridLineColor: color
}
ValueAxis {
id: connectedAxis
min: 0
max: 1
visible: false
}
DateTimeAxis {
id: xAxis
gridVisible: false
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .2)
tickCount: chartView.width / 70
labelsFont.pixelSize: app.smallFont
labelsColor: app.foregroundColor
titleText: {
if (xAxis.min.getYear() === xAxis.max.getYear()
&& xAxis.min.getMonth() === xAxis.max.getMonth()
&& xAxis.min.getDate() === xAxis.max.getDate()) {
return Qt.formatDate(xAxis.min)
}
return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max)
}
titleBrush: app.foregroundColor
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"
}
min: {
var date = new Date();
date.setHours(date.getHours() - 6);
return date;
}
max: new Date()
}
AreaSeries {
axisX: xAxis
axisY: connectedAxis
name: qsTr("Not connected")
visible: root.hasConnectable
upperSeries: LineSeries {
XYPoint {x: xAxis.min.getTime(); y: 1}
XYPoint {x: xAxis.max.getTime(); y: 1}
}
lowerSeries: LineSeries {
id: connectedLineSeries
}
color: "#55ff0000"
}
AreaSeries {
axisX: xAxis
axisY: yAxis
name: root.stateType.displayName
borderColor: app.accentColor
borderWidth: 4
upperSeries: LineSeries {
id: lineSeries1
}
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) {
scrollRightLimited(lastX - mouseX)
lastX = mouse.x
}
}
}
}
}
}

View File

@ -0,0 +1,84 @@
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"
import "../customviews"
ColumnLayout {
id: root
property var device: null
property var stateType: null
TabBar {
id: zoomTabBar
Layout.fillWidth: true
TabButton {
text: qsTr("6 h")
property int avg: ValueLogsProxyModel.AverageQuarterHour
property date startTime: {
var date = new Date();
date.setHours(new Date().getHours() - 6)
date.setMinutes(0)
date.setSeconds(0)
return date;
}
}
TabButton {
text: qsTr("24 h")
property int avg: ValueLogsProxyModel.AverageHourly
property date startTime: {
var date = new Date();
date.setHours(new Date().getHours() - 24);
date.setMinutes(0)
date.setSeconds(0)
return date;
}
}
TabButton {
text: qsTr("7 d")
property int avg: ValueLogsProxyModel.AverageDayTime
property date startTime: {
var date = new Date();
date.setDate(new Date().getDate() - 7);
date.setHours(0)
date.setMinutes(0)
date.setSeconds(0)
return date;
}
}
}
Graph {
Layout.fillWidth: true
Layout.fillHeight: true
mode: settings.graphStyle
color: app.accentColor
Timer {
id: updateTimer
interval: 10
repeat: false
onTriggered: {
graphModel.update()
}
}
model: ValueLogsProxyModel {
id: graphModel
deviceId: root.device.id
typeIds: [stateType.id]
average: zoomTabBar.currentItem.avg
startTime: zoomTabBar.currentItem.startTime
Component.onCompleted: updateTimer.start();
onAverageChanged: updateTimer.start()
onStartTimeChanged: updateTimer.start();
engine: _engine
// Live doesn't work yet with ValueLogsProxyModel
// live: true
}
}
}

View File

@ -5,7 +5,6 @@ import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
import "../customviews"
import QtCharts 2.2
Page {
id: root
@ -24,7 +23,7 @@ Page {
}
header: GuhHeader {
text: qsTr("History")
text: qsTr("History for %1").arg(root.stateType.displayName)
onBackPressed: pageStack.pop()
}
@ -45,8 +44,6 @@ Page {
deviceId: root.device.id
typeIds: [root.stateType.id]
live: true
graphSeries: lineSeries1
viewStartTime: xAxis.min
}
ColumnLayout {
@ -62,9 +59,6 @@ Page {
TabButton {
text: qsTr("Graph")
}
TabButton {
text: qsTr("Graph NG")
}
}
SwipeView {
@ -96,225 +90,19 @@ Page {
}
}
ColumnLayout {
Loader {
id: graphLoader
width: swipeView.width
height: swipeView.height
TabBar {
id: zoomTabBar
Layout.fillWidth: true
TabButton {
text: qsTr("6 h")
property int avg: ValueLogsProxyModel.AverageQuarterHour
property date startTime: {
var date = new Date();
date.setHours(new Date().getHours() - 6)
date.setMinutes(0)
date.setSeconds(0)
return date;
}
}
TabButton {
text: qsTr("24 h")
property int avg: ValueLogsProxyModel.AverageHourly
property date startTime: {
var date = new Date();
date.setHours(new Date().getHours() - 24);
date.setMinutes(0)
date.setSeconds(0)
return date;
}
}
TabButton {
text: qsTr("7 d")
property int avg: ValueLogsProxyModel.AverageDayTime
property date startTime: {
var date = new Date();
date.setDate(new Date().getDate() - 7);
date.setHours(0)
date.setMinutes(0)
date.setSeconds(0)
return date;
}
Component.onCompleted: {
var source;
if (engine.jsonRpcClient.ensureServerVersion("1.10")) {
source = Qt.resolvedUrl("../customviews/GenericTypeGraph.qml");
} else {
source = Qt.resolvedUrl("../customviews/GenericTypeGraphPre110.qml");
}
setSource(source, {device: root.device, stateType: root.stateType})
}
Graph {
Layout.fillWidth: true
Layout.fillHeight: true
mode: settings.graphStyle
color: app.accentColor
Timer {
id: updateTimer
interval: 10
repeat: false
onTriggered: {
graphModel.update()
}
}
model: ValueLogsProxyModel {
id: graphModel
deviceId: root.device.id
typeIds: [stateType.id]
average: zoomTabBar.currentItem.avg
startTime: zoomTabBar.currentItem.startTime
Component.onCompleted: updateTimer.start();
onAverageChanged: updateTimer.start()
onStartTimeChanged: updateTimer.start();
engine: _engine
// Live doesn't work yet with ValueLogsProxyModel
// live: true
}
}
}
Item {
width: swipeView.width
height: swipeView.height
ColumnLayout {
anchors.fill: parent
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;
}
}
}
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
ValueAxis {
id: yAxis
min: logsModelNg.minValue
max: logsModelNg.maxValue
labelsFont.pixelSize: app.smallFont
tickCount: chartView.height / 40
}
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"
}
min: {
var date = new Date();
date.setHours(date.getHours() - 6);
return date;
}
max: new Date()
}
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) {
scrollRightLimited(lastX - mouseX)
lastX = mouse.x
}
}
}
}
}
}
}
}