Recharts advanced patterns unlock custom visualizations — <Tooltip content={<CustomTooltip />}> accepts a React component with TooltipProps<ValueType, NameType>. <Line dot={<CustomDot />}> renders custom dot components. <ComposedChart> combines Bar, Line, and Area series. <ReferenceArea> and <ReferenceLine> add annotations and threshold overlays. <Brush> enables time range selection on a chart. <defs><linearGradient> creates gradient fills for Area charts. Custom <Legend content={<LegendRenderer />}> renders a completely custom legend. <LabelList> adds value labels to bar tops. onClick on any chart element returns the full data payload. isAnimationActive={false} disables animations for performance. Real-time: update the data prop with a ring buffer of N points. yAxis.domain={[dataMin, dataMax]} auto-scales the Y axis. <Sector> renders custom pie slices with hover expansion. Claude Code generates Recharts dashboards, custom tooltips, annotated charts, and real-time streaming charts.
CLAUDE.md for Recharts Advanced
## Recharts Advanced Stack
- Version: recharts >= 2.13
- Custom tooltip: <Tooltip content={({ active, payload, label }) => active && payload?.length ? <div>{payload.map(p => <p key={p.name}>{p.name}: {p.value}</p>)}</div> : null} />
- ComposedChart: <ComposedChart data={data}><XAxis /><YAxis /><Bar /><Line /><Area /></ComposedChart>
- Gradient fill: <defs><linearGradient id="grad" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#6366f1" stopOpacity={0.8}/><stop offset="100%" stopColor="#6366f1" stopOpacity={0}/></linearGradient></defs> then fill="url(#grad)"
- ReferenceArea: <ReferenceArea x1="Jan" x2="Mar" fill="red" fillOpacity={0.1} label="Q1"/>
- Brush: <Brush dataKey="date" height={30} stroke="#8884d8" />
- Real-time: maintain const [buffer, setBuffer] = useState<D[]>([]); setBuffer(prev => [...prev.slice(-60), newPoint])
Custom Tooltip
// components/charts/CustomTooltip.tsx — recharts custom tooltip
import type { TooltipProps } from "recharts"
import type { ValueType, NameType } from "recharts/types/component/DefaultTooltipContent"
interface MetricTooltipProps extends TooltipProps<ValueType, NameType> {
currencyFormat?: boolean
percentFormat?: boolean
}
export function MetricTooltip({ active, payload, label, currencyFormat, percentFormat }: MetricTooltipProps) {
if (!active || !payload?.length) return null
const format = (value: ValueType) => {
const num = Number(value)
if (currencyFormat) return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2 })}`
if (percentFormat) return `${num.toFixed(1)}%`
return num.toLocaleString()
}
return (
<div className="bg-popover border rounded-xl shadow-lg p-3 text-sm min-w-[160px]">
<p className="font-medium text-xs text-muted-foreground mb-2">{label}</p>
<div className="space-y-1">
{payload.map((entry, i) => (
<div key={i} className="flex items-center justify-between gap-4">
<div className="flex items-center gap-1.5">
<div
className="size-2.5 rounded-full flex-shrink-0"
style={{ backgroundColor: entry.color }}
/>
<span className="text-muted-foreground truncate max-w-[100px]">{entry.name}</span>
</div>
<span className="font-medium tabular-nums">{format(entry.value ?? 0)}</span>
</div>
))}
</div>
</div>
)
}
Revenue Dashboard Chart
// components/charts/RevenueDashboard.tsx — ComposedChart with annotations
"use client"
import {
ComposedChart,
Bar,
Line,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ReferenceArea,
ReferenceLine,
ResponsiveContainer,
Brush,
LabelList,
} from "recharts"
import { MetricTooltip } from "./CustomTooltip"
import { useState } from "react"
type MonthlyRevenue = {
month: string
revenue: number
expenses: number
profit: number
target: number
}
const MONTHS_DATA: MonthlyRevenue[] = [
{ month: "Jan", revenue: 42000, expenses: 28000, profit: 14000, target: 40000 },
{ month: "Feb", revenue: 38000, expenses: 25000, profit: 13000, target: 42000 },
{ month: "Mar", revenue: 51000, expenses: 31000, profit: 20000, target: 44000 },
{ month: "Apr", revenue: 47000, expenses: 29000, profit: 18000, target: 46000 },
{ month: "May", revenue: 53000, expenses: 33000, profit: 20000, target: 48000 },
{ month: "Jun", revenue: 61000, expenses: 36000, profit: 25000, target: 50000 },
]
export function RevenueDashboard() {
const [brushRange, setBrushRange] = useState<{ startIndex: number; endIndex: number } | null>(null)
const [selectedBar, setSelectedBar] = useState<string | null>(null)
const visibleData = brushRange
? MONTHS_DATA.slice(brushRange.startIndex, brushRange.endIndex + 1)
: MONTHS_DATA
const avgProfit = Math.round(visibleData.reduce((sum, d) => sum + d.profit, 0) / visibleData.length)
return (
<div className="rounded-2xl border bg-card p-6 space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold">Revenue Overview</h3>
<p className="text-sm text-muted-foreground">
Avg. profit: ${avgProfit.toLocaleString()}
</p>
</div>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span className="flex items-center gap-1"><span className="size-2.5 rounded bg-indigo-500 inline-block"/>Revenue</span>
<span className="flex items-center gap-1"><span className="size-2.5 rounded bg-rose-500 inline-block"/>Expenses</span>
<span className="flex items-center gap-1"><span className="size-2 rounded-full bg-emerald-500 inline-block"/>Profit</span>
</div>
</div>
<ResponsiveContainer width="100%" height={320}>
<ComposedChart data={MONTHS_DATA} barGap={4} onClick={(data) => {
setSelectedBar(data?.activeLabel ?? null)
}}>
<defs>
<linearGradient id="revenueGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#6366f1" stopOpacity={0.3} />
<stop offset="100%" stopColor="#6366f1" stopOpacity={0.03} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" vertical={false} />
<XAxis
dataKey="month"
tick={{ fontSize: 12, fill: "hsl(var(--muted-foreground))" }}
tickLine={false}
axisLine={false}
/>
<YAxis
tick={{ fontSize: 12, fill: "hsl(var(--muted-foreground))" }}
tickLine={false}
axisLine={false}
tickFormatter={(v) => `$${(v / 1000).toFixed(0)}k`}
/>
<Tooltip
content={<MetricTooltip currencyFormat />}
cursor={{ fill: "hsl(var(--muted))", opacity: 0.5 }}
/>
{/* Highlight Q1 */}
<ReferenceArea
x1="Jan"
x2="Mar"
fill="hsl(var(--muted))"
fillOpacity={0.4}
label={{ value: "Q1", position: "insideTopLeft", fontSize: 11, fill: "hsl(var(--muted-foreground))" }}
/>
{/* Average profit line */}
<ReferenceLine
y={avgProfit}
stroke="#22c55e"
strokeDasharray="4 4"
label={{ value: "Avg profit", position: "right", fontSize: 11, fill: "#22c55e" }}
/>
{/* Target line */}
<Line
type="monotone"
dataKey="target"
stroke="#f59e0b"
strokeDasharray="6 3"
dot={false}
strokeWidth={1.5}
name="Target"
/>
{/* Revenue area */}
<Area
type="monotone"
dataKey="revenue"
fill="url(#revenueGrad)"
stroke="#6366f1"
strokeWidth={2}
name="Revenue"
/>
{/* Expense bars */}
<Bar dataKey="expenses" fill="#f43f5e" fillOpacity={0.7} radius={[4, 4, 0, 0]} name="Expenses">
<LabelList
dataKey="expenses"
position="top"
formatter={(v: number) => `$${(v / 1000).toFixed(0)}k`}
style={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
/>
</Bar>
{/* Profit line */}
<Line
type="monotone"
dataKey="profit"
stroke="#22c55e"
strokeWidth={2.5}
dot={{ fill: "#22c55e", r: 4, strokeWidth: 0 }}
activeDot={{ r: 6, fill: "#22c55e" }}
name="Profit"
/>
<Brush
dataKey="month"
height={24}
stroke="hsl(var(--border))"
fill="hsl(var(--muted))"
travellerWidth={8}
onChange={(range) => setBrushRange(range as any)}
startIndex={0}
endIndex={MONTHS_DATA.length - 1}
/>
</ComposedChart>
</ResponsiveContainer>
</div>
)
}
Real-Time Streaming Chart
// components/charts/RealtimeMetric.tsx — live updating chart
"use client"
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"
import { useEffect, useState, useCallback } from "react"
type DataPoint = { time: string; value: number; anomaly?: boolean }
const MAX_POINTS = 60
function generatePoint(): DataPoint {
return {
time: new Date().toLocaleTimeString("en-US", { hour12: false }),
value: Math.round(40 + Math.random() * 40 + Math.sin(Date.now() / 5000) * 15),
}
}
interface RealtimeMetricProps {
title: string
unit?: string
warningThreshold?: number
}
export function RealtimeMetric({ title, unit = "", warningThreshold }: RealtimeMetricProps) {
const [data, setData] = useState<DataPoint[]>(() =>
Array.from({ length: 20 }, (_, i) => ({
time: new Date(Date.now() - (20 - i) * 1000).toLocaleTimeString("en-US", { hour12: false }),
value: Math.round(50 + Math.random() * 20),
})),
)
const addPoint = useCallback(() => {
const point = generatePoint()
if (warningThreshold) point.anomaly = point.value > warningThreshold
setData((prev) => [...prev.slice(-(MAX_POINTS - 1)), point])
}, [warningThreshold])
useEffect(() => {
const id = setInterval(addPoint, 1000)
return () => clearInterval(id)
}, [addPoint])
const latest = data[data.length - 1]?.value ?? 0
const isWarning = warningThreshold !== undefined && latest > warningThreshold
return (
<div className={`rounded-2xl border p-5 transition-colors ${isWarning ? "border-amber-500/50 bg-amber-50/5" : "bg-card"}`}>
<div className="flex items-center justify-between mb-3">
<h3 className="font-medium text-sm">{title}</h3>
<div className="flex items-center gap-2">
{isWarning && (
<span className="text-xs font-medium text-amber-600 bg-amber-100 px-2 py-0.5 rounded-full">
Warning
</span>
)}
<span className={`text-xl font-bold tabular-nums ${isWarning ? "text-amber-600" : ""}`}>
{latest}{unit}
</span>
</div>
</div>
<ResponsiveContainer width="100%" height={120}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border))" />
<XAxis dataKey="time" tick={false} axisLine={false} tickLine={false} />
<YAxis
domain={warningThreshold ? [0, warningThreshold * 1.3] : ["auto", "auto"]}
tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
tickLine={false}
axisLine={false}
width={32}
/>
<Tooltip
contentStyle={{ fontSize: 12 }}
formatter={(v: number) => [`${v}${unit}`, title]}
/>
<Line
type="monotone"
dataKey="value"
stroke={isWarning ? "#f59e0b" : "#6366f1"}
strokeWidth={2}
dot={false}
isAnimationActive={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
)
}
For the Victory Charts alternative when React Native support is needed (Victory works in both React Native and web with the same component API) or a stacked/grouped chart variant with more built-in statistical chart types like box plots is preferred — Victory is more comprehensive for native environments while Recharts is better for web-only dashboards, see the Victory guide. For the Nivo alternative when server-side rendered SVG charts, a rich set of chart types (heatmap, voronoi, sunburst, chord), and a centralized theming context for consistent dashboards is needed — Nivo has more chart variety while Recharts is simpler and more performant for common chart types, see the Nivo guide. The Claude Skills 360 bundle includes Recharts advanced skill sets covering custom tooltips, annotations, and real-time charts. Start with the free tier to try data visualization generation.