Adaptive piecewise scale — keeping outliers on screen without crushing the data

Most real-world financial/count data is lopsided: 80%+ of the values bunch into a narrow band, then a handful of outliers run one or two orders of magnitude past them. Small business loans, property sales, citation counts, earthquake energy — same shape every time. Linear crushes the cluster into a pixel-wide sliver against the outliers. Log fixes the outliers but distorts the cluster’s internal structure and can’t handle zero or negatives.

scaleAdaptive splits the domain into three regions instead: a log tail on each side of a linear “focus window” that holds the dense cluster. The interesting part is the boundary — naively joining a log scale to a linear scale produces a visible kink, because the two scales’ slopes don’t match at the seam. So the tail’s symlog constant is solved numerically (bisection over ~80 iterations) so its slope at the boundary equals the window’s linear slope. The transition ends up C¹-continuous — no kink, no visual seam, even though the underlying math is two completely different scale types.

A few other things it does that I haven’t seen in other outlier-scale approaches:

The window is draggable and pannable, and it’s capped so it can never slide far enough to swallow an outlier tail entirely — that’s a structural guarantee (a pixel-reserve system), not a “please don’t drag too far” heuristic.

Clicking a log tail “travels” the focus window onto it — the data that was compressed becomes the new cluster, and the old cluster becomes the tail. Useful for exploring a dataset that has outliers on both sides at very different scales.

It implements the full d3 continuous-scale contract (domain, range, ticks, tickFormat, nice, clamp, unknown, invert, copy), so it drops into d3.axisBottom like any other scale.

86 tests covering four invariants: monotonicity, invertibility, boundary continuity, and graceful degradation (no outliers → it’s just scaleLinear).

Live notebook: https://observablehq.com/d/ea148faa812f43c9 — try dragging the window or clicking a tail on the real dataset (600 SBA small-business loans, $5k–$5M).

npm install d3-scale-adaptive (npm, source).

If anyone wants the longer story of how this evolved — including a hatch-texture experiment I built and then mostly abandoned, and a few genuinely bad ideas along the way — I wrote it up When Linear and Log Both Fail: An Adaptive D3 Scale — Wouter Schreuders, but everything above is the whole technique.

3 Likes