{"spec_id":"donut-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// donut-basic: Basic Donut Chart\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-25\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// Choose readable text color for a given fill hex (maximizes contrast)\nfunction labelColor(hex) {\n  const r = parseInt(hex.slice(1, 3), 16) / 255;\n  const g = parseInt(hex.slice(3, 5), 16) / 255;\n  const b = parseInt(hex.slice(5, 7), 16) / 255;\n  const lin = (v) => (v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);\n  const L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n  return L > 0.179 ? \"#1A1A17\" : \"#F0EFE8\";\n}\n\n// --- Data: Annual Budget Allocation by Department ---\nconst segments = [\n  { label: \"Product Dev\",      value: 34 },\n  { label: \"Marketing\",        value: 23 },\n  { label: \"Operations\",       value: 17 },\n  { label: \"Customer Success\", value: 13 },\n  { label: \"HR & People\",      value: 8 },\n  { label: \"Finance & Legal\",  value: 5 },\n];\n\n// --- Layout ---\nconst titleH     = 70;\nconst legendRowH = 46;\nconst legendPad  = 36;\nconst legendH    = 2 * legendRowH + legendPad;\nconst chartTop   = titleH + 28;\nconst chartH     = height - chartTop - legendH;\nconst cx         = width / 2;\nconst cy         = chartTop + chartH / 2;\nconst outerRadius = (Math.min(chartH, width) / 2) * 0.86;\nconst innerRadius = outerRadius * 0.54;\n\n// --- SVG root ---\nconst svg = d3\n  .select(\"#container\")\n  .append(\"svg\")\n  .attr(\"width\", width)\n  .attr(\"height\", height);\n\n// --- Pie + arc generators ---\nconst pie = d3.pie().value((d) => d.value).sort(null).padAngle(0.022);\n\nconst arc = d3.arc()\n  .innerRadius(innerRadius)\n  .outerRadius(outerRadius)\n  .cornerRadius(5);\n\nconst midArc = d3.arc()\n  .innerRadius(innerRadius + (outerRadius - innerRadius) * 0.52)\n  .outerRadius(innerRadius + (outerRadius - innerRadius) * 0.52);\n\nconst slices = pie(segments);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${cx},${cy})`);\n\n// --- Donut segments ---\ng.selectAll(\"path\")\n  .data(slices)\n  .join(\"path\")\n  .attr(\"d\", arc)\n  .attr(\"fill\", (d, i) => t.palette[i])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 3);\n\n// --- Percentage labels inside each segment ---\ng.selectAll(\".pct\")\n  .data(slices)\n  .join(\"text\")\n  .attr(\"class\", \"pct\")\n  .attr(\"transform\", (d) => `translate(${midArc.centroid(d)})`)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"dominant-baseline\", \"middle\")\n  .attr(\"fill\", (d, i) => labelColor(t.palette[i]))\n  .style(\"font-size\", (d) => (d.data.value >= 10 ? \"17px\" : \"13px\"))\n  .style(\"font-weight\", \"600\")\n  .text((d) => `${d.data.value}%`);\n\n// --- Center annotation ---\ng.append(\"text\")\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"y\", -16)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"15px\")\n  .text(\"Annual Budget\");\n\ng.append(\"text\")\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"y\", 22)\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"30px\")\n  .style(\"font-weight\", \"700\")\n  .text(\"$2.4M\");\n\n// --- Title ---\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 44)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"donut-basic · javascript · d3 · anyplot.ai\");\n\n// --- Legend: 2 rows × 3 columns ---\nconst swatchW   = 18;\nconst swatchGap = 10;\nconst legendW   = width * 0.75;\nconst colW      = legendW / 3;\nconst lgX0      = (width - legendW) / 2;\nconst lgY0      = height - legendH + 20;\n\nsegments.forEach((seg, i) => {\n  const col = i % 3;\n  const row = Math.floor(i / 3);\n  const lx  = lgX0 + col * colW;\n  const ly  = lgY0 + row * legendRowH;\n\n  const item = svg.append(\"g\").attr(\"transform\", `translate(${lx},${ly})`);\n\n  item\n    .append(\"rect\")\n    .attr(\"width\", swatchW)\n    .attr(\"height\", swatchW)\n    .attr(\"rx\", 4)\n    .attr(\"fill\", t.palette[i]);\n\n  item\n    .append(\"text\")\n    .attr(\"x\", swatchW + swatchGap)\n    .attr(\"y\", swatchW / 2)\n    .attr(\"dominant-baseline\", \"middle\")\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"14px\")\n    .text(seg.label);\n});\n"}