# Pie Chart Examples

> Complete pie and donut layouts for simple part-to-whole relationships.

Source: https://www.honestui.com/docs/charts/pie-chart/blocks

Use these layouts only when the categories form a meaningful whole and there are few enough sectors to compare. The values are sample data. Open the Code tab and update sector keys, values, labels, totals, and text alternatives together.

## Coffee export destinations [#coffee-export-destinations]

Use this donut layout for a small set of regional shares. Replace the sector name and value fields, then update the total in the center. Keep the visible category list because sector color alone is not enough.

### Coffee export destinations

```tsx
"use client";

import { PieChart, type ChartConfig } from "honestui/charts";
import { cn } from "@/lib/utils";
import { useState } from "react";

// Scenario: Coffee export destinations
const SERIES = [
  { key: "skyline", label: "Europe", value: 24, swatch: "bg-[#0a0a0a] dark:bg-[#ffffff]" },
  { key: "datawell", label: "North America", value: 20, swatch: "bg-[#262626] dark:bg-[#dedede]" },
  { key: "cloudpeak", label: "East Asia", value: 18, swatch: "bg-[#3d3d3d] dark:bg-[#bebebe]" },
  { key: "taskbridge", label: "Middle East", value: 16, swatch: "bg-[#545454] dark:bg-[#a0a0a0]" },
  { key: "insightloop", label: "Oceania", value: 13, swatch: "bg-[#6b6b6b] dark:bg-[#868686]" },
  {
    key: "streamforge",
    label: "Africa",
    value: 9,
    swatch: "bg-[#7d7d7d] dark:bg-[#6f6f6f]",
  },
] as const;

const chartData = [...SERIES].reverse().map(({ key, value }) => ({
  product: key,
  value,
  share: `${value}%`,
}));

const chartConfig = {
  skyline: { label: "Europe", colors: { light: ["#0a0a0a"], dark: ["#ffffff"] } },
  datawell: { label: "North America", colors: { light: ["#262626"], dark: ["#dedede"] } },
  cloudpeak: { label: "East Asia", colors: { light: ["#3d3d3d"], dark: ["#bebebe"] } },
  taskbridge: { label: "Middle East", colors: { light: ["#545454"], dark: ["#a0a0a0"] } },
  insightloop: { label: "Oceania", colors: { light: ["#6b6b6b"], dark: ["#868686"] } },
  streamforge: { label: "Africa", colors: { light: ["#7d7d7d"], dark: ["#6f6f6f"] } },
} satisfies ChartConfig;

const TOTAL = SERIES.reduce((sum, { value }) => sum + value, 0);

export function MarketSharePieChart() {
  const [selected, setSelected] = useState<string | null>(null);

  return (
    <div className="flex h-full w-full flex-col p-4">
      <div className="relative min-h-0 w-full flex-1">
        <PieChart
          data={chartData}
          config={chartConfig}
          dataKey="value"
          nameKey="product"
          className="h-full w-full"
          selectedSector={selected}
          onSelectionChange={(selection) => setSelected(selection?.dataKey ?? null)}
        >
          <PieChart.Tooltip />
          <PieChart.Pie
            isClickable
            innerRadius="52%"
            outerRadius="94%"
            paddingAngle={3}
            startAngle={90}
            endAngle={-270}
          >
            <PieChart.Label dataKey="share" />
          </PieChart.Pie>
        </PieChart>

        <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-0.5">
          <span className="text-primary text-xl font-semibold tracking-tight sm:text-3xl">
            {TOTAL}M kg
          </span>
          <span className="text-muted-foreground text-[10px] sm:text-xs">Coffee exports</span>
        </div>
      </div>

      <div className="border-border mt-3 grid grid-flow-col grid-cols-2 grid-rows-3 gap-x-6 gap-y-1.5 border-t pt-3">
        {SERIES.map(({ key, label, value, swatch }) => (
          <button
            key={key}
            type="button"
            aria-pressed={selected === key}
            onClick={() => setSelected((prev) => (prev === key ? null : key))}
            className={cn(
              "flex cursor-pointer items-center gap-2 text-left text-xs transition-opacity",
              selected !== null && selected !== key && "opacity-40",
            )}
          >
            <span className={cn("size-3 shrink-0 rounded-[3px]", swatch)} />
            <span className="text-primary font-medium">{label}</span>
            <span className="text-muted-foreground">{value}M kg</span>
            <span className="text-muted-foreground/60">({value}%)</span>
          </button>
        ))}
      </div>
    </div>
  );
}

```

## Habitat recovery survey [#habitat-recovery-survey]

Use these rings for a small set of independently labeled percentages, not for sectors that must sum to one whole. Replace each value and caption together. The visible percentage and sentence carry the meaning; the ring is supporting emphasis.

### Habitat recovery survey

