diff --git a/plots/quiver-basic/implementations/r/ggplot2.R b/plots/quiver-basic/implementations/r/ggplot2.R new file mode 100644 index 0000000000..e7f5e46cac --- /dev/null +++ b/plots/quiver-basic/implementations/r/ggplot2.R @@ -0,0 +1,117 @@ +#' anyplot.ai +#' quiver-basic: Basic Quiver Plot +#' Library: ggplot2 3.5.1 | R 4.4.1 +#' Quality: 92/100 | Created: 2026-07-24 + +library(ggplot2) +library(dplyr) +library(tidyr) +library(ragg) + +set.seed(42) + +# --- Theme tokens ------------------------------------------------------- +THEME <- Sys.getenv("ANYPLOT_THEME", "light") +PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" +ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" +INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" +INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" + +# Imprint sequential cmap (single-polarity: wind speed magnitude) +IMPRINT_SEQ_LOW <- "#009E73" +IMPRINT_SEQ_HIGH <- "#4467A3" + +# --- Data: cyclonic wind field around a low-pressure eye ---------------- +# Rotational flow (u = -y, v = x) modulated by a radial Gaussian decay so +# speed peaks in a ring around the calm eye and fades toward the outskirts. +# WIND_SCALE converts the unitless rotation field into storm-force m/s so the +# "Cyclonic" framing matches the numbers on the legend (peak ~34 m/s, low-end +# Category 1 hurricane strength). +WIND_SCALE <- 12 +grid <- expand_grid( + x = seq(-8, 8, by = 1), + y = seq(-4.5, 4.5, by = 1) +) + +wind <- grid %>% + mutate( + radius = sqrt(x^2 + y^2), + decay = exp(-radius^2 / 40), + u = -y * decay * WIND_SCALE, + v = x * decay * WIND_SCALE, + speed = sqrt(u^2 + v^2) + ) + +# Scale arrow length so the fastest vector spans ~80% of the 1-unit grid +# spacing, keeping neighbouring arrows from overlapping. +arrow_scale <- 0.8 / max(wind$speed) +wind <- wind %>% + mutate( + xend = x + u * arrow_scale, + yend = y + v * arrow_scale + ) + +# Dashed radial guide marking the ring of peak tangential wind speed, i.e. +# the radius that maximises r * exp(-r^2/40) (found analytically at r = sqrt(20)). +eyewall_radius <- sqrt(20) +eyewall_theta <- seq(0, 2 * pi, length.out = 200) +eyewall <- tibble::tibble( + x = eyewall_radius * cos(eyewall_theta), + y = eyewall_radius * sin(eyewall_theta) +) + +# --- Plot ---------------------------------------------------------------- +p <- ggplot(wind, aes(x = x, y = y, xend = xend, yend = yend, color = speed)) + + geom_path( + data = eyewall, aes(x = x, y = y), inherit.aes = FALSE, + linetype = "dashed", linewidth = 0.4, color = INK_SOFT, alpha = 0.5 + ) + + geom_segment( + linewidth = 0.7, + arrow = arrow(length = unit(2, "mm"), type = "closed", angle = 20) + ) + + annotate( + "point", x = 0, y = 0, + shape = 21, size = 3, stroke = 1, color = INK_SOFT, fill = PAGE_BG + ) + + annotate( + "text", x = 0, y = -0.9, label = "eye", + color = INK_SOFT, size = 2.6, fontface = "italic" + ) + + coord_fixed(ratio = 1, expand = TRUE) + + scale_color_gradient( + low = IMPRINT_SEQ_LOW, high = IMPRINT_SEQ_HIGH, + name = "Wind speed (m/s)" + ) + + labs( + x = "Distance east of eye (km)", + y = "Distance north of eye (km)", + title = "Cyclonic Wind Field · quiver-basic · r · ggplot2 · anyplot.ai" + ) + + 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 = element_line(color = INK_SOFT, linewidth = 0.15, linetype = "dotted"), + panel.grid.minor = element_blank(), + axis.title = element_text(color = INK, size = 10), + axis.text = element_text(color = INK_SOFT, size = 8), + axis.ticks = element_blank(), + plot.title = element_text(color = INK, size = 12), + legend.background = element_rect(fill = ELEVATED_BG, color = NA), + legend.text = element_text(color = INK_SOFT, size = 8), + legend.title = element_text(color = INK, size = 10), + legend.key = element_rect(fill = PAGE_BG, color = NA), + plot.margin = margin(10, 14, 10, 10) + ) + +# --- Save ------------------------------------------------------------------ +ggsave( + filename = sprintf("plot-%s.png", THEME), + plot = p, + device = ragg::agg_png, + width = 8, + height = 4.5, + units = "in", + dpi = 400 +) diff --git a/plots/quiver-basic/metadata/r/ggplot2.yaml b/plots/quiver-basic/metadata/r/ggplot2.yaml new file mode 100644 index 0000000000..dbbb89063d --- /dev/null +++ b/plots/quiver-basic/metadata/r/ggplot2.yaml @@ -0,0 +1,257 @@ +library: ggplot2 +language: r +specification_id: quiver-basic +created: '2026-07-24T18:20:51Z' +updated: '2026-07-24T18:37:05Z' +generated_by: claude-sonnet +workflow_run: 30116255071 +issue: 1014 +language_version: 4.4.1 +library_version: 3.5.1 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/quiver-basic/r/ggplot2/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/quiver-basic/r/ggplot2/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: 92 +review: + strengths: + - Wind speed range now genuinely cyclone-force (~5-34 m/s, peaking right at Category + 1 hurricane strength) instead of the previous light-breeze scale, so the 'Cyclonic + Wind Field' title now matches the data (fixes prior DQ-03 weakness) + - New dashed eyewall guide ring at the radius of peak tangential speed plus an annotated + open-circle 'eye' marker give the plot a clear focal narrative device beyond color/length + redundancy alone (fixes prior DE-03 weakness) + - Faint dotted reference grid now helps read exact arrow positions across the 170-arrow + field without competing visually with the data (fixes prior DE-02 weakness) + - 'Correct grammar-of-graphics quiver approach: geom_segment() + arrow(type=''closed'') + with coord_fixed(ratio = 1) so directions are not visually distorted' + - Arrow direction AND magnitude are doubly encoded via length and imprint_seq color, + giving a clear visual gradient from calm eye to fast outer ring + - 170-arrow 17x10 grid sits squarely in the spec's recommended 100-400 arrow / 10x10-20x20 + range, uniformly spaced with no overlap (confirmed by close-up crop around the + eye marker on both renders) + - Theme-adaptive chrome correctly threaded through title/axis/tick/legend/grid colors + and both plot + legend backgrounds for light and dark renders + - 'Clean, reproducible, single-pass script: set.seed(42), only used imports (ggplot2, + dplyr, tidyr, ragg), no functions/classes, correct ragg::agg_png save at the exact + landscape canvas size (3200x1800)' + weaknesses: + - Still no library-distinctive technique beyond the baseline geom_segment+arrow()+coord_fixed + pattern for the core vector field itself — the new guide ring and eye marker add + storytelling but don't showcase a further ggplot2-specific quiver capability. + - 'Aesthetic sophistication is strong but not exceptional: the color+length redundant + encoding, eyewall ring, and eye annotation cover the main polish opportunities, + leaving little further headroom without added complexity.' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1-class), not pure white, not dark. + Chrome: Title "Cyclonic Wind Field · quiver-basic · r · ggplot2 · anyplot.ai" in dark ink, ~60% of plot width, comfortably sized and fully within the canvas; x-axis label "Distance east of eye (km)" and y-axis label "Distance north of eye (km)" in dark ink, both similarly sized and clearly legible; tick labels (-5, 0, 5 / -5.0 to 5.0) in a softer gray, all readable; a new faint dotted major grid (both axes) is visible without competing with the data; legend "Wind speed (m/s)" with a vertical color-bar and numeric ticks (10-30), housed in a subtly elevated off-white box. + Data: 170 arrows on a 17x10 grid form a clear rotational (cyclonic) pattern — tangential flow circling a calm "eye" near the origin, now marked with an open dark-ink circle and an italic "eye" label directly below it, plus a dashed light-gray eyewall ring at the radius of peak tangential speed. Arrow color runs from brand green (#009E73, slow, near the eye) to blue (~#4467A3, fast, outer ring, ~30-34 m/s), matching the imprint_seq sequential colormap — now genuinely cyclone-force rather than light-breeze speeds. Close-up inspection around the eye marker confirms no overlap between the circle, the "eye" label, the dashed ring, and the nearby arrows. + Legibility verdict: PASS — all text has strong contrast against the light background; no light-on-light issues. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17-class), not pure black, not light. + Chrome: Same title, axis labels, tick labels, and new dotted grid, now rendered in light ink (off-white for title/axis titles, softer light gray for tick labels and grid) — fully legible against the dark background, no dark-on-dark failures observed. Legend box uses a barely-lighter dark elevated fill with light text, also legible. + Data: Same green-to-blue gradient, same arrow geometry/positions, same eye marker (light-ink circle) and dashed eyewall ring as the light render — data colors and annotation geometry are identical between themes, confirming only chrome flipped. Close-up crop around the eye marker shows no overlap issues on this theme either. + Legibility verdict: PASS — title, axis labels, tick labels, grid, and legend text are all clearly readable against the dark surface; no black-on-black or washed-out text detected; brand green (#009E73) remains clearly visible near the eye. + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: Explicit font sizes throughout; all text (incl. new grid/eye label) + readable in both themes at full resolution + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: 170 uniformly spaced arrows with no collisions; eye marker + label + + eyewall ring do not overlap arrows (verified via close-up crop) + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Arrow length/color appropriately sized for medium-density 17x10 grid + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Sequential green-blue imprint_seq gradient is CVD-safe; no red-green + sole signal + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas gate passed (3200x1800); title ~60% width; coord_fixed keeps + proportions correct; nothing clipped + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Descriptive axis labels with units (km); legend labeled with units + (m/s) + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'imprint_seq (#009E73 -> #4467A3) used for continuous magnitude; + correct theme backgrounds both renders' + design_excellence: + score: 15 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Redundant length+color encoding plus the new eyewall guide ring and + eye annotation create noticeably more polish than the prior attempt + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: New faint dotted reference grid aids readability without adding clutter; + spines still removed, margins generous + - id: DE-03 + name: Data Storytelling + score: 5 + max: 6 + passed: true + comment: Eye marker + label + dashed eyewall ring now give an explicit focal + narrative device beyond the calm-eye vs. fast-ring color/length gradient + alone + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: geom_segment + arrow() is the correct/only idiomatic ggplot2 quiver + approach + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Uniform spacing, scaled arrow length, magnitude via color — all spec + notes covered + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: x/y grid positions correct; coord_fixed shows all data without distortion + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title matches mandated format exactly; legend labeled with units + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Position, direction, and magnitude (length + color) all shown + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Neutral meteorological framing (cyclonic wind field around a low-pressure + eye) + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Speeds now ~5-34 m/s, peaking at genuine Category-1-hurricane strength, + matching the 'Cyclonic' title framing — prior light-breeze mismatch fixed + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: No functions/classes, single linear pipeline + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: set.seed(42) present + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: ggplot2, dplyr, tidyr, ragg all used + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Appropriate complexity, no fake UI/interactivity, comments explain + non-obvious derivations (WIND_SCALE, eyewall radius) + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png via ragg::agg_png at correct canvas size + library_mastery: + score: 8 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: geom_segment+arrow() with coord_fixed is the correct idiomatic ggplot2 + pattern for vector fields + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: false + comment: Layering geom_path guide + dual annotate() calls over the quiver + shows more compositional skill than baseline, but no further library-distinctive + technique beyond that + verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - colorbar + - annotations + - layer-composition + patterns: + - data-generation + dataprep: [] + styling: + - custom-colormap + - minimal-chrome + - alpha-blending