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
110 changes: 110 additions & 0 deletions plots/swarm-basic/implementations/r/ggplot2.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#' anyplot.ai
#' swarm-basic: Basic Swarm Plot
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 84/100 | Created: 2026-07-26

library(ggplot2)
library(dplyr)
library(ragg)
library(scales)

set.seed(42)

# --- Theme tokens ------------------------------------------------------------
THEME <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
IMPRINT_PALETTE <- c("#009E73", "#C475FD", "#4467A3", "#BD8233",
"#AE3030", "#2ABCCD", "#954477", "#99B314")

# --- Data ----------------------------------------------------------------
# Reaction times across a cognitive-load experiment (psychology research)
conditions <- c("Baseline", "Low Load", "High Load", "Time Pressure")
means <- c(420, 460, 540, 580)
sds <- c(45, 50, 65, 70)
n_per_condition <- 45

reaction_df <- bind_rows(lapply(seq_along(conditions), function(i) {
tibble::tibble(
condition = conditions[i],
reaction_time = rnorm(n_per_condition, mean = means[i], sd = sds[i])
)
})) %>%
mutate(condition = factor(condition, levels = conditions))

# --- Swarm layout ----------------------------------------------------------
# Bin observations along the value axis, then rank within each bin so
# points that fall close in value are spread symmetrically around the
# category's center line - a lightweight beeswarm approximation built
# entirely from geom_point (ggplot2 has no native beeswarm geom).
bin_width <- diff(range(reaction_df$reaction_time)) / 24
point_spacing <- 0.05
max_offset <- 0.42

reaction_df <- reaction_df %>%
mutate(value_bin = round(reaction_time / bin_width)) %>%
group_by(condition, value_bin) %>%
mutate(
bin_rank = rank(reaction_time, ties.method = "first"),
bin_n = n(),
x_offset = pmin(pmax((bin_rank - (bin_n + 1) / 2) * point_spacing, -max_offset), max_offset)
) %>%
ungroup() %>%
mutate(condition_x = as.numeric(condition) + x_offset)

condition_means <- reaction_df %>%
group_by(condition) %>%
summarise(mean_rt = mean(reaction_time), x_pos = as.numeric(condition[1]), .groups = "drop")

# --- Plot ------------------------------------------------------------------
p <- ggplot(reaction_df, aes(x = condition_x, y = reaction_time)) +
geom_point(
aes(fill = condition), shape = 21, size = 3.1, stroke = 0.3,
color = PAGE_BG, alpha = 0.85
) +
geom_line(
data = condition_means,
aes(x = x_pos, y = mean_rt),
color = scales::alpha(INK, 0.4), linewidth = 0.5, linetype = "dashed"
) +
geom_point(
data = condition_means,
aes(x = x_pos, y = mean_rt),
shape = 18, size = 3.4, color = INK, alpha = 0.9
) +
scale_fill_manual(values = IMPRINT_PALETTE[seq_along(conditions)], guide = "none") +
scale_x_continuous(
breaks = seq_along(conditions),
labels = conditions,
expand = expansion(mult = c(0.1, 0.1))
) +
labs(
title = "swarm-basic · r · ggplot2 · anyplot.ai",
x = "Experimental Condition",
y = "Reaction Time (ms)"
) +
theme_minimal(base_size = 8) +
theme(
plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),
panel.background = element_rect(fill = PAGE_BG, color = NA),
panel.grid.major.x = element_blank(),
panel.grid.minor = element_blank(),
panel.grid.major.y = element_line(color = scales::alpha(INK, 0.15), linewidth = 0.3),
axis.title = element_text(color = INK, size = 10),
axis.text = element_text(color = INK_SOFT, size = 8),
axis.ticks = element_blank(),
axis.line = element_line(color = INK_SOFT, linewidth = 0.3),
plot.title = element_text(color = INK, size = 12)
)

