Skip to main content

Charts benchmarks: render performance

The pondjs comparison measures the core library's data operations. This page measures the @pond-ts/charts render path — how fast a chart mounts, draws, and stays responsive — against two widely-used canvas charting libraries, uPlot and Chart.js.

The workload is uPlot's own bench: a real dstat capture of 55,550 timestamps × 3 series = 166,650 points, drawn on a 1920×600 chart with two y-scales, then swept with the mouse for 10 s. Each library runs the identical workload through the same harness.

Single-machine medians (Apple M1 Pro, headless Chromium, ~120 Hz display cap). Treat the shape as the signal — which library is fast on which axis — not the absolute milliseconds. Re-run instructions are in packages/charts/perf/external/.

Results​

MetricuPlotChart.jspond-charts
Chart create → painted14.5 ms36.4 ms11.4 ms
Per-layer draw (166k pts)——2.5 ms ¹
Mousemove, 10 s script107 ms502 ms352 ms ²
Mousemove FPS120120120
Heap, peak / final13 / 3 MB21 / 10 MB24 / 7 MB
Total load script25 ms40 ms62 ms ³

¹ pond's canvas draw of all 166,650 points, decimation off. With decimation on (≈7,300 points/layer after M4 pixel-bucketing) it's 1.9 ms. uPlot/Chart.js don't expose an equivalent per-layer number. ² On this two-axis chart; the per-hover cost scales with the number of axes / rows. Both modes hold the 120 fps display cap. ³ Includes React boot — the single biggest constant pond pays that the vanilla-canvas libraries don't.

Where pond is strong​

  • Chart create is the fastest of the three — 11.4 ms vs uPlot's 14.5 and Chart.js's 36, and it barely moves whether it strokes all 166k points or the decimated ~7k. TimeSeries keeps data in typed-array columns with a monotonic x, so the first paint reads straight from the buffers.
  • Per-frame draw is ~1 ms/layer. Stroking 166k points takes 2.5 ms for all three overlaid series (2.5 ms total, not per series); with M4 decimation on, 1.9 ms. This is the metric that governs pan/zoom smoothness, and it holds the frame budget with room to spare.
  • Frame rate is capped at the display refresh (120 fps) during the mouse sweep — pointer interaction never drops frames on this workload.

The honest weak columns​

pond-charts is a React component library built on d3-scale / d3-shape, and that shows up where a hand-tuned vanilla-canvas library doesn't pay the same constants:

  • Bundle size. The charting layer is competitive, but the whole stack is larger:

    Packageminifiedgzipped
    uPlot (everything)51 KB22 KB
    Chart.js (everything)203 KB68 KB
    @pond-ts/charts + d3149 KB50 KB
    pond-ts core344 KB94 KB
    react + react-dom188 KB58 KB

    A pond app already ships React and pond-ts, so the marginal cost of adding charts is the 50 KB gzipped charts+d3 row — but the all-in footprint is bigger than uPlot's.

  • Load script (~62 ms) and heap peak (~24 MB) are both higher, driven by React boot and by materializing the columnar TimeSeries (row-array intake + Float64Array buffers) before the first draw. Final heap (7 MB) settles between uPlot (3 MB) and Chart.js (10 MB).

Reading it​

pond isn't trying to beat uPlot on bytes or boot — it's a different trade. What you buy for the larger footprint is the whole pond-ts data model (streaming LiveSeries, decimation, trading-time calendars, typed schemas) composed declaratively as React components. On the metrics that govern the interactive experience once mounted — create time and per-frame draw — pond is at or ahead of uPlot here; on static footprint it is not.

For how the draw path scales to millions of points and how pond compares to a GPU-accelerated engine, see Charts benchmarks: scaling to millions of points.