# Donut Chart with Center Total

> Card with a donut chart of minutes by genre, the total printed in the center, and a footer list giving each genre's swatch, minutes and share. Client component on recharts 3.8.

A shadcn/ui block from the [@01orbital](https://01orbital.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/01orbital/pie-chart-01).

- Page, with a live preview: https://shadcn.exchange/r/01orbital/pie-chart-01
- Screenshot: https://shadcn.exchange/shot/01orbital/pie-chart-01/light.png
- Registry item: https://01orbital.dev/r/pie-chart-01.json
- Categories: charts
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/01orbital/pie-chart-01.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `recharts`
- Registry items: `card`, `@01orbital/chart`

## Files

### `blocks/charts/pie-chart-01/index.tsx`

```tsx
"use client"

import { Label, Pie, PieChart } from "recharts"

import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Listening by genre",
  description: "Minutes listened in October",
  total: "minutes",
  breakdown: "Minutes by genre",
} as const

const GENRES = [
  { genre: "ambient", label: "Ambient", minutes: 6120, fill: "var(--chart-1)" },
  { genre: "jazz", label: "Jazz", minutes: 4380, fill: "var(--chart-2)" },
  {
    genre: "classical",
    label: "Classical",
    minutes: 3410,
    fill: "var(--chart-3)",
  },
  {
    genre: "electronic",
    label: "Electronic",
    minutes: 2760,
    fill: "var(--chart-4)",
  },
  { genre: "folk", label: "Folk", minutes: 1730, fill: "var(--chart-5)" },
]

const CHART_CONFIG = {
  minutes: { label: "Minutes" },
  ...Object.fromEntries(
    GENRES.map((genre) => [
      genre.genre,
      { label: genre.label, color: genre.fill },
    ])
  ),
} satisfies ChartConfig

const TOTAL = GENRES.reduce((sum, genre) => sum + genre.minutes, 0)

export default function PieChart01() {
  return (
    <Card className="mx-auto w-full max-w-md">
      <CardHeader>
        <CardTitle>
          <h2>{COPY.title}</h2>
        </CardTitle>
        <CardDescription>{COPY.description}</CardDescription>
      </CardHeader>
      <CardContent>
        <ChartContainer
          config={CHART_CONFIG}
          className="mx-auto aspect-square max-h-64"
        >
          <PieChart accessibilityLayer>
            <ChartTooltip
              cursor={false}
              content={<ChartTooltipContent hideLabel />}
            />
            {/* A card-colored gap parts the slices; the list below holds the values. */}
            <Pie
              data={GENRES}
              dataKey="minutes"
              nameKey="genre"
              innerRadius={64}
              stroke="var(--card)"
              strokeWidth={3}
            >
              <Label
                content={({ viewBox }) => {
                  if (!viewBox || !("cx" in viewBox)) return null
                  return (
                    <text
                      x={viewBox.cx}
                      y={viewBox.cy}
                      textAnchor="middle"
                      dominantBaseline="middle"
                    >
                      <tspan
                        x={viewBox.cx}
                        y={viewBox.cy}
                        className="fill-foreground text-2xl font-medium tabular-nums"
                      >
                        {TOTAL.toLocaleString("en-US")}
                      </tspan>
                      <tspan
                        x={viewBox.cx}
                        y={(viewBox.cy ?? 0) + 22}
                        className="fill-muted-foreground"
                      >
                        {COPY.total}
                      </tspan>
                    </text>
                  )
                }}
              />
            </Pie>
          </PieChart>
        </ChartContainer>
      </CardContent>
      <CardFooter>
        <ul aria-label={COPY.breakdown} className="grid w-full gap-2 text-sm">
          {GENRES.map((genre) => (
            <li key={genre.genre} className="flex items-center gap-2">
              <span
                aria-hidden="true"
                className="size-2.5 shrink-0 rounded-[2px]"
                style={{ backgroundColor: genre.fill }}
              />
              {genre.label}
              <span className="ml-auto tabular-nums">
                {genre.minutes.toLocaleString("en-US")}
              </span>
              <span className="w-10 text-right text-muted-foreground tabular-nums">
                {Math.round((genre.minutes / TOTAL) * 100)}%
              </span>
            </li>
          ))}
        </ul>
      </CardFooter>
    </Card>
  )
}
```
