import QtQuick Canvas { id: root // Data - array of 0-100 percentage values property var history: [] property color color: "white" property bool active: true // Max x-axis slots (0 = use history.length, right-aligns shorter data) property int maxSamples: 0 // Optional background tint using `color` at low opacity property real backgroundTint: 0 // 0 = off, e.g. 0.15 for memory, 0.08 for temperature // Area chart mode (battery-style filled curve + stroke line) property bool areaMode: false property real areaOpacity: 0.18 property real lineWidth: 1.5 // Per-bar color override: function(value) => color. Null = uniform `color`. property var colorFunction: null // Horizontal threshold lines: [{value: 70, color: "#..."}] property var thresholds: [] onHistoryChanged: if (root.active) requestPaint() onColorChanged: if (root.active) requestPaint() onVisibleChanged: if (visible) requestPaint() onPaint: { const ctx = getContext("2d"); if (!ctx) return; ctx.clearRect(0, 0, width, height); const d = history; if (!d.length || (areaMode && d.length < 2)) return; const samples = maxSamples > 0 ? maxSamples : d.length; const step = width / samples; const offset = samples - d.length; // Background tint if (backgroundTint > 0) { ctx.fillStyle = Qt.rgba(color.r, color.g, color.b, backgroundTint).toString(); ctx.fillRect(0, 0, width, height); } // Threshold lines if (thresholds.length) { ctx.globalAlpha = 0.3; ctx.lineWidth = 1; 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.stroke(); } ctx.setLineDash([]); ctx.globalAlpha = 1.0; } if (areaMode) { // Filled area under curve const xOff = offset * step; ctx.beginPath(); ctx.moveTo(xOff, height); for (let i = 0; i < d.length; i++) { ctx.lineTo(xOff + i * step, height - height * (d[i] / 100)); } ctx.lineTo(xOff + (d.length - 1) * step, height); ctx.closePath(); ctx.fillStyle = Qt.rgba(color.r, color.g, color.b, areaOpacity).toString(); ctx.fill(); // Top stroke 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); else ctx.lineTo(x, y); } ctx.strokeStyle = color.toString(); ctx.lineWidth = root.lineWidth; ctx.stroke(); } else { // Bar chart const hasCF = typeof colorFunction === "function"; 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); } } } }