{"spec_id":"donut-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\ndonut-basic: Basic Donut Chart\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-06-25\n\"\"\"\n\nimport math\nimport os\nimport sys\n\n\n# Remove script directory from path to avoid shadowing the installed plotnine package\n_HERE = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != _HERE]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_polygon,\n    geom_text,\n    ggplot,\n    labs,\n    scale_fill_identity,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — positions 1–5; brand green is always first series\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\nLABEL_ON_WEDGE = \"#F0EFE8\"\n\n# Data — annual budget allocation by department (USD thousands)\ncategories = [\"Engineering\", \"Marketing\", \"Operations\", \"Sales\", \"Support\"]\nvalues = [480, 210, 155, 125, 55]\ntotal = sum(values)\n\n# Ring geometry\nINNER_R = 0.62\nOUTER_R = 1.00\nLABEL_R = 1.32  # category labels outside ring — pushed further out to prevent overlap\nPCT_R = 0.75  # pct labels slightly inside ring midpoint for better separation\nSMALL_THRESHOLD = 0.08  # segments under 8%: combine pct into category label\n\nwedge_rows = []\nlabel_rows = []\npct_rows = []\n\nstart_angle = math.pi / 2  # Start at 12 o'clock, clockwise\nfor category, value, color in zip(categories, values, IMPRINT, strict=True):\n    sweep = (value / total) * 2 * math.pi\n    end_angle = start_angle - sweep\n\n    gap = 0.008\n    a0, a1 = end_angle + gap, start_angle - gap\n    n_pts = 80\n    inner_arc = np.linspace(a0, a1, n_pts)\n    outer_arc = np.linspace(a1, a0, n_pts)\n\n    points = [(INNER_R * math.cos(a), INNER_R * math.sin(a)) for a in inner_arc]\n    points += [(OUTER_R * math.cos(a), OUTER_R * math.sin(a)) for a in outer_arc]\n\n    for order, (x, y) in enumerate(points):\n        wedge_rows.append({\"x\": x, \"y\": y, \"segment\": category, \"order\": order, \"fill\": color})\n\n    mid = (start_angle + end_angle) / 2\n    pct = value / total\n    if pct < SMALL_THRESHOLD:\n        label_rows.append(\n            {\"x\": LABEL_R * math.cos(mid), \"y\": LABEL_R * math.sin(mid), \"label\": f\"{category} {pct * 100:.1f}%\"}\n        )\n    else:\n        label_rows.append({\"x\": LABEL_R * math.cos(mid), \"y\": LABEL_R * math.sin(mid), \"label\": category})\n        pct_rows.append({\"x\": PCT_R * math.cos(mid), \"y\": PCT_R * math.sin(mid), \"label\": f\"{pct * 100:.1f}%\"})\n\n    start_angle = end_angle\n\nwedge_df = pd.DataFrame(wedge_rows)\nlabel_df = pd.DataFrame(label_rows)\npct_df = pd.DataFrame(pct_rows)\n\n# Title with scaled fontsize for the mandated ~67-char title\nTITLE = \"Budget by Department · donut-basic · python · plotnine · anyplot.ai\"\nn = len(TITLE)\nratio = 67 / n if n > 67 else 1.0\ntitle_size = max(8, round(12 * ratio))\n\n# Plot\nplot = (\n    ggplot()\n    + geom_polygon(aes(x=\"x\", y=\"y\", group=\"segment\", fill=\"fill\"), data=wedge_df, color=PAGE_BG, size=1.0)\n    # Percentage labels inside ring — bold, on-wedge colour\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=pct_df, size=16, fontweight=\"bold\", color=LABEL_ON_WEDGE)\n    # Category labels outside ring — regular weight, ink colour\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=label_df, size=14, color=INK)\n    # Center metric — prominent value\n    + annotate(\"text\", x=0, y=0.12, label=f\"${total:,}K\", size=26, fontweight=\"bold\", color=INK, ha=\"center\")\n    # Center sub-label — softer, smaller\n    + annotate(\"text\", x=0, y=-0.10, label=\"Total Budget\", size=14, color=INK_SOFT, ha=\"center\")\n    + scale_fill_identity()\n    + coord_fixed(ratio=1)\n    + scale_x_continuous(limits=(-1.65, 1.65))\n    + scale_y_continuous(limits=(-1.55, 1.55))\n    + labs(title=TITLE)\n    + theme(\n        figure_size=(6, 6),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(size=title_size, color=INK, ha=\"center\", margin={\"b\": 16}),\n        axis_title=element_blank(),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        legend_position=\"none\",\n    )\n)\n\n# Save — 2400×2400 px (square format for symmetric donut chart)\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}