← back to Ken
kalshi-dash/src/components/Sparkline.jsx
92 lines
import React, { useMemo } from 'react';
/**
* Sparkline — tiny inline SVG line chart with gradient fill
*
* Props:
* data — array of numbers
* color — CSS color or var() (default: var(--cyan))
* width — px (default: 60)
* height — px (default: 20)
* id — unique string for gradient ID (required when multiple sparklines are on the same page)
*
* Usage:
* <Sparkline data={[12,18,14,22,19,25,21]} color="var(--green)" id="balance-spark" />
*/
export default function Sparkline({ data = [], color = 'var(--cyan)', width = 60, height = 20, id = 'spark' }) {
const points = useMemo(() => {
if (!data || data.length < 2) return null;
const min = Math.min(...data);
const max = Math.max(...data);
const range = max - min || 1;
const pad = 2; // px padding top/bottom
const xs = data.map((_, i) => (i / (data.length - 1)) * width);
const ys = data.map(v => height - pad - ((v - min) / range) * (height - pad * 2));
// Polyline points string
const polyline = xs.map((x, i) => `${x},${ys[i]}`).join(' ');
// Closed path for fill (go to bottom-right corner, bottom-left, back up)
const fillPath =
`M ${xs[0]},${ys[0]} ` +
xs.slice(1).map((x, i) => `L ${x},${ys[i + 1]}`).join(' ') +
` L ${xs[xs.length - 1]},${height} L ${xs[0]},${height} Z`;
return { polyline, fillPath };
}, [data, width, height]);
if (!points) {
return <svg width={width} height={height} />;
}
const gradId = `spark-grad-${id}`;
return (
<svg
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
style={{ display: 'inline-block', verticalAlign: 'middle', flexShrink: 0 }}
aria-hidden="true"
>
<defs>
<linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity="0.25" />
<stop offset="100%" stopColor={color} stopOpacity="0.01" />
</linearGradient>
</defs>
{/* Gradient fill area */}
<path
d={points.fillPath}
fill={`url(#${gradId})`}
/>
{/* The line */}
<polyline
points={points.polyline}
fill="none"
stroke={color}
strokeWidth="1.5"
strokeLinejoin="round"
strokeLinecap="round"
/>
{/* Endpoint dot */}
{(() => {
const lastX = (data.length - 1) / (data.length - 1) * width;
const min = Math.min(...data);
const max = Math.max(...data);
const range = max - min || 1;
const pad = 2;
const lastY = height - pad - ((data[data.length - 1] - min) / range) * (height - pad * 2);
return (
<circle cx={lastX} cy={lastY} r="2" fill={color} />
);
})()}
</svg>
);
}