import { useRef, useState } from 'react' export interface SparkSeries { name: string; values: number[]; color: string } interface Props { series: SparkSeries[] max?: number // fixed ceiling (e.g. 100 for percent); otherwise auto from data format: (v: number) => string height?: number } const W = 300 // Thin-line sparkline with a crosshair tooltip. Stroke stays 2px at any width via // non-scaling-stroke; the box is stretched with preserveAspectRatio="none". export default function Sparkline({ series, max, format, height = 44 }: Props) { const box = useRef(null) const [hover, setHover] = useState(null) const n = Math.max(...series.map((s) => s.values.length), 0) if (n < 2) return
let top = max ?? 0 if (max === undefined) for (const s of series) for (const v of s.values) if (v > top) top = v if (top <= 0) top = 1 const H = height const x = (i: number) => (i / (n - 1)) * W const y = (v: number) => H - 2 - (Math.min(v, top) / top) * (H - 4) const path = (vals: number[]) => { const off = n - vals.length return vals.map((v, i) => `${i === 0 ? 'M' : 'L'}${x(i + off).toFixed(1)},${y(v).toFixed(1)}`).join(' ') } const move = (clientX: number) => { const r = box.current?.getBoundingClientRect() if (!r || r.width === 0) return const f = Math.min(1, Math.max(0, (clientX - r.left) / r.width)) setHover(Math.round(f * (n - 1))) } return (
move(e.clientX)} onMouseLeave={() => setHover(null)} onTouchStart={(e) => move(e.touches[0].clientX)} onTouchMove={(e) => move(e.touches[0].clientX)} onTouchEnd={() => setHover(null)}> {hover !== null && (
{series.map((s) => { const v = s.values[hover - (n - s.values.length)] return v === undefined ? null : {s.name} {format(v)} })}
)}
) }