battery applet: add wattage history sparkline, sparkline supports signed data with auto-range

This commit is contained in:
Damocles 2026-04-24 21:14:32 +02:00
parent b5be146619
commit 42d11e7a14
4 changed files with 67 additions and 26 deletions

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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;
}
}