declutterScreen
declutterScreen(
count,sx,sy,radius,order,width,height,spacing,out,scratch?,ignore?,winners?):Uint8Array
Defined in: core/declutter.ts:117
Greedy screen-space declutter. Visits glyphs in order (importance descending; omitted ⇒ index
order) and keeps each unless its centre is within spacing·(rᵢ + rⱼ) of an already-kept glyph — so
the drawn circles don’t overlap and the most important glyph in a cluster survives. A glyph whose
centre is off-screen is always kept and never occludes others (so panning never culls what’s barely
out of view). O(n): a uniform radius uses one grid sized to the exclusion radius, so any overlapping
pair falls in the 3×3 cell neighbourhood. Per-glyph radii add one grid per radius class (cell
sizes halving down from 2·spacing·maxR): each kept glyph also goes into the class that fits its own
radius, and a candidate scans each class over its reach spacing·(r + that class's largest radius).
So a small glyph beside a few big ones scans the few cells around it, not a cell sized to the biggest
glyph (with the network’s 26 px aggregate cap and 2-3 px leaves that was ~9·52² px of packed kept
leaves per rejected leaf). A glyph much larger than a non-empty class would scan many empty cells of
it, so when its class boxes hold more cells than the single grid’s 3×3 neighbourhood holds kept
glyphs, it scans that neighbourhood instead: no candidate costs much more than the single grid. Same
kept set and winners as the single grid (the winner is the occluder the single grid’s scan order
meets first).
sx/sy are screen-pixel centres. radius is the per-glyph exclusion radius in px — a number for
the uniform case (a point layer’s fixed spacing is passed as half the centre-to-centre distance,
since two glyphs collide when dist < rᵢ + rⱼ). A per-glyph radius below 0 counts as 0 (a point).
out (length ≥ count, written in index order) and scratch are reused across frames by the
caller. Returns out.
ignore(i, j) (optional) drops a specific overlap from the test: when candidate i would be occluded
by an already-kept glyph j, returning true means “not a real overlap” so i is not culled by j
(but is still tested against every other glyph, and still occludes others). Used by the LOD cross-fade
(#133): a glyph transitioning across the expand threshold ignores its ancestor as an occluder — so
a fading parent doesn’t cull its fading-in children — while children still declutter against siblings.
Omitted ⇒ no ignored pairs (zero added cost).
winners (optional, length ≥ count) records, for each glyph, the kept glyph it is represented by:
a kept glyph maps to itself (winners[i] = i), a hidden glyph to the already-kept glyph that occluded
it (winners[i] = p). One extra store per glyph in the loop we already run. Lets a hit on the kept
survivor enumerate the glyphs absorbed under it (members()): scan for all i with winners[i] === K
(#105 N7c-2). Omitted ⇒ not tracked (zero added cost).
Parameters
Section titled “Parameters”number
ArrayLike<number>
ArrayLike<number>
radius
Section titled “radius”number | ArrayLike<number>
ArrayLike<number> | undefined
number
height
Section titled “height”number
spacing
Section titled “spacing”number
Uint8Array
scratch?
Section titled “scratch?”DeclutterScratch = ...
ignore?
Section titled “ignore?”(i, j) => boolean
winners?
Section titled “winners?”Int32Array<ArrayBufferLike>
Returns
Section titled “Returns”Uint8Array