Repository navigation
Conversation
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.
Tests asserting formatted timestamps assumed UTC and failed on machines in other timezones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
novykh
force-pushed
the
feat/uplot-renderer
branch
from
September 30, 2026 07:55
d4323dc to
0969af9
Compare
uPlot 1.6 keeps the pixel ratio on the constructor (uPlot.pxRatio), not on chart instances, so every self.pxRatio read fell back to 1. On high-density screens this doubled the y-axis label density and mis-sized plot budgets, bar outlines, hover dots, zoom math and the anomaly/annotation markers.
- uPlot charts get their own series palette ([light, dark] pairs) that clears colour-blind separation on adjacent series; red stays reserved for alerts. dygraph keeps dimensionColors.js, and the container colour memo is keyed per palette so both renderers can sit in one container. - Horizontal grid only, no tick marks or axis border, muted axis labels (themeAxisLabelColor), 2px lines and a cached area gradient. - Y labels at least 30px apart, and ticks rounded past the scale range are dropped so edge labels are no longer clipped. - Design refresh story compares both renderers; showcase payload helpers move to helpers/makeWavePayload for reuse.
Static Storybook mockups (Mockups/*) for the redesign: legend and hover recipes, the modern card (attention headline, adaptive legend, quiet chrome) and the modern treatment of the other chart types, tooltip and dropdown. The plan records the approved decisions, the delivery contract (designFlavour "modern") and where every current function lives.
useIsModern gates everything rendered under designFlavour "modern"; the default and minimal flavours keep their current output. Shared tokens hold the numerals font, radii and the adaptive legend choice by width and series count.
Under designFlavour "modern" the d3pie chart renders an SVG donut with gapped slices, a centre total that switches to the hovered slice, and a legend with value and share; hovering a slice or row dims the rest. It keeps the library's top five plus grouped remainder and follows hover, hidden dimensions and units. The d3pie library skips its own drawing in this flavour; default and minimal render as before.
Under designFlavour "modern" the bars chart shows ranked horizontal bars with name, value and units and an emphasised top bar. It ranks by value unless dimensionsSort is set, keeps hover re-ranking, hidden dimensions, windowing and, with cols "full", non-empty anomaly rates and annotation flags. Default and minimal keep the dimensions grid.
Under designFlavour "modern" the gauge renders as SVG: a 240 degree rounded track, a gradient value arc that takes the zone colour inside a warning or critical zone, zone segments on an outer ring built from gaugeThresholds, a knob at the value, the value and units in the centre, a sparkline of the window, min and max labels, and a Warning / Critical / within-thresholds pill driven by the value zone and payload alerts. The gauge library skips the canvas gauge under modern but keeps mounting, the value range, yAxisChange and rendered events. Default and minimal flavours keep the canvas gauge unchanged.
Under designFlavour "modern" the table dropdowns (nodes, instances, dimensions, labels, group by) get a search field with a count placeholder, share-of-volume bars, muted zero anomaly rates, alert pills for non-zero counts and a footer with the selection count, Clear, Reset and Apply. Secondary columns (Unique, Instances, Metrics, Min, Avg, Max, Range) stay reachable behind a column toggle whose choice is kept in the optional filterColumnVisibility attribute. Single select dropdowns (aggregate, time aggregation, context scope) get rounded modern items. Default and minimal flavours are unchanged.
…tention state Under designFlavour "modern" the number chart shows the value in the numerals font, its units, the change against the window mean, a sparkline of the window and an attention pill when alerts are raised. Value thresholds from gaugeThresholds colour the value. Other flavours render as before.
Under designFlavour "modern" the easy pie library skips the canvas and the component draws a thin SVG ring with the value in the centre, using the same row and value range as the canvas ring. The arc stays neutral unless an alert is raised or a value threshold is crossed. Other flavours keep the canvas ring.
…malies
Under designFlavour "modern" the uPlot overlays follow the redesign
mockups; the default and minimal flavours draw exactly as before.
- New opt-in "threshold" overlay ({ warning, critical, direction }):
dashed lines with a labelled pill on the left edge and a faint band
above each threshold; close labels slide right; a critical value below
the warning one bands downwards. No producer sends threshold values
yet, so nothing draws until a consumer adds the overlay.
- Annotations hang a small flag from the plot top on a thin rule, so no
marker covers the time axis labels.
- The anomaly ribbon becomes a strip of small rounded marks along the
top edge whose opacity follows the anomaly rate.
- Alarm, alarm range and alert transition overlays use themed alert
colours with thinner lines and lighter tints.
- Alert colours are new [light, dark] theme attributes.
Under designFlavour "modern" the optional focusedDimensionId attribute fades every other series (lines, areas, stacked and bars) so the series under the legend pointer stands out. Other flavours ignore it.
Under designFlavour "modern" the line card picks its legend from the card
width and series count: labels at the line ends, one wrapping line, a side
table (Last or At cursor, Mean, Max, anomaly rate; sortable headers,
scrolling with a fade) or none on tiles. A visible legend follows the
hovered point and replaces the popover; without one a compact tooltip
shows up to six sorted rows, anomaly and annotation columns only when
non-empty, and the granularity. Hovering an entry focuses its series.
Click and Shift/Ctrl/Cmd+click toggling, dimension sort, the heatmap
legend, highlight range zoom, latest/hovered time and the drawer
("Compare, drill down, correlate") stay reachable. The optional
legendLayout attribute forces a layout. Default and minimal flavours
render as before.
Under designFlavour "modern" the line card header becomes a title row with hover-revealed actions (filters, the existing toolbox elements, More), a one-line scope summary that opens the existing FilterToolbox and carries the loading, error and no-data states, and an attention headline built from the alert summary and alarm overlays. The More menu holds the navigation modes, zoom in/out/reset, the anomaly and annotation layers, dimension sort, the existing settings tabs, chart info and reload. The floating navigation toolbox gives way to a reset chip while the window is zoomed. Settings content accepts an optional initial tab. Default and minimal flavours render as before.
Under designFlavour "modern" the table shows a status dot per row from the node or instance alert counts, a small meter in percentage cells, a trend line in the other value cells, calmer row rules and a stronger sorted header. Group boxes draw rounded boxes from a single-hue sequential scale, lay groups out as labelled rows, show a stepped scale legend and highlight values at or above the new optional groupBoxesThreshold attribute in the error colour. The default and minimal flavours render as before.
The modern header opens the filter bar from its scope line, so the line card no longer renders the fixed bar as well. Without a header (hasHeader false) the fixed bar stays, so filters remain reachable.
Readouts use 0-2 decimals by converted magnitude instead of the auto-scaled maximum of 4, and keep staticFractionDigits when the user set it.
… same order as main
…t of scroll anchoring
…hen the window has anomalies
…ns to the last live window
…-aware popover shadows
…payloads under fixtures
…onents and uses netdata-ui props and tokens
novykh
force-pushed
the
feat/uplot-renderer
branch
from
October 2, 2026 19:40
61b029d to
33db114
Compare
kapantzak
previously approved these changes
Oct 2, 2026
…ic; +N more opens a dimension drop
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds uPlot as a second time-series chart library, at feature parity with dygraph.
The default does not change
chartLibrarystill ships as"dygraph". uPlot is opt-in — setchartLibrary: "uplot"on the SDK root, or map a single type withchartLibrariesByType.Flipping the default later is a one-attribute change:
chartLibraryis now the single selector,chartLibrariesByTypedefaults to{}and only overrides per-type, andgetRendererForChartTypefalls back tochartLibrary. The flip is deliberately not in this PR — it is gated on the real-dashboard measurement protocol indocs/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
drawhooks: 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
perfMonitorSDK 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 underscripts/.src/chartLibraries/helpers/— colour, overlay-area and dimension-visibility helpers now shared by both renderers.src/helpers/deepMergeremoved; 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
uplotis a new runtime dependency (~48KB), so consumers resolve it transitively — no change needed in theirpackage.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 insrc/components/line/chartContentWrapper.js, scoped to the chart container. Upstream's remaining selectors are all legend/title chrome, and the renderer setslegend: { show: false }and no title, souplot/dist/uPlot.min.cssdoes not need importing.A consumer that maps
chartLibraryto its own React component must add auplotentry. It maps to the same genericcomponents/linecomponent as dygraph —components/linehas no renderer-specific branching, and the renderer is resolved from thechartLibraryattribute viasdk.ui. Verified in cloud-frontend:src/charts/index.jshas abyTypemap with adygraphkey and nouplotkey, andChartdoesif (!Component) return null, so charts render as empty containers with no console error. So enabling uPlot there is a two-line change (uplot: LineinbyType, plus thechartLibraryattribute) — not the one-attribute changedocs/uplot-migration-progress.mdclaims.Verification
yarn buildcompiles clean: 537 CJS / 540 ES6.Notes for review
Squashed from
explore/uplot-spike(124 commits), which is left in place unchanged.The
docs/uplot-*.mdfiles are the working record of the migration.uplot-migration-progress.mdis 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 inuplot-parity-worklist.md(§ "Queued work").