356 lines
12 KiB
QML
356 lines
12 KiB
QML
import QtQuick 2.4
|
|
import QtQuick.Controls 2.1
|
|
import Guh 1.0
|
|
|
|
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()
|
|
|
|
Label {
|
|
anchors.centerIn: parent
|
|
width: parent.width - 2 * app.margins
|
|
wrapMode: Text.WordWrap
|
|
text: "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 (lower == undefined || upper == undefined) {
|
|
return 0
|
|
}
|
|
|
|
print("upper", upper, "lower", lower)
|
|
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 (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 * 4
|
|
property int leftMargins: app.margins * 3
|
|
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;
|
|
}
|
|
// Pixel per section
|
|
property real pps: contentHeight / sections;
|
|
|
|
onPaint: {
|
|
|
|
print("painting canvas", totalSections, sections)
|
|
var minTemp
|
|
|
|
var ctx = canvas.getContext('2d');
|
|
ctx.save();
|
|
|
|
ctx.reset()
|
|
|
|
ctx.translate(leftMargins, topMargins)
|
|
|
|
ctx.globalAlpha = 1//canvas.alpha;
|
|
//ctx.fillStyle = canvas.fillStyle;
|
|
ctx.font = "" + app.smallFont + "px Ubuntu";
|
|
|
|
paintGrid(ctx)
|
|
enumerate(ctx)
|
|
|
|
if (root.mode == "bezier") {
|
|
paintGraph(ctx)
|
|
} else {
|
|
paintBars(ctx)
|
|
}
|
|
|
|
ctx.restore();
|
|
|
|
}
|
|
|
|
function paintGrid(ctx) {
|
|
ctx.strokeStyle = canvas.gridColor;
|
|
ctx.fillStyle = "black"
|
|
ctx.lineWidth = 1;
|
|
|
|
ctx.beginPath();
|
|
ctx.rect(0, 0, contentWidth, contentHeight)
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
|
|
// Horizontal lines
|
|
var tempInterval = (maxTemp - minTemp) / sections;
|
|
|
|
for (var i = 0; i <= sections; i++) {
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeStyle = canvas.gridColor
|
|
ctx.moveTo(0, i * pps);
|
|
ctx.lineTo(contentWidth, i * pps)
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
|
|
ctx.beginPath();
|
|
var label = maxTemp - (tempInterval * i).toFixed(0)
|
|
var textSize = ctx.measureText(label)
|
|
ctx.strokeStyle = "black"
|
|
ctx.fillStyle = "black"
|
|
ctx.lineWidth = 0;
|
|
ctx.text(label, -textSize.width - app.margins, i * pps + 5)
|
|
// ctx.stroke();
|
|
ctx.fill()
|
|
ctx.closePath()
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = "black"
|
|
ctx.fillStyle = "black"
|
|
ctx.lineWidth = 0;
|
|
var label = "°C"
|
|
var textSize = ctx.measureText(label)
|
|
ctx.text(label, -textSize.width - 1, -1 * pps + 5)
|
|
// ctx.stroke();
|
|
ctx.fill()
|
|
ctx.closePath()
|
|
|
|
}
|
|
|
|
function enumerate(ctx) {
|
|
// enumate x axis
|
|
ctx.beginPath();
|
|
ctx.globalAlpha = 1;
|
|
ctx.strokeStyle = "black"
|
|
ctx.fillStyle = "black"
|
|
ctx.lineWidth = 0;
|
|
// enumerate Y axis
|
|
|
|
var lastTextX = -1;
|
|
for (var i = 0; i < model.count; i++) {
|
|
var x = contentWidth / (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, contentHeight + 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, contentHeight + app.smallFont * 2 + app.margins)
|
|
lastTextX = x + textSize.width;
|
|
}
|
|
|
|
// ctx.stroke();
|
|
ctx.fill()
|
|
ctx.closePath();
|
|
}
|
|
|
|
function paintGraph(ctx) {
|
|
if (model.count <= 1) {
|
|
return;
|
|
}
|
|
|
|
var tempInterval = (maxTemp - minTemp) / 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 : (contentWidth / (model.count - 2) * i);
|
|
point.y = contentHeight - (value - minTemp) / tempInterval * pps;
|
|
points.push(point);
|
|
}
|
|
|
|
paintBezier(ctx, points);
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
|
|
ctx.beginPath();
|
|
paintBezier(ctx, points)
|
|
ctx.lineTo(contentWidth, contentHeight);
|
|
ctx.lineTo(0, contentHeight);
|
|
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 : (contentWidth / (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) {
|
|
if (model.count <= 1) {
|
|
return;
|
|
}
|
|
|
|
var tempInterval = (maxTemp - minTemp) / 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 = contentWidth / (model.count) * i;
|
|
var y = contentHeight - (value - minTemp) / tempInterval * pps;
|
|
|
|
var slotWidth = contentWidth / model.count
|
|
ctx.rect(x,y, slotWidth - 5, contentHeight - y)
|
|
ctx.fillRect(x,y, slotWidth - 5, contentHeight - y);
|
|
ctx.stroke();
|
|
ctx.fill();
|
|
ctx.closePath();
|
|
}
|
|
}
|
|
|
|
function hourToX(hour) {
|
|
var entries = root.day.count;
|
|
return canvas.contentWidth / entries * hour
|
|
}
|
|
}
|
|
}
|