Victory provides a cross-platform charting library that works in both React web and React Native — <VictoryChart> is the container for all chart primitives. <VictoryLine data={data} x="date" y="value"> draws line charts. <VictoryBar data={data}> draws bar charts. <VictoryPie data={data} x="label" y="amount"> draws pie charts. <VictoryArea> fills below a line. <VictoryScatter> draws scatter plots. <VictoryAxis tickFormat={(t) => format(t)} dependentAxis> configures axes. <VictoryTooltip> with <VictoryVoronoiContainer> shows hover tooltips. <VictoryBrushContainer brushDimension="x"> handles range selection. VictoryTheme.material or a custom theme object styles all elements. <VictoryStack> stacks bars or areas. animate={{ duration: 400, easing: "quadInOut" }} animates transitions. For React Native: import { VictoryChart } from "victory-native" — same API, SVG rendered via react-native-svg. Claude Code generates Victory charts, donut charts, and brushable dashboards.
CLAUDE.md for Victory Charts
## Victory Charts Stack
- Version: victory >= 37, victory-native >= 41 (for React Native)
- Web: import { VictoryChart, VictoryLine, VictoryBar, VictoryPie, VictoryAxis, VictoryTooltip, VictoryVoronoiContainer } from "victory"
- React Native: import { VictoryChart, ... } from "victory-native" — requires react-native-svg >= 15
- Theme: <VictoryChart theme={VictoryTheme.clean}> or custom theme object
- Tooltip: <VictoryChart containerComponent={<VictoryVoronoiContainer voronoiDimension="x" labels={({ datum }) => datum.y} labelComponent={<VictoryTooltip />} />}>
- Animate: animate={{ duration: 300, onLoad: { duration: 400 } }} on each primitive
- Colors: colorScale={["#6366f1","#f43f5e","#22c55e"]}
Dashboard Charts Component
// components/charts/VictoryDashboard.tsx — multi-chart Victory dashboard
"use client"
import {
VictoryChart,
VictoryLine,
VictoryBar,
VictoryAxis,
VictoryTooltip,
VictoryVoronoiContainer,
VictoryLegend,
VictoryTheme,
VictoryArea,
VictoryStack,
VictoryBrushContainer,
type VictoryStyleObject,
} from "victory"
import { useState } from "react"
type DataPoint = { x: string; y: number }
type SeriesData = { name: string; data: DataPoint[]; color: string }
const STROKE_STYLES: VictoryStyleObject = {
data: { strokeWidth: 2, fillOpacity: 0.15 },
}
interface MetricLineChartProps {
series: SeriesData[]
height?: number
}
export function MetricLineChart({ series, height = 280 }: MetricLineChartProps) {
const [selectedDomain, setSelectedDomain] = useState<{ x?: [string, string] } | null>(null)
return (
<div className="rounded-2xl border bg-card p-4">
{/* Main chart */}
<VictoryChart
height={height}
padding={{ top: 20, right: 24, bottom: 50, left: 56 }}
theme={VictoryTheme.clean}
containerComponent={
<VictoryVoronoiContainer
voronoiDimension="x"
labels={({ datum }) => `${datum.y.toLocaleString()}`}
labelComponent={
<VictoryTooltip
style={{ fontSize: 10 }}
flyoutStyle={{ fill: "white", stroke: "#e5e7eb", strokeWidth: 1, borderRadius: 6 }}
flyoutPadding={{ top: 4, bottom: 4, left: 8, right: 8 }}
/>
}
/>
}
>
<VictoryAxis
tickFormat={(t: string) => t}
style={{
tickLabels: { fontSize: 10, fill: "#6b7280" },
axis: { stroke: "#e5e7eb" },
grid: { stroke: "#f3f4f6" },
}}
/>
<VictoryAxis
dependentAxis
tickFormat={(t: number) => `${(t / 1000).toFixed(0)}k`}
style={{
tickLabels: { fontSize: 10, fill: "#6b7280" },
axis: { stroke: "transparent" },
grid: { stroke: "#f3f4f6", strokeDasharray: "4,4" },
}}
/>
{series.map((s) => (
<VictoryLine
key={s.name}
data={s.data}
style={{ data: { stroke: s.color, strokeWidth: 2 } }}
animate={{ duration: 400 }}
/>
))}
<VictoryLegend
x={0}
y={height - 35}
orientation="horizontal"
gutter={24}
style={{ labels: { fontSize: 10, fill: "#6b7280" } }}
data={series.map((s) => ({ name: s.name, symbol: { fill: s.color } }))}
/>
</VictoryChart>
</div>
)
}
interface StackedBarChartProps {
categories: string[]
groups: { name: string; data: number[]; color: string }[]
height?: number
}
export function StackedBarChart({ categories, groups, height = 260 }: StackedBarChartProps) {
const seriesData = groups.map((g) => ({
...g,
points: g.data.map((y, i) => ({ x: categories[i], y })),
}))
return (
<div className="rounded-2xl border bg-card p-4">
<VictoryChart
height={height}
padding={{ top: 16, right: 16, bottom: 40, left: 48 }}
domainPadding={{ x: 20 }}
theme={VictoryTheme.clean}
>
<VictoryAxis
style={{ tickLabels: { fontSize: 10, fill: "#6b7280" }, axis: { stroke: "#e5e7eb" } }}
/>
<VictoryAxis
dependentAxis
tickFormat={(t: number) => `${t.toLocaleString()}`}
style={{ tickLabels: { fontSize: 10, fill: "#6b7280" }, axis: { stroke: "transparent" }, grid: { stroke: "#f3f4f6", strokeDasharray: "4,4" } }}
/>
<VictoryStack>
{seriesData.map((s) => (
<VictoryBar
key={s.name}
data={s.points}
style={{ data: { fill: s.color, fillOpacity: 0.85 } }}
animate={{ duration: 300 }}
/>
))}
</VictoryStack>
<VictoryLegend
x={0}
y={height - 32}
orientation="horizontal"
gutter={20}
style={{ labels: { fontSize: 10, fill: "#6b7280" } }}
data={groups.map((g) => ({ name: g.name, symbol: { fill: g.color } }))}
/>
</VictoryChart>
</div>
)
}
Donut Chart
// components/charts/DonutChart.tsx — VictoryPie donut with center label
"use client"
import { VictoryPie, VictoryLabel } from "victory"
type Slice = { label: string; value: number; color: string }
interface DonutChartProps {
data: Slice[]
title?: string
subtitle?: string
size?: number
}
export function DonutChart({ data, title, subtitle, size = 280 }: DonutChartProps) {
const total = data.reduce((sum, d) => sum + d.value, 0)
return (
<div className="rounded-2xl border bg-card p-4">
<div className="relative" style={{ width: size, height: size, margin: "0 auto" }}>
<svg viewBox={`0 0 ${size} ${size}`} width={size} height={size}>
<VictoryPie
standalone={false}
width={size}
height={size}
data={data.map((d) => ({ x: d.label, y: d.value }))}
colorScale={data.map((d) => d.color)}
innerRadius={size * 0.28}
padAngle={2}
animate={{ duration: 500 }}
style={{
labels: { display: "none" },
}}
/>
{/* Center label */}
{title && (
<VictoryLabel
textAnchor="middle"
verticalAnchor="middle"
x={size / 2}
y={size / 2 - 8}
text={title}
style={{ fontSize: 24, fontWeight: 700, fill: "#111" }}
/>
)}
{subtitle && (
<VictoryLabel
textAnchor="middle"
verticalAnchor="middle"
x={size / 2}
y={size / 2 + 18}
text={subtitle}
style={{ fontSize: 12, fill: "#6b7280" }}
/>
)}
</svg>
</div>
{/* Legend */}
<div className="mt-3 space-y-2">
{data.map((slice) => (
<div key={slice.label} className="flex items-center justify-between text-sm">
<div className="flex items-center gap-2">
<span className="size-3 rounded-sm flex-shrink-0" style={{ backgroundColor: slice.color }} />
<span className="text-muted-foreground">{slice.label}</span>
</div>
<div className="flex items-center gap-3">
<span className="font-medium">{slice.value.toLocaleString()}</span>
<span className="text-xs text-muted-foreground w-10 text-right">
{((slice.value / total) * 100).toFixed(1)}%
</span>
</div>
</div>
))}
</div>
</div>
)
}
For the Recharts alternative when a more familiar JSX component API tightly integrated with React state (not a standalone SVG render), built-in responsive container, and wider community examples are preferred — Recharts is the most popular React charting library for web-only apps while Victory’s primary advantage is the same API running in React Native, see the Recharts guide. For the D3.js alternative when completely custom SVG geometries, physics-based force layouts, geographic projections, or maximum control over every rendering detail is needed — D3 is the foundation Victory is built on, offering the lowest-level primitives while Victory provides pre-built chart components, see the D3.js guide. The Claude Skills 360 bundle includes Victory skill sets covering cross-platform charts, donut charts, and stacked bar charts. Start with the free tier to try React Native chart generation.