One element per datapoint has a cost. This page measures it, on your device.
The numbers below are the ones that matter, because they come from the machine you are reading this on. A phone will be several times slower than a laptop, and that difference decides whether this library suits your case far more than any figure we could publish.
Nothing renders until you press a button — a ten-thousand-bar chart is not something to do on page load.
Pick a size to render.
| Type | Points | Build markup | To rendered | To painted | SVG elements |
|---|
The chart tells you when it has drawn. dc-render fires after every render
and carries the number of data elements it drew, so no polling or guessing is needed:
const chart = document.querySelector('#demo');
// Time spent building the markup string
const t0 = performance.now();
chart.innerHTML = buildMarkup(count);
const built = performance.now() - t0;
// The chart redraws itself; dc-render says when
chart.addEventListener('dc-render', e => {
console.log(`drew ${e.detail.count} elements in ${performance.now() - t0}ms`);
}, { once: true });
"To painted" is measured a further two animation frames later, which is the first moment the browser has actually put pixels on screen. It is always the larger number and the more honest one.
Cost is roughly linear in element count. Ten times the data takes something like ten times as long, because every datapoint is a DOM element that has to be parsed, laid out and painted. There is no cliff — but there is no free lunch either.
Element count is the thing to watch, and it varies by chart type. A bar is one element per datapoint; a line is two, because each point draws a marker as well as feeding the path; a scatter is three. Ten thousand scatter points is thirty thousand SVG elements in your document, and that is a memory and layout cost that outlives the render.
Interaction stays fast even when rendering does not. Hover and keyboard navigation are a few milliseconds at ten thousand points, because they are hit-testing an already-drawn tree rather than rebuilding it.
A bar chart runs out of room before it runs out of speed. A bar has to be at least one unit wide, so a 900-unit-wide plot holds about 800 of them and no more. Past that the surplus is drawn beyond the right edge, where it cannot be seen — which is what you would expect when you ask for more bars than there are pixels.
It is worth knowing only because it is otherwise silent, so the library says so:
DC116 reports exactly how many bars fell outside the plot. Widen the chart or
show fewer. Lines and scatters have no such floor — a point is a coordinate rather than a
rectangle that has to fit — which is why the timings above use a line.
For reference, one developer laptop (Chromium, 2026) pressing the buttons above. These are line charts, because a line draws every point it is given at any of these counts — see the note on bar charts below:
| Points | To painted | SVG elements |
|---|---|---|
| 100 | 132 ms | 250 |
| 1,000 | 166 ms | 2,042 |
| 5,000 | 581 ms | 10,036 |
| 10,000 | 1,166 ms | 20,036 |
Those are measured by this page, so pressing the buttons yourself produces the
comparable figure. A separate harness (npm run bench) reports larger numbers
for the same counts because it loads the markup as a fresh HTML document and counts the
parse; this page hands an already-open page a new string of children, which is what a live
update actually costs.
So: a few hundred points is comfortable, a thousand is unremarkable, five thousand is a page you notice loading, and ten thousand is a second or so you have chosen to spend. Beyond that the cost keeps climbing in the same straight line, and at some point a canvas-based library becomes the better tool — this one draws real DOM elements on purpose, and that is the trade.
If a bar chart needs more bars than the plot has units, the answer does not have to be
fewer bars. Give the chart the width the data actually needs and put it in a horizontally
scrolling box. This needs nothing from the library beyond an explicit width — the
<svg> is already width: 100% of its host, so a host wider
than the page simply overflows, and the wrapper scrolls it.
<style>
.chart-scroller { overflow-x: auto; }
/* The chart's own width attribute is viewBox units; this is CSS pixels.
Set both, to the same number, and one unit is one pixel. */
.chart-scroller dc-chart { width: 3000px; }
</style>
<div class="chart-scroller">
<!-- gutter="1": the default 10-unit gap between bars costs more than the
bars themselves at this count, and the layout would compress it away -->
<dc-chart width="3000" height="300" gutter="1">
<!-- 600 bars, comfortably -->
</dc-chart>
</div>
One thing to know before you reach for this: the value axis scrolls away with everything else. Scroll the chart above to the right and the numbers down the left-hand side are gone — the grid lines still give you the shape, but not the magnitude. SVG has no equivalent of a sticky table header, so freezing the axis means drawing it as a second, separate element pinned over the scroller. That is a page-level decision, not something the chart can do for you.
It works well for a dense series someone is meant to pan through, and badly for a chart someone is meant to read at a glance.
dc-render is not only for benchmarks. It fires whenever a chart finishes
drawing, including after the markup changes, which makes it the hook for anything that has
to stay in step with the chart.
<dc-chart id="watched" width="500" height="350">
<dc-bar value="30" label="Q1"></dc-bar>
<dc-bar value="70" label="Q2"></dc-bar>
<dc-bar value="50" label="Q3"></dc-bar>
</dc-chart>
…