radial-chart-03
Three small radial progress rings for daily goals: focus hours, tasks and breaks. Each ring has a percentage, label and exact target, so progress reads without relying on color. Built on shadcn Card and Chart with recharts 3.8.
pnpm dlx shadcn add @01orbital/radial-chart-03"use client"
import {
Label,
PolarAngleAxis,
PolarRadiusAxis,
RadialBar,
RadialBarChart,
} from "recharts"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { ChartContainer, type ChartConfig } from "@/components/ui/chart"
const COPY = {
title: "Daily targets",
description: "Today’s progress",
} as const
const GOALS = [
{
key: "focus",
label: "Focus",
value: 3,
target: 4,
unit: "hours",
color: "var(--chart-2)",
},
{
key: "tasks",
label: "Tasks",
value: 8,
target: 10,
unit: "tasks",
color: "var(--chart-3)",
},
{
key: "breaks",
label: "Breaks",
value: 3,
target: 5,
unit: "breaks",
color: "var(--primary)",
},
] as const
const CHART_CONFIG = Object.fromEntries(
GOALS.map((goal) => [goal.key, { label: goal.label, color: goal.color }])
) satisfies ChartConfig
/** Three small progress rings, each paired with its own label and exact target. */
export default function RadialChart03() {
return (
<Card className="mx-auto w-full max-w-md">
<CardHeader>
<CardTitle>
<h2>{COPY.title}</h2>
</CardTitle>
<CardDescription>{COPY.description}</CardDescription>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-6 min-[16rem]:grid-cols-3 min-[16rem]:gap-3">
{GOALS.map((goal) => (
<figure key={goal.key} className="min-w-0 space-y-3 text-center">
<ChartContainer
config={CHART_CONFIG}
role="img"
aria-label={`${goal.label}: ${goal.value} of ${goal.target} ${goal.unit}`}
className="mx-auto aspect-square w-full max-w-28"
>
<RadialBarChart
accessibilityLayer={false}
data={[{ value: goal.value, fill: `var(--color-${goal.key})` }]}
startAngle={90}
endAngle={-270}
innerRadius="72%"
outerRadius="100%"
margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
>
<PolarAngleAxis
type="number"
domain={[0, goal.target]}
tick={false}
/>
<RadialBar dataKey="value" background cornerRadius={6} />
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
<Label
content={({ viewBox }) => {
if (!viewBox || !("cx" in viewBox)) return null
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
fontSize={16}
className="fill-foreground font-medium tabular-nums"
>
{Math.round((goal.value / goal.target) * 100)}%
</text>
)
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
</ChartContainer>
<figcaption className="space-y-1 text-sm">
<p className="font-medium">{goal.label}</p>
<p className="text-xs/5 text-muted-foreground tabular-nums">
{goal.value} / {goal.target} {goal.unit}
</p>
</figcaption>
</figure>
))}
</CardContent>
</Card>
)
}
Dependencies
npm: recharts@3.8.0. The CLI installs them for you.
Registry pieces it installs with it: