{"spec_id":"swarm-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nswarm-basic: Basic Swarm Plot\nLibrary: plotly 6.9.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-07-26\n\"\"\"\n\nimport sys\n\n\n# Remove the script directory from sys.path so this file (plotly.py) does not\n# shadow the installed plotly package.\nsys.path.pop(0)\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nGRID = \"rgba(26, 26, 23, 0.15)\" if THEME == \"light\" else \"rgba(240, 239, 232, 0.15)\"\n\n# Imprint categorical palette — first series always #009E73\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\nIMPRINT_RGB = [(0, 158, 115), (196, 117, 253), (68, 103, 163), (189, 130, 51)]\n\n# Data - student test scores across 4 classrooms with varied distributions\nnp.random.seed(42)\nclassrooms = [\"Room A\", \"Room B\", \"Room C\", \"Room D\"]\n\nscores_a = np.concatenate([np.random.normal(75, 8, 35), np.random.normal(90, 5, 10)])\nscores_b = np.random.normal(68, 12, 50)\nscores_c = np.concatenate([np.random.normal(60, 6, 20), np.random.normal(82, 6, 25)])\nscores_d = np.random.normal(78, 6, 40)\n\nall_scores = [scores_a, scores_b, scores_c, scores_d]\n\n# Layout geometry (pre-scale coordinate space, matches write_image width=800/\n# height=450 below; scale=4 is applied uniformly so pixel ratios are unaffected).\nCANVAS_W, CANVAS_H = 800, 450\nMARGIN = {\"l\": 70, \"r\": 90, \"t\": 70, \"b\": 55}\nPLOT_W_PX = CANVAS_W - MARGIN[\"l\"] - MARGIN[\"r\"]\nPLOT_H_PX = CANVAS_H - MARGIN[\"t\"] - MARGIN[\"b\"]\n\nX_RANGE = (-0.6, len(classrooms) - 1 + 0.6)\nY_RANGE = (30, 110)\nX_SCALE = PLOT_W_PX / (X_RANGE[1] - X_RANGE[0])  # px per 1 x-data-unit\nY_SCALE = PLOT_H_PX / (Y_RANGE[1] - Y_RANGE[0])  # px per 1 y-data-unit\n\nMARKER_SIZE = 9\nSEP_PX = MARKER_SIZE * 1.2  # min center-to-center pixel distance -> no overlap\nMAX_OFFSET = 0.42  # data-units; keeps swarm clear of the neighboring category\n\n\ndef swarm_offsets(y_values, x_scale, y_scale, sep_px, max_offset):\n    \"\"\"Greedy beeswarm packing: place each point (sorted by y) at the offset\n    closest to the category center whose rendered marker circle does not\n    intersect any already-placed marker's circle, in true pixel space.\"\"\"\n    n = len(y_values)\n    offsets = np.zeros(n)\n    order = np.argsort(y_values)\n    step = (sep_px / 6.0) / x_scale\n    max_steps = int(np.ceil(max_offset / step)) + 1\n\n    placed = []  # (offset, y)\n    for idx in order:\n        y = y_values[idx]\n        chosen = None\n        for k in range(max_steps + 1):\n            for cand in [0.0] if k == 0 else [k * step, -k * step]:\n                if abs(cand) > max_offset:\n                    continue\n                collides = False\n                for off, py in placed:\n                    dx_px = (cand - off) * x_scale\n                    dy_px = (y - py) * y_scale\n                    if dx_px * dx_px + dy_px * dy_px < sep_px * sep_px:\n                        collides = True\n                        break\n                if not collides:\n                    chosen = cand\n                    break\n            if chosen is not None:\n                break\n        if chosen is None:\n            chosen = max_offset if (len(placed) % 2 == 0) else -max_offset\n        placed.append((chosen, y))\n        offsets[idx] = chosen\n    return offsets\n\n\n# Plot — go.Box (fully transparent fill, faint outline, no points) is kept as\n# a de-emphasized quartile/spread guide; the actual beeswarm points are a\n# separate go.Scatter trace whose x-offsets are computed by swarm_offsets()\n# so marker circles never intersect, instead of relying on go.Box's\n# uniform-random jitter.\nfig = go.Figure()\n\nfor i, (classroom, scores) in enumerate(zip(classrooms, all_scores, strict=False)):\n    color = IMPRINT[i]\n    r, g, b = IMPRINT_RGB[i]\n\n    fig.add_trace(\n        go.Box(\n            y=scores,\n            x0=i,\n            width=0.3,\n            name=classroom,\n            boxpoints=False,\n            line={\"color\": f\"rgba({r}, {g}, {b}, 0.35)\", \"width\": 1},\n            fillcolor=\"rgba(0, 0, 0, 0)\",\n            whiskerwidth=0.3,\n            boxmean=False,\n            hoverinfo=\"skip\",\n        )\n    )\n\n    offsets = swarm_offsets(scores, X_SCALE, Y_SCALE, SEP_PX, MAX_OFFSET)\n    fig.add_trace(\n        go.Scatter(\n            x=i + offsets,\n            y=scores,\n            mode=\"markers\",\n            marker={\"color\": color, \"size\": MARKER_SIZE, \"opacity\": 0.8, \"line\": {\"width\": 1.2, \"color\": PAGE_BG}},\n            showlegend=False,\n            hovertemplate=f\"{classroom}<br>Score: %{{y:.1f}}<extra></extra>\",\n        )\n    )\n    fig.add_trace(\n        go.Scatter(\n            x=[i],\n            y=[float(np.mean(scores))],\n            mode=\"markers\",\n            marker={\"symbol\": \"diamond\", \"size\": 13, \"color\": color, \"line\": {\"width\": 1.5, \"color\": PAGE_BG}},\n            showlegend=False,\n            hovertemplate=f\"{classroom} mean<br>Score: %{{y:.1f}}<extra></extra>\",\n        )\n    )\n\n# Annotate Room C's bimodal shape — its most analytically interesting feature\nfig.add_annotation(\n    x=2,\n    y=71,\n    text=\"Bimodal: two<br>skill clusters\",\n    showarrow=True,\n    arrowhead=2,\n    arrowcolor=INK_SOFT,\n    ax=55,\n    ay=-10,\n    font={\"size\": 10, \"color\": INK},\n    bgcolor=ELEVATED_BG,\n    bordercolor=INK_SOFT,\n    borderwidth=1,\n)\n\n# Layout\nfig.update_layout(\n    autosize=False,\n    width=CANVAS_W,\n    height=CANVAS_H,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    title={\n        \"text\": \"swarm-basic · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 16, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Classroom\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"tickvals\": list(range(len(classrooms))),\n        \"ticktext\": classrooms,\n        \"range\": list(X_RANGE),\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zeroline\": False,\n        \"showgrid\": False,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Test Score (points)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": GRID,\n        \"range\": list(Y_RANGE),\n    },\n    legend={\"bgcolor\": ELEVATED_BG, \"bordercolor\": INK_SOFT, \"borderwidth\": 1, \"font\": {\"size\": 10, \"color\": INK_SOFT}},\n    showlegend=True,\n    margin=MARGIN,\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=CANVAS_W, height=CANVAS_H, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}