Benchmarks — an honest look

Most libraries claim to be "optimized for large datasets." Here are real, reproducible numbers instead — including the cases where a competitor is faster. We would rather you trust the comparison than win it.

Every number below is best-of-repeated-runs render time (one fresh page per measurement) in headless Chromium at 900×600, comparing CanvasXpress 68.1 against Plotly 2.35.2 (SVG scatter and WebGL scattergl), ECharts 5.5 (large canvas mode), Chart.js 4.4, and deck.gl 8.9 (WebGL). All timed the same way — wall-clock from construction to render complete. JS-heap deltas are Chrome-only and approximate.

Don't take our word for it — run the benchmark live in your own browser (CanvasXpress vs Plotly SVG vs Plotly WebGL, 10k–1M points). For a feature-by-feature comparison, see the library comparisons.

Scatter — six libraries, same data

The same random scatter rendered by six libraries across three technologies — SVG/DOM, 2D canvas, and WebGL/GPU. All timed identically: wall-clock from construction to render complete. We show the numbers as they fall, including where CanvasXpress is not the fastest.

Points CanvasXpress
canvas
ECharts
canvas
Chart.js
canvas
Plotly SVG Plotly WebGL deck.gl
WebGL
10,00059 ms25 ms28 ms85 ms84 ms28 ms
100,000185 ms53 ms151 ms580 ms122 ms37 ms
500,000741 ms163 ms2,724 ms246 ms51 ms
1,000,0001,744 ms305 ms5,318 ms374 ms79 ms

Read honestly: for raw scatter throughput, CanvasXpress is not the fastest. A GPU library (deck.gl, ~79 ms at 1M) leads, and an optimized canvas path (ECharts large mode, ~305 ms) and Plotly's WebGL (~374 ms) also beat it; CanvasXpress (~1.7 s) only clears Plotly's SVG. Notably it is slower even though it decimates to a handful of representative marks while the others draw every point — so the cost is not drawing, it is CanvasXpress's data pipeline. (Chart.js is not built for high volume and is omitted above 100k.)

Why spend that time? Because CanvasXpress is a visualization and analysis engine, not a pure renderer — most of its time builds an interactive data model (see below). If your need is purely drawing a million points fast, deck.gl or ECharts is the better tool, and we say so. A WebGL data layer for CanvasXpress is planned; when it lands this table gets rerun, not rewritten.

Note on D3.js: it is deliberately absent — D3 is a low-level toolkit for building visualizations from primitives, not a charting library with a scatter to call, so timing it would mean benchmarking a hand-written implementation, not the library. The D3 comparison covers that distinction.

Full-fidelity scatter — every point drawn

The table above uses CanvasXpress's automatic density decimation (one representative per pixel cell), which is the right default. When a figure genuinely needs every point drawn — a spread embedding coloured by a continuous value, where each point matters — the gap narrows but Plotly's WebGL still leads:

Points (all drawn) CanvasXpress (2D canvas) Plotly (scattergl / WebGL)
150,000 (107,809 marks)959 ms · 179 MB362 ms · 93 MB

This is the case a WebGL data layer for CanvasXpress targets. It is exactly like-for-like — both render all points — so it is the honest number to watch as that work lands.

What the cold-render time actually buys

A first render is not just "where does each point go." Most of CanvasXpress's cold time is spent building an analytical model of the data — and that is the difference that matters, not the pixels.

Of the ~1 second to first-render the 150k full-fidelity scatter above, only about a third is the actual draw (~300 ms — the part a WebGL layer would accelerate). The other two-thirds (~600 ms) is data work: detecting column types, indexing samples and variables, computing ranges, building the density structure, and freezing the data model. That work makes filtering, grouping, clustering, recolouring, hover, cross-plot broadcast, and reproducible export immediate once the chart is up — because the model is already there.

A thin GPU renderer is fast at first paint precisely because it does not do this — it uploads coordinates and draws. Ask it to filter a category, recolour by a new field, cluster, or hand back a figure that round-trips, and that work either happens later, on you, or isn't available. So the honest reading of the tables above: on first paint CanvasXpress pays up front for a model competitors don't build; on every interaction after, that model is already paid for. WebGL is on our roadmap for the draw third — it will not (and is not meant to) remove the analysis, which is the point of the library.

It loads more than it plots

There is a second cost the tables don't show, and it is the one that most sets CanvasXpress apart: it ingests the whole dataset, not only the columns the current chart happens to draw. Extra variables, sample and variable annotations, factor levels, and metadata all come in and stay live — available for analysis even though they were never plotted.

A pure renderer takes the two or three arrays it needs for the marks on screen and discards the rest; ask it about a column you didn't map and it never had it. CanvasXpress keeps the full table indexed behind the figure, so you can colour or group by a variable that isn't on either axis, filter on an annotation the chart never showed, compute a new field, cluster on the unplotted dimensions, pivot the same data into a different chart, or export the analysed result — all without reloading or re-sending the data. The chart is a view onto a data model, not the data itself.

That is why the honest comparison isn't only "how fast are the pixels." Part of the cold-render time above buys a queryable, analysable copy of everything you handed the library, plotted or not — which is a different product from a fast draw of the subset you chose to show.

Where another library is the stronger choice

Where CanvasXpress is the stronger choice

How to read (and trust) these numbers

CanvasXpress timing is the engine's own reported render total (excluding harness overhead); Plotly timing is the Plotly.newPlot wall-clock. Each measurement uses a fresh page so an accumulated JS heap can't inflate the result. The exact fixtures, scales, and method are fixed and deterministic, and the raw results are versioned alongside the figures they describe. These numbers were measured on the date shown on this page against the versions named above; they are rerun as the engine changes rather than quoted once and forgotten.