sparkline: smooth quadratic curves between data points

This commit is contained in:
Damocles 2026-04-25 10:49:08 +02:00
parent 2ed0b3da3a
commit f6c93ad9d9

View file

@ -103,30 +103,43 @@ Canvas {
ctx.stroke(); ctx.stroke();
} }
// Build polygon path // Smooth curve through sample midpoints
function midX(i) {
const [bx, bw] = xOf(i);
return bx + bw / 2;
}
function traceCurve() {
ctx.moveTo(0, yOf(d[0]));
if (n === 1) {
ctx.lineTo(width, yOf(d[0]));
} else {
ctx.lineTo(midX(0), yOf(d[0]));
for (let i = 0; i < n - 1; i++) {
const mx = (midX(i) + midX(i + 1)) / 2;
ctx.quadraticCurveTo(midX(i), yOf(d[i]), mx, (yOf(d[i]) + yOf(d[i + 1])) / 2);
}
ctx.lineTo(midX(n - 1), yOf(d[n - 1]));
ctx.lineTo(width, yOf(d[n - 1]));
}
}
// Filled polygon
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);
for (let i = 0; i < n; i++) { traceCurve();
const [bx, bw] = xOf(i);
const y = yOf(d[i]);
ctx.lineTo(bx, y);
ctx.lineTo(bx + bw, y);
}
ctx.lineTo(width, baseY); ctx.lineTo(width, baseY);
ctx.closePath(); ctx.closePath();
// Vertical gradient fill // Vertical gradient fill
const grad = ctx.createLinearGradient(0, height, 0, 0); const grad = ctx.createLinearGradient(0, height, 0, 0);
if (colorAt) { if (colorAt) {
// Explicit colorAt: sample across the value range
const steps = 8; const steps = 8;
for (let s = 0; s <= steps; s++) { for (let s = 0; s <= steps; s++) {
const frac = s / steps; const frac = s / steps;
grad.addColorStop(frac, colorAt(lo + frac * range).toString()); grad.addColorStop(frac, colorAt(lo + frac * range).toString());
} }
} else if (thresholds.length) { } else if (thresholds.length) {
// Auto-derive from thresholds as smooth gradient
const sorted = thresholds.slice().sort((a, b) => a.value - b.value); const sorted = thresholds.slice().sort((a, b) => a.value - b.value);
grad.addColorStop(0, strokeColor.toString()); grad.addColorStop(0, strokeColor.toString());
for (const th of sorted) { for (const th of sorted) {
@ -137,7 +150,6 @@ Canvas {
} }
grad.addColorStop(1, sorted[sorted.length - 1].color.toString()); grad.addColorStop(1, sorted[sorted.length - 1].color.toString());
} else { } else {
// Uniform fill
grad.addColorStop(0, strokeColor.toString()); grad.addColorStop(0, strokeColor.toString());
grad.addColorStop(1, strokeColor.toString()); grad.addColorStop(1, strokeColor.toString());
} }
@ -146,15 +158,7 @@ Canvas {
// Stroke line on top // Stroke line on top
ctx.beginPath(); ctx.beginPath();
for (let i = 0; i < n; i++) { traceCurve();
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.strokeStyle = strokeColor.toString();
ctx.lineWidth = root.lineWidth; ctx.lineWidth = root.lineWidth;
ctx.stroke(); ctx.stroke();