Plotly Scatter vs Scattergl: Choosing a Renderer for Large Datasets
Decide between Plotly's SVG scatter and WebGL scattergl traces: a compact comparison, the real trade-offs, a threshold-gated implementation, and how to validate with your own data.
24 May 2026, 18:29 UTC

Your Plotly chart works fine with a few thousand points, then someone loads a full production extract and the browser tab grinds to a halt. The fix is usually a one-line change: swap go.Scatter for go.Scattergl. But the swap is not free — WebGL traces support a subset of features, and static exports can differ. This guide walks through the decision, the trade-offs, and a way to validate the choice with your own data.
The decision and its constraints
Plotly's standard scatter trace renders every point as an SVG element in the DOM. That gives you full feature support — text labels, error bars, rich hover formatting, crisp vector output — but DOM size grows linearly with point count. Somewhere in the tens of thousands of points, rendering and zoom/pan interactions become noticeably sluggish.
scattergl renders the same kind of trace through WebGL into a canvas instead of the DOM. Interaction stays smooth into the hundreds of thousands or millions of points, but you give up some feature parity, and output is rasterized in some export paths.
Comparison at a glance
| Aspect | scatter (SVG) | scattergl (WebGL) |
|---|---|---|
| Comfortable point count | Up to ~tens of thousands | Hundreds of thousands to millions |
| Feature support | Full: text, error bars, all marker symbols, rich hover | Subset: some marker symbols, text, and hover/selection behaviors differ |
| Output quality | Crisp vector (SVG) in browser and exports | Canvas/raster in browser; static export may differ visually |
| Environment needs | Any browser | WebGL available on the client; can fail in headless or virtualized environments |
| Code change required | Baseline | Drop-in: change the trace constructor |
Trade-offs worth weighing
Feature parity is version-dependent. The exact set of attributes scattergl supports has shifted across Plotly releases. Before committing, check the changelog and reference docs for your pinned version, especially if you rely on per-point text, error bars, or unusual marker symbols.
WebGL is a client-side dependency. If your charts render in a headless server, an old Citrix session, or a locked-down browser, WebGL may be unavailable. Test in the environments your users actually run.
Static export is a separate code path. If you export PNG/PDF via Kaleido (fig.write_image), verify that WebGL traces export with acceptable fidelity — the rendered image may not match the interactive view exactly.
Thresholds are heuristics, not laws. The point at which SVG becomes unusable depends on the user's GPU, browser, and how many traces share the figure. Treat "switch at 50k points" as a starting hypothesis to measure, not a guarantee.
A middle path exists. Rather than switching the whole figure, you can downsample or bin dense data, or render only the dense trace with scattergl while keeping annotations and sparse overlays as SVG traces.
Implementation: gate the switch on point count
Because go.Scattergl accepts the same core arguments as go.Scatter, you can pick the trace type at build time. Run this in your Python environment where Plotly is installed (no special permissions needed):
import plotly.graph_objects as go
WEBGL_THRESHOLD = 50_000 # starting heuristic; tune by measurement
def scatter_trace(x, y, **kwargs):
cls = go.Scattergl if len(x) > WEBGL_THRESHOLD else go.Scatter
return cls(x=x, y=y, mode="markers", **kwargs)
fig = go.Figure(scatter_trace(x=x_data, y=y_data, name="samples"))
fig.show()
Expected check: with a small dataset, fig.data[0].type is "scatter"; above the threshold it is "scattergl". Add a unit test asserting the switch fires at the boundary so a future refactor can't silently break it.
Validate with your real data
- Build the identical figure with both trace types at production-scale volume.
- In the browsers your users run, compare time-to-first-render and zoom/pan responsiveness. Browser dev tools' performance panel gives you concrete numbers.
- Confirm required features behave identically: hover text, marker appearance, legend behavior, and any click/selection handlers.
- If you export static images, run
fig.write_image("out.png")for both variants and compare the results visually. - Adjust
WEBGL_THRESHOLDbased on where SVG actually degrades in your measurements.
Limitations
Feature parity and export behavior vary by Plotly version, so verify against your installed release rather than assuming this guide's comparison holds exactly. The threshold value is hardware-dependent and must be tuned per audience. If WebGL is unavailable in your target environment, scattergl is not an option at all — downsampling or server-side aggregation becomes the fallback.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.