Switching Plotly Scatter Traces to WebGL for Large Datasets
Use go.Scattergl to keep Plotly responsive with tens of thousands of points, then verify static exports before shipping.
17 Nov 2025, 17:43 UTC

Your scatter plot is fast until it is not
Your Plotly dashboard loads, but the moment you hover over a scatter plot with 50,000 points, the browser stutters. Zooming feels sluggish, and the Jupyter notebook tab becomes unresponsive. The problem is usually not the data size alone; it is how Plotly draws the points.
go.Scatter, Plotly's default scatter trace, renders with SVG. That means the browser creates a document object model (DOM) element for every marker and line segment. SVG is excellent for print-quality vector output and detailed styling, but it buckles under tens of thousands of elements. go.Scattergl draws the same data into a single WebGL canvas instead, which keeps the graphics processing unit busy and the main thread free.
Takeaway: if your figure has more than roughly 10,000 points and you mainly need interactive exploration, try go.Scattergl. It is usually a one-line trace-class swap, but you must verify hover templates, marker symbols, and static image export afterward.
What changes when you switch to WebGL
go.Scattergl lives in plotly.graph_objects alongside go.Scatter. It shares most styling attributes, including mode, marker, line, hovertemplate, and customdata. Because the API overlaps, migrating often means changing the class name and nothing else.
WebGL is a browser API for GPU-accelerated graphics. Unlike SVG, it does not create a DOM node per point. Instead it uploads point data to the graphics card and draws everything to one canvas element. That is why interaction stays smooth at scale, but it also means the output is not fully vector-scalable.
Worked example: 50,000 random points
Run this in a Jupyter notebook or Python script. It builds two identical figures, one with SVG and one with WebGL, then prints the trace type so you can confirm which renderer Plotly used.
import numpy as np
import plotly.graph_objects as go
N_POINTS = 50_000
rng = np.random.default_rng(42)
x = rng.normal(size=N_POINTS)
y = rng.normal(size=N_POINTS)
fig_svg = go.Figure(
data=go.Scatter(x=x, y=y, mode="markers", marker=dict(size=4, opacity=0.6))
)
fig_gl = go.Figure(
data=go.Scattergl(x=x, y=y, mode="markers", marker=dict(size=4, opacity=0.6))
)
# Diagnostic check: confirm which renderer Plotly used
print(fig_svg.data[0].type) # scatter
print(fig_gl.data[0].type) # scattergl
fig_gl.show()
Where to run: any Python environment with Plotly installed. No special permissions are required. The only state change is the figure object in memory unless you add a file write.
Expected checks:
fig_gl.data[0].typereturnsscattergl.- If you save the figure to HTML and inspect the source, you should see a canvas element rather than thousands of SVG path and circle tags.
Risks: opening a 50,000-point go.Scatter figure in a browser can freeze the tab. Close the tab or interrupt the notebook kernel if it hangs. Do not assume the two figures look identical; WebGL antialiasing and marker rendering differ slightly from SVG.
Limitations and what to verify
Static export is the biggest gotcha. plotly.io.write_image uses Kaleido to render figures to PNG, PDF, or SVG. Kaleido handles SVG traces natively, but WebGL output is rasterized during export. Marker shapes, line dash patterns, and text labels may not match the on-screen interactive version. If you need print-quality vector figures, export the go.Scatter version or inspect the raster output carefully.
Other unsupported or differently rendered features include certain dash styles and complex marker symbols. WebGL context limits also vary by browser and graphics hardware. A very large dataset can still crash the tab or force a software fallback, which is slower than hardware acceleration.
Avoid mixing many go.Scattergl traces with heavy SVG annotations or shapes. The browser has to composite the WebGL canvas with the SVG document object model layer, and too many annotations can erase the performance gain.
Practical check: after switching, export both versions with write_image and compare them side by side. Open the interactive figure in your target browser and pan, zoom, and hover. If hover text or custom data stops working, fall back to go.Scatter or simplify the trace.
Choosing between SVG and WebGL
| Factor | go.Scatter (SVG) | go.Scattergl (WebGL) |
|---|---|---|
| Best for | Under roughly 10,000 points, print export | Tens of thousands of points, dashboards |
| Rendering | DOM elements per point | Single canvas |
| Static export | Clean SVG or PDF via Kaleido | Rasterized; verify fidelity |
| Hover and styling | Full support | Mostly supported; test dashes and symbols |
Rollback: because this is a trace-class change in code, rollback means replacing go.Scattergl with go.Scatter in your figure definition and redeploying. There is no database migration or configuration file to revert.
Closing
Start with go.Scattergl when your interaction feels sluggish, but treat it as a performance optimization, not a drop-in replacement. Verify the trace type, check the canvas in your exported HTML, and compare static images before you ship. If your dataset stays under ten thousand points or your report requires vector output, the SVG trace is still the better default.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.