Claude Code for Recharts Advanced: Custom Charts and Tooltips — Claude Skills 360 Blog
Blog / Frontend / Claude Code for Recharts Advanced: Custom Charts and Tooltips
Frontend

Claude Code for Recharts Advanced: Custom Charts and Tooltips

Published: June 14, 2027
Read time: 6 min read
By: Claude Skills 360

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.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free