There is no single fastest JavaScript chart library. We measured 9 setups rendering identical data in headless Chromium and the ranking flips with the chart shape: uPlot renders a 100,000-point line in 9 ms but takes 6 seconds on a 50,000-point scatter, while Chart.js leads scatter at 89 ms. The ApexCharts 6 canvas renderer draws that 100,000-point line in 29 ms, a shade ahead of Chart.js, and replaces a full 50,000-point dataset in 3.9 ms. Every number is a 5-trial median from a pinned, scripted harness, measured on 20 July 2026 with ApexCharts 6.3.0, and the raw dataset is downloadable.

Here are the results. Toggle the scenarios; the bars are the measured medians:

Measured 20 July 2026. One node per point is where SVG DOM cost explodes and the canvas renderer pays off. Chart.js leads; the ApexCharts canvas renderer holds second, and the remaining gap is per-marker attribute weight. uPlot has no first-class scatter mode; its points-only configuration is measured as disclosed in the methodology. Apple M4 Pro, headless Chromium 147, 5-trial medians. apexcharts@6.3.0, chart.js@4.5.1, echarts@6.1.0, uplot@1.6.32, highcharts@13.0.0, recharts@3.9.2, fusioncharts@4.2.2. Raw dataset: render-benchmark-2026.json.

Key takeaways

  • Chart shape decides the winner, not the library's reputation. uPlot owns lines (9 ms at 100k), Chart.js owns scatter (89 ms at 50k). Nobody won both.
  • SVG was never the problem for lines. A single-series line is one path element; ApexCharts renders 100,000 points in 41 ms as SVG. The canvas renderer trims that to 29 ms, ahead of Chart.js's 33.
  • Node-per-point shapes are where SVG hurts. At 20,000 scatter points, ApexCharts SVG builds 20,102 DOM nodes; the canvas renderer builds 104 and is 3.9x faster.
  • The ApexCharts 6 canvas renderer holds second place at 50k scatter (120 ms), behind only Chart.js, ahead of ECharts, Highcharts, uPlot, Recharts, and FusionCharts.
  • Update throughput spans a wide range. Replacing a full 50,000-point line, uPlot does ~2,560 updates/sec, the ApexCharts canvas renderer ~256, Chart.js ~232, and Highcharts ~13. uPlot is in a class of its own; ApexCharts and Chart.js are close.
  • Below 10,000 points, do not choose a library on speed. Everything rendered in well under a quarter of a second.

What did we measure, and how?

The task: render a single-series chart of a deterministic random walk (identical values for every library, seeded PRNG) into an 800x400 container, with animations disabled, in headless Chromium 147 driven by Playwright on an Apple M4 Pro.

The clock: starts when the chart is constructed, stops after the library's own completion signal (render promise, finished event, or synchronous return) plus a double requestAnimationFrame, which guarantees a painted frame. Each cell is the median of 5 trials, each trial in a fresh browser context. A 60-second timeout would mark a DNF; nothing hit it. Timing to a painted frame is what makes SVG and canvas comparable (SVG defers rasterization past the API return), but it gives the method a resolution floor equal to the double-rAF barrier, about 16.7 ms in this environment: cells in the 10 to 20 ms range mean "within the frame budget" rather than precise CPU cost, which is why the smallest sizes and the update row cluster there. The update-throughput probe below removes that floor where it matters.

The scenarios:

ScenarioSizesWhy it matters
Line1k, 10k, 50k, 100k pointsOne path element in SVG; tests raw geometry throughput
Scatter5k, 20k, 50k pointsOne element per point in SVG; tests DOM-node cost
Update10k line, 10 full data replacesTests the steady-state redraw loop, not the first paint

The contestants: ApexCharts 6.3.0 in SVG mode and in its canvas renderer (measured as two configurations), Chart.js 4.5, ECharts 6.1, uPlot 1.6, Highcharts 13 with and without its opt-in Boost module, Recharts 3.9 on React 18, and FusionCharts 4.2. Exact versions are pinned in the harness manifest, and the dataset embeds the full environment description. Every table below is from one session on 20 July 2026, all libraries measured on the same machine.

The full numbers

Initial render, median milliseconds. Lower is better.

Line (single path):

Library1k10k50k100k
uPlot10999
ApexCharts 6 (Canvas)16182629
Chart.js16192433
ApexCharts 6 (SVG)15183141
ECharts20253547
Highcharts182863107
Highcharts + Boost157162174188
Recharts2447138264
FusionCharts80127216317

Scatter (node per point in SVG):

Library5k20k50k
Chart.js244789
ApexCharts 6 (Canvas)2959120
Highcharts + Boost163174182
Highcharts49144307
ApexCharts 6 (SVG)64233527
Recharts2106981,820
ECharts1254661,923
FusionCharts1468484,596
uPlot246525,962

