{"spec_id":"donut-nested","library":"echarts","language":"javascript","code":"// anyplot.ai\n// donut-nested: Nested Donut Chart\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-08-18\n\n//# anyplot-orientation: square\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Company operating budget ($ thousands): department (inner ring) rolls up\n// from expense category (outer ring). Each department's categories are\n// listed largest-first so the color-family shading (below) tracks value.\nconst budget = [\n  {\n    department: \"Engineering\",\n    categories: [\n      { name: \"Salaries & Benefits\", value: 340 },\n      { name: \"Cloud Infrastructure\", value: 180 },\n      { name: \"Software Licenses\", value: 95 },\n    ],\n  },\n  {\n    department: \"Marketing\",\n    categories: [\n      { name: \"Digital Advertising\", value: 150 },\n      { name: \"Content Production\", value: 80 },\n      { name: \"Events & Sponsorship\", value: 60 },\n    ],\n  },\n  {\n    department: \"Sales\",\n    categories: [\n      { name: \"Sales Commissions\", value: 210 },\n      { name: \"Travel & Entertainment\", value: 70 },\n      { name: \"CRM Tools\", value: 40 },\n    ],\n  },\n  {\n    department: \"Operations\",\n    categories: [\n      { name: \"Facilities\", value: 120 },\n      { name: \"Logistics\", value: 95 },\n      { name: \"IT Support\", value: 65 },\n    ],\n  },\n];\n\n// --- Color families: same hue per department, lightness steps per category -\nconst LIGHTNESS_STEPS = [0, 0.22, 0.42];\nconst departmentColor = (i) => t.palette[i];\nconst categoryColor = (deptIndex, catIndex) =>\n  echarts.color.lift(departmentColor(deptIndex), LIGHTNESS_STEPS[catIndex]);\n\n// Fixed near-black / near-white label tones (not theme-driven — the data\n// fill colors themselves never change with theme, so label contrast must be\n// computed from the fill, not from ANYPLOT_THEME).\nconst LABEL_ON_LIGHT_FILL = \"#1A1A17\";\nconst LABEL_ON_DARK_FILL = \"#F0EFE8\";\n\nfunction relativeLuminance(hex) {\n  const c = hex.replace(\"#\", \"\");\n  const [r, g, b] = [0, 2, 4].map((i) => parseInt(c.substr(i, 2), 16) / 255);\n  const lin = (v) => (v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4));\n  return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\n}\nfunction labelColorFor(hex) {\n  return relativeLuminance(hex) > 0.55 ? LABEL_ON_LIGHT_FILL : LABEL_ON_DARK_FILL;\n}\n\nconst innerData = budget.map((d, i) => {\n  const total = d.categories.reduce((sum, c) => sum + c.value, 0);\n  const color = departmentColor(i);\n  return {\n    name: d.department,\n    value: total,\n    itemStyle: { color },\n    label: { color: labelColorFor(color) },\n  };\n});\n\nconst outerData = budget.flatMap((d, di) =>\n  d.categories.map((cat, ci) => {\n    const color = categoryColor(di, ci);\n    return { name: cat.name, value: cat.value, itemStyle: { color } };\n  })\n);\n\n// --- Title (fontsize scales down if the descriptive prefix runs long) ------\nconst title = \"Operating Budget by Department · donut-nested · javascript · echarts · anyplot.ai\";\nconst titleFontSize = Math.round(22 * Math.min(1, 67 / title.length));\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option -------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: title,\n    left: \"center\",\n    top: 28,\n    textStyle: { color: t.ink, fontSize: titleFontSize, fontWeight: 600 },\n  },\n  tooltip: {\n    trigger: \"item\",\n    formatter: \"{b}: ${c}k ({d}%)\",\n  },\n  legend: {\n    bottom: 12,\n    left: \"center\",\n    width: \"90%\",\n    itemWidth: 14,\n    itemHeight: 14,\n    itemGap: 16,\n    textStyle: { color: t.inkSoft, fontSize: 13 },\n    data: outerData.map((c) => c.name),\n  },\n  series: [\n    {\n      name: \"Department\",\n      type: \"pie\",\n      center: [\"50%\", \"53%\"],\n      radius: [\"0%\", \"30%\"],\n      padAngle: 2,\n      label: {\n        show: true,\n        position: \"inside\",\n        formatter: \"{b}\",\n        fontSize: 16,\n        fontWeight: 600,\n      },\n      labelLine: { show: false },\n      itemStyle: { borderColor: t.pageBg, borderWidth: 3 },\n      data: innerData,\n    },\n    {\n      name: \"Expense category\",\n      type: \"pie\",\n      center: [\"50%\", \"53%\"],\n      radius: [\"40%\", \"68%\"],\n      padAngle: 1.5,\n      minShowLabelAngle: 12,\n      label: {\n        show: true,\n        position: \"outside\",\n        formatter: \"{b}\",\n        fontSize: 13,\n        color: t.inkSoft,\n      },\n      labelLine: { length: 14, length2: 10, lineStyle: { color: t.grid } },\n      labelLayout: { hideOverlap: true },\n      itemStyle: { borderColor: t.pageBg, borderWidth: 3 },\n      data: outerData,\n    },\n  ],\n});\n"}