Skip to content

React

d3gl ships first-class React bindings at @mapequation/d3gl/react. For maps there is <GeoMap>, which mounts the project-once map engine for you. For everything else — trees, generic 2D plots — there is a fully declarative API: a <Plot> element with <Layer> and <Points> children. You describe the chart as JSX; <Plot> builds the imperative plot engine under the hood, translates each child into an engine.layer / engine.points call (sibling order = paint order), and manages the lifecycle — no useEffect, no DOM, no engine plumbing in your component. Both examples below render through the same universal control bar as every other example on this site (backend switch, export, live perf); the components just receive backend and the canvas size as props.

<GeoMap> mounts the map engine and hands it back via the onReady callback, where you add layers, enable zoom, and render once. It takes width, height, a d3-geo projection, and an optional backend ("webgl", "canvas", or "svg"). When the backend prop changes the component keeps the engine alive and calls map.setBackend() — no remount — so the layers and the current zoom/pan are preserved.

fps 0frame 0 ms
WorldMapReact.tsx
import { geoNaturalEarth1 } from "d3-geo";
import { GeoMap } from "@mapequation/d3gl/react";
import { fitProjection } from "@mapequation/d3gl/geo";
import Example from "../../components/Example.js";
import { loadWorld } from "../shared/geo-data.js";
const OCEAN = "#d4e6f5";
const LAND = "#e3e6ea";
/**
* The zoomable world map built with the React `<GeoMap>` wrapper. The universal
* <Example> harness supplies `backend` + the measured `width`/`height` and an
* export hook via `registerEngine` — this file is just the viz.
*/
export default function WorldMapReact() {
return (
<Example width={720} height={380}>
{({ backend, width, height, registerEngine }) => (
<GeoMap
width={width}
height={height}
backend={backend}
projection={fitProjection(geoNaturalEarth1(), { type: "Sphere" }, width, height)}
onReady={(map) => {
const world = loadWorld();
map.layer("ocean", [world.sphere], { fill: OCEAN });
map.layer("land", [world.land], { fill: LAND, stroke: "#9aa3ad", lineWidth: 0.5 });
map.enableZoom([1, 50]);
map.render();
registerEngine(map);
}}
/>
)}
</Example>
);
}

The same 64-tip rectangular phylogram as the vanilla simple-tree example (d3.link(curveStepBefore) branches, schemeCategory10 tip nodes), written declaratively with <Plot>. The whole chart is JSX: a <Layer> draws the branches with a d3-shape link generator straight into the d3gl context, a <Points> places the coloured tip nodes, and zoom={[0.5, 40]} enables scroll-to-zoom / drag-to-pan. The layout is recomputed for the harness-measured width/height. There is no useEffect and no engine handling in the component — <Plot> builds the plot engine, applies the children, and hands it back through onReady so the harness can drive export and backend switching (which preserves the current zoom/pan).

fps 0frame 0 ms
PhyloTreeReact.tsx
import { schemeCategory10 } from "d3-scale-chromatic";
import { link as d3link, curveStepBefore } from "d3-shape";
import type { HierarchyPointNode, HierarchyPointLink } from "d3-hierarchy";
import { Plot, Layer, Points } from "@mapequation/d3gl/react";
import Example from "../../components/Example.js";
import { makeTree, type TreeNode } from "../shared/tree.js";
import { layoutRectangular, nodeXY } from "../shared/layout.js";
type PNode = HierarchyPointNode<TreeNode>;
type PLink = HierarchyPointLink<TreeNode>;
// Rectangular step links: a d3-shape link generator drawing straight into the d3gl context.
const gen = d3link<PLink, PNode>(curveStepBefore).x((d) => d.y).y((d) => d.x);
/**
* The 64-tip rectangular phylogram, written declaratively in React with the
* `<Plot>` / `<Layer>` / `<Points>` components from `@mapequation/d3gl/react`.
* The layout is recomputed for the harness-measured `width`/`height`; everything
* else is pure JSX — no `useEffect`, no DOM, no engine plumbing. `onReady` hands
* the engine to the harness so it can drive export and backend switching.
*/
export default function PhyloTreeReact() {
return (
<Example width={720} height={460}>
{({ backend, width, height, registerEngine }) => {
const root = layoutRectangular(makeTree(64), width, height, "linear");
return (
<Plot
width={width}
height={height}
backend={backend}
zoom={[0.5, 40]}
onReady={registerEngine}
>
<Layer
name="links"
data={root.links()}
draw={(ctx, l) => {
gen.context(ctx);
gen(l);
}}
stroke="#555"
lineWidth={0.8}
/>
<Points
name="nodes"
data={root.leaves()}
x={(n) => nodeXY(n, "rectangular")[0]}
y={(n) => nodeXY(n, "rectangular")[1]}
radius={2.6}
fill={(n) => schemeCategory10[n.data.group % 10] ?? "#888"}
/>
</Plot>
);
}}
</Example>
);
}