{"spec_id":"donut-nested","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ndonut-nested: Nested Donut Chart\nLibrary: plotly 6.9.0 | Python 3.13.15\nQuality: 95/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\nimport plotly.graph_objects as go\n\n\n# Theme tokens (see prompts/default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette (canonical order, positions 1-4 for inner ring)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Company budget allocation by department and expense category\ndepartments = [\"Engineering\", \"Marketing\", \"Sales\", \"Operations\"]\ndept_values = [45, 25, 18, 12]  # Millions\ndept_colors = IMPRINT\n\n# Child categories (outer ring) with consistent color families (lighter shades)\ncategories = [\n    # Engineering\n    \"Salaries\",\n    \"Infrastructure\",\n    \"R&D\",\n    \"Tools\",\n    # Marketing\n    \"Advertising\",\n    \"Events\",\n    \"Content\",\n    # Sales\n    \"Commissions\",\n    \"Travel\",\n    \"Training\",\n    # Operations\n    \"Facilities\",\n    \"IT Support\",\n]\ncat_values = [\n    # Engineering: 45M total\n    22,\n    12,\n    8,\n    3,\n    # Marketing: 25M total\n    14,\n    7,\n    4,\n    # Sales: 18M total\n    10,\n    5,\n    3,\n    # Operations: 12M total\n    8,\n    4,\n]\n\n# Child colors as progressively lighter tints of their parent department's hue\n# (precomputed: base blended toward white at 15/30/45/60%, one step per child)\ncat_colors = [\n    \"#26ac88\",\n    \"#4cbb9d\",\n    \"#72c9b2\",\n    \"#99d8c7\",  # Engineering tints\n    \"#cc89fd\",\n    \"#d59efd\",\n    \"#deb3fd\",  # Marketing tints\n    \"#607db0\",\n    \"#7c94be\",\n    \"#98abcc\",  # Sales tints\n    \"#c69451\",\n    \"#d0a770\",  # Operations tints\n]\n\n# Only label outer segments large enough to hold text without crowding;\n# smaller categories rely on the legend (per spec notes). The single largest\n# category (Salaries) is bolded as the chart's focal point.\nouter_label_threshold = 5\nouter_labels = [\n    (f\"<b>{category}</b>\" if category == \"Salaries\" else category) if value >= outer_label_threshold else \"\"\n    for category, value in zip(categories, cat_values, strict=True)\n]\n\n# Inner-ring text: label + share of total, with the largest department\n# (Engineering) bolded as the chart's focal point.\ndept_total = sum(dept_values)\ninner_labels = [\n    f\"<b>{name}<br>{value / dept_total:.0%}</b>\" if name == \"Engineering\" else f\"{name}<br>{value / dept_total:.0%}\"\n    for name, value in zip(departments, dept_values, strict=True)\n]\n\n# Pull the largest department/category slightly outward as a focal-point cue\ninner_pull = [0.04, 0, 0, 0]\nouter_pull = [0.04 if category == \"Salaries\" else 0 for category in categories]\n\n# Create figure with two pie traces (inner and outer rings)\nfig = go.Figure()\n\n# Inner ring - Departments (parent categories) with both name and percentage.\n# Its domain is shrunk slightly inside the [0.30, 0.58] radius band (of the\n# shared full-size domain below) so a thin gap separates its outer edge from\n# where the outer ring's hole starts, sharpening the two-level hierarchy read\n# instead of the rings touching directly.\nfig.add_trace(\n    go.Pie(\n        values=dept_values,\n        labels=departments,\n        text=inner_labels,\n        hole=0.533,\n        domain={\"x\": [0.3776, 0.6624], \"y\": [0.2468, 0.7532]},\n        marker={\"colors\": dept_colors, \"line\": {\"color\": PAGE_BG, \"width\": 3}},\n        pull=inner_pull,\n        textinfo=\"text\",\n        textposition=\"inside\",\n        insidetextorientation=\"horizontal\",\n        textfont={\"size\": 11, \"color\": \"white\"},\n        hovertemplate=\"<b>%{label}</b><br>$%{value}M<br>%{percent}<extra></extra>\",\n        name=\"Departments\",\n        sort=False,\n    )\n)\n\n# Outer ring - Expense categories (child categories)\nfig.add_trace(\n    go.Pie(\n        values=cat_values,\n        labels=categories,\n        text=outer_labels,\n        hole=0.58,\n        domain={\"x\": [0.16, 0.88], \"y\": [0.05, 0.95]},\n        marker={\"colors\": cat_colors, \"line\": {\"color\": PAGE_BG, \"width\": 2}},\n        pull=outer_pull,\n        textinfo=\"text\",\n        textposition=\"outside\",\n        textfont={\"size\": 11, \"color\": INK},\n        hovertemplate=\"<b>%{label}</b><br>$%{value}M<br>%{percent}<extra></extra>\",\n        name=\"Categories\",\n        sort=False,\n    )\n)\n\n# Layout with theme-adaptive colors\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": \"Company Budget Allocation · donut-nested · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 15, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    showlegend=True,\n    legend={\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"orientation\": \"v\",\n        \"x\": 0.84,\n        \"y\": 0.5,\n        \"yanchor\": \"middle\",\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    margin={\"l\": 40, \"r\": 50, \"t\": 50, \"b\": 40},\n    annotations=[\n        {\n            \"text\": \"<b>$100M</b><br>Total Budget\",\n            \"x\": 0.52,\n            \"y\": 0.5,\n            \"font\": {\"size\": 16, \"color\": INK},\n            \"showarrow\": False,\n        }\n    ],\n)\n\n# Save as PNG (3200 x 1800 px)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\n\n# Save interactive HTML\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}