Rendering backends
d3gl renders the same projected-and-tessellated Scene through
one of several backends. They are interchangeable: geometry is built once (backend-independent),
and each backend turns it into pixels (or vector nodes) its own way. Pick one with the backend
option on geoMap / plot (and the React <GeoMap> component):
import { geoMap } from "@mapequation/d3gl/map";
const map = geoMap(host, { width, height, projection, backend: "webgl" }); // the defaultbackend defaults to "webgl" when omitted.
The backends
Section titled “The backends”| Backend | Startup | Best for | Export |
|---|---|---|---|
"webgl" (default) | One-time GPU device creation (can be 100s of ms cold) | Large/dense scenes, smooth pan/zoom, the GPU globe | toPNG() + toSVG() |
"canvas" | Instant (synchronous) | Small/medium scenes, fastest first paint, no GPU dependency | toPNG() + toSVG() |
"svg" | Instant (synchronous) | Vector output on screen, print, hand-editable DOM | toSVG() |
"auto" | Instant first paint, then upgrades | The best of canvas + webgl — see below | toPNG() + toSVG() |
All four share the same engine API (layer, recolor, enableZoom, setTransform, pick,
setProjection, …); switching backend never changes how you drive the map.
auto — canvas first, WebGL when ready
Section titled “auto — canvas first, WebGL when ready”WebGL is the best backend for an interactive map, but creating its GPU device has a fixed cost that delays the first paint — noticeable on a cold load, especially where the browser spins up a GPU process. Canvas2D has effectively zero startup but is slower for large or frequently-redrawn scenes.
backend: "auto" gives you both:
- It installs the Canvas2D backend synchronously and paints immediately.
- In the background it creates the WebGL device and, once ready, swaps to it transparently — preserving your layers, colors, view transform, and interaction.
- If WebGL is unavailable (or device creation fails), it stays on Canvas and logs a warning. The map keeps working.
const map = geoMap(host, { width, height, projection, backend: "auto" });map.layer("ocean", [{ type: "Sphere" }], { fill: "#d4e6f5" });map.layer("land", [world.land], { fill: "#e3e6ea" });map.enableZoom([1, 8]);
// whenReady() resolves at the CANVAS first paint (early) — not when WebGL is ready.await map.whenReady();// The map is already visible and interactive here; the WebGL upgrade happens// transparently a beat later.The trade-off is a brief visual change when WebGL takes over (Canvas and WebGL anti-alias
slightly differently). For most maps this is unnoticeable; if you need pixel-stable rendering
from the first frame, choose "webgl" or "canvas" explicitly.
Large inputs skip the canvas placeholder
Section titled “Large inputs skip the canvas placeholder”The canvas phase only pays for itself while it is cheaper than the WebGL device it is bridging.
Content that a vector backend has to draw one shape at a time — a network() graph, a
decluttered plot.points() layer — costs far more on Canvas2D than the whole upgrade does, so
above ~10,000 elements the engine withholds it from the placeholder and lets the incoming
WebGL backend paint the first frame instead. On a 611k-edge network that turns ~19 s of blocked
main thread into ~0.2 s, with time-to-first-frame matching backend: "webgl".
The same threshold applies to geometry WebGL renders too — every geoMap layer, plot.layer(),
every non-decluttered points() layer. That geometry is still built (it is shared, so
skipping it would only move identical work later), but above ~10,000 drawables it is not pushed
to the placeholder and not painted on it: the canvas frame would be thrown away by the WebGL
install a moment later. Measured on a 120,000-polygon geoMap, that takes ~104 ms of main-thread
work out of the layer() call; the cost scales linearly, so ~0.9 s at a million polygons.
You get a blank canvas for the (short) duration of the upgrade rather than a slow one — its box
is reserved by your CSS, so nothing on the page moves when the real map arrives. Everything else
is unchanged: whenReady() still resolves at the same moment, smaller scenes still paint
instantly on canvas, and if WebGL turns out to be unavailable the engine falls back to canvas and
draws the full detail there after all.
Switching at runtime
Section titled “Switching at runtime”Call setBackend(...) to switch a live map — layers, colors, and the current zoom/pan are
preserved across the swap:
map.setBackend("svg"); // e.g. to inspect or hand-edit the live vector DOMmap.setBackend("webgl");Switching to the backend that is already live is a no-op (no re-render, no flicker). In
particular, once "auto" has upgraded, the live backend is "webgl", so selecting "webgl"
does nothing.
A network’s GPU force layout (layout({ backend: "gpu" })) runs on the WebGL backend’s device, so it
moves with a swap: it frees its GPU memory before the old backend goes and continues where it was on the
new one — on the GPU again for WebGL (including "auto"), on the CPU worker for Canvas and SVG. See
Network.
When the browser takes the GPU away
Section titled “When the browser takes the GPU away”A browser can take a page’s WebGL context away at any time, for example when its GPU process resets. The
WebGL backend then stops drawing and builds nothing until the context is back, so nothing throws. When
the browser gives the context back, the engine redraws on a fresh WebGL backend, as it does after a
setBackend swap: the same layers, view and selection. A running GPU layout continues on the CPU worker.
Changes made while the context is gone (new data, styles, the view) are kept: toSVG() exports them, and
the redraw shows them. toPNG() throws while the context is gone, since there is no drawn image to copy.
If the context has not come back after 10 s, or it came back but building the new WebGL backend failed,
the engine calls onContextUnrestored once, so you can tell the user, for example to reload the page, or
switch to Canvas with setBackend("canvas"). setBackend("webgl") rebuilds at once without waiting.
const net = network(host, { backend: "webgl", onContextUnrestored: () => showMessage("The graphics card stopped responding. Reload to redraw."),});A context that comes back later still redraws.
The <GeoMap> component takes the same backend prop, including "auto":
import { GeoMap } from "@mapequation/d3gl/react";
<GeoMap width={720} height={380} projection={projection} backend="auto" onReady={(map) => { /* … */ }} />Exporting
Section titled “Exporting”toSVG() works on every backend and always serializes the current view — you do not need to
switch to "svg" to get vector output. The raster backends (webgl, canvas, and an upgraded
auto) additionally offer toPNG().
const svg = map.toSVG(); // vector, on any backendconst png = map.toPNG(); // raster, on webgl/canvas/autotoPNG() is what the screen shows: the canvas as drawn, at its device pixels (the CSS size times
devicePixelRatio, so a 650×560 view exports 1300×1120 on a retina screen), with the same antialiasing
and colours on both raster backends. The labels the WebGL backend shows in its HTML overlay are painted
on top.
On WebGL, content the GPU draws outside the retained scene — the instanced network glyphs/links, an
LOD cut frontier, decluttered plot points — has no scene geometry to serialize, so the engine builds
a vector view of the lanes’ current emit at export time and hands it to the backend alongside the
retained layers. That happens only inside toSVG(), so it costs nothing while you pan and zoom.