Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions plots/swarm-basic/implementations/julia/makie.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# anyplot.ai
# swarm-basic: Basic Swarm Plot
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 91/100 | Created: 2026-07-26

using CairoMakie
using Colors
using Random
using Statistics

Random.seed!(42)

# --- Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome") ----
const THEME = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"

# Imprint categorical palette — first 4 positions used, one per department
const IMPRINT_PALETTE = [
colorant"#009E73", # 1 — brand green (always first series)
colorant"#C475FD", # 2 — lavender
colorant"#4467A3", # 3 — blue
colorant"#BD8233", # 4 — ochre
]

# --- Data ---------------------------------------------------------------------
# Employee performance scores by department
departments = ["Engineering", "Sales", "Support", "Marketing"]
group_sizes = [45, 52, 38, 47]
group_means = [78.0, 71.0, 82.0, 75.0]
group_stds = [7.0, 11.0, 5.5, 9.0]

# Swarm layout: greedy nearest-free-slot placement so points spread
# horizontally (category axis) without overlapping, while keeping the
# true value on the vertical axis. Rectangular collision check (separate
# x/y thresholds) since the two axes carry different units.
min_dist_y = 1.1 # vertical threshold below which points compete for space (score points)
step_x = 0.055 # horizontal offset increment (category-axis units)

swarm_x = Float64[]
swarm_y = Float64[]
swarm_color = eltype(IMPRINT_PALETTE)[]
group_medians = Float64[]
max_offset = 0.0

for (group_index, (department, n, group_mean, group_std)) in
enumerate(zip(departments, group_sizes, group_means, group_stds))
scores = clamp.(randn(n) .* group_std .+ group_mean, 0.0, 100.0)
order = sortperm(scores)
placed_offsets = Float64[]
placed_scores = Float64[]
offsets = zeros(n)

for i in order
score = scores[i]
level = 0
chosen_offset = 0.0
while true
candidates = level == 0 ? (0.0,) : (level * step_x, -level * step_x)
free_slot = 0.0
found = false
for candidate in candidates
conflict = false
for j in eachindex(placed_scores)
if abs(placed_scores[j] - score) < min_dist_y &&
abs(placed_offsets[j] - candidate) < step_x
conflict = true
break
end
end
if !conflict
free_slot = candidate
found = true
break
end
end
if found
chosen_offset = free_slot
break
end
level += 1
end
offsets[i] = chosen_offset
push!(placed_offsets, chosen_offset)
push!(placed_scores, score)
end

append!(swarm_x, group_index .+ offsets)
append!(swarm_y, scores)
append!(swarm_color, fill(IMPRINT_PALETTE[group_index], n))
push!(group_medians, median(scores))
global max_offset = max(max_offset, maximum(abs, offsets))
end

# Headline comparison for the subtitle + callout: the department with the
# highest median score drives the data story beyond the per-group medians.
best_idx = argmax(group_medians)
subtitle_text = "$(departments[best_idx]) leads with the highest median score " *
"($(round(Int, group_medians[best_idx])))"

# --- Plot -----------------------------------------------------------------------
fig = Figure(
resolution = (1600, 900),
fontsize = 14,
backgroundcolor = PAGE_BG,
)

ax = Axis(
fig[1, 1];
title = "swarm-basic · julia · makie · anyplot.ai",
titlesize = 20,
titlecolor = INK,
subtitle = subtitle_text,
subtitlesize = 14,
subtitlecolor = INK_SOFT,
xlabel = "Department",
ylabel = "Performance Score (0–100)",
xlabelsize = 16,
ylabelsize = 16,
xlabelcolor = INK,
ylabelcolor = INK,
xticklabelsize = 13,
yticklabelsize = 13,
xticklabelcolor = INK_SOFT,
yticklabelcolor = INK_SOFT,
xtickcolor = INK_SOFT,
ytickcolor = INK_SOFT,
backgroundcolor = PAGE_BG,
topspinevisible = false,
rightspinevisible = false,
leftspinecolor = INK_SOFT,
bottomspinecolor = INK_SOFT,
xgridvisible = false,
ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),
yminorgridvisible = false,
xticks = (1:length(departments), departments),
)

scatter!(ax, swarm_x, swarm_y;
color = swarm_color, markersize = 11,
strokewidth = 0.75, strokecolor = INK)

# Median marker per department; the top performer gets a bolder line so the
# subtitle's callout has a visible anchor on the chart.
for (group_index, group_median) in enumerate(group_medians)
lines!(ax, [group_index - 0.32, group_index + 0.32], [group_median, group_median];
color = INK, linewidth = group_index == best_idx ? 3.5 : 2.5)
end

# Symmetric padding derived from the widest swarm actually placed, so the
# canvas margins stay balanced regardless of how far any one department spreads.
pad = max_offset + 0.12
xlims!(ax, 1 - pad, length(departments) + pad)

