flow: structured dtypes, and the widgets that read them
A series, record or list message declares its shape instead of riding DType.JSON, so a widget binds a shape rather than some JSON and a wrong binding is refused before anything runs. A list declares its item type, which is what keeps list[float] expressible for a pipeline. On top of that: an agenda over a list, a notification over a record, and a dashboard alert channel that publishes engine faults as one — so a panel can show what went wrong without a flow wiring it by hand. Also: only None means a node published nothing, a falsy value of the wrong shape is now the named error it always should have been; and the gauge's readout says its size is viewBox geometry rather than type scale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,8 @@ export function UplotChart({
|
||||
labels,
|
||||
plots,
|
||||
empty = "Nothing has come through yet.",
|
||||
unit,
|
||||
yRange,
|
||||
onCursor,
|
||||
onSelect,
|
||||
}: {
|
||||
@@ -66,6 +68,10 @@ export function UplotChart({
|
||||
/** The points of each series, in the same order as `labels`. */
|
||||
plots: HistoryPoint[][]
|
||||
empty?: string
|
||||
/** Written after every reading, on the axis and in the legend. */
|
||||
unit?: string
|
||||
/** A y axis fixed to these bounds; unset lets it follow the data. */
|
||||
yRange?: [number, number]
|
||||
/** 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. */
|
||||
@@ -85,8 +91,9 @@ export function UplotChart({
|
||||
|
||||
const points = plots.reduce((total, plot) => total + plot.length, 0)
|
||||
// The identity of the series set: the chart is rebuilt when it changes,
|
||||
// while a new reading only sets its data.
|
||||
const key = labels.join(" ")
|
||||
// while a new reading only sets its data. The unit and the fixed range are
|
||||
// part of it — both are baked into the axes when the chart is built.
|
||||
const key = `${labels.join(" ")}|${unit ?? ""}|${yRange?.join(",") ?? ""}`
|
||||
// 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.
|
||||
@@ -139,16 +146,21 @@ export function UplotChart({
|
||||
},
|
||||
],
|
||||
},
|
||||
scales: { x: { time: true } },
|
||||
scales: {
|
||||
x: { time: true },
|
||||
...(yRange ? { y: { range: yRange } } : {}),
|
||||
},
|
||||
axes: [
|
||||
{ ...axis, size: 28 },
|
||||
{
|
||||
...axis,
|
||||
size: 46,
|
||||
// The unit is written after every tick, so the gutter widens to
|
||||
// hold it rather than clipping the number in front of it.
|
||||
size: unit ? 62 : 46,
|
||||
// The gutter has a fixed width, so a grouped "15,000" would be
|
||||
// clipped to something that reads as a different number entirely.
|
||||
values: (_self: uPlot, ticks: number[]) =>
|
||||
ticks.map((value) => si(value)),
|
||||
ticks.map((value) => (unit ? `${si(value)} ${unit}` : si(value))),
|
||||
},
|
||||
],
|
||||
series: [
|
||||
@@ -160,8 +172,9 @@ export function UplotChart({
|
||||
// rebuilt chart.
|
||||
stroke: () => seriesColor(index),
|
||||
// The cursor readout is what decides how wide the legend gets, so
|
||||
// it is shortened here and the unit named in the card's title.
|
||||
value: (_self: uPlot, raw: number) => si(raw),
|
||||
// it is shortened here; a named unit is short enough to keep.
|
||||
value: (_self: uPlot, raw: number) =>
|
||||
unit ? `${si(raw)} ${unit}` : si(raw),
|
||||
// Series arrive on their own clocks; a joined table is mostly
|
||||
// holes, and a line with a hole per point is not a line.
|
||||
spanGaps: true,
|
||||
|
||||
Reference in New Issue
Block a user