Add a runs screen: a table, a run in full, and curves side by side

This commit is contained in:
2026-08-25 11:44:13 +02:00
parent 7e422c0047
commit d2951a325e
17 changed files with 1564 additions and 5 deletions
@@ -236,6 +236,7 @@ export function UplotChart({
yLabel,
palette,
smooth = false,
xTime = true,
onCursor,
onSelect,
}: {
@@ -262,6 +263,10 @@ export function UplotChart({
* Monotone rather than plain cubic on purpose: a spline that overshoots
* invents readings between two the sensor actually took. */
smooth?: boolean
/** The x axis reads as time. False when x is a count rather than a moment —
* a run's metric is indexed by step, and drawn as time it would date every
* point to 1970. */
xTime?: boolean
/** The x value under the pointer, and null once it leaves the plot. */
onCursor?: (ts: number | null) => void
/** The x value clicked, or null for a click that landed on no point. */
@@ -284,7 +289,7 @@ export function UplotChart({
// while a new reading only sets its data. The unit, the fixed range and the
// axis title are part of it — all are baked into the axes at build time —
// and so are the line shape and the palette, which the series close over.
const key = `${labels.join(" ")}|${unit ?? ""}|${yRange?.join(",") ?? ""}|${yLabel ?? ""}|${smooth}|${palette?.join("") ?? ""}`
const key = `${labels.join(" ")}|${unit ?? ""}|${yRange?.join(",") ?? ""}|${yLabel ?? ""}|${smooth}|${palette?.join("") ?? ""}|${xTime}`
// uPlot leaves its axes half-initialised while the scales have no range, and
// a resize in that window (a card still settling, say) draws them anyway and
// throws. Waiting for the first reading avoids the state altogether.
@@ -344,7 +349,7 @@ export function UplotChart({
],
},
scales: {
x: { time: true },
x: { time: xTime },
...(yRange ? { y: { range: yRange } } : {}),
},
axes: [