# --- Save --------------------------------------------------------------
ggsave(
filename = sprintf("plot-%s.png", THEME),
plot = p,
device = ragg::agg_png,
width = 8,
height = 4.5,
units = "in",
dpi = 400
)
256 changes: 256 additions & 0 deletions plots/swarm-basic/metadata/r/ggplot2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
library: ggplot2
language: r
specification_id: swarm-basic
created: '2026-07-26T07:17:07Z'
updated: '2026-07-26T07:31:37Z'
generated_by: claude-sonnet
workflow_run: 30192366615
issue: 974
language_version: 4.4.1
library_version: 3.5.1
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/r/ggplot2/plot-light.png
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/swarm-basic/r/ggplot2/plot-dark.png
preview_html_light: null
preview_html_dark: null
quality_score: 84
review:
strengths:
- Clever custom beeswarm layout built entirely from geom_point (bin the value axis,
then rank and symmetrically offset points within each bin) since ggplot2 has no
native beeswarm geom — a genuine library-mastery technique.
- 'Imprint palette applied correctly in canonical order with first series #009E73;
theme-adaptive chrome (background, ink, grid) flips correctly between light and
dark while data colors stay identical.'
- 'Strong spec compliance: correct title format, descriptive axis labels with units,
per-category mean markers, clear spacing between category groups, and a realistic
psychology reaction-time dataset with plausible escalating means across cognitive-load
conditions.'
- Correct 3200×1800 canvas, theme_minimal with spines/heavy grid stripped, generous
whitespace, and explicit font sizes throughout.
weaknesses:
- The dashed mean-trend connector (geom_line over condition_means, color = scales::alpha(INK,
0.4)) does not render at all in either theme — pixel-level inspection of both
PNGs found zero line pixels anywhere between the four mean-diamond markers, only
the diamonds themselves are visible. This is dead/non-functional code; either
fix the rendering (verify the 8-digit alpha hex string is honoured by the ragg
device, check the layer isn't being clipped or dropped) or remove the geom_line
layer entirely since the diamond markers alone already satisfy the spec's 'subtle
mean marker' note.
- Points overlap fairly densely in the Low Load / High Load / Time Pressure clusters
at fill alpha 0.85, making some individual observations hard to distinguish at
the busiest bins — slightly smaller point_spacing increments or a touch more alpha
reduction would improve separation.
- No legend for the categorical fill (guide = "none") — acceptable here since x-axis
tick labels already identify each category, but worth confirming this is the intended
encoding rather than leftover from copying a legend-bearing pattern.
image_description: |-
Light render (plot-light.png):
Background: Warm off-white, matches #FAF8F1 target, not pure white.
Chrome: Title "swarm-basic · r · ggplot2 · anyplot.ai" top-left in dark ink; axis titles "Experimental Condition" (x) and "Reaction Time (ms)" (y) in dark ink; tick labels in soft grey-ink; subtle horizontal gridlines only. All clearly readable against the light background.
Data: Four category swarms (Baseline, Low Load, High Load, Time Pressure) as filled circles with light-background-colored strokes, colors #009E73 (green), #C475FD (lavender), #4467A3 (blue), #BD8233 (ochre) in canonical Imprint order, first series correctly green. A dark diamond mean-marker sits at the center of each swarm. No dashed connector line between the diamonds is visible despite being present in the code (see weaknesses).
Legibility verdict: PASS

Dark render (plot-dark.png):
Background: Warm near-black, matches #1A1A17 target, not pure black.
Chrome: Same title and axis titles now in light ink (#F0EFE8), tick labels in lighter soft-ink (#B8B7B0), gridlines subtle against the dark background. No dark-on-dark text anywhere — all chrome is clearly legible.
Data: Identical swatch of four colors (#009E73, #C475FD, #4467A3, #BD8233) to the light render, confirming data colors are unchanged between themes; mean diamonds now render as light markers against the dark background (theme-correctly flipped from dark in light mode). Same missing connector line as in the light render.
Legibility verdict: PASS
criteria_checklist:
visual_quality:
score: 24
max: 30
items:
- id: VQ-01
name: Text Legibility
score: 7
max: 8
passed: true
comment: Explicit font sizes throughout; all text readable in both themes,
no dark-on-dark or light-on-light.
- id: VQ-02
name: No Overlap
score: 4
max: 6
passed: true
comment: No text collisions; data points overlap noticeably in the denser
High Load / Time Pressure clusters.
- id: VQ-03
name: Element Visibility
score: 3
max: 6
passed: false
comment: Swarm markers and mean diamonds are clearly visible, but the intended
dashed mean-trend connector line does not render at all (confirmed via pixel
inspection of both renders).
- id: VQ-04
name: Color Accessibility
score: 2
max: 2
passed: true
comment: Imprint palette hues are distinct and CVD-safe; no red-green as sole
signal.
- id: VQ-05
name: Layout & Canvas
score: 4
max: 4
passed: true
comment: Canvas gate passed (3200x1800); title and axis proportions balanced,
nothing cut off.
- id: VQ-06
name: Axis Labels & Title
score: 2
max: 2
passed: true
comment: Descriptive axis labels with units (ms).
- id: VQ-07
name: Palette Compliance
score: 2
max: 2
passed: true
comment: 'First series #009E73, canonical Imprint order, correct theme-adaptive
backgrounds in both renders.'
design_excellence:
score: 14
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
score: 6
max: 8
passed: true
comment: Custom beeswarm binning algorithm and polished diamond mean markers
show real design effort.
- id: DE-02
name: Visual Refinement
score: 5
max: 6
passed: true
comment: Spines removed, grid limited to subtle horizontal lines, generous
whitespace.
- id: DE-03
name: Data Storytelling
score: 3
max: 6
passed: false
comment: Escalating trend across conditions is visible from swarm position/color
alone, but the intended trend-connector guide line is invisible, weakening
the storytelling device.
spec_compliance:
score: 15
max: 15
items:
- id: SC-01
name: Plot Type
score: 5
max: 5
passed: true
comment: Correct swarm/beeswarm plot via custom binned-offset layout.
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
comment: Per-category mean markers, color-coded categories, clear group spacing,
appropriately sized points.
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
comment: Category on x, reaction time on y, correct and complete.
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
comment: Title matches mandated format; legend intentionally omitted since
categories are already labeled on the x-axis.
data_quality:
score: 14
max: 15
items:
- id: DQ-01
name: Feature Coverage
score: 5
max: 6
passed: true
comment: Shows full distribution shape, density, and an outlier per condition.
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
comment: Cognitive-load reaction-time experiment is realistic and neutral.
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
comment: 300-700ms range and escalating means are sensible for psychology
RT data.
code_quality:
score: 9
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: No functions/classes, straightforward pipeline.
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
comment: set.seed(42) used.
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
comment: ggplot2, dplyr, ragg, scales all actually used.
- id: CQ-04
name: Code Elegance
score: 1
max: 2
passed: false
comment: Non-functional geom_line layer that renders nothing is dead code.
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
comment: Saves plot-{THEME}.png via ragg::agg_png.
library_mastery:
score: 8
max: 10
items:
- id: LM-01
name: Idiomatic Usage
score: 4
max: 5
passed: true
comment: Idiomatic dplyr + ggplot2 grammar composition.
- id: LM-02
name: Distinctive Features
score: 4
max: 5
passed: true
comment: Manual bin-and-rank beeswarm approximation is a distinctive technique
given ggplot2 has no native beeswarm geom.
verdict: APPROVED
impl_tags:
dependencies: []
techniques:
- manual-ticks
- layer-composition
patterns:
- data-generation
- groupby-aggregation
- iteration-over-groups
dataprep:
- binning
styling:
- alpha-blending
- edge-highlighting
Loading