{"spec_id":"swarm-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nswarm-basic: Basic Swarm Plot\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-07-26\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\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 = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Employee performance scores across departments\nnp.random.seed(42)\n\ndepartments = [\"Engineering\", \"Marketing\", \"Sales\", \"HR\"]\nn_per_dept = [45, 38, 52, 35]\n\ndata = []\nfor dept, n in zip(departments, n_per_dept, strict=True):\n    if dept == \"Engineering\":\n        scores = np.random.normal(78, 8, n)\n    elif dept == \"Marketing\":\n        scores = np.random.normal(72, 12, n)\n    elif dept == \"Sales\":\n        scores = np.concatenate([np.random.normal(65, 6, n // 2), np.random.normal(82, 5, n - n // 2)])\n    else:  # HR\n        scores = np.concatenate([np.random.normal(68, 9, n - 3), np.array([45, 92, 95])])\n    scores = np.clip(scores, 30, 100)\n    for score in scores:\n        data.append({\"Department\": dept, \"Performance Score\": score})\n\ndf = pd.DataFrame(data)\n\n# Numeric x position per department, with jitter drawn from the same seeded\n# generator as the scores above — deterministic across renders, unlike\n# Vega-Lite's in-chart random() which reseeds on every render.\ndept_positions = {dept: i for i, dept in enumerate(departments)}\ndf[\"x_pos\"] = df[\"Department\"].map(dept_positions)\ndf[\"x_jitter\"] = df[\"x_pos\"] + np.random.uniform(-0.28, 0.28, len(df))\n\n# Calculate means\nmeans = df.groupby(\"Department\")[\"Performance Score\"].mean().reset_index()\nmeans[\"x_pos\"] = means[\"Department\"].map(dept_positions)\n\n# Flag outliers (>2 std from the department mean) for a hierarchy-emphasis layer\ndf[\"dept_mean\"] = df[\"Department\"].map(means.set_index(\"Department\")[\"Performance Score\"])\ndf[\"dept_std\"] = df[\"Department\"].map(df.groupby(\"Department\")[\"Performance Score\"].std())\noutliers = df[(df[\"Performance Score\"] - df[\"dept_mean\"]).abs() > 2 * df[\"dept_std\"]]\n\n# Swarm points — jitter precomputed in pandas (see above) for reproducibility.\n# A thin background-matching stroke cuts a halo around each circle so dense\n# clusters (Sales, Engineering) stay individually distinguishable.\nswarm = (\n    alt.Chart(df)\n    .mark_circle(size=140, opacity=0.75, stroke=PAGE_BG, strokeWidth=0.6)\n    .encode(\n        x=alt.X(\n            \"x_jitter:Q\",\n            scale=alt.Scale(domain=[-0.65, 3.65]),\n            axis=alt.Axis(\n                values=list(range(4)),\n                labelExpr=\"['Engineering', 'Marketing', 'Sales', 'HR'][datum.value]\",\n                title=\"Department\",\n                grid=False,\n                labelAngle=0,\n            ),\n        ),\n        y=alt.Y(\"Performance Score:Q\", scale=alt.Scale(domain=[30, 100])),\n        color=alt.Color(\n            \"Department:N\", scale=alt.Scale(domain=departments, range=IMPRINT), legend=alt.Legend(orient=\"right\")\n        ),\n        tooltip=[\"Department\", \"Performance Score\"],\n    )\n)\n\n# Mean diamond markers (theme-adaptive color)\nmean_markers = (\n    alt.Chart(means)\n    .mark_point(shape=\"diamond\", size=260, filled=True, color=INK, strokeWidth=1.5)\n    .encode(\n        x=\"x_pos:Q\",\n        y=\"Performance Score:Q\",\n        tooltip=[alt.Tooltip(\"Department\"), alt.Tooltip(\"Performance Score:Q\", title=\"Mean\", format=\".1f\")],\n    )\n)\n\n# Mean reference lines (theme-adaptive color)\nmean_lines = (\n    alt.Chart(means)\n    .mark_rule(color=INK, strokeWidth=1.5, strokeDash=[4, 4])\n    .encode(x=alt.X(\"x_start:Q\"), x2=\"x_end:Q\", y=\"Performance Score:Q\")\n    .transform_calculate(x_start=\"datum.x_pos - 0.35\", x_end=\"datum.x_pos + 0.35\")\n)\n\n# Outlier rings — unfilled ink-stroke circles emphasize the notable\n# out-of-band observations (e.g. the HR 45/92/95 points) as a hierarchy cue\noutlier_rings = (\n    alt.Chart(outliers)\n    .mark_point(shape=\"circle\", size=220, filled=False, stroke=INK, strokeWidth=1.5)\n    .encode(x=\"x_jitter:Q\", y=\"Performance Score:Q\")\n)\n\n# Compose and apply theme-adaptive chrome\nchart = (\n    (swarm + mean_lines + outlier_rings + mean_markers)\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(\"swarm-basic · altair · anyplot.ai\", fontSize=16, anchor=\"middle\"),\n    )\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.12,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n    )\n    .configure_view(fill=PAGE_BG, stroke=None)\n    .configure_title(color=INK)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=10,\n    )\n)\n\n# Save — hard target 3200x1800 (landscape). vl-convert pads the small inner\n# view with title/axis/legend extents, then we pad-only to the exact target.\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(f\"altair vl-convert produced {_w}x{_h}, exceeds target {TW}x{TH}. Shrink chart dims.\")\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}