SIGN IN SIGN UP

perf(charts): draw the gauges without Recharts (#1975)

* perf(charts): draw the gauges without Recharts

The doughnut gauges were the last always-on 1Hz surface still on Recharts.
Each tick re-rendered a RadialBarChart per gauge and restarted a JS tween
that re-rendered the chart again on every animation frame — the mechanism
behind the macOS fan noise that shortening the tween to 300ms only reduced.

Draw the ring as a stroked circle instead: the value is the visible run of
the stroke, so a tick sets `stroke-dashoffset` once and the transition to the
new value runs on the compositor rather than through React. Geometry, colours
and the readout keep the sizes the Recharts gauge used at each breakpoint,
and the readout stays inside the view box so it scales with the container.

Out-of-range readings now pin the ring instead of wrapping past the start,
which would have read as a much lower value.

Per gauge: 42 DOM elements -> 12. Dashboard Recharts wrappers: 6 -> 1.
vendor-chart: 440.45 kB -> 413.32 kB (gzip 123.97 -> 118.00).

Refs #1581

* fix(charts): size the gauge view box to its container

The compact gauge drew its Recharts-era radii into the 200-unit view box the
xl gauge uses, so every coordinate was halved: the backing disc covered 50px
of the 100px surface and the readout shrank with it. Recharts sized its
canvas to the container, which is what those radii were written against.

Give each breakpoint the view box that matches its container, and size the
loading placeholder per breakpoint too — a fixed 128px circle overflowed the
100px compact surface.

* docs(charts): state why the gauge readout lives in the view box

* perf(charts): lengthen the gauge tween to 500ms

Half a second reads more smoothly than 300ms while still settling well
inside the 1Hz tick, leaving the drawing pipeline idle for the rest of each
second. The guard that matters is unchanged: a tween at or above the tick
would restart before settling, so the gauge would never reach the reading it
displays.

Also corrects the note beside the transition. `stroke-dashoffset` repaints as
it animates rather than being composited, so what the change removed is the
per-frame React render, not the frames themselves — which is exactly why the
duration is bounded by the tick.
S
shm committed
643261c30efbef8c7bedff48edc528ebd82ad405
Parent: 4311d60
Committed by GitHub <noreply@github.com> on 8/22/2026, 9:00:12 AM