{"spec_id":"errorbar-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nerrorbar-basic: Basic Error Bar Plot\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 92/100 | Updated: 2026-06-30\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Remove script dir from sys.path so 'bokeh.py' doesn't shadow the installed bokeh package\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p != _here]\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, Label, TeeHead, Whisker\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens\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# Imprint categorical palette (positions 1-6)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\"]\n\n# Data — experimental measurements with associated uncertainties\nnp.random.seed(42)\ncategories = [\"Control\", \"Treatment A\", \"Treatment B\", \"Treatment C\", \"Treatment D\", \"Treatment E\"]\nmeans = np.array([25.3, 38.7, 42.1, 35.8, 48.2, 31.5])\n\n# Asymmetric errors — Treatment C has highest variability, Treatment D has highest mean\nlower_errors = np.array([2.1, 3.5, 2.8, 6.5, 4.8, 2.5])\nupper_errors = np.array([2.1, 3.5, 2.8, 2.8, 2.2, 2.5])\n\nupper = means + upper_errors\nlower = means - lower_errors\ncolors = IMPRINT[: len(categories)]\n\nsource = ColumnDataSource(\n    data={\"categories\": categories, \"means\": means, \"upper\": upper, \"lower\": lower, \"colors\": colors}\n)\n\n# Canvas: 3200×1800 (landscape) — hard rule, no deviation\nW, H = 3200, 1800\n\np = figure(\n    width=W,\n    height=H,\n    x_range=categories,\n    title=\"errorbar-basic · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Experimental Group\",\n    y_axis_label=\"Response Value (units)\",\n    toolbar_location=None,\n    tools=\"\",\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\n# Error bars (Whisker with TeeHead caps) — one whisker per group so each can take its own color\nfor cat, up, lo, col in zip(categories, upper, lower, colors, strict=True):\n    grp_source = ColumnDataSource(data={\"x\": [cat], \"upper\": [up], \"lower\": [lo]})\n    whisker = Whisker(\n        base=\"x\",\n        upper=\"upper\",\n        lower=\"lower\",\n        source=grp_source,\n        line_color=col,\n        line_width=5,\n        upper_head=TeeHead(size=40, line_color=col, line_width=5),\n        lower_head=TeeHead(size=40, line_color=col, line_width=5),\n    )\n    p.add_layout(whisker)\n\n# Mean markers — colored per group\np.scatter(x=\"categories\", y=\"means\", source=source, size=28, color=\"colors\", line_color=PAGE_BG, line_width=2)\n\n# Annotation: highlight highest variability (Treatment C, index 3)\nfocus_idx = int(np.argmax(lower_errors + upper_errors))\nfocus_label = Label(\n    x=focus_idx,\n    y=float(lower[focus_idx]),\n    x_units=\"data\",\n    y_units=\"data\",\n    x_offset=24,\n    y_offset=-12,\n    text=\"highest variability\",\n    text_color=INK_MUTED,\n    text_font_size=\"20pt\",\n    text_font_style=\"italic\",\n)\np.add_layout(focus_label)\n\n# Style — background and outline\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\n# Title\np.title.text_color = INK\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"normal\"\np.title.align = \"left\"\n\n# Axis labels\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_font_style = \"normal\"\np.yaxis.axis_label_text_font_style = \"normal\"\n\n# Tick labels\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\n\n# Axis lines and ticks\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.xaxis.minor_tick_line_color = None\np.yaxis.minor_tick_line_color = None\n\n# Subtle y-grid only\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.15\np.xgrid.grid_line_color = None\n\n# Y-range trimmed to data — eliminates dead space below\ny_min = float(min(lower))\ny_max = float(max(upper))\ny_pad = (y_max - y_min) * 0.15\np.y_range.start = max(0.0, y_min - y_pad)\np.y_range.end = y_max + y_pad\n\n# Save HTML (required interactive artifact)\noutput_file(f\"plot-{THEME}.html\", title=\"errorbar-basic · python · bokeh · anyplot.ai\")\nsave(p)\n\n# Screenshot with headless Chrome via Selenium — export_png uses chromedriver snap shim which fails\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)\n# Use CDP to set exact viewport dimensions (--window-size alone can be 100-150px short due to browser chrome)\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"}