Update (10,000-point line, median of 10 full data replaces): 16 ms for Chart.js, ECharts, and both ApexCharts renderers, plus Highcharts + Boost; 17 ms for uPlot; 25 ms for Highcharts and Recharts; 104 ms for FusionCharts. This frame-paced row now reads the same for ApexCharts as for the incremental redrawers, but it always did (see the note below); the real difference shows up only when you remove the paint pacing.

A note on reading that row: because our clock stops only at a painted frame, its resolution floor is the double-rAF paint barrier, about 16.7 ms in this environment. Every library whose update work fits inside that window reads as 16 to 17 ms; the occasional 8 ms cycle in the raw data (Chart.js, ECharts) shows the real work is well under it. So this row says who stays inside the frame budget, not how much headroom each library has. To answer that, we ran a second probe.

How fast can each library actually update?

We removed the paint pacing and measured raw update throughput: a sequential loop of full data replaces for one second, each update awaited to the library's own completion signal, on the six mainline configs. Median of two runs:

ConfigUpdates/sec, 10k lineUpdates/sec, 50k lineCost per update at 50k
uPlot~7,000~2,5600.4 ms
ApexCharts 6 (Canvas)~576~2563.9 ms
Chart.js~915~2324.3 ms
ApexCharts 6 (SVG)~377~8611.6 ms
ECharts~367~8112.3 ms
Highcharts~66~1377 ms

The frame-floored row above hid a wide spread. The ApexCharts canvas renderer replaces a full 50,000-point line in 3.9 ms, second only to uPlot and just ahead of Chart.js, with the SVG renderer in the same band as ECharts. Two honest caveats. First, uPlot is an order of magnitude ahead: at 50k it replaces the data and redraws ~2,560 times a second where ApexCharts manages ~256. It is built for exactly this and nothing else. Second, Chart.js still edges the ApexCharts canvas renderer at 10k (~915 vs ~576 updates/sec); ApexCharts only pulls ahead once the per-update work is large enough to dwarf its fixed overhead. Under a companion fps probe (one update issued per frame), uPlot, Chart.js, and the ApexCharts canvas renderer all sustained the environment's full ~120 Hz at 50k points, while the ApexCharts SVG renderer held ~85 and Highcharts dropped to ~14. The probe script and its raw output ship with the harness.

Why does the winner change with the chart shape?

Because the cost model changes. A single-series SVG line is one <path> element regardless of point count; the browser parses one long geometry string and rasterizes it once. That is why ApexCharts SVG holds 41 ms at 100,000 points and why the SVG-vs-canvas gap on lines is modest: one path was never the DOM's problem. What costs time at that size is the geometry pipeline itself, the per-point work that builds the path before the browser ever sees it.

A scatter chart is the opposite. In SVG, every point becomes a DOM element that the browser must create, style, lay out, and composite. We counted the nodes in each mounted chart: at 20,000 scatter points, ApexCharts SVG creates 20,102 elements, Recharts creates 60,082 (three per point), Highcharts creates 20,066, and the ApexCharts canvas renderer creates 104, because 20,000 points become pixels on one <canvas>, not nodes. The node counts predict the timings almost perfectly.

Two results deserve an honest note:

  • Highcharts SVG still leads ApexCharts SVG at scatter (307 vs 527 ms at 50k). Both create one node per point, so the difference is per-marker attribute weight: our markers carry more per-element state for interactivity, and that is what still separates them on the SVG side.
  • uPlot collapses on scatter (5,962 ms at 50k) while being untouchable on lines. uPlot is a time-series line library; it has no first-class scatter mode, and its points-only configuration draws each point as an individual canvas arc. This is not what it is for, and it shows.

What does the canvas renderer change in ApexCharts 6?

One import and one option:

import ApexCharts from 'apexcharts'
import 'apexcharts/features/renderer-canvas'

const chart = new ApexCharts(el, {
  chart: {
    type: 'scatter',
    renderer: 'auto', // 'svg' (default) | 'canvas' | 'auto'
  },
  // ...
})

With 'auto', the series layer switches to canvas above chart.rendererThreshold points (default 8,000) and stays SVG below it. Axes, gridlines, labels, legend, and tooltips remain SVG in both modes, so themes, CSS, and interactivity are unchanged, and chart.getActiveRenderer() tells you which backend drew the series. The full option reference is at chart.renderer.

In the benchmark, the canvas renderer beats our own SVG mode by 2.2x at 5,000 scatter points, 3.9x at 20,000, and 4.4x at 50,000 (120 vs 527 ms), and on lines its numeric fast path skips SVG path-string parsing entirely for a 41-to-29 ms edge at 100k, enough to put it ahead of Chart.js. For the deeper background on the two rendering models, see SVG vs Canvas for charts.

What about Highcharts Boost, ECharts large mode, and other accelerators?

