import { max } from 'd3-array' import { scaleLinear } from 'd3-scale' import type { ScaleLinear } from 'd3-scale' import sortByFn from 'lodash/sortBy' import { computed, toValue } from 'vue' import type { ComputedRef, MaybeRefOrGetter } from 'vue' /** * A single datum of a horizontal bar chart. `value` is the measured quantity; * `label` and `highlight` are optional presentation hints. */ export interface BarChartDatum { value: number | number[] highlight?: boolean label?: string } /** * The geometry of a single rendered bar, in canvas pixels, carrying the source * datum's value and highlight flag for the template. */ export type BarChartBar = { width: number height: number x: number y: number } & BarChartDatum /** * The geometry of a single rendered label, in canvas pixels. */ export interface BarChartLabel { label?: string x: number y: number } /** * The chart's inner padding box, once the axis margins are subtracted. */ export interface BarChartPadded { width: number height: number } /** * The chart's margins, in canvas pixels. Only the left margin is non-zero (it * reserves room for the labels); the others are kept for symmetry with the * padded-box computation. */ export interface BarChartMargin { left: number right: number top: number bottom: number } /** * Reactive inputs driving {@link useBarChart}. They mirror the `BarChart` * component's loaded data, its measured dimensions and the geometry-affecting * props, accepted as plain values, refs or getters so the composable adapts to * how the caller wires its state. */ export interface UseBarChartOptions { /** * The chart's loaded data (inline array or fetched), as exposed by `useChart`. */ loadedData: MaybeRefOrGetter /** * Measured outer width of the chart, in pixels. */ width: MaybeRefOrGetter /** * Measured (or fixed) width reserved for the labels column, in pixels. */ labelWidth: MaybeRefOrGetter /** * Measured (or fixed) width reserved for the values, in pixels. Already * includes the value gap. */ valueWidth: MaybeRefOrGetter /** * Field name(s) to sort the data by, or `null`/`undefined` to keep the * loaded order. */ sortBy: MaybeRefOrGetter /** * Height of each bar, in pixels. */ barHeight: MaybeRefOrGetter /** * Vertical gap between consecutive bars, in pixels. */ barGap: MaybeRefOrGetter /** * Horizontal gap between the labels and the bars, in pixels. */ labelGap: MaybeRefOrGetter } /** * Reactive API returned by {@link useBarChart}. */ export interface UseBarChart { /** * The loaded data, optionally sorted by the `sortBy` option. */ sortedData: ComputedRef /** * The chart's margins (only the left margin reserves label room). */ margin: ComputedRef /** * The inner padding box, after subtracting the margins. */ padded: ComputedRef /** * The linear scale mapping a datum value to a bar width in pixels. */ scale: ComputedRef<{ x: ScaleLinear }> /** * The geometry of every bar, in source-data order (after sorting). */ bars: ComputedRef /** * The geometry of every label, in source-data order (after sorting). */ labels: ComputedRef /** * The total SVG height needed to stack every bar. */ height: ComputedRef } /** * Owns the pure d3 geometry of the `BarChart` component: it sorts the data, * builds the linear value scale, and derives the bar, label, margin and padded * boxes. It holds no DOM state — the measured `width`/`labelWidth`/`valueWidth` * are passed in, and rendering stays in the component. * * @param options - Reactive geometry options (see {@link UseBarChartOptions}). * @returns The {@link UseBarChart} API of derived geometry. * @example * // Internal building block of the `BarChart` component; not exported from the * // package root. Inside a `