{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart-pie",
  "title": "Chart Pie",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A pie or donut with the total in the hole, outside labels, a pulled-out slice and a half ring, in a frame with a legend that switches slices off, a table of value and share, a written ranking, patterns and states.",
  "dependencies": [
    "recharts@^3"
  ],
  "registryDependencies": [
    "https://ui.ballmac.com/r/chart.json",
    "https://ui.ballmac.com/r/chart-frame.json",
    "https://ui.ballmac.com/r/i18n.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/chart-pie.tsx",
      "content": "// Ballmac UI: Chart Pie. https://ui.ballmac.com/components/chart-pie\n\"use client\"\n\nimport * as React from \"react\"\nimport { Cell, Pie, PieChart, Sector } from \"recharts\"\n\nimport { ChartCenterLabel, ChartTooltip, ChartTooltipContent } from \"@/components/ballmac/chart\"\nimport { ChartFrame, type ChartFrameProps, type ChartRow, type ChartSeries } from \"@/components/ballmac/chart-frame\"\nimport { useLocale, useMessages } from \"@/lib/ballmac/i18n\"\n\ntype ChartPieProps = Omit<ChartFrameProps, \"children\" | \"series\" | \"xKey\" | \"summaryMode\" | \"skeleton\" | \"table\" | \"data\"> & {\n  /** One row per slice: its name under `nameKey`, its number under `valueKey`, and optionally a `label` and a `color`. */\n  data: ChartRow[]\n  /** The key of each slice's name. */\n  nameKey?: string\n  /** The key of each slice's number. */\n  valueKey?: string\n  /** What the numbers count, for the center and the table: \"visitors\". */\n  valueLabel?: string\n  /** A ring with a hole (default) or a full pie. */\n  donut?: boolean\n  /** Draw only the upper half, like a gauge. */\n  half?: boolean\n  /** Write the total in the hole of a donut. Pass your own `{ value, label }`, or false to leave it empty. */\n  centerLabel?: boolean | { value: string; label: string }\n  /** \"outside\" writes each slice's name and share beside it. */\n  labels?: \"none\" | \"outside\"\n  /** A slice to pull out, by name. */\n  highlight?: string\n  /** Leave a small gap between slices. */\n  gap?: boolean\n  tooltip?: boolean\n}\n\n/**\n * A pie or donut in a ChartFrame: the total in the hole, outside labels, a pulled-out slice, a half ring, a legend that switches slices off (the total and\n * the shares follow), a table of value and share, a written ranking, patterns, and loading, empty and error states.\n */\nfunction ChartPie({\n  data,\n  nameKey = \"name\",\n  valueKey = \"value\",\n  valueLabel,\n  donut = true,\n  half = false,\n  centerLabel = true,\n  labels = \"none\",\n  highlight,\n  gap = true,\n  tooltip = true,\n  categoryLabel,\n  categoryFormatter,\n  valueFormatter,\n  summary,\n  ...frame\n}: ChartPieProps) {\n  const msg = useMessages()\n  const locale = useLocale()\n  const nf = React.useMemo(() => new Intl.NumberFormat(locale), [locale])\n  const pct = React.useMemo(() => new Intl.NumberFormat(locale, { style: \"percent\", maximumFractionDigits: 0 }), [locale])\n  const number = (v: unknown) => (typeof v === \"number\" && Number.isFinite(v) ? v : 0)\n  const write = (v: number) => (valueFormatter ? valueFormatter(v, valueKey) : nf.format(v))\n\n  const items: ChartSeries[] = data.map((row) => {\n    const name = String(row[nameKey])\n    return { key: name, label: typeof row.label === \"string\" ? row.label : categoryFormatter ? categoryFormatter(row[nameKey]) : name, color: typeof row.color === \"string\" ? row.color : undefined }\n  })\n  const total = data.reduce((sum, row) => sum + number(row[valueKey]), 0)\n  const share = (v: number) => (total > 0 ? pct.format(v / total) : \"–\")\n  const ranked = [...data].sort((a, b) => number(b[valueKey]) - number(a[valueKey]))\n  const labelOf = (name: string) => items.find((i) => i.key === name)?.label ?? name\n\n  const totalText = valueLabel ? `${write(total)} ${valueLabel}` : write(total)\n  const written =\n    summary ??\n    (data.length\n      ? [\n          msg(\"chart-pie.total\", { one: \"{total} in one group.\", other: \"{total} in total across {count} groups.\" }, { total: totalText, count: data.length }),\n          ranked.map((row) => `${labelOf(String(row[nameKey]))} ${share(number(row[valueKey]))} (${write(number(row[valueKey]))})`).join(\", \") + \".\",\n        ].join(\" \")\n      : \"\")\n\n  return (\n    <ChartFrame\n      data={data}\n      xKey={nameKey}\n      series={items}\n      summary={written}\n      aspect={half ? \"wide\" : \"square\"}\n      skeleton=\"donut\"\n      legend=\"toggle\"\n      categoryLabel={categoryLabel}\n      valueFormatter={valueFormatter}\n      categoryFormatter={categoryFormatter}\n      table={{\n        columns: [\n          { key: nameKey, label: categoryLabel ?? msg(\"chart-pie.category\", \"Category\"), format: (v) => labelOf(String(v)) },\n          { key: valueKey, label: valueLabel ? valueLabel.charAt(0).toUpperCase() + valueLabel.slice(1) : msg(\"chart-pie.value\", \"Value\"), numeric: true, format: (v) => write(number(v)) },\n          { key: \"__share\", label: msg(\"chart-pie.share\", \"Share\"), numeric: true, format: (_v, row) => share(number(row[valueKey])) },\n        ],\n        rows: data,\n      }}\n      {...frame}\n    >\n      {(chart) => {\n        const shown = data.filter((row) => !chart.isHidden(String(row[nameKey])))\n        const shownTotal = shown.reduce((sum, row) => sum + number(row[valueKey]), 0)\n        // Slices run the way the page reads: clockwise left to right, counterclockwise right to left.\n        const [startAngle, endAngle] = half ? (chart.rtl ? [0, 180] : [180, 0]) : chart.rtl ? [90, 450] : [90, -270]\n        const center = centerLabel === true ? { value: write(shownTotal), label: valueLabel ?? msg(\"chart-pie.totalLabel\", \"Total\") } : centerLabel || null\n        return (\n          <PieChart accessibilityLayer margin={{ top: labels === \"outside\" ? 24 : 8, right: labels === \"outside\" ? 76 : 8, bottom: half ? 0 : 8, left: labels === \"outside\" ? 76 : 8 }}>\n            <defs>{chart.defs}</defs>\n            {tooltip ? <ChartTooltip content={<ChartTooltipContent hideLabel nameKey={nameKey} />} /> : null}\n            <Pie\n              data={shown}\n              dataKey={valueKey}\n              nameKey={nameKey}\n              innerRadius={donut ? (half ? \"100%\" : \"58%\") : 0}\n              outerRadius={half ? \"165%\" : labels === \"outside\" ? \"70%\" : \"92%\"}\n              startAngle={startAngle}\n              endAngle={endAngle}\n              cy={half ? \"88%\" : \"50%\"}\n              paddingAngle={gap ? 2 : 0}\n              cornerRadius={donut ? 4 : 2}\n              stroke=\"var(--card)\"\n              strokeWidth={gap ? 0 : 2}\n              isAnimationActive={chart.animate}\n              animationDuration={700}\n              label={\n                labels === \"outside\"\n                  ? (p: { name?: string; percent?: number; x?: number; y?: number; textAnchor?: string; payload?: ChartRow }) => (\n                      <text x={p.x} y={p.y} textAnchor={p.textAnchor as \"start\" | \"middle\" | \"end\"} dominantBaseline=\"central\" className=\"fill-foreground text-xs\">\n                        {labelOf(String(p.name ?? \"\"))} <tspan className=\"fill-muted-foreground\">{pct.format(p.percent ?? 0)}</tspan>\n                      </text>\n                    )\n                  : false\n              }\n              labelLine={labels === \"outside\" ? { stroke: \"var(--border)\" } : false}\n              {...(highlight !== undefined\n                ? {\n                    shape: (p: React.ComponentProps<typeof Sector> & { payload?: ChartRow }) => (\n                      <Sector {...p} outerRadius={(p.outerRadius ?? 0) + (String(p.payload?.[nameKey]) === highlight ? 8 : 0)} />\n                    ),\n                  }\n                : {})}\n            >\n              {shown.map((row) => {\n                const key = String(row[nameKey])\n                return <Cell key={key} fill={chart.fill(key)} stroke={chart.patterns ? chart.color(key) : undefined} strokeWidth={chart.patterns ? 1.5 : undefined} />\n              })}\n            </Pie>\n            {donut && center ? <ChartCenterLabel value={center.value} label={center.label} cy={half ? 0.88 : 0.5} /> : null}\n          </PieChart>\n        )\n      }}\n    </ChartFrame>\n  )\n}\n\nexport { ChartPie, type ChartPieProps }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/chart-pie.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "chart",
      "pie",
      "donut",
      "share",
      "proportion",
      "gauge",
      "recharts",
      "graph"
    ],
    "version": "1.0.0",
    "updated": "2026-10-18",
    "ai": {
      "summary": "<ChartPie title description data={[{ name, label?, value, color? }]} valueLabel='visitors' />. donut (default) or donut={false}; half for a semicircle; centerLabel (true writes the total; or { value, label }); labels='outside'; highlight (a slice name); gap; legend; patterns; state. The legend switches slices off and the total and shares follow. Slices are rows, not series.",
      "whenToUse": [
        "A few parts of one whole (up to about six)",
        "A goal or quota ring (two slices, custom centerLabel)"
      ],
      "whenNotToUse": [
        "More than six categories or exact comparison (use chart-bar with layout='bars')",
        "Change over time (use chart-area or chart-line)"
      ],
      "composesWith": [
        "chart-frame",
        "stat-card"
      ],
      "a11y": [
        {
          "keys": "Arrow keys",
          "action": "Move between slices (accessibilityLayer)"
        },
        {
          "keys": "Tab, Enter / Space",
          "action": "Legend buttons switch slices off and on; the Chart / Table switch shows value and share as a table"
        },
        {
          "keys": "Screen readers",
          "action": "The summary lists every slice with its share and value, largest first"
        }
      ],
      "customization": [
        "donut, half and gap",
        "slice colors through `color` or the theme's chart colors",
        "centerLabel for a custom total",
        "valueFormatter for units"
      ]
    },
    "examples": [
      "chart-pie-demo",
      "chart-pie-full",
      "chart-pie-half",
      "chart-pie-highlight",
      "chart-pie-patterns",
      "chart-pie-center"
    ],
    "url": "https://ui.ballmac.com/components/chart-pie"
  },
  "categories": [
    "data-display"
  ],
  "type": "registry:ui"
}