import { line as d3Line } from 'd3-shape'; import { scaleLinear } from 'd3-scale'; import { CHART, MARK } from './palette'; import { extent, periodToX, splitForecast, type SeriesPoint } from './scales'; /** * Tiny server-renderable line (no axes) with an end dot. Fixed viewBox so it scales with its box; * strokes use `vector-effect: non-scaling-stroke` so lines stay 1.5 px at any size. */ export function Sparkline({ points, width = 96, height = 28, color = CHART.accent, className, ariaLabel, direction, }: { points: SeriesPoint[]; width?: number; height?: number; color?: string; className?: string; ariaLabel?: string; /** Colour the end dot by direction (up/down) — the line stays neutral. */ direction?: 'up' | 'down' | 'flat' | null; }) { const clean = points.filter((p) => p.value != null && Number.isFinite(p.value)); if (clean.length < 2) return ; const xs = clean.map(periodToX); const xDom = extent(xs)!; const yDom = extent(clean.map((p) => p.value))!; const pad = 3; const x = scaleLinear().domain(xDom).range([pad, width - pad]); const y = scaleLinear() .domain(yDom[0] === yDom[1] ? [yDom[0] - 1, yDom[1] + 1] : yDom) .range([height - pad, pad]); const gen = d3Line() .x((p) => x(periodToX(p))) .y((p) => y(p.value!)); const runs = splitForecast(clean); const last = clean.filter((p) => !p.is_forecast).at(-1) ?? clean.at(-1)!; const dotColor = direction === 'up' ? CHART.up : direction === 'down' ? CHART.down : color; return ( {runs.map((r, i) => ( ))} ); }