```tsx
"use client";

import { PieChart, type ChartConfig } from "honestui/charts";

// Scenario: Habitat recovery survey
const DOT_COUNT = 40;
const SECTORS = DOT_COUNT * 2;

const STATS = [
  { id: "support", value: 62, caption: "Wetland habitat restored this season." },
  { id: "forecast", value: 38, caption: "Wildlife corridors now connected." },
] as const;

const FILLED = { light: ["#E43861"], dark: ["#E43861"] };
const TRACK = { light: ["#d4d4d4"], dark: ["#3f3f3f"] };
const GAP = { light: ["transparent"], dark: ["transparent"] };

const dotsFor = (value: number) => Math.round((DOT_COUNT * value) / 100);

const chartData = (id: string) =>
  Array.from({ length: SECTORS }, (_, i) => ({ dot: `${id}-${i}`, value: 1 }));

const chartConfig = (id: string, value: number): ChartConfig =>
  Object.fromEntries(
    Array.from({ length: SECTORS }, (_, i) => {
      const colors = i % 2 ? GAP : i / 2 < dotsFor(value) ? FILLED : TRACK;
      return [`${id}-${i}`, { label: "", colors }];
    }),
  );

export function ProgressRingsPieChart() {
  return (
    <div className="flex h-full w-full flex-col p-4">
      <div className="flex items-start justify-between gap-4">
        <div className="flex flex-col gap-0.5">
          <span className="text-muted-foreground text-[10px] tracking-wide uppercase">
            Field survey
          </span>
          <span className="text-primary text-base leading-tight font-medium tracking-tight sm:text-xl">
            Habitat recovery priorities
          </span>
        </div>
        <span className="text-muted-foreground shrink-0 text-[10px] sm:text-xs">
          860 field observations
        </span>
      </div>

      <div className="mt-3 grid min-h-0 flex-1 grid-cols-2 gap-4">
        {STATS.map(({ id, value, caption }) => (
          <div key={id} className="relative min-h-0">
            <PieChart
              data={chartData(id)}
              config={chartConfig(id, value)}
              dataKey="value"
              nameKey="dot"
              className="h-full w-full"
            >
              <PieChart.Pie
                innerRadius="85%"
                outerRadius="92%"
                paddingAngle={0}
                cornerRadius={6}
                startAngle={90}
                endAngle={-270}
              />
            </PieChart>

            <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-1 bg-[radial-gradient(circle_closest-side,rgba(0,0,0,0.04)_0_79%,transparent_79%)] px-[25%] text-center dark:bg-[radial-gradient(circle_closest-side,rgba(255,255,255,0.05)_0_79%,transparent_79%)]">
              <span className="text-primary text-2xl leading-none font-medium tracking-tight sm:text-4xl">
                {value}%
              </span>
              <span className="text-muted-foreground text-[10px] leading-snug text-balance sm:text-xs">
                {caption}
              </span>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

```

## Museum funding mix [#museum-funding-mix]

Use this layout when four or fewer funding categories make up a total. Replace `channel`, `value`, labels, and the center summary. If precise comparison matters, use a bar chart or include a table.

### Museum funding mix

```tsx
"use client";

import { PieChart, type ChartConfig } from "honestui/charts";
import { cn } from "@/lib/utils";

// Scenario: Museum funding mix
const chartData = [
  {
    channel: "direct",
    label: "Admissions",
    value: 52400,
    swatch: "bg-[#7c3aed] dark:bg-[#a78bfa]",
  },
  {
    channel: "marketplace",
    label: "Memberships",
    value: 38900,
    swatch: "bg-[#4f46e5] dark:bg-[#818cf8]",
  },
  {
    channel: "wholesale",
    label: "Grants",
    value: 24150,
    swatch: "bg-[#0284c7] dark:bg-[#38bdf8]",
  },
  {
    channel: "affiliate",
    label: "Donations",
    value: 16300,
    swatch: "bg-[#059669] dark:bg-[#34d399]",
  },
];

const chartConfig = {
  direct: {
    label: "Admissions",
    colors: { light: ["#7c3aed", "#a855f7"], dark: ["#a78bfa", "#c4b5fd"] },
  },
  marketplace: {
    label: "Memberships",
    colors: { light: ["#4f46e5", "#6366f1"], dark: ["#818cf8", "#a5b4fc"] },
  },
  wholesale: {
    label: "Grants",
    colors: { light: ["#0284c7", "#0ea5e9"], dark: ["#38bdf8", "#7dd3fc"] },
  },
  affiliate: {
    label: "Donations",
    colors: { light: ["#059669", "#10b981"], dark: ["#34d399", "#6ee7b7"] },
  },
} satisfies ChartConfig;

const ORDERS = 1462;

const money = (value: number) => value.toLocaleString("en-US");

export function RevenueMixPieChart() {
  return (
    <div className="flex h-full w-full items-center gap-3 p-4 sm:gap-6">
      <div className="relative aspect-square w-[40%] max-w-72 shrink-0">
        <PieChart
          data={chartData}
          config={chartConfig}
          dataKey="value"
          nameKey="channel"
          className="h-full w-full"
        >
          <PieChart.Tooltip />
          <PieChart.Pie
            innerRadius="62%"
            outerRadius="92%"
            paddingAngle={6}
            cornerRadius={12}
            startAngle={90}
            endAngle={-270}
          />
        </PieChart>

        <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
          <div className="border-border flex aspect-square w-[56%] flex-col items-center justify-center rounded-full border border-dashed">
            <span className="text-primary text-lg leading-none font-semibold tracking-tight sm:text-2xl">
              {money(ORDERS)}
            </span>
            <span className="text-muted-foreground mt-1 text-[10px] sm:text-xs">Annual visitors</span>
          </div>
        </div>
      </div>

      <div className="flex min-h-0 min-w-0 flex-1 flex-col justify-center">
        {chartData.map(({ channel, label, value, swatch }) => (
          <div key={channel} className="flex items-center gap-2 py-1.5 sm:py-2">
            <span className={cn("size-2.5 shrink-0 rounded-[3px]", swatch)} />
            <span className="text-muted-foreground truncate text-xs">{label}</span>
            <span className="text-primary ml-auto text-xs font-semibold">${money(value)}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

```

