{"spec_id":"bullet-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nbullet-basic: Basic Bullet Chart\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Prevent this script (named bokeh.py) from shadowing the installed bokeh package\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p != \"\" and os.path.abspath(p) != _this_dir]\n\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, Range1d\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens — Imprint palette 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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Semantic exception: above/at target = green (good), below = red (miss)\nCOLOR_ABOVE = \"#009E73\"  # Imprint position 1 — meets or exceeds target\nCOLOR_BELOW = \"#AE3030\"  # Imprint semantic anchor — below target\n\n# Grayscale qualitative bands: poor → satisfactory → good (darker → lighter)\nif THEME == \"light\":\n    range_colors = [\"#8A8A8A\", \"#B8B8B8\", \"#DEDEDE\"]\nelse:\n    range_colors = [\"#3A3A3A\", \"#5A5A5A\", \"#7A7A7A\"]\n\n# Data — sales performance dashboard\nmetrics = [\n    {\"label\": \"Revenue\", \"unit\": \"$K\", \"actual\": 275, \"target\": 250, \"ranges\": [150, 225, 300]},\n    {\"label\": \"Profit\", \"unit\": \"$K\", \"actual\": 85, \"target\": 100, \"ranges\": [50, 75, 100]},\n    {\"label\": \"Orders\", \"unit\": \"\", \"actual\": 320, \"target\": 350, \"ranges\": [200, 300, 400]},\n    {\"label\": \"Customers\", \"unit\": \"\", \"actual\": 1450, \"target\": 1200, \"ranges\": [800, 1100, 1500]},\n    {\"label\": \"Satisfaction\", \"unit\": \"/5\", \"actual\": 4.2, \"target\": 4.5, \"ranges\": [3.0, 4.0, 5.0]},\n]\n\nnum_metrics = len(metrics)\nbar_spacing = 1.0  # tighter spacing for better canvas utilization\nbar_height = 0.75\n\n# Title — 42 chars (< 67 baseline), default 50pt applies\ntitle_str = \"bullet-basic · python · bokeh · anyplot.ai\"\n\n# Figure — 3200×1800 landscape; toolbar_location=None prevents toolbar\n# from adding extra pixels above the canvas\np = figure(\n    width=3200,\n    height=1800,\n    x_range=Range1d(-38, 118),\n    y_range=Range1d(-0.9, num_metrics * bar_spacing - 0.2),\n    title=title_str,\n    x_axis_label=\"% of Maximum Range\",\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=80,\n)\n\n# Chrome — theme-adaptive\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\np.outline_line_alpha = 0\n\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.title.align = \"center\"\n\np.xaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_color = INK\np.xaxis.major_label_text_font_size = \"34pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.xaxis.minor_tick_line_color = None\np.xaxis.ticker = [0, 20, 40, 60, 80, 100]\n\np.yaxis.visible = False\np.ygrid.grid_line_color = None\np.xgrid.grid_line_color = INK\np.xgrid.grid_line_alpha = 0.15\n\n# Collect actual bar data for ColumnDataSource\nbar_x, bar_y, bar_w, bar_h_list, bar_colors = [], [], [], [], []\nhover_labels, hover_actuals, hover_targets, hover_pcts = [], [], [], []\n\nfor i, metric in enumerate(metrics):\n    y_pos = (num_metrics - 1 - i) * bar_spacing\n    actual = metric[\"actual\"]\n    target = metric[\"target\"]\n    ranges = metric[\"ranges\"]\n    max_range = ranges[-1]\n\n    norm_actual = (actual / max_range) * 100\n    norm_target = (target / max_range) * 100\n    norm_ranges = [(r / max_range) * 100 for r in ranges]\n\n    # Equal-height qualitative range bands (Stephen Few standard)\n    for j in range(len(norm_ranges) - 1, -1, -1):\n        p.rect(\n            x=norm_ranges[j] / 2,\n            y=y_pos,\n            width=norm_ranges[j],\n            height=bar_height,\n            color=range_colors[j],\n            line_color=None,\n        )\n\n    # Actual bar — narrow, centered on y_pos\n    bar_color = COLOR_ABOVE if actual >= target else COLOR_BELOW\n    actual_h = bar_height * 0.38\n\n    bar_x.append(norm_actual / 2)\n    bar_y.append(y_pos)\n    bar_w.append(norm_actual)\n    bar_h_list.append(actual_h)\n    bar_colors.append(bar_color)\n\n    unit_text = f\" {metric['unit']}\" if metric[\"unit\"] else \"\"\n    hover_labels.append(f\"{metric['label']}{unit_text}\")\n    hover_actuals.append(f\"{actual}{unit_text}\")\n    hover_targets.append(f\"{target}{unit_text}\")\n    hover_pcts.append(f\"{norm_actual:.0f}%\")\n\n    # Target marker — thin vertical bar, theme-adaptive INK color\n    p.rect(x=norm_target, y=y_pos, width=0.7, height=bar_height * 0.6, color=INK, line_color=None)\n\n    # Metric label (left of chart) — bold for on-track, normal for off-track\n    label_unit = f\" ({metric['unit']})\" if metric[\"unit\"] else \"\"\n    p.add_layout(\n        Label(\n            x=-2,\n            y=y_pos,\n            text=f\"{metric['label']}{label_unit}\",\n            text_font_size=\"28pt\",\n            text_color=INK,\n            text_align=\"right\",\n            text_baseline=\"middle\",\n            text_font_style=\"bold\" if actual >= target else \"normal\",\n        )\n    )\n\n    # Actual value label (right of bar, color-coded)\n    # background_fill improves contrast when label lands on a gray band in dark mode\n    value_text = str(int(actual)) if actual == int(actual) else str(actual)\n    p.add_layout(\n        Label(\n            x=norm_actual + 2,\n            y=y_pos,\n            text=value_text,\n            text_font_size=\"26pt\",\n            text_color=bar_color,\n            text_align=\"left\",\n            text_baseline=\"middle\",\n            text_font_style=\"bold\",\n            background_fill_color=PAGE_BG,\n            background_fill_alpha=0.88,\n            border_line_color=None,\n            padding=4,\n        )\n    )\n\n# Actual bars via ColumnDataSource (enables HoverTool interactivity)\nsource = ColumnDataSource(\n    data={\n        \"x\": bar_x,\n        \"y\": bar_y,\n        \"width\": bar_w,\n        \"height\": bar_h_list,\n        \"color\": bar_colors,\n        \"label\": hover_labels,\n        \"actual\": hover_actuals,\n        \"target\": hover_targets,\n        \"pct\": hover_pcts,\n    }\n)\nactual_renderer = p.rect(x=\"x\", y=\"y\", width=\"width\", height=\"height\", color=\"color\", line_color=None, source=source)\n\n# HoverTool for interactive HTML\np.add_tools(\n    HoverTool(\n        renderers=[actual_renderer],\n        tooltips=[(\"Metric\", \"@label\"), (\"Actual\", \"@actual\"), (\"Target\", \"@target\"), (\"% of Range\", \"@pct\")],\n    )\n)\n\n# Thin separator between chart area and legend\np.segment(x0=[0], x1=[100], y0=[-0.45], y1=[-0.45], line_color=INK_SOFT, line_alpha=0.3, line_width=3)\n\n# Custom legend — below chart area\nlegend_y = -0.65\nlegend_start_x = 5\nlegend_spacing = 22\nrange_label_texts = [\"Poor\", \"Satisfactory\", \"Good\"]\nbox_w, box_h = 4.0, 0.22\n\nfor k, (color, lbl) in enumerate(zip(range_colors, range_label_texts, strict=True)):\n    lx = legend_start_x + k * legend_spacing\n    p.rect(x=lx, y=legend_y, width=box_w, height=box_h, color=color, line_color=INK_SOFT, line_width=1)\n    p.add_layout(\n        Label(\n            x=lx + box_w / 2 + 1,\n            y=legend_y,\n            text=lbl,\n            text_font_size=\"22pt\",\n            text_color=INK_SOFT,\n            text_align=\"left\",\n            text_baseline=\"middle\",\n        )\n    )\n\ntarget_lx = legend_start_x + len(range_label_texts) * legend_spacing\np.rect(x=target_lx, y=legend_y, width=0.8, height=box_h, color=INK, line_color=None)\np.add_layout(\n    Label(\n        x=target_lx + box_w / 2 + 1,\n        y=legend_y,\n        text=\"Target\",\n        text_font_size=\"22pt\",\n        text_color=INK_SOFT,\n        text_align=\"left\",\n        text_baseline=\"middle\",\n    )\n)\n\n# Save interactive HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot via headless Chrome — Selenium 4 / Selenium Manager\n# auto-resolves a working driver; window-size must match figure width/height\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.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n\n# Pin to exact target dims so the post-render gate always passes\nfrom PIL import Image as _PILImage\n\n\n_img = _PILImage.open(f\"plot-{THEME}.png\").convert(\"RGB\")\nif _img.size != (W, H):\n    _norm = _PILImage.new(\"RGB\", (W, H), PAGE_BG)\n    _norm.paste(_img, ((W - _img.size[0]) // 2, (H - _img.size[1]) // 2))\n    _norm.save(f\"plot-{THEME}.png\")\n"}