{"spec_id":"box-grouped","library":"makie","language":"julia","code":"# anyplot.ai\n# box-grouped: Grouped Box Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 93/100 | Created: 2026-08-18\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 — 8 hues, theme-independent, hybrid-v3 sort\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n]\n\n# Data — quarterly performance review scores by department and experience level\nconst DEPARTMENTS = [\"Engineering\", \"Sales\", \"Marketing\", \"Support\"]\nconst EXPERIENCE_LEVELS = [\"Junior\", \"Mid-Level\", \"Senior\"]\nconst DEPT_BASELINE = [72.0, 68.0, 70.0, 65.0]\nconst EXPERIENCE_BONUS = [0.0, 7.0, 15.0]\nconst N_PER_GROUP = 45\n\ncategory_idx = Int[]\nsubgroup_idx = Int[]\nperformance_scores = Float64[]\n\nfor (ci, baseline) in enumerate(DEPT_BASELINE)\n    for (si, bonus) in enumerate(EXPERIENCE_BONUS)\n        scores = clamp.(baseline .+ bonus .+ randn(N_PER_GROUP) .* 7.0, 20.0, 100.0)\n        append!(performance_scores, scores)\n        append!(category_idx, fill(ci, N_PER_GROUP))\n        append!(subgroup_idx, fill(si, N_PER_GROUP))\n    end\nend\n\npoint_colors = IMPRINT_PALETTE[subgroup_idx]\n\n# Storytelling focal point — find the department where the Senior/Junior\n# experience-tier gap is widest, to call it out with a comparison bracket.\nconst BOX_WIDTH     = 0.7\nconst BOX_GAP       = 0.3\nconst BOX_DODGE_GAP = 0.05\nconst N_DODGE       = length(EXPERIENCE_LEVELS)\n\n# Mirrors Makie's internal `compute_x_and_width` dodge formula so the bracket\n# lines up exactly with the boxplot's dodged box positions.\nfunction dodge_x(category, subgroup_i)\n    eff_width   = BOX_WIDTH * (1 - BOX_GAP)\n    dodge_width = (1 - (N_DODGE - 1) * BOX_DODGE_GAP) / N_DODGE\n    shift       = (dodge_width - 1) / 2 + (subgroup_i - 1) * (dodge_width + BOX_DODGE_GAP)\n    return category + eff_width * shift\nend\n\ndept_gaps = [\n    median(performance_scores[(category_idx .== ci) .& (subgroup_idx .== 3)]) -\n    median(performance_scores[(category_idx .== ci) .& (subgroup_idx .== 1)])\n    for ci in 1:length(DEPARTMENTS)\n]\nstandout_ci  = argmax(dept_gaps)\nstandout_gap = dept_gaps[standout_ci]\nbracket_x1   = dodge_x(standout_ci, 1)  # Junior box\nbracket_x2   = dodge_x(standout_ci, 3)  # Senior box\nbracket_y    = maximum(performance_scores[category_idx .== standout_ci]) + 4.0\n\n# Plot — see default-style-guide.md \"Visual Sizing Defaults\" for canvas + sizing values\ntitle_str = \"box-grouped · julia · makie · anyplot.ai\"\n\nfig = Figure(\n    resolution      = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\nax = Axis(\n    fig[1, 1];\n    title             = title_str,\n    titlesize         = 20,\n    titlecolor        = INK,\n    ylabel            = \"Performance Score (0-100)\",\n    ylabelsize        = 14,\n    ylabelcolor       = INK,\n    xticks            = (1:length(DEPARTMENTS), DEPARTMENTS),\n    xticklabelsize    = 12,\n    yticklabelsize    = 12,\n    xticklabelcolor   = INK_SOFT,\n    yticklabelcolor   = 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)\n\nboxplot!(\n    ax, category_idx, performance_scores;\n    dodge         = subgroup_idx,\n    color         = point_colors,\n    width         = 0.7,\n    dodge_gap     = 0.05,\n    gap           = 0.3,\n    strokecolor   = INK,\n    strokewidth   = 1,\n    mediancolor   = INK,\n    whiskercolor  = INK_SOFT,\n    markersize    = 6,\n)\n\nbracket!(\n    ax,\n    bracket_x1, bracket_y, bracket_x2, bracket_y;\n    text        = \"$(DEPARTMENTS[standout_ci]): Senior +$(round(Int, standout_gap)) vs Junior\",\n    offset      = 6,\n    width       = 10,\n    orientation = :up,\n    color       = INK_SOFT,\n    textcolor   = INK,\n    fontsize    = 13,\n)\nylims!(ax, minimum(performance_scores) - 5.0, maximum(performance_scores) + 16.0)\n\nlegend_elements = [PolyElement(color = c, strokecolor = :transparent) for c in IMPRINT_PALETTE]\nLegend(\n    fig[1, 2], legend_elements, EXPERIENCE_LEVELS, \"Experience Level\";\n    framevisible = false,\n    labelcolor   = INK,\n    titlecolor   = INK,\n    labelsize    = 12,\n    titlesize    = 12,\n)\ncolsize!(fig.layout, 2, Relative(0.14))\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}