{"spec_id":"pie-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\npie-basic: Basic Pie Chart\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-28\n\"\"\"\n\nimport io\nimport math\nimport os\nimport time\nfrom pathlib import Path\n\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, Label, Legend, LegendItem\nfrom bokeh.plotting import figure\nfrom PIL import Image\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data - Cloud infrastructure market share (2024)\ncategories = [\"AWS\", \"Azure\", \"Google Cloud\", \"Alibaba\", \"Others\"]\nvalues = [33, 23, 11, 4, 29]\n# First 4 use canonical Imprint palette order; \"Others\" uses semantic muted anchor\ncolors = [IMPRINT_PALETTE[0], IMPRINT_PALETTE[1], IMPRINT_PALETTE[2], IMPRINT_PALETTE[3], INK_MUTED]\n\n# Compute angles and percentages\ntotal = sum(values)\npercentages = [v / total * 100 for v in values]\nangles = [v / total * 2 * math.pi for v in values]\n\n# Start/end angles (clockwise from top)\nstart_angles = []\nend_angles = []\ncurrent = math.pi / 2\nfor a in angles:\n    start_angles.append(current)\n    current -= a\n    end_angles.append(current)\n\nmid_angles = [(s + e) / 2 for s, e in zip(start_angles, end_angles, strict=True)]\n\n# Explode the largest slice (AWS) for emphasis\nexplode_idx = 0\nexplode_r = 0.06\noffsets_x = [0.0] * len(categories)\noffsets_y = [0.0] * len(categories)\noffsets_x[explode_idx] = explode_r * math.cos(mid_angles[explode_idx])\noffsets_y[explode_idx] = explode_r * math.sin(mid_angles[explode_idx])\n\n# Title sizing — scale down only if longer than 67-char baseline\ntitle = \"pie-basic · python · bokeh · anyplot.ai\"\ntitle_pt = round(50 * (67 / len(title))) if len(title) > 67 else 50\n\n# Figure — 2400×2400 square (canonical for symmetric plots like pie charts)\nW, H = 2400, 2400\nradius = 0.82\n\np = figure(\n    width=W,\n    height=H,\n    title=title,\n    x_range=(-1.2, 1.2),\n    y_range=(-1.05, 1.1),\n    toolbar_location=None,\n    min_border_bottom=200,\n    min_border_left=60,\n    min_border_top=130,\n    min_border_right=60,\n)\n\n# Wedges\nsource = ColumnDataSource(\n    data={\"x\": offsets_x, \"y\": offsets_y, \"start\": start_angles, \"end\": end_angles, \"color\": colors}\n)\nrenderers = p.wedge(\n    x=\"x\",\n    y=\"y\",\n    radius=radius,\n    start_angle=\"start\",\n    end_angle=\"end\",\n    direction=\"clock\",\n    fill_color=\"color\",\n    line_color=PAGE_BG,\n    line_width=4,\n    source=source,\n)\n\n# Percentage labels — near-white on all palette colors (all medium-dark)\n# except ANYPLOT_MUTED in dark theme which is lighter and needs dark text\nslice_label_colors = [\"#F0EFE8\"] * 4 + [\"#1A1A17\" if THEME == \"dark\" else \"#F0EFE8\"]\n\nlabel_r = radius * 0.62\nfor i in range(len(categories)):\n    lx = label_r * math.cos(mid_angles[i]) + offsets_x[i]\n    ly = label_r * math.sin(mid_angles[i]) + offsets_y[i]\n    p.add_layout(\n        Label(\n            x=lx,\n            y=ly,\n            text=f\"{percentages[i]:.0f}%\",\n            text_font_size=\"34pt\",\n            text_color=slice_label_colors[i],\n            text_font_style=\"bold\",\n            text_align=\"center\",\n            text_baseline=\"middle\",\n        )\n    )\n\n# Legend with category names and percentages\nlegend_items = [\n    LegendItem(label=f\"{categories[i]} ({percentages[i]:.0f}%)\", renderers=[renderers], index=i)\n    for i in range(len(categories))\n]\nlegend = Legend(\n    items=legend_items,\n    location=\"center\",\n    orientation=\"horizontal\",\n    label_text_font_size=\"34pt\",\n    label_text_color=INK_SOFT,\n    glyph_width=40,\n    glyph_height=40,\n    spacing=30,\n    padding=20,\n    background_fill_color=ELEVATED_BG,\n    background_fill_alpha=0.9,\n    border_line_color=INK_SOFT,\n)\np.add_layout(legend, \"below\")\n\n# Styling\np.title.text_font_size = f\"{title_pt}pt\"\np.title.align = \"center\"\np.title.text_color = INK\np.axis.visible = False\np.grid.visible = False\np.outline_line_color = PAGE_BG\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\n\n# Save HTML (interactive catalog artifact)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome — window is H+200 tall so bokeh canvas fills\n# exactly W×H; PIL crops to the target rect before saving.\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H + 200}\",\n    \"--hide-scrollbars\",\n    \"--force-device-scale-factor=1\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H + 200)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\nraw = driver.get_screenshot_as_png()\ndriver.quit()\nImage.open(io.BytesIO(raw)).crop((0, 0, W, H)).save(f\"plot-{THEME}.png\")\n"}