Files
meadows/docs/adr/0005-uplot-behaviour-chart.md
Julien Calixte c361b05ec4 feat(editor): chart behaviour over time with uPlot
Draw the simulation's Stock trajectories with uPlot (ADR-0005): a real time
axis, hover-to-read values, and a playhead-marker hook the canvas animation
drives. SimChart owns the imperative uPlot lifecycle behind a small prop API.
2026-06-20 14:35:35 +02:00

3.0 KiB

uPlot draws the behaviour-over-time chart

Part of meadows · see DESIGN.md.

The Results panel (ADR-0004's phase 2) traces each Stock's value over time. It began as a hand-built SVG — a deliberate lean-substrate choice, no charting dependency. But a polyline is all that substrate could draw: no time axis, no gridlines, and a legend that could only show a Stock's final value, never the value under the cursor. For a tool whose whole point is reading behaviour over time, "what is this Stock at t = 40?" is the question you most want to answer by pointing at the curve.

Decision

Adopt uPlot (uplot, ~15 KB gzip) — a small, fast canvas time-series library — for the behaviour-over-time chart. It earns the dependency with the three things the SVG could not give: a real time axis with ticks and gridlines, and a live legend that reads each Stock's value at the hovered time.

uPlot is imperative and canvas-based, so it is wrapped in SimChart.vue, which owns its lifecycle (build on mount, setData on recompute, rebuild on a track-set change, ResizeObserver for width, destroy on unmount). ResultsPanel stays declarative: it assembles plain aligned data (Stocks only — the system's memory, per ADR-0004) and hands it down.

Considered Options

  • Keep the hand-built SVG (zero deps) — rejected: re-implementing axes, tick placement, a hover cursor, and hit-testing is rebuilding a charting library, badly. The lean-substrate note was a code comment, not an ADR; nothing prior is contradicted by spending one dependency where it clearly pays.
  • @gouvfr/dsfr-chart (the original prompt) — rejected: it requires the entire DSFR design system (CSS + JS API) and embeds its own Vue and Chart.js, so we would ship Vue twice and a second design language clashing with DaisyUI. It is built for French-government dashboards (region maps, gauges), not generic time series.
  • Chart.js / vue-chartjs — viable and same engine class, but heavier; uPlot is lighter and faster for many series and live-updating data, which is exactly the simulation's shape.
  • uPlot (chosen) — lightest credible option that gives the axis + hover.

Consequences

  • This is the project's first runtime charting dependency. The price, as with Vue Flow (ADR-0002), is a thin wrapper layer: SimChart.vue translates between reactive props and uPlot's imperative API.
  • The x scale runs with time: false — x is simulation time (start…stop), not wall-clock, which uPlot would otherwise format as calendar dates.
  • Track colours are read from the DaisyUI theme variables. The app ships a single light theme, so they are resolved once at build time; a future dark theme would need the plot re-initialised on theme change.
  • Swapping charting libraries later means rewriting only SimChart.vueResultsPanel passes plain [times, ...tracks] aligned data and knows nothing of uPlot.