Chart
A visual representation of data in various formats.
chart-demo.tsx
"use client"
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Chart,
ChartConfig,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartData = [
{ month: "Jan", revenue: 1500 },
{ month: "Feb", revenue: 3200 },
{ month: "Mar", revenue: 2900 },
{ month: "Apr", revenue: 2100 },
{ month: "May", revenue: 4000 },
{ month: "Jun", revenue: 3700 },
{ month: "Jul", revenue: 4300 },
{ month: "Aug", revenue: 4900 },
{ month: "Sep", revenue: 4700 },
{ month: "Oct", revenue: 5200 },
{ month: "Nov", revenue: 6000 },
{ month: "Dec", revenue: 7200 },
]
const chartConfig = {
revenue: {
label: "Revenue",
color: "var(--chart-2)",
},
expenses: {
label: "Expenses",
color: "var(--chart-3)",
},
} satisfies ChartConfig
export function ChartDemo() {
const totalRevenue = chartData.reduce((sum, item) => sum + item.revenue, 0)
const averageRevenue = totalRevenue / chartData.length
const highestRevenue = Math.max(...chartData.map((item) => item.revenue))
return (
<Card className="flex w-full flex-col">
<CardHeader className="items-center">
<CardTitle>Monthly Revenue</CardTitle>
<CardDescription>Performance overview for 2024</CardDescription>
</CardHeader>
<CardContent className="flex-1">
<Chart config={chartConfig} className="max-h-[300px]">
<BarChart accessibilityLayer data={chartData}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
/>
<ChartTooltip
cursor={{ fill: "var(--background)" }}
content={<ChartTooltipContent />}
/>
<Bar
dataKey="revenue"
fill="var(--chart-2)"
radius={[4, 4, 0, 0]}
/>
</BarChart>
</Chart>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm leading-none">
<div className="flex w-full justify-between font-medium">
<span>Total Revenue:</span>
<span>${totalRevenue.toLocaleString()}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Monthly Average:</span>
<span>
$
{averageRevenue.toLocaleString(undefined, {
maximumFractionDigits: 0,
})}
</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Highest Month:</span>
<span>${highestRevenue.toLocaleString()}</span>
</div>
</CardFooter>
</Card>
)
}
About
The Chart is built on top of recharts
, which is a React component library for building charts.
Installation
npx shadcn@latest add https://9ui.dev/r/chart.json
Examples
Area Chart
chart-area.tsx
"use client"
import { TrendingUpIcon } from "lucide-react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Chart,
ChartConfig,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartData = [
{ month: "Jan", revenue: 1500 },
{ month: "Feb", revenue: 3200 },
{ month: "Mar", revenue: 2900 },
{ month: "Apr", revenue: 2100 },
{ month: "May", revenue: 4000 },
{ month: "Jun", revenue: 3700 },
{ month: "Jul", revenue: 4300 },
{ month: "Aug", revenue: 4900 },
{ month: "Sep", revenue: 4700 },
{ month: "Oct", revenue: 5200 },
{ month: "Nov", revenue: 6000 },
{ month: "Dec", revenue: 7200 },
]
const chartConfig = {
revenue: {
label: "Revenue",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export function ChartAreaDemo() {
const totalRevenue = chartData.reduce((sum, item) => sum + item.revenue, 0)
const averageRevenue = totalRevenue / chartData.length
const lastMonthGrowth =
((chartData[11].revenue - chartData[10].revenue) / chartData[10].revenue) *
100
return (
<Card className="flex w-full flex-col">
<CardHeader className="items-center">
<CardTitle>Monthly Revenue Trend</CardTitle>
<CardDescription>Performance overview for 2024</CardDescription>
</CardHeader>
<CardContent className="flex-1">
<Chart config={chartConfig} className="max-h-[300px]">
<AreaChart
accessibilityLayer
data={chartData}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent indicator="line" />}
/>
<Area
dataKey="revenue"
type="natural"
fill="var(--chart-1)"
fillOpacity={0.2}
stroke="var(--chart-1)"
/>
</AreaChart>
</Chart>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm leading-none">
<div className="flex w-full justify-between font-medium">
<span>Total Revenue:</span>
<span>${totalRevenue.toLocaleString()}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Monthly Average:</span>
<span>
$
{averageRevenue.toLocaleString(undefined, {
maximumFractionDigits: 0,
})}
</span>
</div>
<div className="mt-2 flex items-center gap-2 text-muted-foreground">
<span>Month-over-month growth:</span>
<span className="flex items-center gap-1 font-medium text-primary">
{lastMonthGrowth.toFixed(1)}%
<TrendingUpIcon className="size-4" />
</span>
</div>
</CardFooter>
</Card>
)
}
Bar Chart
chart-bar.tsx
"use client"
import { TrendingUpIcon } from "lucide-react"
import { Bar, BarChart, CartesianGrid, Legend, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Chart,
ChartConfig,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartData = [
{ month: "Jan", revenue: 1500, expenses: 1200 },
{ month: "Feb", revenue: 3200, expenses: 2800 },
{ month: "Mar", revenue: 2900, expenses: 2500 },
{ month: "Apr", revenue: 2100, expenses: 1900 },
{ month: "May", revenue: 4000, expenses: 3500 },
{ month: "Jun", revenue: 3700, expenses: 3200 },
]
const chartConfig = {
revenue: {
label: "Revenue",
color: "var(--chart-1)",
},
expenses: {
label: "Expenses",
color: "var(--chart-3)",
},
} satisfies ChartConfig
export function ChartBarDemo() {
const totalRevenue = chartData.reduce((sum, item) => sum + item.revenue, 0)
const totalExpenses = chartData.reduce((sum, item) => sum + item.expenses, 0)
const netProfit = totalRevenue - totalExpenses
const profitMargin = (netProfit / totalRevenue) * 100
return (
<Card className="flex w-full flex-col">
<CardHeader className="items-center">
<CardTitle>Revenue vs Expenses</CardTitle>
<CardDescription>First half of 2024</CardDescription>
</CardHeader>
<CardContent className="flex-1">
<Chart config={chartConfig} className="max-h-[300px]">
<BarChart
data={chartData}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={{ fill: "var(--background)" }}
content={<ChartTooltipContent />}
/>
<Legend />
<Bar
dataKey="revenue"
fill={chartConfig.revenue.color}
radius={[4, 4, 0, 0]}
maxBarSize={32}
/>
<Bar
dataKey="expenses"
fill={chartConfig.expenses.color}
radius={[4, 4, 0, 0]}
maxBarSize={32}
/>
</BarChart>
</Chart>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm leading-none">
<div className="flex w-full justify-between font-medium">
<span>Net Profit:</span>
<span>${netProfit.toLocaleString()}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Profit Margin:</span>
<span className="flex items-center gap-1">
<TrendingUpIcon className="size-4" />
{profitMargin.toFixed(1)}%
</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Total Expenses:</span>
<span>${totalExpenses.toLocaleString()}</span>
</div>
</CardFooter>
</Card>
)
}
Line Chart
chart-line.tsx
"use client"
import { CartesianGrid, Legend, Line, LineChart, XAxis } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Chart,
ChartConfig,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartData = [
{ month: "Jan", users: 100, activeUsers: 80, newUsers: 20 },
{ month: "Feb", users: 120, activeUsers: 90, newUsers: 30 },
{ month: "Mar", users: 150, activeUsers: 100, newUsers: 50 },
{ month: "Apr", users: 200, activeUsers: 140, newUsers: 60 },
{ month: "May", users: 250, activeUsers: 180, newUsers: 70 },
{ month: "Jun", users: 300, activeUsers: 220, newUsers: 80 },
]
const chartConfig = {
users: {
label: "Total Users",
color: "var(--chart-1)",
},
activeUsers: {
label: "Active Users",
color: "var(--chart-2)",
},
newUsers: {
label: "New Users",
color: "var(--chart-3)",
},
} satisfies ChartConfig
export function ChartLineDemo() {
const totalUsers = chartData[chartData.length - 1].users
const totalActiveUsers = chartData[chartData.length - 1].activeUsers
const userGrowth =
((chartData[5].users - chartData[0].users) / chartData[0].users) * 100
const activeUsersRate = (totalActiveUsers / totalUsers) * 100
return (
<Card className="flex w-full flex-col">
<CardHeader className="items-center">
<CardTitle>User Growth</CardTitle>
<CardDescription>User metrics for first half of 2024</CardDescription>
</CardHeader>
<CardContent className="flex-1">
<Chart config={chartConfig} className="max-h-[300px]">
<LineChart
data={chartData}
margin={{
left: 12,
right: 12,
}}
>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip content={<ChartTooltipContent />} />
<Legend />
<Line
type="monotone"
dataKey="users"
stroke={chartConfig.users.color}
strokeWidth={2}
dot={false}
/>
<Line
type="monotone"
dataKey="activeUsers"
stroke={chartConfig.activeUsers.color}
strokeWidth={2}
dot={false}
/>
<Line
type="monotone"
dataKey="newUsers"
stroke={chartConfig.newUsers.color}
strokeWidth={2}
dot={false}
/>
</LineChart>
</Chart>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm leading-none">
<div className="flex w-full justify-between font-medium">
<span>Total Users:</span>
<span>{totalUsers.toLocaleString()}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Active Users Rate:</span>
<span>{activeUsersRate.toFixed(1)}%</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>6-Month Growth:</span>
<span>{userGrowth.toFixed(1)}%</span>
</div>
</CardFooter>
</Card>
)
}
Pie Chart
chart-pie.tsx
"use client"
import { Cell, Legend, Pie, PieChart } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Chart,
ChartConfig,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartData = [
{ category: "Sales", amount: 4000, fill: "var(--chart-1)" },
{ category: "Marketing", amount: 3000, fill: "var(--chart-2)" },
{ category: "IT", amount: 2000, fill: "var(--chart-3)" },
{ category: "HR", amount: 1000, fill: "var(--chart-4)" },
{ category: "Operations", amount: 1000, fill: "var(--chart-5)" },
]
const chartConfig = {
sales: {
label: "Sales",
color: "var(--chart-1)",
},
marketing: {
label: "Marketing",
color: "var(--chart-2)",
},
it: {
label: "IT",
color: "var(--chart-3)",
},
hr: {
label: "HR",
color: "var(--chart-4)",
},
operations: {
label: "Operations",
color: "var(--chart-5)",
},
} satisfies ChartConfig
export function ChartPieDemo() {
const totalBudget = chartData.reduce((sum, item) => sum + item.amount, 0)
const highestBudget = Math.max(...chartData.map((item) => item.amount))
const highestCategory = chartData.find(
(item) => item.amount === highestBudget
)?.category
return (
<Card className="flex w-full flex-col">
<CardHeader className="items-center pb-0">
<CardTitle>Budget Distribution</CardTitle>
<CardDescription>Department budget allocation for 2024</CardDescription>
</CardHeader>
<CardContent className="flex-1">
<Chart config={chartConfig} className="max-h-[300px]">
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Legend />
<Pie
data={chartData}
dataKey="amount"
nameKey="category"
cx="50%"
cy="50%"
>
{chartData.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.fill} />
))}
</Pie>
</PieChart>
</Chart>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm leading-none">
<div className="flex w-full justify-between font-medium">
<span>Total Budget:</span>
<span>${totalBudget.toLocaleString()}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Largest Department:</span>
<span>{highestCategory}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Highest Budget:</span>
<span>${highestBudget.toLocaleString()}</span>
</div>
</CardFooter>
</Card>
)
}
Radar Chart
chart-radar.tsx
"use client"
import { Legend, PolarAngleAxis, PolarGrid, Radar, RadarChart } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Chart,
ChartConfig,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartData = [
{ category: "Performance", a: 90, b: 60 },
{ category: "Reliability", a: 75, b: 90 },
{ category: "Scalability", a: 95, b: 90 },
{ category: "Security", a: 88, b: 65 },
{ category: "Usability", a: 92, b: 88 },
]
const chartConfig = {
a: {
label: "Product A",
color: "var(--chart-1)",
},
b: {
label: "Product B",
color: "var(--chart-2)",
},
} satisfies ChartConfig
export function ChartRadarDemo() {
const productAAverage =
chartData.reduce((sum, item) => sum + item.a, 0) / chartData.length
const productBAverage =
chartData.reduce((sum, item) => sum + item.b, 0) / chartData.length
const bestPerformer =
productAAverage > productBAverage ? "Product A" : "Product B"
return (
<Card className="flex w-full flex-col">
<CardHeader className="items-center">
<CardTitle>Product Comparison</CardTitle>
<CardDescription>
Performance metrics across key categories
</CardDescription>
</CardHeader>
<CardContent className="flex-1">
<Chart config={chartConfig} className="max-h-[300px]">
<RadarChart data={chartData}>
<PolarGrid />
<PolarAngleAxis dataKey="category" />
<ChartTooltip content={<ChartTooltipContent />} />
<Legend />
<Radar
name="Product A"
dataKey="a"
stroke={chartConfig.a.color}
fill={chartConfig.a.color}
fillOpacity={0.2}
/>
<Radar
name="Product B"
dataKey="b"
stroke={chartConfig.b.color}
fill={chartConfig.b.color}
fillOpacity={0.2}
/>
</RadarChart>
</Chart>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm leading-none">
<div className="flex w-full justify-between font-medium">
<span>Best Overall:</span>
<span>{bestPerformer}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Product A Average:</span>
<span>{productAAverage.toFixed(1)}%</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Product B Average:</span>
<span>{productBAverage.toFixed(1)}%</span>
</div>
</CardFooter>
</Card>
)
}
Radial Bar Chart
chart-radial-bar.tsx
"use client"
import { RadialBar, RadialBarChart } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Chart,
ChartConfig,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartData = [
{ browser: "chrome", visitors: 540, fill: "var(--color-chrome)" },
{ browser: "safari", visitors: 410, fill: "var(--color-safari)" },
{ browser: "firefox", visitors: 262, fill: "var(--color-firefox)" },
{ browser: "edge", visitors: 160, fill: "var(--color-edge)" },
{ browser: "other", visitors: 100, fill: "var(--color-other)" },
]
const chartConfig = {
visitors: {
label: "Visitors",
},
chrome: {
label: "Chrome",
color: "var(--chart-1)",
},
safari: {
label: "Safari",
color: "var(--chart-2)",
},
firefox: {
label: "Firefox",
color: "var(--chart-3)",
},
edge: {
label: "Edge",
color: "var(--chart-4)",
},
other: {
label: "Other",
color: "var(--chart-5)",
},
} satisfies ChartConfig
export function ChartRadialBarDemo() {
const totalVisitors = chartData.reduce((sum, item) => sum + item.visitors, 0)
const highestVisitors = Math.max(...chartData.map((item) => item.visitors))
const topBrowser = chartData.find(
(item) => item.visitors === highestVisitors
)?.browser
return (
<Card className="flex w-full flex-col">
<CardHeader className="items-center">
<CardTitle>Browser Usage</CardTitle>
<CardDescription>Visitor distribution by browser</CardDescription>
</CardHeader>
<CardContent className="flex-1">
<Chart
config={chartConfig}
className="mx-auto aspect-square max-h-[250px]"
>
<RadialBarChart data={chartData} innerRadius={30} outerRadius={110}>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel nameKey="browser" />}
/>
<RadialBar dataKey="visitors" background />
</RadialBarChart>
</Chart>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm leading-none">
<div className="flex w-full justify-between font-medium">
<span>Total Visitors:</span>
<span>{totalVisitors.toLocaleString()}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Most Used Browser:</span>
<span className="capitalize">{topBrowser}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Peak Visitors:</span>
<span>{highestVisitors.toLocaleString()}</span>
</div>
</CardFooter>
</Card>
)
}
Scatter Chart
chart-scatter.tsx
"use client"
import {
CartesianGrid,
Scatter,
ScatterChart,
XAxis,
YAxis,
ZAxis,
} from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Chart,
ChartConfig,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartData = [
{ population: 850000, price: 425000, city: "San Francisco" },
{ population: 2700000, price: 385000, city: "Chicago" },
{ population: 8400000, price: 750000, city: "New York" },
{ population: 4000000, price: 890000, city: "Los Angeles" },
{ population: 2300000, price: 350000, city: "Houston" },
{ population: 1600000, price: 420000, city: "Philadelphia" },
{ population: 730000, price: 480000, city: "Seattle" },
{ population: 690000, price: 445000, city: "Boston" },
{ population: 710000, price: 320000, city: "Denver" },
{ population: 950000, price: 295000, city: "Austin" },
]
const chartConfig = {
scatter: {
label: "Cities",
color: "var(--chart-1)",
},
} satisfies ChartConfig
export function ChartScatterDemo() {
const averagePrice =
chartData.reduce((sum, item) => sum + item.price, 0) / chartData.length
const highestPrice = Math.max(...chartData.map((item) => item.price))
const mostExpensiveCity = chartData.find(
(item) => item.price === highestPrice
)?.city
return (
<Card className="flex w-full flex-col">
<CardHeader className="items-center">
<CardTitle>Housing Market Analysis</CardTitle>
<CardDescription>
Population vs House Prices in Major Cities
</CardDescription>
</CardHeader>
<CardContent>
<Chart config={chartConfig} className="max-h-[300px]">
<ScatterChart>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
type="number"
dataKey="price"
name="Average House Price"
unit="$"
tickLine={false}
axisLine={false}
tickFormatter={(value) => `$${(value / 1000).toFixed(0)}k`}
/>
<YAxis
type="number"
dataKey="population"
name="Population"
tickLine={false}
axisLine={false}
tickFormatter={(value) => `${(value / 1000000).toFixed(1)}M`}
/>
<ZAxis type="category" dataKey="city" name="City" />
<ChartTooltip
content={
<ChartTooltipContent
nameKey="city"
labelKey="price"
hideIndicator
/>
}
/>
<Scatter
name="Cities"
data={chartData}
fill={chartConfig.scatter.color}
/>
</ScatterChart>
</Chart>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm leading-none">
<div className="flex w-full justify-between font-medium">
<span>Most Expensive City:</span>
<span>{mostExpensiveCity}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Average House Price:</span>
<span>${averagePrice.toLocaleString()}</span>
</div>
<div className="flex w-full justify-between text-muted-foreground">
<span>Highest House Price:</span>
<span>${highestPrice.toLocaleString()}</span>
</div>
</CardFooter>
</Card>
)
}