Chart cursor, range picker in the header, line smoothing
A dashboard canvas is CSS-scaled to fit its panel while uPlot maps the pointer against its own unscaled plot width, so the cursor drifted further right the further into a chart it went. A `cursor.move` refiner divides the visual offset back into layout pixels; unscaled hosts get a no-op. A querying chart's range picker moves onto the frame's title line through a new `useHeaderSlot`, giving the plot back the row it spent. The editor's drag handle is the header, so the picker is exempted from it. Charts can be drawn as a monotone cubic spline — uPlot's own path builder, monotone so a smoothed line never invents a reading between two real ones. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZeGnqVsf5VHQqvz4HdUhN
This commit is contained in:
@@ -84,6 +84,7 @@ export function UplotChart({
|
||||
unit,
|
||||
yRange,
|
||||
yLabel,
|
||||
smooth = false,
|
||||
onCursor,
|
||||
onSelect,
|
||||
}: {
|
||||
@@ -101,6 +102,10 @@ export function UplotChart({
|
||||
yRange?: [number, number]
|
||||
/** A named y axis; the unit stays on the ticks. */
|
||||
yLabel?: string
|
||||
/** Draw the lines as a monotone cubic spline rather than straight segments.
|
||||
* Monotone rather than plain cubic on purpose: a spline that overshoots
|
||||
* invents readings between two the sensor actually took. */
|
||||
smooth?: 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. */
|
||||
@@ -122,7 +127,7 @@ export function UplotChart({
|
||||
// The identity of the series set: the chart is rebuilt when it changes,
|
||||
// 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.
|
||||
const key = `${labels.join(" ")}|${unit ?? ""}|${yRange?.join(",") ?? ""}|${yLabel ?? ""}`
|
||||
const key = `${labels.join(" ")}|${unit ?? ""}|${yRange?.join(",") ?? ""}|${yLabel ?? ""}|${smooth}`
|
||||
// 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.
|
||||
@@ -142,6 +147,9 @@ export function UplotChart({
|
||||
/** The x value the page was last told about, so a move within one bucket
|
||||
* does not re-render it. */
|
||||
let told: number | null = null
|
||||
// One builder for the whole chart: it is a factory, and the series only
|
||||
// need the function it returns.
|
||||
const spline = smooth ? { paths: uPlot.paths.spline?.() } : {}
|
||||
const under = (self: uPlot) =>
|
||||
self.cursor.idx == null ? null : Number(self.data[0][self.cursor.idx])
|
||||
|
||||
@@ -150,7 +158,21 @@ export function UplotChart({
|
||||
width: element.clientWidth || 320,
|
||||
height: canvasHeight(element),
|
||||
padding: PADDING,
|
||||
cursor: { y: false },
|
||||
cursor: {
|
||||
y: false,
|
||||
// A dashboard canvas is CSS-scaled to fit its panel, and uPlot maps
|
||||
// the pointer with `clientX - rect.left` — visual pixels — against
|
||||
// its own unscaled plot width. On a scaled panel the cursor then
|
||||
// drifts further right the further in it goes. Dividing by the ratio
|
||||
// the element is actually drawn at puts it back in layout pixels;
|
||||
// unscaled, the ratio is 1 and this is a no-op.
|
||||
move: (self, left, top) => {
|
||||
const drawn = self.rect.width / self.over.clientWidth
|
||||
return drawn > 0 && drawn !== 1
|
||||
? [left / drawn, top / drawn]
|
||||
: [left, top]
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
live: true,
|
||||
// Mounted in its own row under the plot rather than inside it: a
|
||||
@@ -216,6 +238,7 @@ export function UplotChart({
|
||||
// holes, and a line with a hole per point is not a line.
|
||||
spanGaps: true,
|
||||
points: { show: false },
|
||||
...spline,
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user