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:
2026-08-23 06:27:48 +02:00
co-authored by Claude Opus 5
parent 39bc711683
commit 939c14bada
7 changed files with 158 additions and 21 deletions
+5 -2
View File
@@ -901,6 +901,9 @@ ENERGY_WIDGETS = [
"history": {"points": 720},
"unit": " kW",
"y_label": "kW",
# Twelve hours of readings a minute apart: the shape is what is
# being read, not any single sample.
"smooth": True,
# Fixed, so the picture does not rescale under you every time the
# sun goes behind something. Negative is exporting.
"y_min": -7,
@@ -914,8 +917,8 @@ ENERGY_WIDGETS = [
"layout": at(7, 0, 6, 4),
"config": {
# The other kind of chart: it asks, rather than reading the ring
# the engine keeps. The range picker at the top of the tile is what
# publishes the request.
# the engine keeps. The range picker on the tile's title line is
# what publishes the request.
"source": "query",
"request": msg(HISTORY, "chart_request"),
"request_dtype": "record",