{"spec_id":"swarm-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nswarm-basic: Basic Swarm Plot\nLibrary: bokeh 3.9.2 | Python 3.13.14\nQuality: 94/100 | Updated: 2026-07-26\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, HoverTool, 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\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — first series always #009E73\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data — employee performance scores by department\nnp.random.seed(42)\n\ndepartments = [\"Engineering\", \"Marketing\", \"Sales\", \"HR\"]\nn_per_group = [45, 38, 52, 35]\n\ncategories = []\nvalues = []\n\nfor dept, n in zip(departments, n_per_group, strict=False):\n    categories.extend([dept] * n)\n    if dept == \"Engineering\":\n        scores = np.random.normal(82, 8, n)\n    elif dept == \"Marketing\":\n        scores = np.random.normal(75, 12, n)\n    elif dept == \"Sales\":\n        scores = np.concatenate([np.random.normal(65, 8, n // 2), np.random.normal(88, 6, n - n // 2)])\n    else:  # HR\n        scores = np.random.normal(78, 10, n)\n        scores[0] = 45\n        scores[1] = 98\n    values.extend(np.clip(scores, 30, 100))\n\nvalues = np.array(values)\ncategories = np.array(categories)\n\n# Canvas geometry (must match the figure() call below) - used to convert\n# the swarm dodge into real screen-pixel distances.\nCANVAS_W, CANVAS_H = 3200, 1800\nBORDER_L, BORDER_R, BORDER_T, BORDER_B = 180, 50, 110, 160\nX_RANGE = (-0.6, len(departments) - 0.4)\nY_RANGE = (25, 108)\nPX_PER_X = (CANVAS_W - BORDER_L - BORDER_R) / (X_RANGE[1] - X_RANGE[0])\nPX_PER_Y = (CANVAS_H - BORDER_T - BORDER_B) / (Y_RANGE[1] - Y_RANGE[0])\n\nMARKER_SIZE = 12\nMIN_GAP_PX = MARKER_SIZE + 1  # marker diameter + a hairline so edges never touch\nMAX_OFFSET = 0.42  # stays clear of the neighboring category's column\n\n\ndef swarm_dodge(dept_values, px_per_x, px_per_y, min_gap_px, max_offset):\n    \"\"\"Greedy incremental beeswarm: points are placed lowest-to-highest value,\n    and each one claims the offset closest to zero whose pixel-space distance\n    clears every already-placed point in the category. Unlike a density-window\n    heuristic with a hard cap, this checks against ALL prior points, so no two\n    points ever end up within one marker-width of each other.\"\"\"\n    order = np.argsort(dept_values)\n    offsets = np.zeros(len(dept_values))\n    placed = []  # (offset, value) of points already positioned\n    step = min_gap_px / px_per_x\n\n    for idx in order:\n        y = dept_values[idx]\n        k = 0\n        chosen = None\n        while chosen is None:\n            candidates = [0.0] if k == 0 else [k * step, -k * step]\n            for c in candidates:\n                if abs(c) > max_offset:\n                    continue\n                if all((c - ox) ** 2 * px_per_x**2 + (y - oy) ** 2 * px_per_y**2 >= min_gap_px**2 for ox, oy in placed):\n                    chosen = c\n                    break\n            if chosen is None:\n                k += 1\n                if k * step > max_offset:\n                    # Column is denser than min_gap_px allows within max_offset -\n                    # fall back to the farthest allowed offset, alternating sides.\n                    chosen = max_offset if len(placed) % 2 == 0 else -max_offset\n        offsets[idx] = chosen\n        placed.append((chosen, y))\n    return offsets\n\n\nx_jitter = np.zeros(len(values))\nfor dept in departments:\n    mask = categories == dept\n    x_jitter[mask] = swarm_dodge(values[mask], PX_PER_X, PX_PER_Y, MIN_GAP_PX, MAX_OFFSET)\n\nx_positions = np.array([departments.index(cat) + x_jitter[i] for i, cat in enumerate(categories)])\n\ncolor_map = {dept: IMPRINT[i] for i, dept in enumerate(departments)}\ncolors = [color_map[cat] for cat in categories]\n\n# Plot\nsource = ColumnDataSource(data={\"x\": x_positions, \"y\": values, \"category\": categories, \"color\": colors})\n\nhover = HoverTool(tooltips=[(\"Department\", \"@category\"), (\"Score\", \"@y{0.0}\")])\n\np = figure(\n    width=CANVAS_W,\n    height=CANVAS_H,\n    title=\"swarm-basic · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Department\",\n    y_axis_label=\"Performance Score\",\n    x_range=X_RANGE,\n    y_range=Y_RANGE,\n    tools=[hover],\n    toolbar_location=None,\n    min_border_bottom=BORDER_B,\n    min_border_left=BORDER_L,\n    min_border_top=BORDER_T,\n    min_border_right=BORDER_R,\n)\n\np.scatter(x=\"x\", y=\"y\", source=source, size=MARKER_SIZE, color=\"color\", alpha=0.75, line_color=PAGE_BG, line_width=1.2)\n\n# Median markers for each category\nfor i, dept in enumerate(departments):\n    mask = categories == dept\n    median_val = np.median(values[mask])\n    p.line(x=[i - 0.32, i + 0.32], y=[median_val, median_val], line_width=4, line_color=INK, line_alpha=0.65)\n\n# Data-storytelling callouts: highlight the bimodal Sales distribution (found\n# via the largest gap between sorted values, not a hardcoded threshold) and\n# label the two HR outliers - the most visually interesting features in the\n# dataset. BoxAnnotation is a bokeh-distinctive annotation, not a generic\n# scatter/hover feature every interactive library shares.\nsales_idx = departments.index(\"Sales\")\nsales_sorted = np.sort(values[categories == \"Sales\"])\nsplit = np.argmax(np.diff(sales_sorted))\ngap_bottom, gap_top = sales_sorted[split], sales_sorted[split + 1]\np.add_layout(\n    BoxAnnotation(\n        left=sales_idx - 0.42,\n        right=sales_idx + 0.42,\n        bottom=gap_bottom,\n        top=gap_top,\n        fill_color=INK,\n        fill_alpha=0.06,\n        line_color=INK_SOFT,\n        line_alpha=0.4,\n        line_dash=\"dashed\",\n    )\n)\np.add_layout(\n    Label(\n        x=sales_idx,\n        y=105,\n        text=\"Bimodal distribution\",\n        text_align=\"center\",\n        text_font_size=\"26pt\",\n        text_font_style=\"italic\",\n        text_color=INK_SOFT,\n    )\n)\n\nhr_idx = departments.index(\"HR\")\nhr_values = values[categories == \"HR\"]\nlo_val, hi_val = hr_values.min(), hr_values.max()\np.add_layout(\n    Label(\n        x=hr_idx,\n        y=hi_val + 2.5,\n        text=\"outlier\",\n        text_align=\"center\",\n        text_baseline=\"bottom\",\n        text_font_size=\"24pt\",\n        text_font_style=\"italic\",\n        text_color=INK_SOFT,\n    )\n)\np.add_layout(\n    Label(\n        x=hr_idx,\n        y=lo_val - 2.5,\n        text=\"outlier\",\n        text_align=\"center\",\n        text_baseline=\"top\",\n        text_font_size=\"24pt\",\n        text_font_style=\"italic\",\n        text_color=INK_SOFT,\n    )\n)\n\n# X-axis category labels\np.xaxis.ticker = list(range(len(departments)))\np.xaxis.major_label_overrides = dict(enumerate(departments))\n\n# Style — theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\np.title.text_color = INK\np.title.text_font_size = \"50pt\"\np.title.align = \"center\"\n\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\"\n\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\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\n\np.xgrid.visible = False\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.10\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome (Selenium 4 / Selenium Manager)\nW, H = CANVAS_W, CANVAS_H\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# Pin viewport exactly via CDP — headless --window-size still reserves a\n# phantom title-bar height, which would otherwise shrink the screenshot below H.\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"}