{"spec_id":"swarm-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nswarm-basic: Basic Swarm Plot\nLibrary: matplotlib 3.11.1 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-07-26\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\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\n# Imprint palette — 4 departments\nCOLORS = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Employee performance scores by department\nnp.random.seed(42)\n\ndepartments = [\"Engineering\", \"Sales\", \"Marketing\", \"Support\"]\nn_points = [50, 45, 40, 55]\n\nscores_data = {\n    \"Engineering\": np.clip(np.random.normal(78, 12, n_points[0]), 0, 100),\n    \"Sales\": np.clip(np.random.normal(72, 15, n_points[1]), 0, 100),\n    \"Marketing\": np.clip(np.random.normal(82, 10, n_points[2]), 0, 100),\n    \"Support\": np.clip(np.random.normal(68, 14, n_points[3]), 0, 100),\n}\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nax.set_xlabel(\"Department\", fontsize=10, color=INK)\nax.set_ylabel(\"Performance Score (0-100)\", fontsize=10, color=INK)\nax.set_title(\"swarm-basic · matplotlib · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.set_xticks(range(len(departments)))\nax.set_xticklabels(departments, fontsize=8)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_ylim(25, 132)\nax.set_yticks(np.arange(30, 101, 10))\nax.set_xlim(-0.6, 3.6)\n\nax.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK)\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor spine in (\"left\", \"bottom\"):\n    ax.spines[spine].set_color(INK_SOFT)\n\n# Finalize the layout before computing swarm offsets: title/labels/ticks are\n# already set, so the axes box below is the real one (the in-plot legend\n# added after data plotting sits inside it and doesn't shift it further).\nplt.tight_layout()\nfig.canvas.draw()\n\n# Pixel-per-data-unit scale factors from the actual transData transform.\n# Category slots (x) and 0-100 scores (y) live on very different scales, so\n# collision testing must compare real on-screen distances, not raw\n# data-space deltas mixing incompatible units.\norigin_px = ax.transData.transform((0, 0))\nx_unit_px = ax.transData.transform((1, 0))[0] - origin_px[0]\ny_unit_px = ax.transData.transform((0, 1))[1] - origin_px[1]\n\nMARKER_SIZE = 90  # scatter `s` (points^2 area) for individual swarm points\nMEAN_SIZE = 350\nmarker_diameter_px = 2 * np.sqrt(MARKER_SIZE / np.pi) * (fig.dpi / 72)\nmin_gap_px = marker_diameter_px * 1.05\nmax_offset = 0.45\n\n# Candidate x-offsets, nearest-to-center first: 0, then +/-step at\n# increasing radius up to max_offset.\nsteps = np.linspace(max_offset / 50, max_offset, 50)\ncandidate_offsets = np.concatenate([[0.0], np.column_stack([steps, -steps]).ravel()])\n\nfor i, dept in enumerate(departments):\n    vals = scores_data[dept]\n    sorted_idx = np.argsort(vals)\n    offsets = np.zeros(len(vals))\n    placed_offsets = np.array([])\n    placed_vals = np.array([])\n\n    for idx in sorted_idx:\n        val = vals[idx]\n        best_offset, best_dist = 0.0, -np.inf\n\n        for test_x in candidate_offsets:\n            if placed_offsets.size:\n                dist = np.hypot((test_x - placed_offsets) * x_unit_px, (val - placed_vals) * y_unit_px).min()\n            else:\n                dist = np.inf\n\n            if dist > best_dist:\n                best_offset, best_dist = test_x, dist\n            if dist >= min_gap_px:\n                break\n\n        offsets[idx] = best_offset\n        placed_offsets = np.append(placed_offsets, best_offset)\n        placed_vals = np.append(placed_vals, val)\n\n    ax.scatter(\n        i + offsets, vals, s=MARKER_SIZE, alpha=0.75, color=COLORS[i], edgecolors=PAGE_BG, linewidth=0.5, label=dept\n    )\n\n    mean_val = np.mean(vals)\n    ax.scatter(i, mean_val, s=MEAN_SIZE, color=COLORS[i], marker=\"D\", edgecolors=INK_SOFT, linewidth=2, zorder=5)\n\n# Invisible mean-marker entry for legend (smaller than the on-plot marker so it doesn't crowd the legend rows)\nax.scatter([], [], s=100, color=INK_SOFT, marker=\"D\", edgecolors=INK_SOFT, linewidth=1, label=\"Mean\")\n\nleg = ax.legend(fontsize=8, loc=\"upper right\", framealpha=0.9)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nplt.setp(leg.get_texts(), color=INK_SOFT)\n\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}