Skip to content

feat(charts): add the uPlot renderer alongside dygraph - #234

Open
novykh wants to merge 2 commits into
mainfrom
feat/uplot-renderer
Open

feat(charts): add the uPlot renderer alongside dygraph#234
novykh wants to merge 2 commits into
mainfrom
feat/uplot-renderer

Conversation

@novykh

@novykh novykh commented Sep 1, 2026

Copy link
Copy Markdown
Member

Adds uPlot as a second time-series chart library, at feature parity with dygraph.

The default does not change

chartLibrary still ships as "dygraph". uPlot is opt-in — set chartLibrary: "uplot" on the SDK root, or map a single type with chartLibrariesByType.

Flipping the default later is a one-attribute change: chartLibrary is now the single selector, chartLibrariesByType defaults to {} and only overrides per-type, and getRendererForChartType falls back to chartLibrary. The flip is deliberately not in this PR — it is gated on the real-dashboard measurement protocol in docs/uplot-migration-progress.md, which has to be run in a browser on a live streaming dashboard.

What is covered

Chart types: line, area, stacked, stackedBar, multiBar, heatmap, sparkline.

Overlays, all drawn from uPlot draw hooks: alarm, alarmRange, alertTransitions, highlight, annotation, proceeded, per-series point markers, anomaly ribbon, and the anomaly-rate badge in the y-axis gutter.

Interaction: pan, wheel zoom, select, pinch, dblclick reset, click-to-annotate — using dygraph's thresholds and semantics. The crosshair lives on its own overlay canvas and is driven by the SDK's synced hoverX, not the local pointer, so cross-chart hover sync spans both renderers.

Geometry: plot area, axis ticks, borders and label widths matched to dygraph and held across resize. Collapsed y-ranges are expanded rather than left to stall uPlot's tick search.

Also in here

  • perfMonitor SDK plugin and a render-timing seam, off by default.
  • yarn perf:bench — a Playwright-driven headless dygraph-vs-uPlot benchmark, plus a browser geometry probe and a CPU profile probe under scripts/.
  • Storybook: a chart-library toolbar arg on the existing stories, a side-by-side renderer comparison story, and a showcase story.
  • src/chartLibraries/helpers/ — colour, overlay-area and dimension-visibility helpers now shared by both renderers.
  • src/helpers/deepMerge removed; it had no remaining references.

Performance

uPlot is 2.7–3.9× cheaper per render than dygraph at every CPU throttle level tested, and cheaper per frame in all 20 rendering cells of the sweep. Under saturation that converts into frames: at 6× throttle, 60±29 → 157±28 renders.

Caveats, the measurement protocol, and the earlier numbers that were retracted (everything measured before the unit-conversion rebuild fix is void) are recorded in docs/uplot-migration-progress.md.

For consumers

uplot is a new runtime dependency (~48KB), so consumers resolve it transitively — no change needed in their package.json.

The stylesheet is not an extra step: the rules uPlot needs (.u-wrap, .u-over, .u-under, .u-axis, .u-select, .u-cursor-*) are inlined in src/components/line/chartContentWrapper.js, scoped to the chart container. Upstream's remaining selectors are all legend/title chrome, and the renderer sets legend: { show: false } and no title, so uplot/dist/uPlot.min.css does not need importing.

A consumer that maps chartLibrary to its own React component must add a uplot entry. It maps to the same generic components/line component as dygraph — components/line has no renderer-specific branching, and the renderer is resolved from the chartLibrary attribute via sdk.ui. Verified in cloud-frontend: src/charts/index.js has a byType map with a dygraph key and no uplot key, and Chart does if (!Component) return null, so charts render as empty containers with no console error. So enabling uPlot there is a two-line change (uplot: Line in byType, plus the chartLibrary attribute) — not the one-attribute change docs/uplot-migration-progress.md claims.

Verification

  • Full suite green: 184 suites, 1928 passed, 2 skipped.
  • yarn build compiles clean: 537 CJS / 540 ES6.
  • Visual verification across chart types and interactions is done in Storybook via the Chart library toolbar arg.

Notes for review

Squashed from explore/uplot-spike (124 commits), which is left in place unchanged.

The docs/uplot-*.md files are the working record of the migration. uplot-migration-progress.md is the entry point; its stale sections have been corrected in a follow-up commit, with the superseded claims kept struck through rather than deleted so the history stays readable. Open parity items live in uplot-parity-worklist.md (§ "Queued work").

Introduces uplot as a second time-series chart library, reaching feature
parity with dygraph across line, area, stacked, stackedBar, multiBar,
heatmap and sparkline, plus every overlay and interaction the dygraph
renderer supports.

The renderer is opt-in. The shipped default stays chartLibrary: "dygraph"
until the real-dashboard measurement in docs/uplot-migration-progress.md
has been run; flipping it is a one-attribute change because chartLibrary
is now the single selector and chartLibrariesByType only overrides
per-type.

Renderer
- Plot geometry, axis ticks, borders and label widths matched to dygraph,
  and held across resize.
- Stacking: per-value +/- accumulation for diverging stacks, correct draw
  order, ranges and mixed signs, stepped interiors, per-pixel row
  thinning.
- Overlays on draw hooks: alarm, alarmRange, alertTransitions, highlight,
  annotation, proceeded, point markers, the anomaly ribbon and the
  anomaly-rate badge in the y-axis gutter.
- Crosshair on its own overlay canvas, driven by the SDK's synced hoverX
  rather than the local pointer, so cross-chart sync spans both
  renderers.
- Navigation: pan, wheel zoom, select, pinch, dblclick reset, with
  dygraph's thresholds and click semantics.
- Smooth curves via uPlot's built-in spline; a collapsed y-range is
  expanded instead of stalling uPlot's tick search.

SDK
- chartLibrariesByType maps a chart type to a renderer, with
  getRendererForChartType and isTimeSeriesRenderer resolving through it.
- perfMonitor plugin and a render-timing seam, off by default.

Tooling
- Playwright-driven headless benchmark (yarn perf:bench), a browser
  geometry probe and a CPU profile probe.
- Storybook: a chart-library toolbar arg, a side-by-side renderer
  comparison story and a showcase story.

Measured: uPlot is 2.7-3.9x cheaper per render than dygraph at every CPU
throttle level, and cheaper per frame in all 20 rendering cells of the
sweep. Under saturation that converts into frames. Details, caveats and
the retracted earlier numbers are in docs/uplot-migration-progress.md.

Note for consumers: uplot ships as a runtime dependency (~48KB) and its
CSS (uplot/dist/uPlot.min.css) is functional, not cosmetic, so a consumer
must import it.

Squashed from the explore/uplot-spike branch.
The "Remaining work" list was written 2026-07-15 and never revised as the
work landed, so it still described heatmap, the overlays, bars and the
stacked top stroke as missing. All four are done; each is now marked with
the file:line that implements it, and the section points at the parity
worklist for genuinely open items.

Two claims were wrong rather than merely stale:

- Consumers do not need to import uplot/dist/uPlot.min.css. The rules the
  renderer actually uses are inlined in chartContentWrapper.js, and the
  only upstream selectors missing from it are legend and title chrome,
  which never render (legend is disabled, no title is set).

- The flip is not a one-attribute change. In cloud-frontend it takes
  three, two of them permanent, and two fail silently: byType needs a
  uplot entry or every chart renders as an empty container with no
  error, and getMenuChartAttributes hardcodes dygraph per-chart, which
  overrides the root attribute and makes both halves of an A/B measure
  dygraph.

Also records that Playwright is available but only drives Storybook,
refreshes the test and build counts, and drops a local absolute path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant