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