{"spec_id":"donut-nested","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ndonut-nested: Nested Donut Chart\nLibrary: matplotlib 3.11.1 | Python 3.13.15\nQuality: 90/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\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 (positions 1-4 for departments)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Budget allocation: departments (inner ring) and expense categories (outer ring)\ndepartments = [\"Engineering\", \"Marketing\", \"Operations\", \"Sales\"]\ncategories = {\n    \"Engineering\": [\"Salaries\", \"Equipment\", \"Software\"],\n    \"Marketing\": [\"Advertising\", \"Events\", \"Content\"],\n    \"Operations\": [\"Facilities\", \"Utilities\", \"Maintenance\"],\n    \"Sales\": [\"Travel\", \"Commissions\", \"Training\"],\n}\nvalues = {\n    \"Engineering\": [450, 120, 80],\n    \"Marketing\": [200, 150, 100],\n    \"Operations\": [180, 90, 60],\n    \"Sales\": [140, 220, 70],\n}\n\n# Department totals for the inner ring (values aggregate outer -> inner)\ndept_totals = [sum(values[dept]) for dept in departments]\n\n# Flatten outer ring data while maintaining department order\nouter_labels = []\nouter_values = []\nfor dept in departments:\n    outer_labels.extend(categories[dept])\n    outer_values.extend(values[dept])\n\ninner_colors = [IMPRINT[i] for i in range(len(departments))]\n\n# Outer ring reuses each department's hue with varying opacity per subcategory,\n# so a category visually belongs to its parent while staying distinguishable.\nouter_colors_with_alpha = []\nfor dept, color in zip(departments, inner_colors, strict=True):\n    rgb = tuple(int(color[j : j + 2], 16) / 255.0 for j in (1, 3, 5))\n    alphas = np.linspace(0.5, 1.0, len(categories[dept]))\n    outer_colors_with_alpha.extend((*rgb, alpha) for alpha in alphas)\n\n# Square canvas for symmetric circular shapes: figsize=(6, 6) x dpi=400 -> 2400x2400 px\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Inner ring (departments)\ninner_wedge_props = {\"width\": 0.4, \"edgecolor\": PAGE_BG, \"linewidth\": 2}\nwedges_inner, _ = ax.pie(dept_totals, radius=0.6, colors=inner_colors, wedgeprops=inner_wedge_props, startangle=90)\n\n# Outer ring (categories within departments)\nouter_wedge_props = {\"width\": 0.35, \"edgecolor\": PAGE_BG, \"linewidth\": 1.5}\nwedges_outer, _ = ax.pie(\n    outer_values, radius=1.0, colors=outer_colors_with_alpha, wedgeprops=outer_wedge_props, startangle=90\n)\n\n# A thin halo behind wedge labels keeps them crisp against every segment shade\nhalo = [pe.withStroke(linewidth=3, foreground=PAGE_BG)]\n\n# Department labels (inner ring) - bold, the largest data-label tier\nfor wedge, dept, total in zip(wedges_inner, departments, dept_totals, strict=True):\n    angle = np.radians((wedge.theta2 + wedge.theta1) / 2)\n    x, y = 0.42 * np.cos(angle), 0.42 * np.sin(angle)\n    ax.text(\n        x,\n        y,\n        f\"{dept}\\n${total}K\",\n        ha=\"center\",\n        va=\"center\",\n        fontsize=12,\n        fontweight=\"bold\",\n        color=INK,\n        path_effects=halo,\n    )\n\n# Category labels (outer ring) - regular weight, larger segments only\nfor wedge, label, value in zip(wedges_outer, outer_labels, outer_values, strict=True):\n    if value >= 100:  # only label segments >= $100K to avoid clutter\n        angle = np.radians((wedge.theta2 + wedge.theta1) / 2)\n        x, y = 0.82 * np.cos(angle), 0.82 * np.sin(angle)\n        ax.text(x, y, f\"{label}\\n${value}K\", ha=\"center\", va=\"center\", fontsize=8, color=INK, path_effects=halo)\n\n# Custom legend: every department + category pair, colored by parent hue\nlegend_elements = [\n    plt.Rectangle((0, 0), 1, 1, facecolor=IMPRINT[i], edgecolor=INK_SOFT, label=f\"{dept}: {cat}\")\n    for i, dept in enumerate(departments)\n    for cat in categories[dept]\n]\nleg = ax.legend(\n    handles=legend_elements,\n    loc=\"center left\",\n    bbox_to_anchor=(1.0, 0.5),\n    fontsize=8,\n    frameon=True,\n    edgecolor=INK_SOFT,\n    facecolor=ELEVATED_BG,\n    handlelength=1.2,\n    handletextpad=0.6,\n    borderaxespad=0.6,\n)\nleg.get_frame().set_linewidth(0.5)\nplt.setp(leg.get_texts(), color=INK_SOFT)\n\n# fig.suptitle (not ax.set_title) so the mandated title centers on the full canvas,\n# not just the donut's axes, which is shifted left to leave room for the legend.\nfig.suptitle(\n    \"donut-nested · python · matplotlib · anyplot.ai\", x=0.5, y=0.965, fontsize=12, fontweight=\"medium\", color=INK\n)\n\nax.set_aspect(\"equal\")\n# Reserve room on the right for the legend instead of bbox_inches=\"tight\" (which drifts canvas size).\n# Height span matches the width span (both 0.64) and is centered vertically (top/bottom margins\n# both 0.18) so aspect=\"equal\" doesn't leave an asymmetric dead zone below the donut.\nfig.subplots_adjust(left=0.02, right=0.66, top=0.82, bottom=0.18)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}