import { useId } from 'react'; import { cn } from '#/lib/utils.js'; /** * A smooth open path through `points` as a Catmull-Rom spline expressed in cubic * Béziers, so the sparkline flows in rounded hills rather than jagged peaks. * Each control point is pulled a sixth of the way along the segment between its * neighbours; the y of every control point is clamped to the drawing box so a * spline overshoot can't dip the fill below the baseline or spill past the top. * @param {[number, number][]} points * @param {number} height * @returns {string} */ function splinePath(points, height) { const clampY = (y) => Math.max(0, Math.min(height, y)); let d = `M${points[0][0].toFixed(2)} ${points[0][1].toFixed(2)}`; for (let i = 0; i < points.length - 1; i++) { const [x0, y0] = points[i - 1] || points[i]; const [x1, y1] = points[i]; const [x2, y2] = points[i + 1]; const [x3, y3] = points[i + 2] || points[i + 1]; const c1x = x1 + (x2 - x0) / 6; const c1y = clampY(y1 + (y2 - y0) / 6); const c2x = x2 - (x3 - x1) / 6; const c2y = clampY(y2 - (y3 - y1) / 6); d += ` C${c1x.toFixed(2)} ${c1y.toFixed(2)} ${c2x.toFixed(2)} ${c2y.toFixed(2)} ${x2.toFixed(2)} ${y2.toFixed(2)}`; } return d; } /** * A tiny area chart drawn edge-to-edge, meant to sit behind a row as an ambient * backdrop rather than a chart you read precisely. `values` are bucket counts, * oldest to newest; the shape is normalized to its own peak, smoothed into a * spline, and stretched to fill via a non-uniform viewBox. The fill fades out * toward the top so text above it stays legible, and the whole thing renders in * the ring indigo at low opacity. Nothing is drawn for an empty or all-zero * series. * @param {{values: number[]|null|undefined, className?: string}} props */ export function Sparkline({ values, className }) { const gradientId = useId(); if (!values || values.length < 2) return null; const max = Math.max(...values); if (max <= 0) return null; const width = 100; const height = 32; const step = width / (values.length - 1); const points = /** @type {[number, number][]} */ ( values.map((value, i) => [i * step, height - (value / max) * height]) ); const line = splinePath(points, height); const area = `${line} L${width} ${height} L0 ${height} Z`; return ( ); }