Interaction
Everything on this page is implemented once on the shared base of both engines (BaseEngine),
so it works identically on geoMap() and
plot() — see Engines. The examples use
map, but read identically for a plot instance: map.on("hover", …), the declarative hover
/ tooltip / selection layer options, select(), and setStyle() are all the same call.
Retained layers are pickable by default (a CPU hit index per layer; disable with
pickable: false). Picking is clip-aware: a layer with clipTo only hits where its
clip source is also hit, so interaction matches what is visibly painted.
Events
Section titled “Events”map.on("hover", (hit, ev) => { ... }); // hit: { layer, id, datum } | nullmap.on("click", (hit, ev) => { ... }); // same hit shape; fires only on a non-drag click (≤ 4 px travel)click coexists with pan/zoom/rotation: a drag never fires it.
Hover highlight
Section titled “Hover highlight”map.layer("cells", geoms, { hover: true, // default: white outline (ring for points) // hover: { stroke: "#fff", lineWidth: 1.5 }, // or replay the item with this style // hover: (d, g) => { ... }, // or fully custom draw (see below)});map.highlight("cells", id, styleOrDraw); // the imperative primitive (pass null to clear)The hovered item is redrawn into a tiny internal overlay layer (inheriting the source
layer’s clipTo/sizeMode, rendered on top). The base layer’s buffers are never touched, so
sweeping fast across a dense grid costs O(one feature) per cell crossed — no fps drop. Because
only one item is re-tessellated, lineWidth is allowed here (unlike bulk overrides).
Custom draw gets a HighlightBuilder scoped to the hovered drawable (world coordinates):
hover: (city, g) => { g.replay({ fill: "#fff" }); // the item itself, restyled — // uses its already-projected geometry const [x, y] = g.anchor!; // non-null for point features g.path((ctx) => ctx.arc(x, y, 8, 0, 2 * Math.PI), // plus anything else { stroke: "#e23b2f", lineWidth: 1.5 }); g.point(x, y - 12, 2, { fill: "#e23b2f" });}Tooltips
Section titled “Tooltips”const map = geoMap(host, { tooltipClass: "my-tooltip" }); // optional styling hookmap.layer("cities", pts, { tooltip: (d, id) => d.name }); // string | HTMLElement | nullOne shared absolutely-positioned div (class="d3gl-tooltip"), engine-managed: filled from the
accessor of the hovered layer, follows the pointer clamped to the host, hidden off-target.
Without tooltipClass it gets a minimal default look. Content is re-evaluated only when the
hovered target changes; re-declare the layer to force a refresh.
Selection and style overrides
Section titled “Selection and style overrides”map.layer("cells", geoms, { selection: { selected: { stroke: "#fff" }, // optional; default keeps base style others: { opacity: 0.3 } }, // default when omitted});map.select("cells", ids); // apply (pass null to clear)
map.setStyle("cells", ids, { fill, stroke, opacity }); // the primitivesmap.clearStyle("cells", ids);Overrides compose over the base accessor colors: fill/stroke replace the base color,
opacity multiplies the base alpha (dimming keeps each item’s hue). They survive projection
switches and rotation rebuilds; re-declaring the layer (map.layer(name, …) again) resets
them. select() rewrites the layer’s whole override table (last write wins vs setStyle).
Bulk overrides are colors-only: stroke geometry bakes its width at tessellation time, so a bulk
lineWidth would be O(n) re-tessellation — use the hover overlay for width changes.
On instanced lanes (network nodes, decluttered points)
Section titled “On instanced lanes (network nodes, decluttered points)”selection.others works the same on GPU-instanced lanes — network nodes (including the
overlapping-module pie glyphs of a state network’s physical view, which fade and stay lit with
their node) and a plot layer’s decluttered points — as on retained layers: with a selection
active, non-selected glyphs fade to others.opacity (default 0.3). The persistent selection
ring stays the selected affordance; the dim is the complementary focus. Lanes honor the
opacity component of others (a colour override there is ignored — instanced glyphs have no
Scene drawable to recolour).
For a network the highlight is applied in the GPU vertex shader from per-instance
group/selected columns plus lane uniforms, so it scales to a full LOD-off draw: a hover is a
single uniform change — no per-frame geometry rebuild or buffer re-upload, even at a million nodes. A
selected node keeps its outgoing links at full strength while the rest dim (“this node and what it
points to” — incident links for an undirected graph; under LOD, the selected aggregate’s outgoing
super-edges). Hovering a node recolours those same links toward the highlight colour (red by
default, matching the ring) preserving each link’s luminance, so a weight-encoded link keeps its weight
cue. hover mirrors selection: hover: { hovered?, others? } — hovered styles the hovered item
(the ring / overlay), others fades the rest on hover (the opt-in hover analogue of selection.others);
hover: true or a bare style still works. These links are a visual companion derived from the node
selection; selection() / on("select") stay node-only.
Selection highlight is ancestor-aware under LOD: selecting a module and zooming in keeps its
expanding children (and their outgoing links) highlighted, even though only the module id is in the
selection set — so selection() stays compact while the on-screen focus follows the subtree.
It works the other way too: a selected node the cut has collapsed into a module shows on that module, which takes a faint ring (the selection colour at reduced opacity) and is not dimmed. Zoom in and the ring moves down the levels until it sits on the node itself, in full. The module’s links stay as they are: they carry the whole module’s flow, so only the selected nodes’ own links light up. Select the nodes that match a search, and every match stays findable at any zoom.
Cost model
Section titled “Cost model”| Operation | Cost | When |
|---|---|---|
| Pointer move within one item | one pick + tooltip reposition | per move |
| Hover crosses into a new item | tessellate 1 feature + tiny upload | per change |
select() / setStyle bulk | O(n) byte writes + one small table upload | per call (e.g. click) |
| Pan/zoom/rotate frames | unchanged — the hover pipeline pauses during gestures | — |
Nothing here adds per-frame work, changes shaders, or rebuilds vertex buffers.