Several libraries have opt-in fast paths, and fairness requires saying so:

  • Highcharts Boost (measured) is a WebGL module. It has a roughly 160 to 190 ms floor at every size we tested, the cost of standing up a WebGL context, and in exchange it barely scales with point count: 182 ms at 50k scatter where standard Highcharts takes 307 ms. Below about 20,000 points it is slower than not using it.
  • ECharts has a large: true series mode that trades some rendering fidelity for speed. We measured the default configuration, which is what you get out of the box; its 1,923 ms at 50k scatter would improve with large enabled.
  • Recharts timings include mounting the React component tree, because that is how Recharts is used. Its line numbers (one path, 264 ms at 100k) are respectable; its scatter cost is React reconciliation over 60,000 elements.
  • FusionCharts parses its JSON data format inside render; that parsing is part of its API shape, so it is part of its number.

When should you not care about any of this?

Most of the time. At 1,000 points, the slowest library we measured (FusionCharts, 80 ms) is still faster than a blink, and at 10,000 points everything except the WebGL floor of Boost is under 210 ms. If your charts hold hundreds or a few thousand points, pick a library on features, API, and maintenance, not on this table.

The numbers start to matter at tens of thousands of points, and above a few hundred thousand the right answer stops being a faster renderer and becomes rendering less: downsample to roughly the pixel width of your plot. ApexCharts ships an LTTB reducer for exactly that (chart.dataReducer), and you can see it running in the large-dataset downsampling demo. A 4,000,000-point series drawn into an 800-pixel-wide chart cannot show more than a few thousand points of information anyway.

How to reproduce this

The harness is a static page plus a Playwright script: pinned library versions, a seeded data generator so every library draws identical values, per-library completion signals, fresh browser context per trial, and a double-rAF paint barrier before the clock stops. The published dataset with every trial, node count, and the full environment description is at /data/render-benchmark-2026.json. Numbers were measured on 20 July 2026 with ApexCharts 6.3.0, all libraries in one session; when a library ships a major renderer change, we rerun and update this post, as we do with our annual State of JavaScript Charting survey.

Summary

Render speed in the browser is a question about shapes, not brands. Lines are cheap everywhere because SVG collapses them into a single path, and uPlot turns that into an art form at 9 ms for 100,000 points. Scatter charts are the real test, and they are decided by how many DOM nodes a library creates: zero for canvas renderers, one to three per point for SVG ones. The canvas renderer in ApexCharts 6 exists precisely for that case, holding second place at 120 ms for 50,000 points, 4.4x ahead of our own SVG mode, enabled by one import and one option, while on lines it draws 100,000 points in 29 ms, a shade ahead of Chart.js. Where you sit under 10,000 points, none of this should drive your choice; above it, match the renderer to the shape of your data, and downsample before you brute-force.

Frequently asked questions

What is the fastest JavaScript chart library?

It depends on the chart shape. In our 2026 measurements, uPlot rendered a 100,000-point line in 9 ms, faster than everything else by 3x or more. For scatter charts, where each point is drawn individually, Chart.js led at 89 ms for 50,000 points, with the ApexCharts 6 canvas renderer second at 120 ms. On lines the ApexCharts canvas renderer draws 100,000 points in 29 ms, ahead of Chart.js's 33. No library won every scenario, so match the library to the shape and size of your data.

Is canvas faster than SVG for charts?

Mostly when the chart creates many DOM nodes. A single-series line is one SVG path element no matter how many points it holds; ApexCharts 6.3 renders a 100,000-point line in 41 ms as SVG and 29 ms as canvas, a modest gap. A scatter chart creates a node per point in SVG (20,102 nodes at 20,000 points), and there the canvas renderer was 3.9x faster in the same library.

How do I enable the canvas renderer in ApexCharts?

ApexCharts 6 ships an opt-in canvas series renderer. Import 'apexcharts/features/renderer-canvas' once, then set chart.renderer to 'canvas', or to 'auto' to switch automatically above chart.rendererThreshold points (default 8000). Axes, labels, and legend stay SVG; only the series layer is drawn to canvas, so styling and interactivity behave the same.

How many data points can a browser chart handle?

More than most dashboards need. Every setup we measured rendered a 100,000-point line in under 320 ms, and node-per-point SVG scatter stayed well under a second to 50,000 points in the leaner SVG implementations. The practical ceiling is not the first render but interaction: hit-testing, tooltips, and re-renders over huge node counts. Downsampling to roughly the visible pixel width is still the right call above a few hundred thousand points.

Do these benchmark numbers apply to my app?

Treat them as relative, not absolute. They come from one machine (Apple M4 Pro, headless Chromium 147) with animations off and a single series. Your absolute times will differ, but the ordering and the scaling behavior (flat vs linear vs superlinear growth in point count) travel well. The harness is pinned and scripted, so you can rerun it on your own hardware.

Why is my chart slow with only a few thousand points?

Below about 10,000 points, every library we measured rendered in under 210 ms, so a slow chart at that size is usually configuration rather than the library: animations left on for large series, markers enabled on every point of a dense line, per-point labels, or re-rendering the whole chart on every state change instead of updating data. Check those before switching libraries.