{"spec_id":"strip-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nstrip-basic: Basic Strip Plot\nLibrary: bokeh 3.9.2 | Python 3.13.14\nQuality: 94/100 | Updated: 2026-08-05\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import BoxAnnotation, ColumnDataSource, Label\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\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Survey response scores by department\nnp.random.seed(42)\n\ncategories = [\"Engineering\", \"Marketing\", \"Sales\", \"HR\"]\nn_per_category = [45, 38, 52, 30]\n\ndata = {\n    \"Engineering\": np.clip(np.random.normal(7.2, 1.5, n_per_category[0]), 1, 10),\n    \"Marketing\": np.clip(np.random.normal(6.8, 1.8, n_per_category[1]), 1, 10),\n    \"Sales\": np.clip(np.random.normal(7.5, 1.2, n_per_category[2]), 1, 10),\n    \"HR\": np.clip(np.random.normal(8.0, 1.0, n_per_category[3]), 1, 10),\n}\n\n# Build arrays for plotting with jitter\nx_values = []\ny_values = []\ncolors = []\ncolor_map = dict(zip(categories, IMPRINT_PALETTE, strict=True))\n\njitter_width = 0.25\n\nfor i, cat in enumerate(categories):\n    values = data[cat]\n    n = len(values)\n    jittered_x = i + np.random.uniform(-jitter_width, jitter_width, n)\n    x_values.extend(jittered_x)\n    y_values.extend(values)\n    colors.extend([color_map[cat]] * n)\n\nsource = ColumnDataSource(data={\"x\": x_values, \"y\": y_values, \"color\": colors})\n\n# Plot — 3200x1800 canonical canvas; toolbar disabled (static catalog render)\np = figure(\n    width=3200,\n    height=1800,\n    title=\"strip-basic · bokeh · anyplot.ai\",\n    x_axis_label=\"Department\",\n    y_axis_label=\"Survey Score (1–10)\",\n    x_range=(-0.5, len(categories) - 0.5),\n    y_range=(0, 11),\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\n# Data storytelling emphasis — HR has the highest mean and tightest spread\nhr_index = categories.index(\"HR\")\nhr_mean = float(np.mean(data[\"HR\"]))\np.add_layout(\n    BoxAnnotation(\n        left=hr_index - 0.45, right=hr_index + 0.45, fill_color=color_map[\"HR\"], fill_alpha=0.08, line_color=None\n    )\n)\np.add_layout(\n    Label(\n        x=hr_index,\n        y=hr_mean + 1.6,\n        text=\"Highest morale,\\ntightest spread\",\n        text_align=\"center\",\n        text_font_size=\"24pt\",\n        text_color=INK_SOFT,\n        background_fill_color=ELEVATED_BG,\n        background_fill_alpha=0.85,\n        border_line_color=INK_SOFT,\n    )\n)\n\np.scatter(x=\"x\", y=\"y\", source=source, size=20, color=\"color\", alpha=0.6, line_color=PAGE_BG, line_width=1.5)\n\n# Mean reference lines — one legend entry shared across all categories\nfor i, cat in enumerate(categories):\n    mean_val = float(np.mean(data[cat]))\n    legend_kw = {\"legend_label\": \"Group Mean\"} if i == 0 else {}\n    p.line(x=[i - 0.35, i + 0.35], y=[mean_val, mean_val], line_color=INK_SOFT, line_width=4, **legend_kw)\n\n# Text sizes for 3200x1800 px (bokeh 'pt' strings rendered through headless Chrome)\np.title.text_font_size = \"50pt\"\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# Categorical tick labels on x-axis\np.xaxis.ticker = list(range(len(categories)))\np.xaxis.major_label_overrides = dict(enumerate(categories))\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\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.axis_line_width = 2\np.yaxis.axis_line_width = 2\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\np.xgrid.grid_line_color = None\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.10\n\nif p.legend:\n    p.legend.background_fill_color = ELEVATED_BG\n    p.legend.border_line_color = INK_SOFT\n    p.legend.label_text_color = INK_SOFT\n    p.legend.label_text_font_size = \"34pt\"\n    p.legend.location = \"top_right\"\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome — window size must match the figure's 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.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\n# Headless Chrome's --window-size sets the OUTER window, which still reserves a\n# phantom title-bar height even headless — pin the viewport exactly via CDP.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}