{"version":3,"file":"scales.cjs","names":[],"sources":["../../src/charts/scales.ts"],"sourcesContent":["/** How many steps the sequential token scale has. */\nexport const SEQUENTIAL_STEP_COUNT = 7;\n\n/** How many steps the diverging token scale has, midpoint included. */\nexport const DIVERGING_STEP_COUNT = 9;\n\n/**\n * First sequential step that clears 2:1 against the chart surface.\n *\n * A sequential scale may let its near-zero end recede into the surface — on a\n * heatmap that is exactly what \"almost nothing\" should look like. An **ordinal**\n * scale may not: every step is a discrete mark someone has to see. Starting an\n * ordinal ramp here is the difference.\n */\nexport const ORDINAL_START_STEP = 3;\n\n/** A `var(--tempest-chart-…)` reference, so the value follows the active theme. */\nexport type ChartColorToken = string;\n\n/** Reference a token by name rather than interpolating the string at each call site. */\nfunction token(name: string): ChartColorToken {\n    return `var(--tempest-chart-${name})`;\n}\n\n/**\n * Clamp `value` into `0…1` against a domain, tolerating a reversed or empty one.\n *\n * A zero-width domain (every datum equal) would otherwise divide by zero and paint\n * the whole chart `NaN`; it resolves to the middle of the scale instead, which is\n * the honest reading of \"no variation\".\n */\nfunction normalize(value: number, min: number, max: number): number {\n    if (!Number.isFinite(value)) return 0;\n    const lo = Math.min(min, max);\n    const hi = Math.max(min, max);\n    if (hi === lo) return 0.5;\n    return Math.min(1, Math.max(0, (value - lo) / (hi - lo)));\n}\n\n/** Map a `0…1` position onto `1…steps`, inclusive. */\nfunction stepOf(t: number, steps: number): number {\n    return Math.min(steps, Math.max(1, Math.round(t * (steps - 1)) + 1));\n}\n\nexport interface SequentialScaleOptions {\n    /** Lowest value in the data. */\n    min: number;\n    /** Highest value in the data. */\n    max: number;\n    /**\n     * Keep every step visible against the surface, for discrete ordered marks.\n     *\n     * Off by default: a heatmap *wants* its near-zero cells to recede. Turn it on\n     * for tiers, funnel stages or anything where each step is its own mark.\n     */\n    ordinal?: boolean;\n}\n\n/**\n * Build a magnitude scale over the sequential tokens.\n *\n * Returns `var(--tempest-chart-sequential-N)` rather than a hex string, so a\n * heatmap painted once follows the theme — including dark mode, whose steps are\n * chosen for the dark surface rather than flipped.\n *\n * @example\n * const color = sequentialScale({ min: 0, max: 250 });\n * <rect fill={color(value)} />\n *\n * @param options - The data domain, and whether every step must stay visible.\n * @returns A function from value to a CSS colour reference.\n */\nexport function sequentialScale(\n    options: SequentialScaleOptions,\n): (value: number) => ChartColorToken {\n    const { min, max, ordinal = false } = options;\n    const first = ordinal ? ORDINAL_START_STEP : 1;\n    const span = SEQUENTIAL_STEP_COUNT - first + 1;\n    return (value) => token(`sequential-${first + stepOf(normalize(value, min, max), span) - 1}`);\n}\n\nexport interface DivergingScaleOptions {\n    /** Lowest value in the data. */\n    min: number;\n    /** Highest value in the data. */\n    max: number;\n    /**\n     * The value that means \"no deviation\". Default `0`.\n     *\n     * It is a parameter because the interesting midpoint is often not zero — a\n     * budget variance diverges around the target, not around nothing.\n     */\n    center?: number;\n}\n\n/**\n * Build a polarity scale over the diverging tokens.\n *\n * Each arm is scaled against its **own** distance from the centre, so an asymmetric\n * domain (say −5…+80) still uses the full cool arm for its small negatives. Scaling\n * both arms by the wider one — the easy mistake — would collapse every negative\n * into the step next to the midpoint and hide the sign entirely.\n *\n * @example\n * const color = divergingScale({ min: -12, max: 40 });   // centre 0\n * const budget = divergingScale({ min: 80, max: 130, center: 100 });\n *\n * @param options - The data domain and the neutral centre.\n * @returns A function from value to a CSS colour reference.\n */\nexport function divergingScale(options: DivergingScaleOptions): (value: number) => ChartColorToken {\n    const { min, max, center = 0 } = options;\n    const mid = Math.ceil(DIVERGING_STEP_COUNT / 2);\n    const armSteps = mid - 1;\n    const coolSpan = Math.abs(center - Math.min(min, max));\n    const warmSpan = Math.abs(Math.max(min, max) - center);\n\n    return (value) => {\n        if (!Number.isFinite(value) || value === center) return token(`diverging-${mid}`);\n        if (value < center) {\n            if (coolSpan === 0) return token(`diverging-${mid}`);\n            const t = Math.min(1, (center - value) / coolSpan);\n            // Step 1 is the cool extreme, so a bigger deviation walks toward it.\n            return token(`diverging-${mid - stepOf(t, armSteps)}`);\n        }\n        if (warmSpan === 0) return token(`diverging-${mid}`);\n        const t = Math.min(1, (value - center) / warmSpan);\n        return token(`diverging-${mid + stepOf(t, armSteps)}`);\n    };\n}\n\n/**\n * Every step of a token scale, in order — for rendering a legend.\n *\n * A continuous scale needs a legend showing the ramp with its end labels; without\n * one the reader has no way to turn a colour back into a number.\n *\n * @param kind - Which scale.\n * @returns The token references, lightest/coolest first.\n */\nexport function scaleSteps(kind: \"sequential\" | \"diverging\"): ChartColorToken[] {\n    const count = kind === \"sequential\" ? SEQUENTIAL_STEP_COUNT : DIVERGING_STEP_COUNT;\n    return Array.from({ length: count }, (_, i) => token(`${kind}-${i + 1}`));\n}\n"],"mappings":"AACA,IAAa,EAAwB,EAGxB,EAAuB,EAUvB,EAAqB,EAMlC,SAAS,EAAM,EAA+B,CAC1C,MAAO,uBAAuB,EAAK,EACvC,CASA,SAAS,EAAU,EAAe,EAAa,EAAqB,CAChE,GAAI,CAAC,OAAO,SAAS,CAAK,EAAG,MAAO,GACpC,IAAM,EAAK,KAAK,IAAI,EAAK,CAAG,EACtB,EAAK,KAAK,IAAI,EAAK,CAAG,EAE5B,OADI,IAAO,EAAW,GACf,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAQ,IAAO,EAAK,EAAG,CAAC,CAC5D,CAGA,SAAS,EAAO,EAAW,EAAuB,CAC9C,OAAO,KAAK,IAAI,EAAO,KAAK,IAAI,EAAG,KAAK,MAAM,GAAK,EAAQ,EAAE,EAAI,CAAC,CAAC,CACvE,CA8BA,SAAgB,EACZ,EACkC,CAClC,GAAM,CAAE,MAAK,MAAK,UAAU,IAAU,EAChC,EAAQ,EAAA,EAA+B,EACvC,EAAA,EAA+B,EAAQ,EAC7C,MAAQ,IAAU,EAAM,cAAc,EAAQ,EAAO,EAAU,EAAO,EAAK,CAAG,EAAG,CAAI,EAAI,GAAG,CAChG,CA+BA,SAAgB,EAAe,EAAoE,CAC/F,GAAM,CAAE,MAAK,MAAK,SAAS,GAAM,EAG3B,EAAW,KAAK,IAAI,EAAS,KAAK,IAAI,EAAK,CAAG,CAAC,EAC/C,EAAW,KAAK,IAAI,KAAK,IAAI,EAAK,CAAG,EAAI,CAAM,EAErD,MAAQ,IACoD,EAApD,CAAC,OAAO,SAAS,CAAK,GAAK,IAAU,EAAqB,cAC1D,EAAQ,EACJ,IAAa,EAAgB,cAGpB,aAAa,EAAM,EAFtB,KAAK,IAAI,GAAI,EAAS,GAAS,CAEF,EAAG,CAAQ,IAElD,IAAa,EAAgB,cAEpB,aAAa,EAAM,EADtB,KAAK,IAAI,GAAI,EAAQ,GAAU,CACF,EAAG,CAAQ,GAT8B,CAWxF,CAWA,SAAgB,EAAW,EAAqD,CAC5E,IAAM,EAAQ,IAAS,aAAA,EAAA,EACvB,OAAO,MAAM,KAAK,CAAE,OAAQ,CAAM,GAAI,EAAG,IAAM,EAAM,GAAG,EAAK,GAAG,EAAI,GAAG,CAAC,CAC5E"}