Diagnosing Slow Plotly Scatter Charts: When and How to Switch to scattergl for Large Datasets
When Plotly scatter charts lag or crash with >100k points, switch to scattergl, downsample, or set static dimensions. This guide walks through diagnosis, checks, fixes, and escalation steps.
21 Sept 2026, 13:49 UTC

Recognizable Condition
When you supply more than ~100,000 points to a scatter trace, the chart may become unresponsive, lag during pan/zoom, or fail to render at all. In the browser console you might see warnings about Maximum call stack size exceeded or high memory usage values.
Cause / Diagnostic Table
| Possible Cause | Diagnostic Check | Expected Result |
|---|---|---|
SVG‑based rendering (default scatter) |
Inspect trace.type in your data array |
Value is "scatter" |
| Too many points (>100k) | Count trace.x.length (or trace.y.length) |
Value > 100,000 |
| Browser main‑thread blocked by SVG DOM | Use console.time('render') before Plotly.newPlot and console.timeEnd('render') after |
Render time > 2 s |
| WebGL not available | Check navigator.getContext('webgl') or navigator.userAgent for legacy browsers |
Result is null or IE11 string |
Layout reflows due to autosize or dynamic width/height |
Look for layout.autosize and CSS that changes container size on interaction |
True or CSS trigger |
Ordered Checks
- Verify trace type – open the JavaScript object that defines the trace and confirm
type: "scattergl"is used instead of"scatter". - Count points – in the console run
console.log(trace.x.length)to ensure you are above the 100k threshold. - Measure render time – add
console.time('render')beforePlotly.newPlotandconsole.timeEnd('render')after to see the difference. - Confirm WebGL support – execute
console.log(navigator.getContext && navigator.getContext('webgl')). If null, the browser cannot usescattergl. - Check layout reflows – temporarily set
layout.widthandlayout.heightto fixed numbers (e.g., 800) and disableautosize.
Fixes Tied to Findings
- Switch to scattergl – change
typeto"scattergl"and ensure you load the WebGL bundle (e.g.,plotly-gl-distorplotly.min.jsversion ≥ 1.50). - Downsample or aggregate – reduce the point count to <50k by using
LTTB, simple averaging, or a spatial index. - Set static dimensions – use
layout.width: 800, layout.height: 600andautosize: falseto avoid layout thrashing. - Enable staticPlot – for non‑interactive displays set
staticPlot: trueto skip expensive interactivity. - Use hovermode="closest" – reduces the number of hover calculations when many points are present.
- Fallback for unsupported browsers – detect WebGL support and render a simplified
scattertrace if unavailable.
Escalation Criteria
If after applying the above fixes the chart still lags or crashes, consider:
- Server‑side rendering or pre‑aggregated tiles (e.g., using Mapbox GL or deck.gl).
- Switching to a dedicated WebGL library such as
deck.glorthree.jsfor extreme data volumes. - Splitting the dataset into multiple smaller traces and layering them.
Concrete Example
Below is a minimal HTML snippet that demonstrates the performance difference. Replace NUM_POINTS with 250000 to test.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.plot.ly/plotly-2.22.0.min.js"></script>
</head>
<body>
<div id="plot"></div>
<script>
const NUM_POINTS = 250000;
const x = Array.from({length: NUM_POINTS}, (_, i) => i);
const y = Array.from({length: NUM_POINTS}, () => Math.random());
const trace = {
x: x,
y: y,
mode: 'markers',
type: 'scattergl', // try changing to 'scatter' to see the slowdown
marker: {size: 3}
};
const layout = {
width: 800,
height: 600,
autosize: false,
hovermode: 'closest'
};
console.time('render');
Plotly.newPlot('plot', [trace], layout).then(() => console.timeEnd('render'));
</script>
</body>
</html>
Open the page, open the browser console, and observe the render time. Switching type to scatter should noticeably increase the time and degrade interactivity.
Limitations & Practical Check
- scattergl is only available from Plotly.js 1.50 onward. Older releases will ignore the trace type.
- Internet Explorer 11 and some embedded browsers do not support WebGL; in those cases you must fall back to
scatter. - Large datasets may still cause memory exhaustion on low‑end devices; monitor
performance.memory.usedJSHeapSizeif available. - Always test on the target browsers before deploying. Use
navigator.getContext('webgl')to confirm support programmatically.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.