Two visual defects reported against the deployed site.
Direct labels collided. Each density is normalized to its own peak so the
curves stay comparable in shape, which means every curve peaks at the *same*
height — so labelling "at the apex" put every label on one line, and the
alternating two-row offset only ever separated two of them. Three groups with
similar rates rendered as "HiWhite:F: Black F".
Labels now live in a reserved band above each row and are packed into lanes by
utils/labelLayout.js: first-fit by x, dropping to a new lane only where the
previous one is occupied, so well-separated groups still share a lane and the
common case stays compact. Rows size themselves from the lane count. Widths are
estimated from character count — SVG text can't be measured before render — and
the estimate is deliberately generous so packing errs toward separation.
Verified by measuring rendered getBBox rects in the browser: zero overlaps for
Clark County unpooled, pooled with all four races checked, and compare-all-four
(16 labels per panel), with nothing outside the viewBox.
The line chart looked like it came from a different app because it did: its
viewBox was 360 wide where the other charts are 760. Both render at width="100%"
in the same card, so its 0.7rem text was scaled up roughly twice as far. Now on
the same 760 grid with matching type sizes (ticks 0.62rem, axis titles 0.64rem),
the beige plot fill dropped to match the other cards, and a baseline under the
waves.
Series ends had no room: the first and last waves sat flush against the plot
edges, clipping half of each end diamond and forcing their labels to be anchored
outward to avoid overflowing. X_PAD insets the scale so every marker has 88px of
clearance and every label centres over its own point. The rate label is one line
("1.71 per 1,000") instead of a stacked number and unit, and the modeled
point-range is thinner and slightly transparent so the observed series reads as
primary.
Also fixes a note that fired too early: ApproxNote rendered while the draws
fetch was still in flight, because with no draws yet every group counts as
approximated — claiming a fallback that hadn't happened, directly above a
spinner saying the real draws were still coming.