sparkline: unified coloring - vertical gradient from colorAt/thresholds, stroke line, 1px border, consistent 32px height
This commit is contained in:
parent
e1d20c2407
commit
c64373313d
6 changed files with 90 additions and 93 deletions
|
|
@ -86,18 +86,16 @@ Column {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 24h history sparkline (area chart)
|
// 24h charge history sparkline
|
||||||
SparklineCanvas {
|
SparklineCanvas {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 44
|
height: 32
|
||||||
history: S.BatteryService.history
|
history: S.BatteryService.history
|
||||||
color: root._stateColor
|
strokeColor: root.accentColor
|
||||||
active: root.active
|
active: root.active
|
||||||
backgroundTint: 0.07
|
|
||||||
areaMode: true
|
|
||||||
thresholds: [
|
thresholds: [
|
||||||
{
|
{
|
||||||
value: S.BatteryService.warnThresh,
|
value: S.BatteryService.warnThresh,
|
||||||
|
|
@ -160,12 +158,11 @@ Column {
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 24
|
height: 24
|
||||||
history: S.BatteryService.rateHistory
|
history: S.BatteryService.rateHistory
|
||||||
color: root._stateColor
|
strokeColor: root._stateColor
|
||||||
|
colorAt: v => v >= 0 ? S.Theme.base0B : root._stateColor
|
||||||
active: root.active
|
active: root.active
|
||||||
maxValue: null
|
maxValue: null
|
||||||
minValue: null
|
minValue: null
|
||||||
backgroundTint: 0.08
|
|
||||||
colorFunction: v => v >= 0 ? S.Theme.base0B : root._stateColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health row
|
// Health row
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,8 @@ Column {
|
||||||
width: 32
|
width: 32
|
||||||
height: 10
|
height: 10
|
||||||
history: root.cores[parent.parent.index]?.history ?? []
|
history: root.cores[parent.parent.index]?.history ?? []
|
||||||
color: parent.parent._barColor
|
strokeColor: parent.parent._barColor
|
||||||
|
colorAt: v => S.Theme.loadColor(v)
|
||||||
active: root.active
|
active: root.active
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,11 @@ Column {
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 36
|
height: 32
|
||||||
history: S.SystemStats.gpuHistory
|
history: S.SystemStats.gpuHistory
|
||||||
|
strokeColor: root.accentColor
|
||||||
|
colorAt: v => S.Theme.loadColor(v)
|
||||||
active: root.active
|
active: root.active
|
||||||
colorFunction: v => S.Theme.loadColor(v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VRAM section
|
// VRAM section
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,11 @@ Column {
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 18
|
height: 32
|
||||||
history: S.SystemStats.memHistory
|
history: S.SystemStats.memHistory
|
||||||
color: root.accentColor
|
strokeColor: root.accentColor
|
||||||
|
colorAt: v => S.Theme.loadColor(v)
|
||||||
active: root.active
|
active: root.active
|
||||||
backgroundTint: 0.15
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Breakdown rows
|
// Breakdown rows
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ Canvas {
|
||||||
|
|
||||||
// Data - array of numeric values. Set maxValue/minValue to null for auto-range.
|
// Data - array of numeric values. Set maxValue/minValue to null for auto-range.
|
||||||
property var history: []
|
property var history: []
|
||||||
property color color: "white"
|
|
||||||
property bool active: true
|
property bool active: true
|
||||||
property var maxValue: 100
|
property var maxValue: 100
|
||||||
property var minValue: 0
|
property var minValue: 0
|
||||||
|
|
@ -13,28 +12,29 @@ Canvas {
|
||||||
readonly property real _max: maxValue !== null && maxValue !== undefined ? maxValue : (history.length ? Math.max(...history) : 100)
|
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)
|
readonly property real _min: minValue !== null && minValue !== undefined ? minValue : (history.length ? Math.min(...history) : 0)
|
||||||
|
|
||||||
// Logarithmic x-axis: compresses old data on the left, expands recent data on the right.
|
// Stroke line color on top of the filled area
|
||||||
// logCurve controls strength (0 = linear, 3 = strong compression). Only visual - does not affect data.
|
property color strokeColor: "white"
|
||||||
property bool logScale: false
|
|
||||||
property real logCurve: 3
|
|
||||||
|
|
||||||
// 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
|
property real lineWidth: 1.5
|
||||||
|
|
||||||
// Per-bar color override: function(value) => color. Null = uniform `color`.
|
// Maps a data value to a fill color. Applied as a vertical gradient.
|
||||||
property var colorFunction: null
|
// When null, auto-derived from thresholds as a smooth gradient
|
||||||
|
// (strokeColor below first threshold, blending through threshold colors).
|
||||||
|
// Set explicitly to override.
|
||||||
|
// qmllint disable use-proper-function
|
||||||
|
property var colorAt: null
|
||||||
|
// qmllint enable use-proper-function
|
||||||
|
|
||||||
|
// Logarithmic x-axis: compresses old data on the left, expands recent data on the right.
|
||||||
|
// logCurve controls strength (0 = linear, 3 = strong compression). Only visual.
|
||||||
|
property bool logScale: false
|
||||||
|
property real logCurve: 3
|
||||||
|
|
||||||
// Horizontal threshold lines: [{value: 70, color: "#..."}]
|
// Horizontal threshold lines: [{value: 70, color: "#..."}]
|
||||||
property var thresholds: []
|
property var thresholds: []
|
||||||
|
|
||||||
onHistoryChanged: if (root.active)
|
onHistoryChanged: if (root.active)
|
||||||
requestPaint()
|
requestPaint()
|
||||||
onColorChanged: if (root.active)
|
onStrokeColorChanged: if (root.active)
|
||||||
requestPaint()
|
requestPaint()
|
||||||
|
|
||||||
onVisibleChanged: if (visible)
|
onVisibleChanged: if (visible)
|
||||||
|
|
@ -46,7 +46,7 @@ Canvas {
|
||||||
return;
|
return;
|
||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
const d = history;
|
const d = history;
|
||||||
if (!d.length || (areaMode && d.length < 2))
|
if (!d.length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const n = d.length;
|
const n = d.length;
|
||||||
|
|
@ -70,11 +70,10 @@ Canvas {
|
||||||
return [i * step, step];
|
return [i * step, step];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background tint
|
// 1px border
|
||||||
if (backgroundTint > 0) {
|
ctx.strokeStyle = Qt.rgba(strokeColor.r, strokeColor.g, strokeColor.b, 0.15).toString();
|
||||||
ctx.fillStyle = Qt.rgba(color.r, color.g, color.b, backgroundTint).toString();
|
ctx.lineWidth = 1;
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.strokeRect(0.5, 0.5, width - 1, height - 1);
|
||||||
}
|
|
||||||
|
|
||||||
// Threshold lines
|
// Threshold lines
|
||||||
if (thresholds.length) {
|
if (thresholds.length) {
|
||||||
|
|
@ -96,7 +95,7 @@ Canvas {
|
||||||
// Zero line when range spans negative values
|
// Zero line when range spans negative values
|
||||||
if (lo < 0 && hi > 0) {
|
if (lo < 0 && hi > 0) {
|
||||||
const zy = yOf(0);
|
const zy = yOf(0);
|
||||||
ctx.strokeStyle = Qt.rgba(color.r, color.g, color.b, 0.2).toString();
|
ctx.strokeStyle = Qt.rgba(strokeColor.r, strokeColor.g, strokeColor.b, 0.2).toString();
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, zy);
|
ctx.moveTo(0, zy);
|
||||||
|
|
@ -104,37 +103,7 @@ Canvas {
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (areaMode) {
|
// Build polygon path
|
||||||
// Filled area under curve
|
|
||||||
const baseY = lo < 0 ? yOf(0) : height;
|
|
||||||
const [x0] = xOf(0);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(x0, baseY);
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
const [x] = xOf(i);
|
|
||||||
ctx.lineTo(x, yOf(d[i]));
|
|
||||||
}
|
|
||||||
const [xLast] = xOf(n - 1);
|
|
||||||
ctx.lineTo(xLast, baseY);
|
|
||||||
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 < n; i++) {
|
|
||||||
const [x] = xOf(i);
|
|
||||||
if (i === 0)
|
|
||||||
ctx.moveTo(x, yOf(d[i]));
|
|
||||||
else
|
|
||||||
ctx.lineTo(x, yOf(d[i]));
|
|
||||||
}
|
|
||||||
ctx.strokeStyle = color.toString();
|
|
||||||
ctx.lineWidth = root.lineWidth;
|
|
||||||
ctx.stroke();
|
|
||||||
} else {
|
|
||||||
// Filled area - continuous polygon, no visible bar boundaries
|
|
||||||
const hasCF = typeof colorFunction === "function";
|
|
||||||
const baseY = lo < 0 ? yOf(0) : height;
|
const baseY = lo < 0 ? yOf(0) : height;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, baseY);
|
ctx.moveTo(0, baseY);
|
||||||
|
|
@ -146,17 +115,48 @@ Canvas {
|
||||||
}
|
}
|
||||||
ctx.lineTo(width, baseY);
|
ctx.lineTo(width, baseY);
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
if (hasCF) {
|
|
||||||
const grad = ctx.createLinearGradient(0, 0, width, 0);
|
// Vertical gradient fill
|
||||||
for (let i = 0; i < n; i++) {
|
const grad = ctx.createLinearGradient(0, height, 0, 0);
|
||||||
const [bx, bw] = xOf(i);
|
if (colorAt) {
|
||||||
grad.addColorStop(Math.min(1, (bx + bw / 2) / width), colorFunction(d[i]).toString());
|
// Explicit colorAt: sample across the value range
|
||||||
|
const steps = 8;
|
||||||
|
for (let s = 0; s <= steps; s++) {
|
||||||
|
const frac = s / steps;
|
||||||
|
grad.addColorStop(frac, colorAt(lo + frac * range).toString());
|
||||||
|
}
|
||||||
|
} else if (thresholds.length) {
|
||||||
|
// Auto-derive from thresholds as smooth gradient
|
||||||
|
const sorted = thresholds.slice().sort((a, b) => a.value - b.value);
|
||||||
|
grad.addColorStop(0, strokeColor.toString());
|
||||||
|
for (const th of sorted) {
|
||||||
|
const frac = Math.max(0, Math.min(1, (th.value - lo) / range));
|
||||||
|
if (frac > 0)
|
||||||
|
grad.addColorStop(Math.max(0, frac - 0.01), strokeColor.toString());
|
||||||
|
grad.addColorStop(frac, th.color.toString());
|
||||||
|
}
|
||||||
|
grad.addColorStop(1, sorted[sorted.length - 1].color.toString());
|
||||||
|
} else {
|
||||||
|
// Uniform fill
|
||||||
|
grad.addColorStop(0, strokeColor.toString());
|
||||||
|
grad.addColorStop(1, strokeColor.toString());
|
||||||
}
|
}
|
||||||
ctx.fillStyle = grad;
|
ctx.fillStyle = grad;
|
||||||
} else {
|
|
||||||
ctx.fillStyle = color.toString();
|
|
||||||
}
|
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
|
||||||
|
// Stroke line on top
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const [bx, bw] = xOf(i);
|
||||||
|
const y = yOf(d[i]);
|
||||||
|
if (i === 0)
|
||||||
|
ctx.moveTo(bx, y);
|
||||||
|
else
|
||||||
|
ctx.lineTo(bx, y);
|
||||||
|
ctx.lineTo(bx + bw, y);
|
||||||
|
}
|
||||||
|
ctx.strokeStyle = strokeColor.toString();
|
||||||
|
ctx.lineWidth = root.lineWidth;
|
||||||
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,11 +109,10 @@ Column {
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 12
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
height: 40
|
height: 32
|
||||||
history: root.history
|
history: root.history
|
||||||
color: root.stateColor
|
strokeColor: root.accentColor
|
||||||
active: root.active
|
active: root.active
|
||||||
backgroundTint: 0.08
|
|
||||||
thresholds: [
|
thresholds: [
|
||||||
{
|
{
|
||||||
value: root.warm,
|
value: root.warm,
|
||||||
|
|
@ -124,7 +123,6 @@ Column {
|
||||||
color: S.Theme.base08
|
color: S.Theme.base08
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
colorFunction: v => v > root.hot ? S.Theme.base08 : v > root.warm ? S.Theme.base0A : root.stateColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Threshold labels
|
// Threshold labels
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue