{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "donut",
  "type": "registry:component",
  "title": "Donut",
  "description": "One total, split into a few parts. Sample data: monthly recurring revenue by plan, Enterprise $82K (45%), Starter $17K (9%).",
  "registryDependencies": [
    "@chartory/core",
    "@chartory/plot-pie"
  ],
  "files": [
    {
      "path": "registry/chartory/components/donut.tsx",
      "type": "registry:component",
      "target": "components/chartory/donut.tsx",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// Donut (Chartory chart 017): sizes xs | s | m | l, laid out again from the data at each\n// size; collection \"hearth\" (default) or \"signal\" (with @chartory/signal, Code and Complete). Data in the type's shape (default: the sample). Docs:\n// https://usechartory.com/docs/charts/donut\nimport { ChartView, type ChartViewProps } from \"@/lib/chartory/charts/chart-view\"\nimport { registerChart } from \"@/lib/chartory/charts/defs\"\nimport { data as sample } from \"@/lib/chartory/charts/types/017-donut/data\"\nimport { def } from \"@/lib/chartory/charts/types/017-donut/layout\"\n\nregisterChart(def, sample)\n\nexport type DonutData = typeof sample\nexport type DonutProps = Omit<ChartViewProps, \"def\" | \"data\" | \"states\"> & {\n  /** the data to draw (default: the sample) */\n  data?: DonutData\n  /** further data states: the chart morphs to them (update) or ticks through them (live, with interval) */\n  states?: DonutData[]\n}\n\nexport function Donut(props: DonutProps) {\n  return <ChartView def={def} {...props} />\n}\n"
    },
    {
      "path": "registry/chartory/charts/types/017-donut/data.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/017-donut/data.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 017 Donut · sample data (Figma chart/017, SaaS: MRR by plan in $K, September; $184K in all).\nimport type { PieData } from \"../../plots/pie\";\n\nconst sl: [string, number, string][] = [[\"Enterprise\", 82, \"data/cat-1\"], [\"Business\", 54, \"data/cat-2\"], [\"Team\", 31, \"data/cat-3\"], [\"Starter\", 17, \"data/cat-4\"]];\nexport const data: PieData = {\n  slices: sl.map(([label, v, tok]) => ({ label, v, tok })),\n  rows: sl.map(([label, v, tok]) => ({ label, tok, text: \"$\" + v + \"K\", pct: Math.round((v / 184) * 100) + \"%\" })),\n};\n"
    },
    {
      "path": "registry/chartory/charts/types/017-donut/layout.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/017-donut/layout.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 017 Donut (Figma chart/017, \"MRR by plan\"): C.pie (a full pie, no hole) with the plan list beside it.\nimport type { ChartDef, TooltipSpec } from \"../../core/shell\";\nimport { pie } from \"../../plots/pie\";\nimport { data } from \"./data\";\nimport { frame, type Frame, type Text } from \"../../core/node\";\n\n/** Deterministic wave in [-1, 1] per index and update step (no randomness: the same step gives the same period). */\nconst wave = (i: number, step: number, f = 1.7) => Math.sin(step * 2.3 + i * f + 0.5);\n/** Whole-percent shares that sum to 100 (largest remainder). */\nconst shares = (vs: number[]): number[] => {\n  const t = vs.reduce((a, b) => a + b, 0), raw = vs.map((v) => (v / t) * 100), out = raw.map(Math.floor);\n  const left = 100 - out.reduce((a, b) => a + b, 0);\n  raw.map((r, i) => [r - Math.floor(r), i]).sort((a, b) => b[0] - a[0]).slice(0, left).forEach(([, i]) => out[i]++);\n  return out;\n};\n/** A text is named by its characters, so an update state takes the sample's text names (same order): its value\n *  labels then stay the same marks and morph in place instead of reading as new ones. */\nfunction keepNames(pl: Frame, ref: Frame): Frame {\n  const texts = (f: Frame): Text[] => f.children.flatMap((c) => (c.removed ? [] : c.kind === \"text\" ? [c] : c.kind === \"frame\" ? texts(c) : []));\n  const a = texts(pl), b = texts(ref);\n  if (a.length === b.length) a.forEach((t, i) => { if (t.name === t.text.slice(0, 40)) t.name = b[i].name; });\n  return pl;\n}\n\n/** Hover tooltip: one plan's slice (its MRR and share, as in the list). */\nconst tip = (d: unknown, g: number): TooltipSpec => {\n  const r = (d as typeof data).rows[g];\n  return { kind: \"value\", text: `${r.label} · ${r.text} · ${r.pct}` };\n};\n\nexport const def: ChartDef = {\n  no: 17,\n  slug: \"donut\",\n  legend: null,\n  flatXs: true,\n  plot: (K, p, w, d) => {\n    const o = (d as typeof data | undefined) ?? data;\n    const pl = pie(K, p, w, o);\n    return o === data ? pl : keepNames(pl, pie(K, frame(null, \"sample\"), w, data));\n  },\n  tip,\n  update: (d, step) => {\n    const o = d as typeof data;\n    // later months: MRR per plan moves ±10 % (whole $K); values and shares are recomputed, shares sum to 100\n    const vs = o.slices.map((s, i) => Math.max(1, Math.round(s.v * (1 + 0.1 * wave(i, step)))));\n    const pc = shares(vs);\n    return {\n      ...o,\n      slices: o.slices.map((s, i) => ({ ...s, v: vs[i] })),\n      rows: o.rows.map((r, i) => ({ ...r, text: \"$\" + vs[i] + \"K\", pct: pc[i] + \"%\" })),\n    };\n  },\n  describe: {\n    title: \"MRR by plan\",\n    summary: `Monthly recurring revenue of $184K by plan: Enterprise $82K (45%), Business $54K (29%), Team $31K (17%), Starter $17K (9%).`,\n    table: { head: [\"Plan\", \"MRR\", \"Share\"], rows: data.rows.map((r) => [r.label, r.text, r.pct ?? \"\"]) },\n  },\n};\n"
    }
  ],
  "categories": [
    "charts",
    "part-of-a-whole"
  ],
  "meta": {
    "access": "free",
    "no": 17,
    "family": "Part of a whole"
  }
}