## Soil health score [#soil-health-score]

Use this gauge-like layout when a score maps to named, ordered bands. Replace the band thresholds, score, sample date, and interpretation together. Keep the score and band name visible so color is not the only signal.

### Soil health score

```tsx
"use client";

import { PieChart, type ChartConfig } from "honestui/charts";
import { cn } from "@/lib/utils";

// Scenario: Soil health score
const MAX = 1000;
const SCORE = 734;
const START_ANGLE = 210;

const chartData = [
  { band: "atrisk", label: "Depleted", from: 0, value: 450, bar: "bg-[#e11d48] dark:bg-[#fb7185]" },
  { band: "fair", label: "Recovering", from: 450, value: 200, bar: "bg-[#f59e0b] dark:bg-[#fbbf24]" },
  { band: "good", label: "Healthy", from: 650, value: 170, bar: "bg-[#84cc16] dark:bg-[#a3e635]" },
  {
    band: "excellent",
    label: "Thriving",
    from: 820,
    value: 180,
    bar: "bg-[#059669] dark:bg-[#34d399]",
  },
];

const chartConfig = {
  atrisk: { label: "Depleted", colors: { light: ["#e11d48"], dark: ["#fb7185"] } },
  fair: { label: "Recovering", colors: { light: ["#f59e0b"], dark: ["#fbbf24"] } },
  good: { label: "Healthy", colors: { light: ["#84cc16"], dark: ["#a3e635"] } },
  excellent: { label: "Thriving", colors: { light: ["#059669"], dark: ["#34d399"] } },
} satisfies ChartConfig;

const BAND = [...chartData].reverse().find(({ from }) => SCORE >= from) ?? chartData[0];

export function ReliabilityScorePieChart() {
  return (
    <div className="flex h-full w-full flex-col p-4">
      <span className="text-primary text-base font-medium tracking-tight sm:text-lg">
        Soil health
      </span>

      <div className="relative mx-auto mt-1 aspect-square w-full max-w-50 shrink-0">
        <PieChart
          data={[...chartData].reverse()}
          config={chartConfig}
          dataKey="value"
          nameKey="band"
          className="h-full w-full"
        >
          <PieChart.Pie
            innerRadius="74%"
            outerRadius="94%"
            paddingAngle={6}
            cornerRadius={10}
            startAngle={-30}
            endAngle={START_ANGLE}
          />
        </PieChart>

        <svg
          viewBox="0 0 100 100"
          className="text-muted-foreground/50 pointer-events-none absolute inset-0"
          aria-hidden
        >
          <path
            d="M 23.15 65.5 A 31 31 0 1 1 76.85 65.5"
            fill="none"
            stroke="currentColor"
            strokeWidth="1"
            strokeLinecap="round"
            strokeDasharray="0.1 5"
          />
        </svg>
        <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
          <span className="text-primary text-3xl font-semibold tracking-tight sm:text-4xl">
            {SCORE}
          </span>
        </div>
      </div>

      <div className="-mt-6 text-center">
        <p className="text-primary text-xs font-medium sm:text-sm">
          Soil condition is {BAND.label.toLowerCase()}
        </p>
        <p className="text-muted-foreground text-[10px] sm:text-xs">Sampled 18 Jul 2026</p>
      </div>

      <div className="mt-auto shrink-0 pt-2">
        <div className="text-muted-foreground flex text-[10px]">
          {chartData.map(({ band, from, value }) => (
            <span key={band} style={{ flexGrow: value, flexBasis: 0 }}>
              {from}
            </span>
          ))}
          <span>{MAX}</span>
        </div>
        <div className="mt-1 flex gap-1">
          {chartData.map(({ band, bar, value }) => (
            <span
              key={band}
              className={cn("h-1.5 rounded-full", bar)}
              style={{ flexGrow: value, flexBasis: 0 }}
            />
          ))}
        </div>
      </div>
    </div>
  );
}

```
