components/tanstack-charts/line/premium-kpi-sparklines.tsx
import { areaY, d3Curve, defineChart, lineY } from "@tanstack/charts";
import { motion } from "@tanstack/charts/motion";
import { Chart } from "@tanstack/react-charts/core";
import { useMemo } from "react";
import { scaleLinear, scalePoint } from "d3-scale";
import { curveMonotoneX } from "d3-shape";
import { TrendingDown, TrendingUp } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
interface SparklineDatum {
id: number;
period: number;
value: number;
}
interface KpiMetric {
id: string;
label: string;
value: string;
trend: string;
trendDirection: "up" | "down";
surface: "area" | "line";
colorToken: string;
rows: SparklineDatum[];
}
const motionRenderer = motion({
initial: "always",
transition: { type: "spring", stiffness: 170, damping: 18, mass: 1 },
});
const smoothCurve = d3Curve(curveMonotoneX);
function generateKpiMetrics(): KpiMetric[] {
const generateRows = (base: number, volatility: number) => {
const rows: SparklineDatum[] = [];
let val = base;
for (let i = 1; i <= 12; i++) {
val += Math.sin(i * 0.6) * volatility + (Math.random() - 0.4) * (volatility * 0.8);
rows.push({ id: i, period: i, value: Math.max(10, val) });
}
return rows;
};
return [
{
id: "revenue",
label: "Monthly Recurring Revenue",
value: "$124,500",
trend: "+12.4%",
trendDirection: "up",
surface: "area",
colorToken: "var(--chart-1)",
rows: generateRows(100, 15),
},
{
id: "customers",
label: "Active Subscriptions",
value: "1,420",
trend: "+8.1%",
trendDirection: "up",
surface: "area",
colorToken: "var(--chart-2)",
rows: generateRows(1200, 60),
},
{
id: "churn",
label: "User Churn Rate",
value: "1.82%",
trend: "-0.4%",
trendDirection: "down",
surface: "line",
colorToken: "var(--chart-3)",
rows: generateRows(2.2, 0.3),
},
];
}
function KpiCard({ metric }: { metric: KpiMetric }) {
const definition = useMemo(() => {
const values = metric.rows.map((r) => r.value);
const minimum = Math.min(...values);
const maximum = Math.max(...values);
const pad = Math.max((maximum - minimum) * 0.15, 0.1);
const baseline = minimum - pad;
const line = lineY(metric.rows, {
id: `${metric.id}-line`,
x: "period",
y: "value",
key: "id",
stroke: metric.colorToken,
strokeWidth: 2.2,
curve: smoothCurve,
});
const marks =
metric.surface === "area"
? [
areaY(metric.rows, {
id: `${metric.id}-area`,
x: "period",
y: "value",
y1: baseline,
key: "id",
fill: `url(#${metric.id}-fill)`,
fillOpacity: 1,
curve: smoothCurve,
}),
line,
]
: [line];
return defineChart({
renderer: motionRenderer,
marks,
gradients:
metric.surface === "area"
? [
{
id: `${metric.id}-fill`,
x1: 0,
y1: 0,
x2: 0,
y2: 1,
stops: [
{ offset: 0, color: metric.colorToken, opacity: 0.3 },
{ offset: 1, color: metric.colorToken, opacity: 0 },
],
},
]
: [],
scales: {
x: {
scale: scalePoint<number>()
.domain(metric.rows.map((r) => r.period))
.padding(0.05),
axis: false,
},
y: {
scale: scaleLinear().domain([baseline, maximum + pad]),
axis: false,
},
},
margin: { top: 4, right: 4, bottom: 4, left: 4 },
theme: {
foreground: "transparent",
grid: "transparent",
background: "transparent",
},
});
}, [metric]);
return (
<Card className="flex flex-col justify-between p-4">
<CardHeader className="flex-row items-center justify-between space-y-0 p-0 pb-2">
<CardTitle className="text-muted-foreground text-xs font-medium">{metric.label}</CardTitle>
<div
className={`flex items-center text-xs font-semibold ${
metric.trendDirection === "up" ? "text-emerald-500" : "text-amber-500"
}`}>
{metric.trendDirection === "up" ? (
<TrendingUp className="mr-1 h-3.5 w-3.5" />
) : (
<TrendingDown className="mr-1 h-3.5 w-3.5" />
)}
{metric.trend}
</div>
</CardHeader>
<CardContent className="space-y-2 p-0">
<div className="text-2xl font-bold tracking-tight">{metric.value}</div>
<div className="h-[60px] w-full">
<Chart
definition={definition}
renderer={motionRenderer}
height={60}
ariaLabel={`${metric.label} sparkline trend`}
/>
</div>
</CardContent>
</Card>
);
}
export function PremiumKpiSparklines() {
const metrics = useMemo(() => generateKpiMetrics(), []);
return (
<div className="grid w-full max-w-xl grid-cols-1 gap-4 sm:grid-cols-3">
{metrics.map((metric) => (
<KpiCard key={metric.id} metric={metric} />
))}
</div>
);
}
export default PremiumKpiSparklines;