# --- Save -------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)
233 changes: 233 additions & 0 deletions plots/swarm-basic/metadata/julia/makie.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
library: makie
language: julia
specification_id: swarm-basic
created: '2026-07-26T07:19:24Z'
updated: '2026-07-26T07:33:48Z'
generated_by: claude-sonnet
workflow_run: 30192426062
issue: 974
language_version: 1.11.9
library_version: 0.21.9
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/julia/makie/plot-light.png
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/julia/makie/plot-dark.png
preview_html_light: null
preview_html_dark: null
quality_score: 91
review:
strengths:
- Correct Imprint palette with brand green first series, verified identical across
both themes
- Theme-adaptive chrome fully correct in both light and dark renders — no legibility
failures
- 'Sound engineering for a library gap: Makie has no native swarm/beeswarm geom,
so the greedy nearest-free-slot collision algorithm is a legitimate, well-commented
solution rather than a workaround'
- Subtitle explicitly ties the data story to the visual (bolded median line for
the leading department)
- Realistic, well-varied data across all four departments (different means, spreads,
and outliers)
weaknesses:
- LM-02 is low — the implementation doesn't lean on anything distinctive to Makie
beyond colorant/RGBAf tokens; consider a Makie-specific touch (e.g. a Legend block,
a custom recipe, or GeometryBasics-based marker shapes) to raise library mastery
- Busiest swarm bands (Sales ~76-81, Marketing ~74-79) read slightly dense — a marginal
reduction in markersize or increase in step_x/min_dist_y would improve point-level
visibility without changing the overall shape
- DE-01/DE-02 are solid but not yet publication-tier — a touch more restraint on
y-axis padding and a slightly stronger visual differentiator for the winning department
(beyond line-width) would push this further
image_description: |-
Light render (plot-light.png):
Background: Warm off-white, consistent with #FAF8F1 — not pure white, not dark.
Chrome: Bold centered title "swarm-basic · julia · makie · anyplot.ai" in dark ink; soft-ink subtitle "Support leads with the highest median score (83)"; axis titles "Department" / "Performance Score (0-100)" and tick labels all in dark/soft-ink tones, fully readable. Subtle horizontal-only gridlines every 10 units.
Data: Four beeswarm columns — Engineering=#009E73 (first series, brand green confirmed), Sales=#C475FD lavender, Support=#4467A3 blue, Marketing=#BD8233 ochre. Solid median line per department, bolder for Support (the leader).
Legibility verdict: PASS

Dark render (plot-dark.png):
Background: Warm near-black, consistent with #1A1A17 — not pure black, not light.
Chrome: Title, subtitle, axis titles, and tick labels flip to light/soft-light ink and remain fully legible; gridlines and median lines switch to light tone appropriate for dark surface. No dark-on-dark failures found.
Data: Colors identical to light render (#009E73/#C475FD/#4467A3/#BD8233) — only chrome flipped, confirmed by direct comparison.
Legibility verdict: PASS
criteria_checklist:
visual_quality:
score: 29
max: 30
items:
- id: VQ-01
name: Text Legibility
score: 8
max: 8
passed: true
comment: All font sizes explicit, readable in both themes, no overflow
- id: VQ-02
name: No Overlap
score: 6
max: 6
passed: true
comment: No text collisions
- id: VQ-03
name: Element Visibility
score: 5
max: 6
passed: true
comment: Collision-avoidance layout works, but busiest bands read visually
dense
- id: VQ-04
name: Color Accessibility
score: 2
max: 2
passed: true
comment: Four hues, good luminance separation, no red-green-only signal
- id: VQ-05
name: Layout & Canvas
score: 4
max: 4
passed: true
comment: Plot fills majority of canvas, balanced margins
- id: VQ-06
name: Axis Labels & Title
score: 2
max: 2
passed: true
comment: Descriptive labels with scale range
- id: VQ-07
name: Palette Compliance
score: 2
max: 2
passed: true
comment: 'First series #009E73, canonical order, theme-correct chrome in both
renders'
design_excellence:
score: 16
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
score: 6
max: 8
passed: true
comment: Custom palette, intentional hierarchy, above defaults, short of publication-tier
- id: DE-02
name: Visual Refinement
score: 5
max: 6
passed: true
comment: Spines removed, subtle grid, generous whitespace
- id: DE-03
name: Data Storytelling
score: 5
max: 6
passed: true
comment: Subtitle callout + bolder median line create a clear focal point
spec_compliance:
score: 15
max: 15
items:
- id: SC-01
name: Plot Type
score: 5
max: 5
passed: true
comment: Genuine hand-rolled beeswarm layout
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
comment: Consistent sizes, median marker, color-coded categories, clear spacing
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
comment: Category on x, value on y, correct
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
comment: Title format exact; no separate legend needed
data_quality:
score: 15
max: 15
items:
- id: DQ-01
name: Feature Coverage
score: 6
max: 6
passed: true
comment: Distinct means/spreads/outliers across departments
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
comment: Neutral business scenario
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
comment: 0-100 scale, plausible department differences
code_quality:
score: 10
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: Linear structure, no functions/classes
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
comment: Random.seed!(42)
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
comment: All imports used
- id: CQ-04
name: Code Elegance
score: 2
max: 2
passed: true
comment: Well-scoped custom collision algorithm, no fake functionality
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
comment: Correct save call, current API
library_mastery:
score: 6
max: 10
items:
- id: LM-01
name: Idiomatic Usage
score: 4
max: 5
passed: true
comment: Clean high-level Figure/Axis/scatter! usage
- id: LM-02
name: Distinctive Features
score: 2
max: 5
passed: false
comment: Generic hand-rolled swarm algorithm, no Makie-distinctive features
verdict: APPROVED
impl_tags:
dependencies: []
techniques:
- manual-ticks
patterns:
- data-generation
- iteration-over-groups
dataprep: []
styling:
- edge-highlighting
- grid-styling
Loading