/** * Integrated chart geometry. Pure functions that turn categories + numeric * series into SVG primitives - the "chart from a grid range" enterprise * feature without bundling a charting library. The `` component * paints the result; this module has no DOM so it is unit-testable. * * Supports: grouped + stacked bars, line, area, pie/donut, combo charts * (per-series type), a secondary (right) Y axis, signed Y domains (negative * values drop below a zero baseline), and nice auto-scaled ticks. */ export type ChartType = 'bar' | 'line' | 'area' | 'pie' | 'scatter' | 'heatmap' | 'waterfall' | 'funnel' | 'radar' | 'calendar' | 'gauge' | 'treemap' | 'sankey'; /** A clicked bar / point / slice - the payload of `SvGridChart`'s `onSelect`. * `rowIds` is populated when the spec was built from grid rows (via * `rowsToChartSpec`) and lets a drill handler filter the grid back to the * source rows for the clicked category / series cell. */ export type ChartSelection = { category: string; series: string; value: number; rowIds?: Array; }; /** A single scatter / bubble point. */ export type ScatterPoint = { x: number; y: number; r?: number; label?: string; }; /** A statistical / smoothing line drawn on top of a source series. * - `'linear'`: ordinary least-squares regression line * - `'sma:N'`: simple moving average over a window of N points * - `'ema:N'`: exponential moving average with smoothing factor 2/(N+1) */ export type SeriesOverlay = 'linear' | `sma:${number}` | `ema:${number}`; /** A texture fill applied in addition to (and on top of) the series color. * Helps colorblind readers distinguish series at a glance. */ export type SeriesPattern = 'solid' | 'stripe' | 'crosshatch' | 'dots' | 'diagonal'; /** Cycle used when `ChartSpec.patternFallback` is true and a series has no * explicit `pattern` set. Skips `'solid'` so every series gets a texture. */ export type ChartSeries = { label: string; values: number[]; color?: string; /** Per-series chart type, for combo charts. Defaults to the spec `type`. */ type?: 'bar' | 'line' | 'area'; /** Plot against the left (default) or right Y axis. */ axis?: 'left' | 'right'; /** Scatter / bubble points (used when `type === 'scatter'`). */ points?: ScatterPoint[]; /** Row IDs contributing to each data point - parallel to `values`. When * present, click handlers receive these in `ChartSelection.rowIds` so * callers can drill the grid back to the source rows. */ rowIds?: Array>; /** Draw a smoothing / trend overlay on top of this series. */ overlay?: SeriesOverlay; /** Color for the overlay line. Defaults to the series color. */ overlayColor?: string; /** Texture fill (e.g. diagonal stripes) layered over the series color. * Lets colorblind viewers tell two series apart even at the same hue. */ pattern?: SeriesPattern; /** Interpolate the line as a curve instead of polylines. `'monotone'` * cubic prevents overshoots between points (best default for data); * `true` is an alias for `'monotone'`. Only meaningful for line/area. */ smooth?: boolean | 'monotone'; /** Upper envelope (e.g. forecast 95th percentile) parallel to `values`. * When set alongside `lowerValues`, the chart shades the band between * the two as a translucent fill in the series color. */ upperValues?: number[]; /** Lower envelope; pair with `upperValues` for a confidence band. */ lowerValues?: number[]; }; /** A pinned label drawn over the plot, anchored to a data point or to an * arbitrary (x, y) in data space. Useful for "Release v1", "Outage", etc. */ export type ChartAnnotation = { /** Anchor in data space. Provide either `category` + `axis` for a point on * an existing series, OR raw `x` / `y` numeric coordinates in data space. */ at: { category: string; series?: string; } | { x: number; y?: number; }; label: string; color?: string; /** Where the label sits relative to the marker. Defaults to 'top'. */ placement?: 'top' | 'bottom' | 'left' | 'right'; }; /** A horizontal reference / target line drawn across the plot. */ export type ChartReferenceLine = { value: number; label?: string; axis?: 'left' | 'right'; color?: string; dashed?: boolean; }; export type ChartSpec = { /** Default type for series that don't set their own `type`. */ type: ChartType; /** X-axis labels (one per data point). */ categories: string[]; series: ChartSeries[]; width?: number; height?: number; /** Palette used when a series has no explicit `color`. */ palette?: string[]; /** Per-category color overrides (by category label) - for pie / donut slice * recolouring, where colour follows the category, not a series. */ categoryColors?: Record; /** Number format for the value axis, tooltips, data labels and reference * lines. Unset = the default compact `1.2k` / `1.2M` style. */ valueFormat?: ChartValueFormat; /** Grouped (nested) category axis: a parent tier spanning consecutive leaf * categories (spans must sum to `categories.length`). Vertical category * charts only (ignored for time / horizontal / pie). */ categoryGroups?: Array<{ label: string; span: number; }>; /** Stack bar / area series (per axis) instead of grouping them. */ stacked?: boolean; /** Stack to 100% (each category normalized to its total). Implies stacked. */ stacked100?: boolean; /** * Bar orientation. `'horizontal'` swaps the axes - categories run down the * left, bars grow rightward - which suits long category labels. Only applies * when every series is a bar (combo / line / area fall back to vertical). */ orientation?: 'vertical' | 'horizontal'; /** Pie only: inner radius as a fraction of the outer radius (0..1) -> donut. */ innerRadius?: number; /** Horizontal target / goal / average lines. */ referenceLines?: ChartReferenceLine[]; /** Treat `categories` as dates -> time-scaled x positions + date ticks. */ xType?: 'category' | 'time'; /** Axis titles (reserve gutter space + render). */ yAxisTitle?: string; y2AxisTitle?: string; xAxisTitle?: string; /** Y-axis scale. `'log'` plots base-10 logarithmic - values <= 0 are * treated as missing. Necessary for wide-range data (money, audience * size, scientific). Default `'linear'`. */ yScale?: 'linear' | 'log'; /** Right (secondary) Y-axis scale. Default `'linear'`. */ y2Scale?: 'linear' | 'log'; /** Pinned text labels at fixed data-space positions (callouts). */ annotations?: ChartAnnotation[]; /** When true, automatically cycle through pattern fills for every series * that doesn't set `pattern` explicitly. Useful as a one-flag colorblind * fallback. Default false. */ patternFallback?: boolean; /** Calendar heatmap: array of date+value samples (one per day). Date * strings are 'YYYY-MM-DD'. Missing days render as blank cells. */ calendarValues?: Array<{ date: string; value: number; }>; /** Calendar heatmap: year window. Default: span the data. */ calendarStart?: string; calendarEnd?: string; /** Gauge: the value to display. */ gaugeValue?: number; /** Gauge: min/max of the dial scale. Defaults [0, 100]. */ gaugeMin?: number; gaugeMax?: number; /** Gauge: target marker (the line/notch on the arc). */ gaugeTarget?: number; /** Gauge: color bands along the arc (e.g. red/amber/green). */ gaugeRanges?: Array<{ from: number; to: number; color: string; }>; /** Gauge: unit / suffix shown next to the value (e.g. '%', 'ms'). */ gaugeUnit?: string; /** Tree-map: hierarchical root. Leaves have `value`; parents are the * sum of their children's totals. */ treemap?: TreeNode; /** Sankey: nodes + flow links between them. Link `source` / `target` * reference node ids. */ sankeyNodes?: Array<{ id: string; label?: string; color?: string; }>; sankeyLinks?: Array<{ source: string; target: string; value: number; color?: string; }>; /** Waterfall: per-category flag marking bars as totals/subtotals that * reset the running sum and span from 0. Same length as `categories`. */ waterfallTotals?: boolean[]; /** Waterfall: explicit colors for positive/negative/total bars. The * series color is ignored when this is set. */ waterfallColors?: { positive?: string; negative?: string; total?: string; }; /** Heatmap color scale. `'sequential'` maps min->max through one hue, * `'diverging'` runs cold->neutral->warm around 0. A custom array * (>=2 hex colors) defines an arbitrary gradient. Default `'sequential'`. */ colorScale?: 'sequential' | 'diverging' | string[]; }; /** A tree-map / sankey / treemap node spec. Used recursively as a tree. */ export type TreeNode = { name: string; value?: number; color?: string; children?: TreeNode[]; }; /** A laid-out tree-map rectangle. */ export type ChartTreemapCell = { x: number; y: number; w: number; h: number; color: string; textColor: string; name: string; value: number; /** Depth from the root - useful for color cycling per level. */ depth: number; }; /** A calendar-heatmap cell (one day). */ export type ChartCalendarCell = { x: number; y: number; size: number; date: string; value: number; /** Defined when a value was supplied for this day; blank otherwise. */ defined: boolean; color: string; }; /** A gauge dial layout. */ export type ChartGaugeLayout = { cx: number; cy: number; r: number; /** Track arc path (background grey). */ trackPath: string; /** Value arc path (filled to the current value). */ valuePath: string; /** Optional colored range arcs. */ rangePaths: Array<{ path: string; color: string; from: number; to: number; }>; /** Pixel position of the target marker (when set). */ target: { x1: number; y1: number; x2: number; y2: number; } | null; /** Tick marks around the dial (major ticks are longer). */ ticks: Array<{ x1: number; y1: number; x2: number; y2: number; major: boolean; }>; /** Pointer needle (a kite shape) + its center hub radius. */ needle: { path: string; hubR: number; }; /** Status color of the value arc (the band the value falls in), or null to * fall back to the theme accent. */ valueColor: string | null; /** Scale end labels positioned under the two arc ends. */ minLabel: { x: number; y: number; }; maxLabel: { x: number; y: number; }; value: number; min: number; max: number; unit: string; }; /** A sankey node + its laid-out rect + total flow. */ export type ChartSankeyNode = { id: string; label: string; color: string; x: number; y: number; w: number; h: number; /** Column (depth) the node was assigned to. */ column: number; totalIn: number; totalOut: number; }; /** A sankey link rendered as a curved ribbon. */ export type ChartSankeyLink = { path: string; color: string; /** Stroke width = link value scaled to pixels. */ width: number; source: string; target: string; value: number; }; /** A single funnel segment (trapezoid) in pixel space. */ export type ChartFunnelSegment = { /** Pre-built SVG path for the trapezoid. */ path: string; color: string; label: string; /** Original value (before any percentile normalisation). */ value: number; /** Conversion vs. first segment, 0..1. */ conversion: number; /** Drop-off from the previous segment, 0..1. */ dropoff: number; /** Centre point (label anchor). */ cx: number; cy: number; /** Auto-picked black/white contrast color for in-segment labels. */ textColor: string; }; /** A radar series' polygon: axis values + the closed polygon path. */ export type ChartRadarSeries = { label: string; color: string; path: string; /** Per-axis (x, y) endpoints so callers can draw dots / hit targets. */ points: Array<{ x: number; y: number; value: number; axis: string; }>; }; /** Radar axis spoke + tick info. */ export type ChartRadarAxis = { label: string; /** Outermost endpoint of the spoke. */ x: number; y: number; }; /** A single heatmap rectangle in pixel space. */ export type ChartHeatmapCell = { x: number; y: number; w: number; h: number; color: string; /** Text color picked for contrast against `color`. */ textColor: string; value: number; rowLabel: string; colLabel: string; }; export type ChartBar = { x: number; y: number; w: number; h: number; color: string; /** Category (x label) this bar belongs to - for tooltips + labels. */ label: string; /** Series label this bar belongs to. */ series: string; value: number; }; export type ChartLinePoint = { x: number; y: number; label: string; value: number; /** False for null / NaN values - the line breaks (gap), no dot is drawn. */ defined: boolean; }; export type ChartLine = { path: string; areaPath: string; color: string; label: string; points: ChartLinePoint[]; /** Confidence-band path (between upperValues + lowerValues) for this * series, when both arrays are supplied. Empty otherwise. */ bandPath?: string; }; export type ChartPieSlice = { path: string; color: string; label: string; value: number; percent: number; /** Centroid - anchor point for a data label. */ cx: number; cy: number; }; export type ChartAxisTick = { value: number; y: number; label: string; }; export type ChartCategoryTick = { label: string; x: number; }; export type ChartLegendItem = { label: string; color: string; }; export type ChartRefLineGeo = { y: number; label: string; color: string; dashed: boolean; }; /** A vertical reference line (horizontal bar charts) positioned by `x`. */ export type ChartRefLineGeoV = { x: number; label: string; color: string; dashed: boolean; }; export type ChartScatterDot = { cx: number; cy: number; r: number; color: string; label: string; series: string; x: number; y: number; }; export type ChartGeometry = { type: ChartType; width: number; height: number; plot: { x: number; y: number; w: number; h: number; }; bars: ChartBar[]; lines: ChartLine[]; slices: ChartPieSlice[]; yTicks: ChartAxisTick[]; /** Right-axis ticks (combo / dual-axis); empty when there's no right axis. */ y2Ticks: ChartAxisTick[]; hasRightAxis: boolean; xTicks: ChartCategoryTick[]; /** Grouped category axis parent tier: label + span extent (pixels). Empty * unless `spec.categoryGroups` is set on a vertical category chart. */ categoryGroupTicks: Array<{ label: string; xCenter: number; x0: number; x1: number; }>; /** True when x labels are long/many and should be rotated. */ xLabelRotated: boolean; legend: ChartLegendItem[]; /** Donut centre (pie + innerRadius), for a centre total label. */ donut: { cx: number; cy: number; r: number; total: number; } | null; /** Horizontal reference / target lines. */ referenceLines: ChartRefLineGeo[]; /** Scatter / bubble points (type === 'scatter'). */ scatterPoints: ChartScatterDot[]; /** Bar orientation. `'horizontal'` uses `valueTicks` / `catTicks` below. */ orientation: 'vertical' | 'horizontal'; /** Horizontal bars: value-axis ticks along the bottom (label + x). */ valueTicks: ChartCategoryTick[]; /** Horizontal bars: category labels down the left (label + y; value = index). */ catTicks: ChartAxisTick[]; /** Horizontal bars: vertical reference / target lines (positioned by x). */ referenceLinesV: ChartRefLineGeoV[]; /** Trend / moving-average overlay lines (parallel to `lines`). Drawn * dashed on top of their source series. */ overlays: ChartLine[]; /** Pinned annotation labels with pre-resolved screen coordinates. */ annotations: Array<{ x: number; y: number; label: string; color: string; placement: 'top' | 'bottom' | 'left' | 'right'; }>; /** Heatmap cells (type === 'heatmap'). */ heatmapCells: ChartHeatmapCell[]; /** Heatmap row labels with pre-resolved y positions (left gutter). */ heatmapRowTicks: ChartAxisTick[]; /** Heatmap column labels (bottom of plot). */ heatmapColTicks: ChartCategoryTick[]; /** Heatmap color-scale legend: ordered stops with value + color. */ heatmapLegend: Array<{ value: number; color: string; label: string; }>; /** Funnel segments (type === 'funnel'). */ funnelSegments: ChartFunnelSegment[]; /** Radar concentric grid rings (centred at `radarCenter`). */ radarRings: number[]; /** Radar axis labels + spoke endpoints. */ radarAxes: ChartRadarAxis[]; /** Radar series polygons. */ radarSeries: ChartRadarSeries[]; /** Centre of the radar / pie. Pre-computed so callers don't re-derive. */ radarCenter: { cx: number; cy: number; r: number; } | null; /** Tree-map cells (type === 'treemap'). */ treemapCells: ChartTreemapCell[]; /** Calendar heatmap (type === 'calendar'). */ calendarCells: ChartCalendarCell[]; /** Calendar month labels along the top. */ calendarMonthTicks: ChartCategoryTick[]; /** Calendar legend stops (sequential ramp). */ calendarLegend: Array<{ value: number; color: string; label: string; }>; /** Gauge layout (type === 'gauge'). Null when not a gauge. */ gauge: ChartGaugeLayout | null; /** Sankey nodes (type === 'sankey'). */ sankeyNodes: ChartSankeyNode[]; /** Sankey links (type === 'sankey'). */ sankeyLinks: ChartSankeyLink[]; }; export declare const DEFAULT_PALETTE: string[]; export type NiceScale = { min: number; max: number; step: number; ticks: number[]; }; /** Sample a hex color from an array of hex stops at fractional position t. * Linearly interpolates between the two nearest stops in RGB space. */ export declare function sampleGradient(stops: string[], t: number): string; /** Pick a black or white text color that has the better contrast against * the given background. Uses the WCAG relative-luminance heuristic. */ export declare function pickContrastText(bgHex: string): string; /** Pick the largest power of 10 that fits at the bottom of [min,max], and * the smallest that covers the top, then enumerate decade boundaries. Used * by log-scale axes (yScale: 'log'). */ export declare function niceLogScale(min: number, max: number): NiceScale; /** Ordinary least-squares regression on (i, values[i]) pairs (i = x index). * Returns the fitted value at each x index, or NaN where the source value * was non-finite. */ /** Build an SVG path from a list of (x,y) pairs, optionally smoothed via * monotone cubic interpolation (preserves local extrema - no overshoots). * Breaks the path at `defined === false` gaps. */ export declare function buildLinePath(pts: Array<{ x: number; y: number; defined: boolean; }>, smooth: boolean): string; export declare function linearTrend(values: number[]): number[]; /** Simple moving average over a window of `period` values. Window centres * trail to the right (typical for time-series). NaN for points before the * window is full. */ export declare function simpleMovingAverage(values: number[], period: number): number[]; /** Exponential moving average. Smoothing factor alpha = 2 / (period + 1). */ export declare function exponentialMovingAverage(values: number[], period: number): number[]; /** Compute overlay values for a series spec like 'sma:7' / 'ema:14' / 'linear'. */ export declare function computeOverlay(values: number[], spec: SeriesOverlay): number[]; /** Round a [min,max] domain out to nice tick boundaries. */ export declare function niceScale(min: number, max: number, tickCount?: number): NiceScale; /** Value-axis / tooltip / label number format. */ export type ChartValueFormat = 'number' | 'currency' | 'percent' | 'compact'; /** * Format a numeric value for display, honouring an optional `valueFormat`. * Builds on the compact `1.2k` / `1.2M` base: currency prefixes `$` (sign * outside), percent multiplies by 100 and suffixes `%`. Unset / `'number'` / * `'compact'` = the plain compact form. */ export declare function formatChartValue(n: number, format?: ChartValueFormat): string; export declare function buildChart(spec: ChartSpec, theme?: 'light' | 'dark'): ChartGeometry; /** * Aggregate flat rows into a chart spec. Group by a category field, reduce a * value field per group. Three multi-series shapes: * - `value: 'revenue'` -> one series * - `value: ['revenue','cost']` -> one series per value field * - `value: 'sales', series: 'region'` -> pivot: one series per distinct * value of the `series` field */ export declare function rowsToChartSpec>(rows: ReadonlyArray, opts: { type: ChartType; category: keyof T & string; value: (keyof T & string) | Array; /** Pivot dimension: one series per distinct value of this field. */ series?: keyof T & string; reduce?: 'sum' | 'avg' | 'count'; seriesLabel?: string; width?: number; height?: number; stacked?: boolean; stacked100?: boolean; palette?: string[]; /** Order categories. Defaults to insertion order (or value-desc when topN). */ sort?: 'value-desc' | 'value-asc' | 'category' | 'none'; /** Keep only the top N categories; bucket the rest into "Other". */ topN?: number; /** Label for the bucketed remainder. Default "Other". */ otherLabel?: string; /** Field carrying each row's stable id. When set, the resulting spec's * series carry `rowIds` arrays so click handlers can drill back to * the source rows. */ idField?: keyof T & string; }): ChartSpec;