{"spec_id":"sunburst-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// sunburst-basic: Basic Sunburst Chart\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-26\n//# anyplot-orientation: square\n// anyplot.ai\n// sunburst-basic: Basic Sunburst Chart\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-07-26\n\nimport { PieChart } from \"@mui/x-charts/PieChart\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Engineering org budget, three hierarchy levels: department -> team -> project.\n// Each level's values sum to the same $1,000K total so the three concentric\n// rings share one d3 pie layout basis and their arcs stay angularly aligned —\n// every child ring exactly spans its parent's angular range.\nconst DEPARTMENTS = [\n  { id: \"eng\", label: \"Engineering\", value: 480 },\n  { id: \"des\", label: \"Design\", value: 220 },\n  { id: \"mkt\", label: \"Marketing\", value: 180 },\n  { id: \"ops\", label: \"Operations\", value: 120 },\n];\n\nconst TEAMS = [\n  { id: \"backend\", label: \"Backend\", value: 200, dept: \"eng\" },\n  { id: \"frontend\", label: \"Frontend\", value: 150, dept: \"eng\" },\n  { id: \"platform\", label: \"Platform\", value: 130, dept: \"eng\" },\n  { id: \"product-design\", label: \"Product Design\", value: 140, dept: \"des\" },\n  { id: \"brand\", label: \"Brand\", value: 80, dept: \"des\" },\n  { id: \"content\", label: \"Content\", value: 100, dept: \"mkt\" },\n  { id: \"growth\", label: \"Growth\", value: 80, dept: \"mkt\" },\n  { id: \"finance\", label: \"Finance\", value: 70, dept: \"ops\" },\n  { id: \"hr\", label: \"HR\", value: 50, dept: \"ops\" },\n];\n\nconst PROJECTS = [\n  { id: \"api-platform\", label: \"API Platform\", value: 120, team: \"backend\" },\n  { id: \"data-pipeline\", label: \"Data Pipeline\", value: 80, team: \"backend\" },\n  { id: \"web-app\", label: \"Web App\", value: 90, team: \"frontend\" },\n  { id: \"mobile-app\", label: \"Mobile App\", value: 60, team: \"frontend\" },\n  { id: \"infra\", label: \"Infra\", value: 80, team: \"platform\" },\n  { id: \"devops\", label: \"DevOps\", value: 50, team: \"platform\" },\n  { id: \"ux-research\", label: \"UX Research\", value: 70, team: \"product-design\" },\n  { id: \"visual-design\", label: \"Visual Design\", value: 70, team: \"product-design\" },\n  { id: \"identity\", label: \"Identity\", value: 50, team: \"brand\" },\n  { id: \"campaigns\", label: \"Campaigns\", value: 30, team: \"brand\" },\n  { id: \"blog\", label: \"Blog\", value: 60, team: \"content\" },\n  { id: \"social\", label: \"Social\", value: 40, team: \"content\" },\n  { id: \"seo\", label: \"SEO\", value: 45, team: \"growth\" },\n  { id: \"paid-ads\", label: \"Paid Ads\", value: 35, team: \"growth\" },\n  { id: \"accounting\", label: \"Accounting\", value: 40, team: \"finance\" },\n  { id: \"payroll\", label: \"Payroll\", value: 30, team: \"finance\" },\n  { id: \"recruiting\", label: \"Recruiting\", value: 30, team: \"hr\" },\n  { id: \"benefits\", label: \"Benefits\", value: 20, team: \"hr\" },\n];\n\n// Department -> Imprint hue (first series is always brand green).\nconst DEPT_COLOR = {\n  eng: t.palette[0],\n  des: t.palette[1],\n  mkt: t.palette[2],\n  ops: t.palette[3],\n};\n\n// Same hue per branch across all three rings; only opacity steps down per\n// ring depth, so a department keeps one identity while its descendants read\n// as clearly nested within it (Imprint identity stays theme-invariant — the\n// page background showing through the translucent fill is what shifts per theme).\nconst hexToRgb = (hex) => [\n  parseInt(hex.slice(1, 3), 16),\n  parseInt(hex.slice(3, 5), 16),\n  parseInt(hex.slice(5, 7), 16),\n];\nconst tint = (hex, alpha) => {\n  const [r, g, b] = hexToRgb(hex);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n};\n\nconst currency = (item) => `$${item.value}K`;\n\nconst departmentData = DEPARTMENTS.map((d) => ({\n  id: d.id,\n  value: d.value,\n  label: d.label,\n  color: DEPT_COLOR[d.id],\n}));\n\nconst teamData = TEAMS.map((team) => ({\n  id: team.id,\n  value: team.value,\n  label: team.label,\n  color: tint(DEPT_COLOR[team.dept], 0.72),\n}));\n\nconst projectData = PROJECTS.map((project) => {\n  const team = TEAMS.find((candidate) => candidate.id === project.team);\n  return {\n    id: project.id,\n    value: project.value,\n    label: project.label,\n    color: tint(DEPT_COLOR[team.dept], 0.46),\n  };\n});\n\nconst TITLE = \"sunburst-basic · javascript · muix · anyplot.ai\";\nconst MARGIN = { top: 110, bottom: 40, left: 40, right: 40 };\nconst TOTAL_BUDGET = DEPARTMENTS.reduce((sum, d) => sum + d.value, 0);\n\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const pieCy = (MARGIN.top + height - MARGIN.bottom) / 2;\n\n  return (\n    <div\n      style={{\n        position: \"relative\",\n        width,\n        height,\n        fontFamily: \"'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif\",\n      }}\n    >\n      {/* Title */}\n      <div\n        style={{\n          position: \"absolute\",\n          top: 20,\n          left: 0,\n          right: 0,\n          textAlign: \"center\",\n          fontSize: 22,\n          fontWeight: 600,\n          color: t.ink,\n          letterSpacing: \"0.01em\",\n          lineHeight: 1.3,\n        }}\n      >\n        {TITLE}\n      </div>\n\n      {/* Sunburst — 3 concentric rings (department / team / project), each ring\n          a PieChart series sharing one 1,000-unit total so arcs stay aligned. */}\n      <PieChart\n        width={width}\n        height={height}\n        skipAnimation\n        margin={MARGIN}\n        series={[\n          {\n            id: \"departments\",\n            data: departmentData,\n            innerRadius: 150,\n            outerRadius: 255,\n            arcLabel: \"label\",\n            arcLabelMinAngle: 0,\n            valueFormatter: currency,\n          },\n          {\n            id: \"teams\",\n            data: teamData,\n            innerRadius: 270,\n            outerRadius: 375,\n            arcLabel: \"label\",\n            arcLabelMinAngle: 15,\n            valueFormatter: currency,\n          },\n          {\n            id: \"projects\",\n            data: projectData,\n            innerRadius: 390,\n            outerRadius: 495,\n            arcLabel: \"label\",\n            arcLabelMinAngle: 28,\n            valueFormatter: currency,\n          },\n        ]}\n        slotProps={{ legend: { hidden: true } }}\n        sx={{\n          \"& .MuiPieArc-root\": { stroke: t.pageBg, strokeWidth: 1.5 },\n          \"& .MuiPieArcLabel-root\": {\n            fill: t.ink,\n            paintOrder: \"stroke\",\n            stroke: t.pageBg,\n            strokeWidth: 4,\n            strokeLinejoin: \"round\",\n          },\n          \"& .MuiPieArcLabel-series-departments\": { fontSize: 19, fontWeight: 700 },\n          \"& .MuiPieArcLabel-series-teams\": { fontSize: 14, fontWeight: 600 },\n          \"& .MuiPieArcLabel-series-projects\": { fontSize: 11, fontWeight: 600 },\n        }}\n      />\n\n      {/* Center overlay — total budget across all departments */}\n      <div\n        style={{\n          position: \"absolute\",\n          top: pieCy,\n          left: width / 2,\n          transform: \"translate(-50%, -50%)\",\n          textAlign: \"center\",\n          pointerEvents: \"none\",\n          lineHeight: 1.2,\n        }}\n      >\n        <div style={{ fontSize: 34, fontWeight: 700, color: t.ink }}>\n          {`$${(TOTAL_BUDGET / 1000).toFixed(1)}M`}\n        </div>\n        <div\n          style={{\n            fontSize: 11,\n            color: t.inkSoft,\n            marginTop: 6,\n            letterSpacing: \"0.08em\",\n            textTransform: \"uppercase\",\n          }}\n        >\n          Total Budget\n        </div>\n      </div>\n    </div>\n  );\n}\n"}