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.
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/autoOn 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.