spark
Server-safe inline SVG sparklines and proportion bars that sit on the text baseline.
chartsparklinesvginline
Preview
Latency trended down over the quarter, error rate spiked once , and cache hit rate sits at 82%.
Installation
shadcn CLI
Terminal
npx shadcn@latest add https://mocoui.site/r/spark.jsonManual
Copy each file from the Source section into its target path:
registry/spark/spark.tsx → components/moco/spark.tsx
registry/spark/spark.css → components/moco/spark.cssNeeds moco items: tokens
Usage
usage.tsx
import { Spark, SparkBar } from "./spark";
export default function SparkDemo() {
return (
<p style={{ fontFamily: "var(--moco-serif)", color: "var(--moco-body)" }}>
Latency trended down <Spark data={[9, 7, 8, 5, 4, 4, 2]} /> over the quarter, error rate
spiked once <Spark data={[1, 1, 6, 2, 1]} tone="warn" dot={false} label="error rate spike" />,
and cache hit rate sits at <SparkBar value={0.82} label="82 percent cache hit rate" /> 82%.
</p>
);
}
Source
components/moco/spark.tsx
import * as React from "react";
import "./spark.css";
export type SparkTone = "accent" | "warn" | "faint";
export interface SparkProps {
data: number[];
w?: number;
h?: number;
dot?: boolean;
tone?: SparkTone;
label?: string;
}
/**
* Inline sparklines. ~60×16, sits on the text baseline, no axes, no labels.
* These are punctuation, not charts.
*/
export function Spark({ data, w = 60, h = 16, dot = true, tone = "accent", label }: SparkProps) {
if (data.length < 2) return null;
const min = Math.min(...data);
const max = Math.max(...data);
const span = max - min || 1;
const x = (i: number) => (i / (data.length - 1)) * (w - 2) + 1;
const y = (v: number) => h - 2 - ((v - min) / span) * (h - 4);
const d = data.map((v, i) => `${i ? "L" : "M"}${x(i).toFixed(2)},${y(v).toFixed(2)}`).join(" ");
return (
<svg
className="moco-spark"
width={w}
height={h}
viewBox={`0 0 ${w} ${h}`}
role="img"
aria-label={label ?? `sparkline, ${data.length} points, ${min.toFixed(2)} to ${max.toFixed(2)}`}
>
<path d={d} fill="none" stroke={`var(--moco-${tone})`} strokeWidth="1" />
{dot ? (
<circle
cx={x(data.length - 1)}
cy={y(data[data.length - 1])}
r="1.6"
fill={`var(--moco-${tone})`}
/>
) : null}
</svg>
);
}
export interface SparkBarProps {
value: number;
max?: number;
w?: number;
h?: number;
tone?: SparkTone;
label?: string;
}
/** A tiny inline proportion bar. */
export function SparkBar({ value, max = 1, w = 60, h = 8, tone = "accent", label }: SparkBarProps) {
const k = Math.max(0, Math.min(1, value / max));
return (
<svg
className="moco-spark"
width={w}
height={h}
viewBox={`0 0 ${w} ${h}`}
role="img"
aria-label={label ?? `${Math.round(k * 100)} percent`}
>
<rect x="0" y={h / 2 - 2} width={w} height="4" fill="var(--moco-line)" />
<rect x="0" y={h / 2 - 2} width={w * k} height="4" fill={`var(--moco-${tone})`} />
</svg>
);
}
components/moco/spark.css
.moco-spark {
display: inline-block;
vertical-align: baseline;
margin: 0 0.15em;
overflow: visible;
}