Pie Chart
Pie and donut chart with smooth hover animations and composable center content
Installation
Dependencies
Examples
Custom Center
"use client"
import { useState } from "react"
import PieChart, { PieCenter, PieSlice, type PieData } from "@/components/vritti/pie-chart"
const salesData: PieData[] = [
{ label: "Electronics", value: 4250, color: "#0ea5e9" },
{ label: "Clothing", value: 3120, color: "#a855f7" },
{ label: "Food", value: 2100, color: "#f59e0b" },
{ label: "Home", value: 1580, color: "#10b981" },
{ label: "Other", value: 1050, color: "#ef4444" },
]
const total = salesData.reduce((sum, d) => sum + d.value, 0)
export function PieChartCustomCenterDemo() {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
return (
<div className="flex justify-center p-4">
<PieChart
data={salesData}
hoveredIndex={hoveredIndex}
innerRadius={80}
onHoverChange={setHoveredIndex}
size={300}
>
{salesData.map((item, index) => (
<PieSlice index={index} key={item.label} />
))}
<PieCenter>
{({ value, label, isHovered, data }) => (
<div className="text-center">
<div
className="font-bold text-3xl"
style={{ color: isHovered ? data.color : undefined }}
>
{value.toLocaleString()}
</div>
<div className="text-muted-foreground text-sm">{label}</div>
{isHovered && (
<div className="mt-1 text-muted-foreground text-xs">
{((data.value / total) * 100).toFixed(1)}% of total
</div>
)}
</div>
)}
</PieCenter>
</PieChart>
</div>
)
}
Donut
"use client"
import PieChart, { PieCenter, PieSlice } from "@/components/vritti/pie-chart"
const data = [
{ label: "React", value: 400, color: "var(--chart-1)" },
{ label: "Vue", value: 200, color: "var(--chart-2)" },
{ label: "Angular", value: 150, color: "var(--chart-3)" },
{ label: "Svelte", value: 100, color: "var(--chart-4)" },
]
export function PieChartDonutDemo() {
return (
<div className="flex justify-center p-8">
<PieChart cornerRadius={4} data={data} innerRadius={80} padAngle={0.04} size={300}>
{data.map((item, index) => (
<PieSlice index={index} key={item.label} />
))}
<PieCenter defaultLabel="Total" />
</PieChart>
</div>
)
}
Gradient
"use client"
import PieChart, { PieData, PieSlice, RadialGradient } from "@/components/vritti/pie-chart"
const gradientData: PieData[] = [
{ label: "Segment A", value: 40 },
{ label: "Segment B", value: 30 },
{ label: "Segment C", value: 30 },
]
export function PieChartGradientDemo() {
return (
<div className="flex justify-center p-4">
<PieChart data={gradientData} size={280}>
<RadialGradient
from="#0ea5e9"
fromOffset="0%"
id="pie-gradient-1"
to="#06b6d4"
toOffset="100%"
/>
<RadialGradient
from="#a855f7"
fromOffset="0%"
id="pie-gradient-2"
to="#ec4899"
toOffset="100%"
/>
<RadialGradient
from="#f59e0b"
fromOffset="0%"
id="pie-gradient-3"
to="#ef4444"
toOffset="100%"
/>
<PieSlice fill="url(#pie-gradient-1)" index={0} />
<PieSlice fill="url(#pie-gradient-2)" index={1} />
<PieSlice fill="url(#pie-gradient-3)" index={2} />
</PieChart>
</div>
)
}
Hover
Hover Effect:
"use client"
import { useState } from "react"
import PieChart, {
PieSlice,
type PieSliceHoverEffect,
type PieData,
} from "@/components/vritti/pie-chart"
const salesData: PieData[] = [
{ label: "Electronics", value: 4250, color: "#0ea5e9" },
{ label: "Clothing", value: 3120, color: "#a855f7" },
{ label: "Food", value: 2100, color: "#f59e0b" },
{ label: "Home", value: 1580, color: "#10b981" },
{ label: "Other", value: 1050, color: "#ef4444" },
]
export function PieChartHoverDemo() {
const [hoverEffect, setHoverEffect] = useState<PieSliceHoverEffect>("translate")
return (
<div className="flex flex-col items-center gap-6 p-4">
<div className="flex items-center gap-3">
<span className="text-muted-foreground text-sm">Hover Effect:</span>
<div className="flex gap-2">
{(["translate", "grow", "none"] as PieSliceHoverEffect[]).map((effect) => (
<button
key={effect}
type="button"
onClick={() => setHoverEffect(effect)}
className={`rounded-md px-3 py-1.5 text-sm transition-colors ${
hoverEffect === effect
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground hover:bg-muted/80"
}`}
>
{effect.charAt(0).toUpperCase() + effect.slice(1)}
</button>
))}
</div>
</div>
<PieChart data={salesData} size={280}>
{salesData.map((item, index) => (
<PieSlice hoverEffect={hoverEffect} index={index} key={item.label} />
))}
</PieChart>
</div>
)
}
Interactive
"use client"
import PieChart, { PieSlice, PieCenter } from "@/components/vritti/pie-chart"
const data = [
{ label: "Marketing", value: 4200, color: "#0ea5e9" },
{ label: "Engineering", value: 8500, color: "#a855f7" },
{ label: "Sales", value: 3100, color: "#f59e0b" },
{ label: "Design", value: 2800, color: "#10b981" },
{ label: "Operations", value: 1900, color: "#ef4444" },
]
export function PieChartInteractiveDemo() {
return (
<div className="flex justify-center p-4">
<PieChart
data={data}
size={300}
innerRadius={70}
padAngle={0.04}
cornerRadius={4}
hoverOffset={12}
>
{data.map((item, index) => (
<PieSlice index={index} key={item.label} hoverEffect="grow" />
))}
<PieCenter
defaultLabel="Budget"
formatOptions={{ style: "currency", currency: "USD", notation: "compact" }}
/>
</PieChart>
</div>
)
}
Legend
Sales by Category
Electronics4,25035%
Clothing3,12026%
Food2,10017%
Home1,58013%
Other1,0509%
"use client"
import { useState } from "react"
import PieChart, {
Legend,
LegendItem,
LegendLabel,
LegendMarker,
LegendValue,
PieSlice,
type LegendItemData,
type PieData,
} from "@/components/vritti/pie-chart"
const salesData: PieData[] = [
{ label: "Electronics", value: 4250, color: "#0ea5e9" },
{ label: "Clothing", value: 3120, color: "#a855f7" },
{ label: "Food", value: 2100, color: "#f59e0b" },
{ label: "Home", value: 1580, color: "#10b981" },
{ label: "Other", value: 1050, color: "#ef4444" },
]
const total = salesData.reduce((sum, d) => sum + d.value, 0)
const legendItems: LegendItemData[] = salesData.map((d) => ({
label: d.label,
value: d.value,
maxValue: total,
color: d.color ?? "",
}))
export function PieChartLegendDemo() {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
return (
<div className="flex flex-col items-center justify-center gap-8 p-4 lg:flex-row lg:gap-12">
<PieChart
data={salesData}
hoveredIndex={hoveredIndex}
onHoverChange={setHoveredIndex}
size={280}
>
{salesData.map((item, index) => (
<PieSlice index={index} key={item.label} />
))}
</PieChart>
<Legend
hoveredIndex={hoveredIndex}
items={legendItems}
onHoverChange={setHoveredIndex}
title="Sales by Category"
>
<LegendItem className="flex items-center gap-3">
<LegendMarker />
<LegendLabel className="flex-1 text-sm font-medium" />
<LegendValue showPercentage />
</LegendItem>
</Legend>
</div>
)
}
Nested
By Browser
By Device1,0001,000 Total
"use client"
import PieChart, { PieSlice, PieCenter } from "@/components/vritti/pie-chart"
const outerData = [
{ label: "Chrome", value: 520, color: "#4285F4" },
{ label: "Safari", value: 190, color: "#FF9500" },
{ label: "Firefox", value: 130, color: "#FF6611" },
{ label: "Edge", value: 110, color: "#0078D4" },
{ label: "Other", value: 50, color: "#999999" },
]
const innerData = [
{ label: "Desktop", value: 620, color: "var(--chart-1)" },
{ label: "Mobile", value: 310, color: "var(--chart-2)" },
{ label: "Tablet", value: 70, color: "var(--chart-3)" },
]
export function PieChartNestedDemo() {
return (
<div className="flex flex-col items-center gap-8 p-4">
<div className="flex items-center gap-12">
<div className="flex flex-col items-center gap-2">
<span className="text-sm font-medium text-muted-foreground">By Browser</span>
<PieChart data={outerData} size={220} innerRadius={60} padAngle={0.03} cornerRadius={3}>
{outerData.map((item, index) => (
<PieSlice index={index} key={item.label} />
))}
</PieChart>
</div>
<div className="flex flex-col items-center gap-2">
<span className="text-sm font-medium text-muted-foreground">By Device</span>
<PieChart data={innerData} size={220} innerRadius={60} padAngle={0.03} cornerRadius={3}>
{innerData.map((item, index) => (
<PieSlice index={index} key={item.label} />
))}
<PieCenter defaultLabel="Total" />
</PieChart>
</div>
</div>
</div>
)
}
Pattern
"use client"
import PieChart, { PatternLines, PieData, PieSlice } from "@/components/vritti/pie-chart"
const patternData: PieData[] = [
{ label: "Category A", value: 35 },
{ label: "Category B", value: 25 },
{ label: "Category C", value: 20 },
{ label: "Category D", value: 20 },
]
export function PieChartPatternDemo() {
return (
<div className="flex justify-center p-4">
<PieChart data={patternData} size={280}>
<PatternLines
height={6}
id="pie-pattern-1"
orientation={["diagonal"]}
stroke="var(--chart-1)"
strokeWidth={1}
width={6}
/>
<PatternLines
height={6}
id="pie-pattern-2"
orientation={["horizontal"]}
stroke="var(--chart-2)"
strokeWidth={1}
width={6}
/>
<PatternLines
height={6}
id="pie-pattern-3"
orientation={["vertical"]}
stroke="var(--chart-3)"
strokeWidth={1}
width={6}
/>
<PatternLines
height={8}
id="pie-pattern-4"
orientation={["diagonalRightToLeft"]}
stroke="var(--chart-4)"
strokeWidth={1}
width={8}
/>
<PieSlice fill="url(#pie-pattern-1)" index={0} />
<PieSlice fill="url(#pie-pattern-2)" index={1} />
<PieSlice fill="url(#pie-pattern-3)" index={2} />
<PieSlice fill="url(#pie-pattern-4)" index={3} />
</PieChart>
</div>
)
}
Semi
"use client"
import PieChart, { PieSlice, PieCenter } from "@/components/vritti/pie-chart"
const data = [
{ label: "Completed", value: 72, color: "var(--chart-1)" },
{ label: "In Progress", value: 18, color: "var(--chart-2)" },
{ label: "Remaining", value: 10, color: "var(--chart-3)" },
]
export function PieChartSemiDemo() {
return (
<div className="flex justify-center p-4">
<PieChart
data={data}
size={300}
innerRadius={80}
padAngle={0.03}
cornerRadius={4}
startAngle={-Math.PI / 2}
endAngle={Math.PI / 2}
>
{data.map((item, index) => (
<PieSlice index={index} key={item.label} />
))}
<PieCenter defaultLabel="Progress" />
</PieChart>
</div>
)
}