battery applet: add wattage history sparkline, sparkline supports signed data with auto-range
This commit is contained in:
parent
b5be146619
commit
42d11e7a14
4 changed files with 67 additions and 26 deletions
|
|
@ -152,6 +152,24 @@ Column {
|
|||
valueColor: root._stateColor
|
||||
}
|
||||
|
||||
// Wattage history sparkline
|
||||
SparklineCanvas {
|
||||
visible: S.BatteryService.rateHistory.length > 1
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 12
|
||||
height: 24
|
||||
history: S.BatteryService.rateHistory
|
||||
color: root._stateColor
|
||||
active: root.active
|
||||
maxSamples: 60
|
||||
maxValue: null
|
||||
minValue: null
|
||||
backgroundTint: 0.08
|
||||
colorFunction: v => v >= 0 ? S.Theme.base0B : root._stateColor
|
||||
}
|
||||
|
||||
// Health row
|
||||
InfoRow {
|
||||
visible: S.BatteryService.healthSupported
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@ import QtQuick
|
|||
Canvas {
|
||||
id: root
|
||||
|
||||
// Data - array of 0-100 percentage values
|
||||
// Data - array of numeric values. Set maxValue/minValue to null for auto-range.
|
||||
property var history: []
|
||||
property color color: "white"
|
||||
property bool active: true
|
||||
property var maxValue: 100
|
||||
property var minValue: 0
|
||||
|
||||
readonly property real _max: maxValue !== null && maxValue !== undefined ? maxValue : (history.length ? Math.max(...history) : 100)
|
||||
readonly property real _min: minValue !== null && minValue !== undefined ? minValue : (history.length ? Math.min(...history) : 0)
|
||||
|
||||
// Max x-axis slots (0 = use history.length, right-aligns shorter data)
|
||||
property int maxSamples: 0
|
||||
|
|
@ -45,6 +50,12 @@ Canvas {
|
|||
const samples = maxSamples > 0 ? maxSamples : d.length;
|
||||
const step = width / samples;
|
||||
const offset = samples - d.length;
|
||||
const lo = _min;
|
||||
const hi = _max;
|
||||
const range = hi - lo || 1;
|
||||
function yOf(v) {
|
||||
return height - height * ((v - lo) / range);
|
||||
}
|
||||
|
||||
// Background tint
|
||||
if (backgroundTint > 0) {
|
||||
|
|
@ -59,26 +70,37 @@ Canvas {
|
|||
ctx.setLineDash([3, 3]);
|
||||
for (let t = 0; t < thresholds.length; t++) {
|
||||
const th = thresholds[t];
|
||||
const ty = height - height * (th.value / 100);
|
||||
ctx.strokeStyle = th.color.toString();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, ty);
|
||||
ctx.lineTo(width, ty);
|
||||
ctx.moveTo(0, yOf(th.value));
|
||||
ctx.lineTo(width, yOf(th.value));
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.setLineDash([]);
|
||||
ctx.globalAlpha = 1.0;
|
||||
}
|
||||
|
||||
// Zero line when range spans negative values
|
||||
if (lo < 0 && hi > 0) {
|
||||
const zy = yOf(0);
|
||||
ctx.strokeStyle = Qt.rgba(color.r, color.g, color.b, 0.2).toString();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, zy);
|
||||
ctx.lineTo(width, zy);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (areaMode) {
|
||||
// Filled area under curve
|
||||
const xOff = offset * step;
|
||||
const baseY = lo < 0 ? yOf(0) : height;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xOff, height);
|
||||
ctx.moveTo(xOff, baseY);
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
ctx.lineTo(xOff + i * step, height - height * (d[i] / 100));
|
||||
ctx.lineTo(xOff + i * step, yOf(d[i]));
|
||||
}
|
||||
ctx.lineTo(xOff + (d.length - 1) * step, height);
|
||||
ctx.lineTo(xOff + (d.length - 1) * step, baseY);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = Qt.rgba(color.r, color.g, color.b, areaOpacity).toString();
|
||||
ctx.fill();
|
||||
|
|
@ -87,25 +109,27 @@ Canvas {
|
|||
ctx.beginPath();
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
const x = xOff + i * step;
|
||||
const y = height - height * (d[i] / 100);
|
||||
if (i === 0)
|
||||
ctx.moveTo(x, y);
|
||||
ctx.moveTo(x, yOf(d[i]));
|
||||
else
|
||||
ctx.lineTo(x, y);
|
||||
ctx.lineTo(x, yOf(d[i]));
|
||||
}
|
||||
ctx.strokeStyle = color.toString();
|
||||
ctx.lineWidth = root.lineWidth;
|
||||
ctx.stroke();
|
||||
} else {
|
||||
// Bar chart
|
||||
// Bar chart - bars grow from zero line (or bottom if all positive)
|
||||
const hasCF = typeof colorFunction === "function";
|
||||
const zy = lo < 0 ? yOf(0) : height;
|
||||
if (!hasCF)
|
||||
ctx.fillStyle = color.toString();
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
const barH = Math.max(1, height * d[i] / 100);
|
||||
if (hasCF)
|
||||
ctx.fillStyle = colorFunction(d[i]).toString();
|
||||
ctx.fillRect((offset + i) * step, height - barH, Math.max(1, step - 0.5), barH);
|
||||
const dy = yOf(d[i]);
|
||||
const barTop = Math.min(dy, zy);
|
||||
const barH = Math.max(1, Math.abs(dy - zy));
|
||||
ctx.fillRect((offset + i) * step, barTop, Math.max(1, step - 0.5), barH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ QtObject {
|
|||
|
||||
// 24h history (1440 samples @ 60s)
|
||||
property var history: []
|
||||
// Wattage history (60 samples @ 60s = 1h), signed: positive = charging
|
||||
property var rateHistory: []
|
||||
|
||||
property var _histTimer: Timer {
|
||||
interval: 60000
|
||||
|
|
@ -39,6 +41,9 @@ QtObject {
|
|||
onTriggered: {
|
||||
const h = root.history.concat([root.percent]);
|
||||
root.history = h.length > 1440 ? h.slice(h.length - 1440) : h;
|
||||
const w = root.charging ? root.changeRate : -root.changeRate;
|
||||
const rh = root.rateHistory.concat([w]);
|
||||
root.rateHistory = rh.length > 60 ? rh.slice(rh.length - 60) : rh;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue