{"spec_id":"swarm-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# swarm-basic: Basic Swarm Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-07-26\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Statistics\n\nRandom.seed!(42)\n\n# --- Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\") ----\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Imprint categorical palette — first 4 positions used, one per department\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green (always first series)\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n    colorant\"#BD8233\",  # 4 — ochre\n]\n\n# --- Data ---------------------------------------------------------------------\n# Employee performance scores by department\ndepartments = [\"Engineering\", \"Sales\", \"Support\", \"Marketing\"]\ngroup_sizes = [45, 52, 38, 47]\ngroup_means = [78.0, 71.0, 82.0, 75.0]\ngroup_stds  = [7.0, 11.0, 5.5, 9.0]\n\n# Swarm layout: greedy nearest-free-slot placement so points spread\n# horizontally (category axis) without overlapping, while keeping the\n# true value on the vertical axis. Rectangular collision check (separate\n# x/y thresholds) since the two axes carry different units.\nmin_dist_y = 1.1   # vertical threshold below which points compete for space (score points)\nstep_x     = 0.055 # horizontal offset increment (category-axis units)\n\nswarm_x = Float64[]\nswarm_y = Float64[]\nswarm_color = eltype(IMPRINT_PALETTE)[]\ngroup_medians = Float64[]\nmax_offset = 0.0\n\nfor (group_index, (department, n, group_mean, group_std)) in\n        enumerate(zip(departments, group_sizes, group_means, group_stds))\n    scores = clamp.(randn(n) .* group_std .+ group_mean, 0.0, 100.0)\n    order = sortperm(scores)\n    placed_offsets = Float64[]\n    placed_scores = Float64[]\n    offsets = zeros(n)\n\n    for i in order\n        score = scores[i]\n        level = 0\n        chosen_offset = 0.0\n        while true\n            candidates = level == 0 ? (0.0,) : (level * step_x, -level * step_x)\n            free_slot = 0.0\n            found = false\n            for candidate in candidates\n                conflict = false\n                for j in eachindex(placed_scores)\n                    if abs(placed_scores[j] - score) < min_dist_y &&\n                       abs(placed_offsets[j] - candidate) < step_x\n                        conflict = true\n                        break\n                    end\n                end\n                if !conflict\n                    free_slot = candidate\n                    found = true\n                    break\n                end\n            end\n            if found\n                chosen_offset = free_slot\n                break\n            end\n            level += 1\n        end\n        offsets[i] = chosen_offset\n        push!(placed_offsets, chosen_offset)\n        push!(placed_scores, score)\n    end\n\n    append!(swarm_x, group_index .+ offsets)\n    append!(swarm_y, scores)\n    append!(swarm_color, fill(IMPRINT_PALETTE[group_index], n))\n    push!(group_medians, median(scores))\n    global max_offset = max(max_offset, maximum(abs, offsets))\nend\n\n# Headline comparison for the subtitle + callout: the department with the\n# highest median score drives the data story beyond the per-group medians.\nbest_idx = argmax(group_medians)\nsubtitle_text = \"$(departments[best_idx]) leads with the highest median score \" *\n                \"($(round(Int, group_medians[best_idx])))\"\n\n# --- Plot -----------------------------------------------------------------------\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = \"swarm-basic · julia · makie · anyplot.ai\",\n    titlesize         = 20,\n    titlecolor        = INK,\n    subtitle          = subtitle_text,\n    subtitlesize      = 14,\n    subtitlecolor     = INK_SOFT,\n    xlabel            = \"Department\",\n    ylabel            = \"Performance Score (0–100)\",\n    xlabelsize        = 16,\n    ylabelsize        = 16,\n    xlabelcolor       = INK,\n    ylabelcolor       = INK,\n    xticklabelsize    = 13,\n    yticklabelsize    = 13,\n    xticklabelcolor   = INK_SOFT,\n    yticklabelcolor   = INK_SOFT,\n    xtickcolor        = INK_SOFT,\n    ytickcolor        = INK_SOFT,\n    backgroundcolor   = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    yminorgridvisible  = false,\n    xticks             = (1:length(departments), departments),\n)\n\nscatter!(ax, swarm_x, swarm_y;\n         color = swarm_color, markersize = 11,\n         strokewidth = 0.75, strokecolor = INK)\n\n# Median marker per department; the top performer gets a bolder line so the\n# subtitle's callout has a visible anchor on the chart.\nfor (group_index, group_median) in enumerate(group_medians)\n    lines!(ax, [group_index - 0.32, group_index + 0.32], [group_median, group_median];\n           color = INK, linewidth = group_index == best_idx ? 3.5 : 2.5)\nend\n\n# Symmetric padding derived from the widest swarm actually placed, so the\n# canvas margins stay balanced regardless of how far any one department spreads.\npad = max_offset + 0.12\nxlims!(ax, 1 - pad, length(departments) + pad)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}