Skip to content

labels

HTML label overlay. LabelLayer keeps geometry on the GPU and renders only the visible labels into the DOM, positioning anchors (reference-space) under a core!ViewTransform; cullLabels does the viewport + collision culling.

import { LabelLayer } from "@mapequation/d3gl/labels";
const labels = new LabelLayer(containerEl, (a) => a.text);
labels.update(anchors, transform, { width, height });
ClassDescription
LabelLayerAn HTML overlay of absolutely-positioned label elements. On each update it maps reference anchors through the view transform to screen pixels, culls to the viewport with collision resolution, and reconciles the DOM (reusing nodes by id). Geometry stays on the GPU; only the surviving labels are in the DOM.
TextMeasurerText measurement for label sets whose text is derived per frame rather than registered once (#204): the network builds its label candidates from the current LOD frontier / viewport, so a width has to be available for every candidate on every placement pass. Measuring there directly would put a measureText on the per-frame path; this memoizes by text so each distinct string is measured exactly ONCE and every later frame is a Map lookup.
InterfaceDescription
CullOptions-
LabelAnchorA label anchored in REFERENCE (projected, pre-transform) pixel space.
LabelBoxA label positioned in SCREEN pixels (after the view transform is applied).
LabelCullScratchReusable buffers for cullLabels. Placement runs on the per-frame path (every setTransform re-places every label), so the caller holds ONE of these and passes it back in: the steady-state cull then allocates nothing but the survivor array. Same shape of contract as core/declutter’s core!DeclutterScratch.
LabelGeometryThe realised screen geometry of a label: its four corners, their AABB, whether it is axis-aligned (fast-path collision), and the CSS transform that reproduces the box.
Type AliasDescription
LabelBaselineWhere the anchor point sits vertically in a PLAIN label’s box. "top" (default) keeps the historical top-left box; "middle" centres the box on the anchor. Oriented labels (rotation) are always centred on their axis, so they ignore it.
LabelStyleInline CSS for overlay label elements: camelCased CSS property → value (the string-valued subset of CSSStyleDeclaration), e.g. { color: "#1f2937", textShadow: "0 0 2px #fff" }. A plain, engine-agnostic shape, so it can lift as-is into other engines’ label overlays (#223).
TextAnchorWhere the anchor point sits along the label’s own text axis — like SVG text-anchor.
VariableDescription
DEFAULT_LABEL_STYLEDefault overlay label look (#224) — a compact dark sans-serif label with a white text-shadow halo, readable over busy geometry with zero user CSS. Applied by the engine label APIs (e.g. network.labels()) via resolveLabelStyle; a raw LabelLayer stays unstyled by default (the low-level primitive keeps today’s inherit-from-container behaviour).
DEFAULT_LABEL_TEXTThe same default look for BACKEND-NATIVE text (SVG <text> / Canvas fillText, incl. export), which CSS can’t reach: the font/color/halo equivalent of DEFAULT_LABEL_STYLE (canvas font shorthand takes no line-height; the 3px halo stroke ≈ the 3px shadow blur).
FunctionDescription
canvasFontStrip a /line-height token from a CSS font shorthand so it is valid for the canvas font property (which rejects line-height), e.g. "600 11px/1 system-ui""600 11px system-ui".
cullLabelsReduce label candidates to a renderable subset: drop anchors outside the viewport (+padding), then place highest-priority first, skipping any whose box collides with an already-placed one. Collision uses each label’s true screen footprint (see labelGeometry) — the box it renders in, oriented labels included — so dense regions thin down to a readable set instead of overprinting (#204), and the survivor of each cluster is the most important label.
fontRowHeightThe label row-box height for a font (px size × 1.25, e.g. 11px → 14) — derived from the font string alone, so getting it never costs a measureText call.
labelCullScratchA fresh, empty scratch (buffers grow lazily on first use). Hold one per label layer.
labelGeometryResolve a LabelBox to its on-screen geometry. Plain labels get an axis-aligned box placed by textAnchor/baseline (default: the historical top-left box); oriented labels (rotation) place the text along the rotated axis, vertically centred on the anchor, with the optional upright flip folded in. The collision corners and the CSS transform come from the same computation, so render and culling stay consistent.
labelTextYThe y a backend drawing native text with a "middle" baseline must use — the vertical centre of the same box. Mirrors labelGeometry’s vertical placement (a unit test pins the two against each other), so overlay and native text sit on the same line.
labelTransformThe CSS transform that renders a label in the box labelGeometry culls against, applied to an element whose left/top is the anchor (with transform-origin at that point). Derived from the same extents as the box: the element’s left edge must land at x + lx0, i.e. a translate of lx0/width — 0% / −50% / −100% for start / middle / end, and likewise 0% / −50% vertically. Returns "" for the default top-left box, so a caller’s own transform still applies there (back-compatible).
measureTextMeasure a label’s screen box (CSS px) for collision. font is a canvas font shorthand (no /line-height — strip it first). Width is measured; height is the font size × 1.25 (a consistent row box; 11px → 14, matching the estimates this replaces).
placeLabelsProject label anchors to screen px (screen = k·ref + (x,y) + the constant offset) and resolve collisions, returning the survivors as LabelBoxes (carrying text/opacity). Shared by the HTML overlay (LabelLayer.update) and the backend-native text path (#105 N7b-2) so both place and cull labels identically — they differ only in how they render the survivors.
resolveLabelStyleThe engine-label styling policy (#224): merge a caller’s inline LabelStyle over DEFAULT_LABEL_STYLE — a partial override keeps the rest — but when a className is given, return only the explicit style (possibly undefined): the class’s CSS keeps full control, since an inline default would beat it. Shared so other engines lift the exact same policy (#223); pass the result as LabelLayer’s style.