Skip to content

Engines

d3gl gives you two engines:

  • geoMap() — geographic. Layers are GeoJSON features drawn through a d3 GeoProjection, with rotation, re-projection, and a GPU globe.
  • plot() — 2D Cartesian. Layers are drawn with a draw(ctx, datum) callback or x/y point accessors in plot coordinates (trees, charts, link diagrams, scatter plots).

They are siblings over a shared base (BaseEngine, internal). They differ only in how a layer’s geometry is defined. Everything else — interaction, per-drawable styling, backends, view transform, and export — lives in the base, so it behaves identically on both. Pick the engine that matches your data; the rest of the API is the same.

import { geoMap } from "@mapequation/d3gl/map"; // geographic
import { plot } from "@mapequation/d3gl/map"; // 2D Cartesian

Once you have an engine instance, these work the same regardless of which one you created:

ConcernAPI
Eventson("hover", …), on("click", …)hit: { layer, id, datum } | null
Hit-testingpick(x, y) (clip-aware; retained layers are pickable by default)
Highlighthighlight(layer, idOrIds, styleOrDraw)
Selectionselect(layer, ids)
Style overridessetStyle(layer, ids, …), clearStyle(layer, ids)
ViewenableZoom(extent), setTransform(t), render()
Sizingwidth / height / aspectRatio options, setSize(w, h) — see Sizing
BackendsetBackend(type), whenReady() — see Rendering backends
ExporttoPNG(), toSVG()
Teardowndestroy()

Retained layers on both engines also accept the same declarative interaction options (InteractiveLayerOptions), forwarded into the layer the same way:

engine.layer("things", data, {
hover: true, // or a style, or a (datum, HighlightBuilder) draw fn
tooltip: (d, id) => `${d.name}`, // string | HTMLElement | null
selection: { others: { opacity: 0.3 } },
pickable: true, // default; false skips the hit index
});

See the Interaction guide for what each one does and what it costs — every example there reads identically whether engine is a geoMap or a plot.

Both engines are responsive by default and resize in place — when their container changes size they reconcile the rendering surface and re-render without tearing down, so the view transform, layers, hover, and selection are all preserved. Three modes, picked by which options you pass (to the plot() / geoMap() constructors or the React <Plot> / <GeoMap> props):

You passModeBehavior
aspectRatiowidth-drivenFills the parent’s width and keeps width ÷ height = aspectRatio.
nothingfill-parentTracks the parent box. The parent must give it a height (from your CSS).
both width & heightfixedA static pixel size — the opt-out (the pre-responsive behavior).
<Plot aspectRatio={16 / 9} /> {/* fills width, keeps the ratio */}
<GeoMap /> {/* fills the parent box */}
<Plot width={800} height={400} /> {/* fixed size */}
// Bare engines take the same options; the engine observes its host element.
const chart = plot(host, { aspectRatio: 2 });
// Resize imperatively at any time (no recreate):
chart.setSize(1024, 512);

A resized geoMap also refits its projection to the new box, so the map keeps filling it. A uniform resize (always the case in aspectRatio mode) preserves your original framing exactly; an aspect-ratio change (possible in fill-parent mode) re-letterboxes the map to the new shape.

What’s unique to each engine is the layer definition (its geometry) plus a few domain methods:

geoMap()plot()
Constructor optionprojection (required)
Layer geometrylayer(name, features, opts) — GeoJSON + projectionlayer(name, data, { draw }) and points(name, data, { x, y })
Domain methodssetProjection(), enableRotation()
Coordinatesgeographic [lon, lat], projected each frameplot coordinates, drawn directly
// geoMap: GeoJSON features through a projection
const map = geoMap(host, { width, height, projection });
map.layer("land", [world.land], { fill: "#e3e6ea", hover: true });
// plot: a draw callback in plot coordinates
const chart = plot(host, { width, height });
chart.layer("boxes", rects, {
draw: (ctx, d) => ctx.rect(d.x, d.y, 40, 40),
fill: "#4477aa",
hover: { fill: "#ee6677" },
});
  • Mapping anything geographic (GeoJSON, projections, a globe) → geoMap().
  • Everything else in a 2D plane (trees, scatter, links, custom marks) → plot().

Because the shared base is the same object, you can move between them without relearning interaction, styling, or backends — only the layer-definition call changes.