import isString from 'lodash/isString' import { range } from 'd3-array' import { interpolateRound } from 'd3-interpolate' import { scaleLinear, scaleLog, scalePow, scaleRadial, scaleSqrt, scaleSequential, scaleSymlog, scaleTime, scaleUtc } from 'd3-scale' import type { ScaleLinear } from 'd3-scale' import { computed, toValue } from 'vue' import type { ComputedRef, MaybeRefOrGetter } from 'vue' // The continuous numeric d3-scale factories that share this composable's // domain()/range() shape — the only scale kinds `colorScale` supports by // name. Anything else must be passed as a ready-made scale function instead. const SCALE_FACTORIES: Record any> = { scaleLinear, scaleLog, scalePow, scaleRadial, scaleSqrt, scaleSequential, scaleSymlog, scaleTime, scaleUtc } /** * Maps a numeric value to a CSS color string. This is the shape returned by * d3 sequential/threshold scales as well as the function callers can pass * directly through the `colorScale` prop. */ export type ColorScaleFn = (v?: number) => string /** * Maps a horizontal canvas pixel offset to the color painted at that column. */ export type WidthScaleFn = (x: number) => string /** * Reactive options driving the scale legend derivation. They mirror the props * of the `LegendScale` component, accepted as plain values, refs or getters so * the composable stays adaptable to how the caller wires its state. */ export interface UseLegendScaleOptions { /** * Width of the legend (and of its color canvas), in pixels. */ width: MaybeRefOrGetter /** * Lower bound of the value domain. */ min: MaybeRefOrGetter /** * Upper bound of the value domain. */ max: MaybeRefOrGetter /** * Current cursor value, or `null`/`undefined` when no cursor is shown. */ cursorValue: MaybeRefOrGetter /** * Either a ready-made color scale function, or the name of one of the * supported continuous d3-scale factories (`'scaleLinear'`, `'scaleLog'`, * `'scalePow'`, `'scaleRadial'`, `'scaleSqrt'`, `'scaleSequential'`, * `'scaleSymlog'`, `'scaleTime'`, `'scaleUtc'`) to build a two-stop scale * from. Any other factory name must be passed as a ready-made function * instead (e.g. `scaleQuantize(...)`). */ colorScale: MaybeRefOrGetter /** * Start color of the built-in two-stop scale (used when `colorScale` is a * factory name). */ colorScaleStart: MaybeRefOrGetter /** * End color of the built-in two-stop scale (used when `colorScale` is a * factory name). */ colorScaleEnd: MaybeRefOrGetter } /** * Reactive API returned by {@link useLegendScale}. */ export interface UseLegendScale { /** * Color scale mapping a value of the domain to a CSS color. When the * `colorScale` option is a factory name, it is a two-stop scale spanning * `[min, max]`; otherwise it is the caller-provided function. */ colorScaleFunction: ComputedRef /** * Maps a canvas pixel column to the value it represents on the `[min, max]` * domain. */ widthScale: ComputedRef> /** * Maps a canvas pixel column directly to the color painted there, composing * {@link widthScale} with {@link colorScaleFunction}. */ widthScaleColor: ComputedRef /** * Pixel columns to paint, from `0` to `width - 1` inclusive: the canvas is * sized to exactly `width`, so those are its only valid columns. */ colorScaleWidthRange: ComputedRef /** * Whether a cursor value is set (and therefore the cursor should render). */ hasCursor: ComputedRef /** * Left offset of the cursor as a CSS percentage string (e.g. `'33%'`), * falling back to `'0%'` when the value is not a number. */ cursorLeft: ComputedRef } /** * Owns the d3 scale and color-domain derivation of the `LegendScale` * component: it builds the color scale over the value domain, the linear scale * mapping canvas pixels to values (and to colors), and the cursor positioning * scale. It holds no DOM state — canvas rendering stays in the component. * * @param options - Reactive scale options (see {@link UseLegendScaleOptions}). * @returns The {@link UseLegendScale} API of derived d3 scales and the cursor * placement helpers. * @example * // Internal building block of the `LegendScale` component; not exported from * // the package root. Inside a `