import type { MetricSeriesPoint } from '../types';
export function Sparkline({ points, field, label }: {
points: MetricSeriesPoint[];
field: 'tps' | 'mspt';
label: string;
}) {
const values = points.map((point) => point[field]).filter((value): value is number => typeof value === 'number');
if (values.length < 2) return <div className="sparkline sparkline--empty">—</div>;
const width = 640;
const height = 164;
const min = Math.min(...values);
const max = Math.max(...values);
const spread = Math.max(max - min, 0.001);
const path = values.map((value, index) => {
const x = (index / (values.length - 1)) * width;
const y = height - 10 - ((value - min) / spread) * (height - 20);
return `${index === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${y.toFixed(1)}`;
}).join(' ');
return (
<div className="sparkline">
<svg viewBox={`0 0 ${width} ${height}`} role="img" aria-label={label} preserveAspectRatio="none">
<defs>
<linearGradient id={`fill-${field}`} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="var(--accent)" stopOpacity="0.26" />
<stop offset="100%" stopColor="var(--accent)" stopOpacity="0" />
</linearGradient>
</defs>
<path className="sparkline__area" d={`${path} L ${width} ${height} L 0 ${height} Z`} fill={`url(#fill-${field})`} />
<path className="sparkline__line" d={path} />
</svg>
<div className="sparkline__range"><span>{min.toFixed(1)}</span><span>{max.toFixed(1)}</span></div>
</div>
);
}
import type { MetricSeriesPoint } from '../types';
export function Sparkline({ points, field, label }: {
points: MetricSeriesPoint[];
field: 'tps' | 'mspt';
label: string;
}) {
const values = points.map((point) => point[field]).filter((value): value is number => typeof value === 'number');
if (values.length < 2) return <div className="sparkline sparkline--empty">—</div>;
const width = 640;
const height = 164;
const min = Math.min(...values);
const max = Math.max(...values);
const spread = Math.max(max - min, 0.001);
const path = values.map((value, index) => {
const x = (index / (values.length - 1)) * width;
const y = height - 10 - ((value - min) / spread) * (height - 20);
return `${index === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${y.toFixed(1)}`;
}).join(' ');
return (
<div className="sparkline">
<svg viewBox={`0 0 ${width} ${height}`} role="img" aria-label={label} preserveAspectRatio="none">
<defs>
<linearGradient id={`fill-${field}`} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="var(--accent)" stopOpacity="0.26" />
<stop offset="100%" stopColor="var(--accent)" stopOpacity="0" />
</linearGradient>
</defs>
<path className="sparkline__area" d={`${path} L ${width} ${height} L 0 ${height} Z`} fill={`url(#fill-${field})`} />
<path className="sparkline__line" d={path} />
</svg>
<div className="sparkline__range"><span>{min.toFixed(1)}</span><span>{max.toFixed(1)}</span></div>
</div>
);
}