Skip to content

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

number

ArrayLike<number>

ArrayLike<number>

number | ArrayLike<number>

ArrayLike<number> | undefined

number

number

number

Uint8Array

DeclutterScratch = ...

(i, j) => boolean

Int32Array<ArrayBufferLike>

Uint8Array