This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
2021-03-06 00:57:32 +01:00

396 lines
14 KiB
QML

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU version 3. This project is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
import QtQuick 2.4
import QtQuick.Controls 2.1
import Nymea 1.0
import QtQuick.Controls.Material 2.2
Item {
id: root
property var model: null
property color color: "grey"
property string mode: "bezier" // "bezier" or "bars"
Connections {
target: model
onCountChanged: canvas.requestPaint()
}
onModelChanged: canvas.requestPaint()
readonly property Thing thing: root.model ? engine.thingManager.things.getThing(root.model.thingId) : null
readonly property ThingClass thingClass: thing ? engine.thingManager.thingClasses.getThingClass(thing.thingClassId) : null
readonly property StateType stateType: thingClass ? thingClass.stateTypes.getStateType(root.model.typeIds[0]) : null
Label {
anchors.centerIn: parent
width: parent.width - 2 * app.margins
wrapMode: Text.WordWrap
text: qsTr("Sorry, there isn't enough data to display a graph here yet!")
visible: !root.model.busy && root.model.count <= 2
horizontalAlignment: Text.AlignHCenter
font.pixelSize: app.largeFont
}
BusyIndicator {
anchors.centerIn: parent
visible: model.busy
}
Canvas {
id: canvas
visible: root.model.count > 2
anchors.fill: parent
property int minTemp: {
var lower = Math.floor(root.model.minimumValue - 2);
var upper = Math.ceil(root.model.maximumValue + 2);
if (isNaN(lower) || isNaN(upper) || lower == undefined || upper == undefined) {
return 0
}
while ((upper - lower) % 10 != 0) {
lower -= 1;
if ((upper - lower) % 10 != 0) {
upper += 1;
}
}
return lower;
}
property int maxTemp: {
var lower = Math.floor(root.model.minimumValue - 2);
var upper = Math.ceil(root.model.maximumValue + 2);
if (isNaN(lower) || isNaN(upper) || lower == undefined || upper == undefined) {
return 0
}
while ((upper - lower) % 10 != 0) {
lower -= 1;
if ((upper - lower) % 10 != 0) {
upper += 1;
}
}
return upper;
}
property int topMargins: app.margins
property int bottomMargins: app.margins
property int leftMargins: app.margins
property int rightMargins: app.margins
property color gridColor: "#d0d0d0"
property int contentWidth: canvas.width - leftMargins - rightMargins
property int contentHeight: canvas.height - topMargins - bottomMargins
property int totalSections: Math.round((maxTemp - minTemp) / 10) * 10
property int sections: {
var tmp = totalSections;
while (tmp >= 10) {
tmp /= 2;
}
return tmp;
}
onPaint: {
// print("painting graph")
var ctx = canvas.getContext('2d');
ctx.save();
ctx.reset()
ctx.font = "" + app.smallFont + "px Ubuntu";
ctx.globalAlpha = 1//canvas.alpha;
//ctx.fillStyle = canvas.fillStyle;
var textSize = ctx.measureText(maxTemp);
var leftTextWidth = textSize.width + app.margins;
var bottomTextHeight = app.smallFont * 2 + app.margins;
var topTextHeight = app.smallFont + app.margins;
ctx.translate(leftMargins + leftTextWidth, topMargins);
var gridWidth = contentWidth - leftTextWidth;
var gridHeight = contentHeight - bottomTextHeight;
paintGrid(ctx, gridWidth, gridHeight)
enumerate(ctx, gridWidth, gridHeight)
if (root.mode == "bezier") {
paintGraph(ctx, gridWidth, gridHeight)
} else {
paintBars(ctx, gridWidth, gridHeight)
}
ctx.restore();
}
function paintGrid(ctx, width, height) {
ctx.strokeStyle = canvas.gridColor;
ctx.fillStyle = Material.foreground
ctx.lineWidth = 1;
ctx.beginPath();
ctx.rect(0, 0, width, height)
ctx.stroke();
ctx.closePath();
// Horizontal lines
var tempInterval = (maxTemp - minTemp) / sections;
var pps = (height / sections);
for (var i = 0; i <= sections; i++) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = canvas.gridColor
ctx.moveTo(0, i * pps);
ctx.lineTo(width, i * pps)
ctx.stroke();
ctx.closePath();
ctx.beginPath();
var label = maxTemp - (tempInterval * i).toFixed(0)
var textSize = ctx.measureText(label)
ctx.strokeStyle = Material.foreground
ctx.fillStyle = Material.foreground
ctx.lineWidth = 0;
ctx.text(label, -textSize.width - app.margins, i * pps + 5)
// ctx.stroke();
ctx.fill()
ctx.closePath()
}
ctx.beginPath();
ctx.strokeStyle = Material.foreground
ctx.fillStyle = Material.foreground
ctx.lineWidth = 0;
var unit = root.stateType ? root.stateType.unit : Types.UnitNone
var label = Types.toUiUnit(unit)
var textSize = ctx.measureText(label)
ctx.text(label, -textSize.width - app.margins, height + app.margins + app.smallFont)
ctx.stroke();
ctx.fill()
ctx.closePath()
}
function enumerate(ctx, width, height) {
// enumate x axis
ctx.beginPath();
ctx.globalAlpha = 1;
ctx.strokeStyle = Material.foreground
ctx.fillStyle = Material.foreground
ctx.lineWidth = 0;
// enumerate Y axis
var lastTextX = -1;
for (var i = 0; i < model.count; i++) {
var x = width / (model.count) * i;
if (x < lastTextX) continue;
var label = model.get(i).dayString
var textSize = ctx.measureText(label)
ctx.text(label.slice(0,2).concat("."), x, height + app.smallFont + app.margins / 2)
switch (model.average) {
case ValueLogsProxyModel.AverageQuarterHour:
case ValueLogsProxyModel.AverageHourly:
case ValueLogsProxyModel.AverageDayTime:
label = model.get(i).timeString
break;
default:
label = model.get(i).dateString
}
textSize = ctx.measureText(label)
ctx.text(label, x, height + app.smallFont * 2 + app.margins)
lastTextX = x + textSize.width;
}
// ctx.stroke();
ctx.fill()
ctx.closePath();
}
function paintGraph(ctx, width, height) {
if (model.count <= 1) {
return;
}
var tempInterval = (maxTemp - minTemp) / sections;
var pps = (height / sections)
ctx.beginPath();
ctx.globalAlpha = 1;
ctx.lineWidth = 2;
var graphStroke = root.color;
var grapFill = Qt.rgba(root.color.r, root.color.g, root.color.b, .4);
ctx.strokeStyle = graphStroke;
ctx.fillStyle = grapFill;
var points = new Array();
for (var i = 0; i < model.count; i++) {
var value = model.get(i).value;
var point = new Object();
// print("painting value", value)
point.x = (i == 0) ? 0 : (width / (model.count - 2) * i);
point.y = height - (value - minTemp) / tempInterval * pps;
points.push(point);
}
paintBezier(ctx, points);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
paintBezier(ctx, points)
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.globalAlpha = 1;
ctx.lineWidth = 2;
ctx.strokeStyle = "green"
ctx.fillStyle = "green"
points = new Array();
for (var i = 0; i < model.count; i++) {
var dayMaxTemp = model.get(i).maxTemp;
var point = new Object();
point.x = (i == 0) ? 0 : (width / (model.count - 1) * i);
point.y = - (dayMaxTemp - maxTemp) / tempInterval * pps;
points.push(point);
}
paintBezier(ctx, points);
ctx.stroke();
ctx.closePath();
}
function paintBezier(ctx, points) {
if (points.length == 2) {
ctx.moveTo(points[0].x, points[0].y)
ctx.lineTo(points[1].x, points[1].y)
} else {
var n = points.length - 1;
points[0].rhsx = points[0].x + 2 * points[1].x;
points[0].rhsy = points[0].y + 2 * points[1].y;
for (var i = 1; i < n - 1; i++) {
points[i].rhsx = 4 * points[i].x + 2 * points[i+1].x;
points[i].rhsy = 4 * points[i].y + 2 * points[i+1].y;
}
points[n - 1].rhsx = (8 * points[n - 1].x + points[n].x) / 2;
points[n - 1].rhsy = (8 * points[n - 1].y + points[n].y) / 2;
var b = 2.0;
n = points.length - 1;
points[0].firstcontrolx = points[0].rhsx / b;
points[0].firstcontroly = points[0].rhsy / b;
for (var i = 1; i < n; i++) {
points[i].tmp = 1 / b;
b = (i < n - 1 ? 4.0 : 3.5) - points[i].tmp;
points[i].firstcontrolx = (points[i].rhsx - points[i - 1].firstcontrolx) / b;
points[i].firstcontroly = (points[i].rhsy - points[i - 1].firstcontroly) / b;
}
for (var i = 1; i < n; i++) {
points[n - i - 1].firstcontrolx -= points[n - i].tmp * points[n - i].firstcontrolx;
points[n - i - 1].firstcontroly -= points[n - i].tmp * points[n - i].firstcontroly;
}
n = points.length - 1;
for (var i = 0; i < n; i++) {
points[i].secondcontrolx = 2 * points[i + 1].x - points[i + 1].firstcontrolx;
points[i].secondcontroly = 2 * points[i + 1].y - points[i + 1].firstcontroly;
}
points[n - 1].secondcontrolx = (points[n].x + points[n - 1].firstcontrolx) / 2;
points[n - 1].secondcontroly = (points[n].x + points[n - 1].firstcontroly) / 2;
ctx.moveTo(points[0].x, points[0].y);
for (var i = 0; i < n - 1; i++) {
// ctx.lineTo(points[i].firstcontrolx, points[i].firstcontroly)
// ctx.lineTo(points[i].secondcontrolx, points[i].secondcontroly)
// ctx.lineTo(points[i+1].x, points[i+1].y)
ctx.bezierCurveTo(points[i].firstcontrolx, points[i].firstcontroly,
points[i].secondcontrolx, points[i].secondcontroly,
points[i + 1].x, points[i + 1].y)
}
}
}
function paintBars(ctx, width, height) {
if (model.count <= 1) {
return;
}
var tempInterval = (maxTemp - minTemp) / sections;
var pps = (height / sections)
ctx.globalAlpha = 1;
ctx.lineWidth = 2;
var graphStroke = root.color;
var grapFill = Qt.rgba(root.color.r, root.color.g, root.color.b, .2);
ctx.strokeStyle = graphStroke;
ctx.fillStyle = grapFill;
for (var i = 0; i < model.count; i++) {
ctx.beginPath();
var value = model.get(i).value;
var x = width / (model.count) * i;
var y = height - (value - minTemp) / tempInterval * pps;
var slotWidth = width / model.count
ctx.rect(x,y, slotWidth - 5, height - y)
ctx.fillRect(x,y, slotWidth - 5, height - y);
ctx.stroke();
ctx.fill();
ctx.closePath();
}
}
function hourToX(hour, width) {
var entries = root.day.count;
return canvas.width / entries * hour
}
}
}