import type { MapScale } from './map.types'; /** * Maps a data value to a pixel size for proportional symbology. * * @description * Nine of the sixteen surveyed applications hand-rolled this same calculation * before plotting circles. It lives here so that a marker's radius and the size * key in `MapLegend` are guaranteed to come from identical math — a legend that * disagrees with the symbols it describes is worse than no legend. * * `'sqrt'` is the default because it makes the rendered *area* proportional to * the value. `'linear'` scales the radius directly and therefore exaggerates * large values; use it only when the mark is a bar or a line, not a disc. * * Degenerate inputs collapse to `range[0]` rather than producing `NaN`: * a zero-width domain, a non-finite value, and (for `'log'`) any non-positive * bound or value. */ export function resolveScale(scale: MapScale): number { const { value, domain, range, method = 'sqrt' } = scale; const [d0, d1] = domain; const [r0, r1] = range; if (!Number.isFinite(value) || !Number.isFinite(d0) || !Number.isFinite(d1)) return r0; if (d0 === d1) return r0; // Clamp into the domain so out-of-range data cannot escape the size range. const lo = Math.min(d0, d1); const hi = Math.max(d0, d1); const clamped = Math.min(Math.max(value, lo), hi); let t: number; if (method === 'log') { // Logs are undefined at and below zero; a domain that reaches zero has no // meaningful log mapping, so fall back to the smallest size. if (lo <= 0 || clamped <= 0) return r0; t = (Math.log(clamped) - Math.log(lo)) / (Math.log(hi) - Math.log(lo)); } else if (method === 'sqrt') { // Shift by the domain floor so negative domains still produce a real root. const span = hi - lo; t = Math.sqrt((clamped - lo) / span); } else { t = (clamped - lo) / (hi - lo); } if (!Number.isFinite(t)) return r0; return r0 + t * (r1 - r0); } /** * Default marker radius, in pixels, when `shape: 'circle'` is used with neither * `radiusPx` nor `scale`. Sized to read as a deliberate dot next to the 32px * pin without competing with it. */ export const DEFAULT_MARKER_RADIUS_PX = 8;