{"spec_id":"bar-spine","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nbar-spine: Spine Plot for Two-Variable Proportions\nLibrary: bokeh 3.10.0 | Python 3.13.15\nQuality: 88/100 | Updated: 2026-09-27\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Prevent this file (bokeh.py) from shadowing the installed bokeh package\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.normpath(p or \".\") != os.path.normpath(_here)]\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, FixedTicker, HoverTool, NumeralTickFormatter, Range1d\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens (Imprint style guide)\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# Semantic-status color mapping (Imprint palette \"Semantic exception\"): the\n# fill categories are literally pass/warning/fail labels, so we reach past\n# canonical ordinal order for the widely-expected traffic-light association.\n# The brand-green requirement (\"first series is always #009E73\") is still\n# satisfied since \"On Time\" is both semantically good AND first in the stack.\nSTATUS_COLOR = {\n    \"On Time\": \"#009E73\",  # Imprint position 1 — brand green, also \"good\"\n    \"Delayed\": \"#DDCC77\",  # Imprint amber anchor — \"warning\"\n    \"Cancelled\": \"#AE3030\",  # Imprint position 5 — matte red, \"bad/error\"\n}\n# Text drawn on top of each status color needs its own contrast check: the\n# amber anchor is light in both themes, so it takes dark ink text while the\n# green/red fills stay dark enough for white text in both themes.\nSTATUS_LABEL_COLOR = {\"On Time\": \"#FFFFFF\", \"Delayed\": \"#1A1A17\", \"Cancelled\": \"#FFFFFF\"}\n\n# Data: project completion status by department\ndepartments = [\"Engineering\", \"Marketing\", \"Sales\", \"Operations\"]\nstatuses = [\"On Time\", \"Delayed\", \"Cancelled\"]\n\n# Counts per (department, status) — columns map to statuses\ncounts = np.array(\n    [\n        [85, 35, 10],  # Engineering: 130 projects\n        [50, 15, 5],  # Marketing:    70 projects\n        [110, 30, 10],  # Sales:       150 projects\n        [68, 10, 2],  # Operations:   80 projects\n    ]\n)\n\n# Marginal totals and normalised bar widths\ndept_totals = counts.sum(axis=1)\ngrand_total = int(dept_totals.sum())\nbar_widths = dept_totals / grand_total\nbar_lefts = np.concatenate([[0.0], np.cumsum(bar_widths[:-1])])\nbar_rights = bar_lefts + bar_widths\nx_centers = (bar_lefts + bar_rights) / 2\n\n# Conditional proportions within each bar\ncond_props = counts / dept_totals[:, np.newaxis]\n\n# Best on-time performer — the callout annotation below\nbest_idx = int(np.argmax(cond_props[:, 0]))\nbest_pct = float(cond_props[best_idx, 0])\nbest_top = float(cond_props[best_idx, 0])\n\n# Build figure — canonical 3200x1800 landscape canvas (see prompts/library/bokeh.md)\np = figure(\n    width=3200,\n    height=1800,\n    x_range=Range1d(-0.01, 1.01),\n    y_range=Range1d(-0.01, 1.18),  # headroom above 100% for the callout and legend clearance\n    title=\"Project Completion Status by Department · bar-spine · python · bokeh · anyplot.ai\",\n    toolbar_location=None,  # bokeh's default toolbar shrinks the saved PNG below the target height\n    min_border_bottom=160,  # room for 34pt x-tick labels + 42pt x-axis label\n    min_border_left=180,  # room for 34pt y-tick labels + 42pt y-axis label\n    min_border_top=110,  # room for 50pt title\n    min_border_right=50,\n)\n\n# Draw one quad call per status so legend_label works correctly.\n# line_color=None keeps adjacent bars/segments visually seamless — the spec\n# requires \"no gaps\", and a background-colored stroke reads as a visible gap.\nfor j, status in enumerate(statuses):\n    bottoms = [float(cond_props[i, :j].sum()) for i in range(len(departments))]\n    tops = [float(cond_props[i, : j + 1].sum()) for i in range(len(departments))]\n\n    source = ColumnDataSource(\n        data={\n            \"left\": list(bar_lefts),\n            \"right\": list(bar_rights),\n            \"bottom\": bottoms,\n            \"top\": tops,\n            \"department\": departments,\n            \"status\": [status] * len(departments),\n            \"count\": [int(counts[i, j]) for i in range(len(departments))],\n            \"pct\": [f\"{cond_props[i, j]:.1%}\" for i in range(len(departments))],\n        }\n    )\n\n    p.quad(\n        left=\"left\",\n        right=\"right\",\n        bottom=\"bottom\",\n        top=\"top\",\n        color=STATUS_COLOR[status],\n        line_color=None,\n        alpha=0.95,\n        legend_label=status,\n        source=source,\n    )\n\n# Hover tool\nhover = HoverTool(\n    tooltips=[(\"Department\", \"@department\"), (\"Status\", \"@status\"), (\"Projects\", \"@count\"), (\"Share\", \"@pct\")]\n)\np.add_tools(hover)\n\n# Percentage labels for segments >= 8% of bar height\nlabel_x, label_y, label_text, label_color = [], [], [], []\nfor i in range(len(departments)):\n    for j, status in enumerate(statuses):\n        prop = float(cond_props[i, j])\n        if prop >= 0.08:\n            bottom = float(cond_props[i, :j].sum())\n            label_x.append(float(x_centers[i]))\n            label_y.append(bottom + prop / 2)\n            label_text.append(f\"{prop:.0%}\")\n            label_color.append(STATUS_LABEL_COLOR[status])\n\np.text(\n    x=label_x,\n    y=label_y,\n    text=label_text,\n    text_align=\"center\",\n    text_baseline=\"middle\",\n    text_font_size=\"28pt\",\n    text_font_style=\"bold\",\n    text_color=label_color,\n)\n\n# Focal-point callout on the best on-time performer: a short connector line\n# from the segment top up to a \"★ Best\" label in the reserved headroom.\np.line(\n    x=[x_centers[best_idx], x_centers[best_idx]],\n    y=[best_top + 0.01, 1.055],\n    line_color=INK_SOFT,\n    line_width=2,\n    line_dash=\"dashed\",\n)\np.text(\n    x=[x_centers[best_idx]],\n    y=[1.09],\n    text=[f\"★ Best: {best_pct:.0%} on-time\"],\n    text_align=\"center\",\n    text_baseline=\"middle\",\n    text_font_size=\"26pt\",\n    text_font_style=\"bold\",\n    text_color=STATUS_COLOR[\"On Time\"],\n)\n\n# X-axis: fixed ticks centred on each bar, labelled with department name\np.xaxis.ticker = FixedTicker(ticks=[float(c) for c in x_centers])\np.xaxis.major_label_overrides = {float(x_centers[i]): departments[i] for i in range(len(departments))}\n\n# Y-axis formatted as percentages, capped display range at 100%\np.yaxis.ticker = FixedTicker(ticks=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0])\np.yaxis.formatter = NumeralTickFormatter(format=\"0%\")\n\n# Axis labels\np.xaxis.axis_label = \"Department  (bar width ∝ project count)\"\np.yaxis.axis_label = \"Proportion of Projects\"\n\n# Text sizes — canonical bokeh 3200x1800 values (prompts/library/bokeh.md)\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"normal\"\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\np.title.text_color = INK\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.xgrid.grid_line_color = None\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.12\n\n# Legend styling — top_left keeps it clear of the \"Best\" callout at top_right\np.legend.location = \"top_left\"\np.legend.background_fill_color = ELEVATED_BG\np.legend.border_line_color = INK_SOFT\np.legend.label_text_color = INK_SOFT\np.legend.label_text_font_size = \"34pt\"\np.legend.padding = 20\np.legend.spacing = 12\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome via Selenium — window size matches the\n# figure exactly, and the CDP viewport override pins innerHeight to avoid\n# the phantom chrome-bar shortfall that clipped the x-axis in earlier renders.\nW, H = 3200, 1800\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}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}