
A chart tooltip displaying custom formatted date label built with TanStack Charts.
components/tanstack-charts/tooltip/tooltip-label-formatter.tsx
import { barY, defineChart, stack } from "@tanstack/charts";
import { motion } from "@tanstack/charts/motion";
import { tooltip } from "@tanstack/charts/tooltip";
import { Chart } from "@tanstack/react-charts/core";
import { useMemo, useRef } from "react";
import { scaleBand, scaleLinear } from "d3-scale";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
interface ActivityDatum {
date: string;
activity: "running" | "swimming";
value: number;
}
function generateDemoData(): ActivityDatum[] {
return [
{ date: "2024-07-15", activity: "running", value: 450 },
{ date: "2024-07-15", activity: "swimming", value: 300 },
{ date: "2024-07-16", activity: "running", value: 380 },
{ date: "2024-07-16", activity: "swimming", value: 420 },
{ date: "2024-07-17", activity: "running", value: 520 },
{ date: "2024-07-17", activity: "swimming", value: 310 },
{ date: "2024-07-18", activity: "running", value: 310 },
{ date: "2024-07-18", activity: "swimming", value: 490 },
{ date: "2024-07-19", activity: "running", value: 600 },
{ date: "2024-07-19", activity: "swimming", value: 350 },
];
}
const activityNames = ["running", "swimming"] as const;
const chartColors = ["var(--chart-1)", "var(--chart-2)"];
const motionRenderer = motion({
initial: "always",
transition: { type: "spring", stiffness: 170, damping: 18, mass: 1 },
});
function formatWeekday(value: string) {
return new Intl.DateTimeFormat("en-US", { weekday: "short" }).format(new Date(value));
}
function titleCase(value: string) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
export function TooltipLabelFormatter() {
const data = useMemo(() => generateDemoData(), []);
const seededTooltip = useRef(false);
const definition = useMemo(() => {
return defineChart(
{
renderer: motionRenderer,
motion: {
transition: { type: "spring", stiffness: 170, damping: 18, mass: 1 },
},
marks: [
barY(data, {
id: "activity-bars",
x: "date",
y: "value",
z: "activity",
color: "activity",
key: (row) => `${row.date}:${row.activity}`,
layout: stack({ order: activityNames }),
radius: 4,
inset: 4,
}),
],
scales: {
x: {
scale: () => scaleBand<string>().paddingInner(0.35).paddingOuter(0.15),
axis: {
line: false,
ticks: { size: 0, padding: 10, format: formatWeekday },
},
},
y: { scale: () => scaleLinear().domain([0, 1000]), axis: false },
},
color: { domain: activityNames, range: chartColors },
margin: { top: 10, right: 10, bottom: 30, left: 10 },
},
{
focus: "group-x",
tooltip: {
use: tooltip,
className:
"font-sans shadow-none! rounded-lg bg-popover text-popover-foreground border p-2.5 text-xs min-w-[140px] pointer-events-none transition-all duration-75",
offset: 8,
sort: "color-domain",
content: (points) => {
if (!points.length) return { title: "", rows: [] };
const rawDate = (points[0]?.datum as ActivityDatum)?.date ?? "";
const dateObj = new Date(rawDate);
const formattedTitle = isNaN(dateObj.getTime())
? rawDate
: dateObj.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
const ordered = [...points].sort(
(l, r) =>
activityNames.indexOf((l.datum as ActivityDatum).activity) -
activityNames.indexOf((r.datum as ActivityDatum).activity),
);
return {
title: formattedTitle,
rows: ordered.map((p) => {
const d = p.datum as ActivityDatum;
return {
label: titleCase(d.activity),
value: `${d.value}`,
color: p.color || (d.activity === "running" ? "var(--chart-1)" : "var(--chart-2)"),
};
}),
};
},
},
},
);
}, [data]);
return (
<Card className="flex w-full max-w-md flex-col">
<CardHeader className="pb-4">
<CardTitle>Daily Active Users (DAU)</CardTitle>
<CardDescription>Active user metrics with formatted timestamp headers</CardDescription>
</CardHeader>
<CardContent>
<div className="mx-auto flex aspect-[16/9] w-full items-center justify-center">
<Chart
definition={definition}
renderer={motionRenderer}
initialWidth={350}
height={200}
ariaLabel="User Engagement Breakdown Chart"
onRender={({ scene, interaction }: any) => {
if (seededTooltip.current) return;
const point = scene.points.find(
(candidate: any) => (candidate.datum as ActivityDatum).date === "2024-07-16",
);
if (!point) return;
seededTooltip.current = true;
interaction.setControlledFocus(point, {
source: "programmatic",
});
}}
/>
</div>
</CardContent>
</Card>
);
}
export default TooltipLabelFormatter;