From e042fd22fa38b394fc395dffee13c646262e7d59 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 May 2026 08:19:47 +0000 Subject: [PATCH 1/5] feat(ggplot2): implement map-projections --- .../implementations/r/ggplot2.R | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 plots/map-projections/implementations/r/ggplot2.R diff --git a/plots/map-projections/implementations/r/ggplot2.R b/plots/map-projections/implementations/r/ggplot2.R new file mode 100644 index 0000000000..8f2d1516f8 --- /dev/null +++ b/plots/map-projections/implementations/r/ggplot2.R @@ -0,0 +1,164 @@ +#' anyplot.ai +#' map-projections: World Map with Mollweide Projection +#' Library: ggplot2 | R +#' Quality: pending | Created: 2026-05-23 + +library(ggplot2) +library(dplyr) +library(ragg) + +# 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" +OCEAN_BG <- if (THEME == "light") "#C4DCF0" else "#152030" + +ANYPLOT_PALETTE <- c( + "#009E73", "#9418DB", "#B71D27", "#16B8F3", + "#99B314", "#D359A7", "#BA843E" +) + +# --- Mollweide projection math ----------------------------------------------- +# Solve 2*theta + sin(2*theta) = pi*sin(lat) via Newton-Raphson +solve_theta <- function(phi_vec) { + sapply(phi_vec, function(p) { + if (abs(p) >= pi / 2 - 1e-9) return(sign(p) * pi / 2) + t <- p + for (i in seq_len(60)) { + delta <- (2 * t + sin(2 * t) - pi * sin(p)) / (2 + 2 * cos(2 * t)) + t <- t - delta + if (abs(delta) < 1e-11) break + } + t + }) +} + +mollweide <- function(lon_deg, lat_deg) { + lon <- lon_deg * pi / 180 + lat <- lat_deg * pi / 180 + theta <- solve_theta(lat) + data.frame( + x = (2 * sqrt(2) / pi) * lon * cos(theta), + y = sqrt(2) * sin(theta) + ) +} + +# --- Projection boundary (Mollweide ellipse: a=2√2, b=√2) ------------------- +t_ell <- seq(0, 2 * pi, length.out = 721) +boundary <- data.frame( + x = 2 * sqrt(2) * cos(t_ell), + y = sqrt(2) * sin(t_ell) +) + +# --- Graticule (lat/lon grid at 30° intervals) -------------------------------- +lat_dense <- seq(-90, 90, length.out = 541) +lon_dense <- seq(-180, 180, length.out = 1081) + +meridians <- do.call(rbind, lapply(seq(-180, 180, by = 30), function(lon0) { + pts <- mollweide(rep(lon0, length(lat_dense)), lat_dense) + data.frame(x = pts$x, y = pts$y, group = paste0("mer_", lon0)) +})) + +parallels <- do.call(rbind, lapply(seq(-90, 90, by = 30), function(lat0) { + pts <- mollweide(lon_dense, rep(lat0, length(lon_dense))) + data.frame(x = pts$x, y = pts$y, group = paste0("par_", lat0)) +})) + +graticule <- rbind(meridians, parallels) + +# --- Tissot indicatrices (small geodesic circles on the sphere) --------------- +# Radius in degrees; longitude offset corrected for latitude (cos projection) +r_deg <- 5 +t_circle <- seq(0, 2 * pi, length.out = 73) # 73 pts → closed polygon + +lat_centers <- seq(-60, 60, by = 30) # 5 latitude bands +lon_centers <- seq(-150, 150, by = 60) # 6 longitude positions + +tissot <- do.call(rbind, lapply(lat_centers, function(lat0) { + cos_lat <- max(cos(lat0 * pi / 180), 0.08) + do.call(rbind, lapply(lon_centers, function(lon0) { + lon_c <- lon0 + r_deg * cos(t_circle) / cos_lat + lat_c <- pmax(pmin(lat0 + r_deg * sin(t_circle), 89.9), -89.9) + pts <- mollweide(lon_c, lat_c) + data.frame( + x = pts$x, + y = pts$y, + group = paste0("t_", lon0, "_", lat0) + ) + })) +})) + +# --- Equator and prime meridian highlights ----------------------------------- +equator <- mollweide(lon_dense, rep(0, length(lon_dense))) +equator$group <- "equator" + +prime_merid <- mollweide(rep(0, length(lat_dense)), lat_dense) +prime_merid$group <- "prime" + +# --- Plot -------------------------------------------------------------------- +p <- ggplot() + + # Ocean fill + geom_polygon( + data = boundary, aes(x = x, y = y), + fill = OCEAN_BG, color = NA + ) + + # Standard graticule (30° grid) + geom_path( + data = graticule, aes(x = x, y = y, group = group), + color = INK_SOFT, linewidth = 0.18, alpha = 0.45 + ) + + # Equator and prime meridian — slightly bolder + geom_path( + data = equator, aes(x = x, y = y, group = group), + color = INK_SOFT, linewidth = 0.35, alpha = 0.70 + ) + + geom_path( + data = prime_merid, aes(x = x, y = y, group = group), + color = INK_SOFT, linewidth = 0.35, alpha = 0.70 + ) + + # Tissot indicatrices — anyplot brand green (position 1) + geom_polygon( + data = tissot, aes(x = x, y = y, group = group), + fill = ANYPLOT_PALETTE[1], color = PAGE_BG, + linewidth = 0.06, alpha = 0.78 + ) + + # Ellipse outline + geom_path( + data = boundary, aes(x = x, y = y), + color = INK_SOFT, linewidth = 0.40 + ) + + coord_fixed() + + labs( + title = "map-projections · r · ggplot2 · anyplot.ai", + subtitle = paste0( + "Mollweide equal-area projection · ", + "Tissot’s indicatrices (green) confirm uniform area: ", + "all circles span identical surface area" + ) + ) + + theme_void(base_size = 8) + + theme( + plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), + plot.title = element_text( + color = INK, size = 12, hjust = 0.5, + margin = margin(t = 14, b = 6) + ), + plot.subtitle = element_text( + color = INK_SOFT, size = 8.5, hjust = 0.5, + margin = margin(b = 12) + ), + plot.margin = margin(8, 50, 12, 50) + ) + +# --- Save -------------------------------------------------------------------- +ggsave( + filename = sprintf("plot-%s.png", THEME), + plot = p, + device = ragg::agg_png, + width = 8, + height = 4.5, + units = "in", + dpi = 400 +) From c1fe544625e96f63a24a16e72eab122750606234 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 May 2026 08:20:01 +0000 Subject: [PATCH 2/5] chore(ggplot2): add metadata for map-projections --- plots/map-projections/metadata/r/ggplot2.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 plots/map-projections/metadata/r/ggplot2.yaml diff --git a/plots/map-projections/metadata/r/ggplot2.yaml b/plots/map-projections/metadata/r/ggplot2.yaml new file mode 100644 index 0000000000..e6ecb92b02 --- /dev/null +++ b/plots/map-projections/metadata/r/ggplot2.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for ggplot2 implementation of map-projections +# Auto-generated by impl-generate.yml + +library: ggplot2 +language: r +specification_id: map-projections +created: '2026-05-23T08:20:00Z' +updated: '2026-05-23T08:20:00Z' +generated_by: claude-sonnet +workflow_run: 26327716523 +issue: 3771 +language_version: 4.4.1 +library_version: 3.5.1 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-projections/r/ggplot2/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-projections/r/ggplot2/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: null +review: + strengths: [] + weaknesses: [] From 92411349f8f6e9c047293bb1a9251157b4e3a342 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 May 2026 08:28:46 +0000 Subject: [PATCH 3/5] chore(ggplot2): update quality score 83 and review feedback for map-projections --- .../implementations/r/ggplot2.R | 6 +- plots/map-projections/metadata/r/ggplot2.yaml | 252 +++++++++++++++++- 2 files changed, 248 insertions(+), 10 deletions(-) diff --git a/plots/map-projections/implementations/r/ggplot2.R b/plots/map-projections/implementations/r/ggplot2.R index 8f2d1516f8..0974a0589d 100644 --- a/plots/map-projections/implementations/r/ggplot2.R +++ b/plots/map-projections/implementations/r/ggplot2.R @@ -1,7 +1,7 @@ #' anyplot.ai -#' map-projections: World Map with Mollweide Projection -#' Library: ggplot2 | R -#' Quality: pending | Created: 2026-05-23 +#' map-projections: World Map with Different Projections +#' Library: ggplot2 3.5.1 | R 4.4.1 +#' Quality: 83/100 | Created: 2026-05-23 library(ggplot2) library(dplyr) diff --git a/plots/map-projections/metadata/r/ggplot2.yaml b/plots/map-projections/metadata/r/ggplot2.yaml index e6ecb92b02..3a41b06820 100644 --- a/plots/map-projections/metadata/r/ggplot2.yaml +++ b/plots/map-projections/metadata/r/ggplot2.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for ggplot2 implementation of map-projections -# Auto-generated by impl-generate.yml - library: ggplot2 language: r specification_id: map-projections created: '2026-05-23T08:20:00Z' -updated: '2026-05-23T08:20:00Z' +updated: '2026-05-23T08:28:46Z' generated_by: claude-sonnet workflow_run: 26327716523 issue: 3771 @@ -15,7 +12,248 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-proje preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-projections/r/ggplot2/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: null +quality_score: 83 review: - strengths: [] - weaknesses: [] + strengths: + - Mathematically rigorous Mollweide projection implemented from scratch (Newton-Raphson + solver for theta), entirely within ggplot2 drawing primitives + - 'Excellent dual-theme adaptation: custom OCEAN_BG tokens (#C4DCF0 light / #152030 + dark) are semantically appropriate and both themes are visually coherent' + - Brand green (#009E73) correctly used for Tissot indicatrices as the first (and + only) data series + - 'Clean visual hierarchy: faint graticule (alpha=0.45) < bolder equator/prime meridian + (alpha=0.70) < prominent green Tissot ellipses — guides the viewer''s eye correctly' + - 'Tissot indicatrices correctly demonstrate the equal-area property of Mollweide: + same surface area at every lat/lon position, with correct latitude cosine correction + applied' + - theme_void() + coord_fixed() is idiomatic ggplot2 for cartographic use — no unnecessary + axis chrome + weaknesses: + - 'Missing land masses and country borders: the spec explicitly requires ''world + country boundaries'' and ''clean coastlines and country borders.'' The entire + plot is ocean-filled with no land polygons visible. Fix: add `library(maps)` and + use `map_data(''world'')` to get lon/lat country boundary coordinates, then project + them through the `mollweide()` function and render with `geom_polygon()` (land + fill) + `geom_path()` (borders) — this avoids `sf` and stays in base ggplot2' + - dplyr is imported (`library(dplyr)`) but never used anywhere in the code — remove + it to satisfy CQ-03 clean imports + - Subtitle at 8.5pt may be hard to read when the image is scaled to mobile widths + (~400px). Consider either raising to 9.5pt or shortening the subtitle text + - Helper functions `solve_theta()` and `mollweide()` break the KISS (no-functions) + code structure. Consider inlining the math or wrapping in a single anonymous call + chain to keep the flat script style + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct anyplot light surface, not pure white. + Chrome: Title "map-projections · r · ggplot2 · anyplot.ai" in dark ink (#1A1A17) at 12pt, centered — clearly readable. Subtitle in INK_SOFT gray at 8.5pt, also centered — readable at full resolution but small. No axis labels or tick labels (theme_void — appropriate for a map). + Data: A Mollweide ellipse filled with light blue ocean (#C4DCF0). Thin, subtle gray graticule lines at 30° intervals (alpha=0.45, linewidth=0.18). Slightly bolder equator and prime meridian lines (alpha=0.70). 30 green (#009E73) Tissot indicatrix ellipses distributed across 5 latitude bands × 6 longitude positions. No land masses or country borders are visible. + Legibility verdict: PASS — all text readable; no light-on-light failures. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct anyplot dark surface, not pure black. + Chrome: Title and subtitle in light ink (#F0EFE8 / #B8B7B0) — clearly readable against the dark background. No dark-on-dark failures anywhere. + Data: Ocean changes to dark navy (#152030), which is semantically appropriate and visually distinct. Graticule lines appear as subtle off-white paths. Tissot indicatrices remain exactly #009E73 — identical to the light render (only chrome flipped, data colors unchanged). Ellipse outline visible as a lighter border. + Legibility verdict: PASS — all text readable; chrome correctly flips; data colors identical to light render. + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: Title (12pt) and subtitle (8.5pt) explicitly set, readable in both + themes; subtitle is on the small side for mobile scale + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping elements; Tissot ellipses well-spaced, no text collisions + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Tissot indicatrices clearly visible and well-sized; graticule subtle + but legible + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Green (#009E73) on blue ocean has good contrast in both themes; CVD-safe + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Map fills ~75% of canvas area, balanced margins, no content cut off + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Title descriptive; subtitle explains projection and Tissot purpose; + no axis labels appropriate for theme_void map + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'Data elements use #009E73 (position 1); ocean blue is semantic (blue=water); + backgrounds correct #FAF8F1/#1A1A17; chrome flips correctly' + design_excellence: + score: 15 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: 'Strong design: custom dual-theme ocean colors, intentional graticule + hierarchy, harmonious green/blue composition — clearly above defaults' + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 + passed: true + comment: theme_void removes all extraneous chrome; graticule very subtle (alpha=0.45); + equator/prime meridian differentiated; clean ellipse outline + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Subtitle explicitly explains what Tissot indicatrices demonstrate; + visual hierarchy guides eye from graticule to reference lines to data ellipses + spec_compliance: + score: 13 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: 'Correct: Mollweide projection world map as specified; spec allows + single projection with clear title' + - id: SC-02 + name: Required Features + score: 2 + max: 4 + passed: false + comment: Graticule at 30° intervals ✓, Tissot indicatrices ✓; but missing + country boundaries and coastlines which spec explicitly requires + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Mollweide math correct; graticule correctly positioned; Tissot circles + at valid lat/lon centers + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title exactly 'map-projections · r · ggplot2 · anyplot.ai'; no legend + needed for single-series map + data_quality: + score: 13 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 4 + max: 6 + passed: false + comment: Shows projection math, graticule, Tissot indicatrices — but missing + the key world map feature (land polygons) + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Real geographic context, Mollweide is a standard equal-area projection, + educational scenario, neutral content + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Projection math correct; Tissot radius (5°) is standard; lat centers + ±60° and lon spacing 60° are textbook positions + code_quality: + score: 8 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 2 + max: 3 + passed: false + comment: Has helper functions solve_theta() and mollweide() — not pure flat + KISS, though they are mathematically necessary + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: 'Fully deterministic: all data is computed mathematically, no random + elements' + - id: CQ-03 + name: Clean Imports + score: 1 + max: 2 + passed: false + comment: library(dplyr) is imported but never used anywhere in the code + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Well-organized; Newton-Raphson solver is correctly implemented; no + fake UI; appropriate complexity + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png using ragg::agg_png at correct dimensions + (8x4.5in, dpi=400 = 3200x1800px) + library_mastery: + score: 5 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 3 + max: 5 + passed: true + comment: Uses theme_void(), coord_fixed(), geom_polygon(), geom_path() idiomatically; + using ggplot2 as a drawing framework rather than exploiting grammar-of-graphics + statistical features + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: theme_void() + coord_fixed() are ggplot2-specific; layer composition + is idiomatic; but projection math is all manual — no ggplot2 geo projection + features leveraged + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - layer-composition + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - minimal-chrome + - alpha-blending From 98ca170ec3949be0c1e57f57b6848827f6c0833f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 May 2026 08:35:53 +0000 Subject: [PATCH 4/5] fix(ggplot2): address review feedback for map-projections Attempt 1/3 - fixes based on AI review --- .../implementations/r/ggplot2.R | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/plots/map-projections/implementations/r/ggplot2.R b/plots/map-projections/implementations/r/ggplot2.R index 0974a0589d..8434a89386 100644 --- a/plots/map-projections/implementations/r/ggplot2.R +++ b/plots/map-projections/implementations/r/ggplot2.R @@ -4,16 +4,16 @@ #' Quality: 83/100 | Created: 2026-05-23 library(ggplot2) -library(dplyr) +library(maps) library(ragg) # 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" OCEAN_BG <- if (THEME == "light") "#C4DCF0" else "#152030" +LAND_BG <- if (THEME == "light") "#D4C9A8" else "#2A3020" ANYPLOT_PALETTE <- c( "#009E73", "#9418DB", "#B71D27", "#16B8F3", @@ -24,6 +24,7 @@ ANYPLOT_PALETTE <- c( # Solve 2*theta + sin(2*theta) = pi*sin(lat) via Newton-Raphson solve_theta <- function(phi_vec) { sapply(phi_vec, function(p) { + if (is.na(p)) return(NA_real_) if (abs(p) >= pi / 2 - 1e-9) return(sign(p) * pi / 2) t <- p for (i in seq_len(60)) { @@ -45,6 +46,12 @@ mollweide <- function(lon_deg, lat_deg) { ) } +# --- World country boundaries (projected) ------------------------------------ +world <- map_data("world") +world_proj <- mollweide(world$long, world$lat) +world$x <- world_proj$x +world$y <- world_proj$y + # --- Projection boundary (Mollweide ellipse: a=2√2, b=√2) ------------------- t_ell <- seq(0, 2 * pi, length.out = 721) boundary <- data.frame( @@ -104,6 +111,16 @@ p <- ggplot() + data = boundary, aes(x = x, y = y), fill = OCEAN_BG, color = NA ) + + # Land masses (country polygons) + geom_polygon( + data = world, aes(x = x, y = y, group = group), + fill = LAND_BG, color = NA + ) + + # Country borders / coastlines + geom_path( + data = world, aes(x = x, y = y, group = group), + color = INK_SOFT, linewidth = 0.10, alpha = 0.55 + ) + # Standard graticule (30° grid) geom_path( data = graticule, aes(x = x, y = y, group = group), @@ -132,11 +149,7 @@ p <- ggplot() + coord_fixed() + labs( title = "map-projections · r · ggplot2 · anyplot.ai", - subtitle = paste0( - "Mollweide equal-area projection · ", - "Tissot’s indicatrices (green) confirm uniform area: ", - "all circles span identical surface area" - ) + subtitle = "Mollweide equal-area projection · Tissot indicatrices (green) confirm equal-area: every circle covers identical surface" ) + theme_void(base_size = 8) + theme( @@ -146,7 +159,7 @@ p <- ggplot() + margin = margin(t = 14, b = 6) ), plot.subtitle = element_text( - color = INK_SOFT, size = 8.5, hjust = 0.5, + color = INK_SOFT, size = 9.5, hjust = 0.5, margin = margin(b = 12) ), plot.margin = margin(8, 50, 12, 50) From 52f1418c5286f78ffedaa40eeb22ad4a07211a8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 May 2026 08:42:49 +0000 Subject: [PATCH 5/5] chore(ggplot2): update quality score 85 and review feedback for map-projections --- .../implementations/r/ggplot2.R | 2 +- plots/map-projections/metadata/r/ggplot2.yaml | 213 +++++++++--------- 2 files changed, 108 insertions(+), 107 deletions(-) diff --git a/plots/map-projections/implementations/r/ggplot2.R b/plots/map-projections/implementations/r/ggplot2.R index 8434a89386..f70587a54b 100644 --- a/plots/map-projections/implementations/r/ggplot2.R +++ b/plots/map-projections/implementations/r/ggplot2.R @@ -1,7 +1,7 @@ #' anyplot.ai #' map-projections: World Map with Different Projections #' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 83/100 | Created: 2026-05-23 +#' Quality: 85/100 | Created: 2026-05-23 library(ggplot2) library(maps) diff --git a/plots/map-projections/metadata/r/ggplot2.yaml b/plots/map-projections/metadata/r/ggplot2.yaml index 3a41b06820..0e3b5e1e02 100644 --- a/plots/map-projections/metadata/r/ggplot2.yaml +++ b/plots/map-projections/metadata/r/ggplot2.yaml @@ -2,7 +2,7 @@ library: ggplot2 language: r specification_id: map-projections created: '2026-05-23T08:20:00Z' -updated: '2026-05-23T08:28:46Z' +updated: '2026-05-23T08:42:49Z' generated_by: claude-sonnet workflow_run: 26327716523 issue: 3771 @@ -12,51 +12,46 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-proje preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-projections/r/ggplot2/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: 83 +quality_score: 85 review: strengths: - - Mathematically rigorous Mollweide projection implemented from scratch (Newton-Raphson - solver for theta), entirely within ggplot2 drawing primitives - - 'Excellent dual-theme adaptation: custom OCEAN_BG tokens (#C4DCF0 light / #152030 - dark) are semantically appropriate and both themes are visually coherent' - - Brand green (#009E73) correctly used for Tissot indicatrices as the first (and - only) data series - - 'Clean visual hierarchy: faint graticule (alpha=0.45) < bolder equator/prime meridian - (alpha=0.70) < prominent green Tissot ellipses — guides the viewer''s eye correctly' - - 'Tissot indicatrices correctly demonstrate the equal-area property of Mollweide: - same surface area at every lat/lon position, with correct latitude cosine correction - applied' - - theme_void() + coord_fixed() is idiomatic ggplot2 for cartographic use — no unnecessary - axis chrome + - Implements Mollweide projection from scratch using Newton-Raphson iteration — + correctly bypasses sf/ggmap (out of scope) without sacrificing accuracy + - 'Theme-adaptive ocean and land colors (not just grey fill): #C4DCF0/#152030 ocean, + #D4C9A8/#2A3020 land — thoughtfully chosen for both surfaces' + - Tissot indicatrices correctly demonstrate the equal-area property; subtitle explains + the visual insight ('every circle covers identical surface') + - 'Perfect palette compliance: brand green #009E73 for Tissot circles, correct backgrounds + in both themes (#FAF8F1 light / #1A1A17 dark)' + - 'Visual hierarchy: equator and prime meridian are bolder than regular graticule + — reads naturally' + - 'Perfect code quality: deterministic, clean imports, idiomatic ggplot2 grammar, + correct canvas size (3200x1800)' weaknesses: - - 'Missing land masses and country borders: the spec explicitly requires ''world - country boundaries'' and ''clean coastlines and country borders.'' The entire - plot is ocean-filled with no land polygons visible. Fix: add `library(maps)` and - use `map_data(''world'')` to get lon/lat country boundary coordinates, then project - them through the `mollweide()` function and render with `geom_polygon()` (land - fill) + `geom_path()` (borders) — this avoids `sf` and stays in base ggplot2' - - dplyr is imported (`library(dplyr)`) but never used anywhere in the code — remove - it to satisfy CQ-03 clean imports - - Subtitle at 8.5pt may be hard to read when the image is scaled to mobile widths - (~400px). Consider either raising to 9.5pt or shortening the subtitle text - - Helper functions `solve_theta()` and `mollweide()` break the KISS (no-functions) - code structure. Consider inlining the math or wrapping in a single anonymous call - chain to keep the flat script style + - Subtitle is ~90 characters long at 9.5pt, causing it to span ~90% of the canvas + width and appear nearly edge-to-edge — reduce subtitle length or reduce font size + to ~8pt to restore visual breathing room + - Only Mollweide projection is shown; the spec description emphasizes demonstrating + multiple projections (Mercator, Robinson, Mollweide, etc.) for comparison — consider + a 2x2 faceted layout showing 4 projections side-by-side to fully satisfy the educational + intent + - Tissot indicatrices are limited to ±60° latitude; including a row at ±75° or near-polar + positions would better illustrate the extreme distortion in those regions image_description: |- Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct anyplot light surface, not pure white. - Chrome: Title "map-projections · r · ggplot2 · anyplot.ai" in dark ink (#1A1A17) at 12pt, centered — clearly readable. Subtitle in INK_SOFT gray at 8.5pt, also centered — readable at full resolution but small. No axis labels or tick labels (theme_void — appropriate for a map). - Data: A Mollweide ellipse filled with light blue ocean (#C4DCF0). Thin, subtle gray graticule lines at 30° intervals (alpha=0.45, linewidth=0.18). Slightly bolder equator and prime meridian lines (alpha=0.70). 30 green (#009E73) Tissot indicatrix ellipses distributed across 5 latitude bands × 6 longitude positions. No land masses or country borders are visible. - Legibility verdict: PASS — all text readable; no light-on-light failures. + Background: Warm off-white #FAF8F1 — correct anyplot light surface + Chrome: Title "map-projections · r · ggplot2 · anyplot.ai" at 12pt in dark ink (#1A1A17), center-aligned, clearly readable. Subtitle in secondary ink (#4A4A44) at 9.5pt — readable but spans ~90% of canvas width approaching edges. No axis labels or tick labels (theme_void appropriate for map). + Data: Ocean fill in muted cornflower blue (#C4DCF0), land masses in sandy beige (#D4C9A8). 30-degree graticule in semi-transparent soft ink. Equator and prime meridian drawn bolder. Tissot indicatrices (30 circles at 5 lat-bands × 6 lon-positions) in brand green #009E73. Ellipse boundary outlined. + Legibility verdict: PASS — all text readable against light background, no light-on-light failures Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct anyplot dark surface, not pure black. - Chrome: Title and subtitle in light ink (#F0EFE8 / #B8B7B0) — clearly readable against the dark background. No dark-on-dark failures anywhere. - Data: Ocean changes to dark navy (#152030), which is semantically appropriate and visually distinct. Graticule lines appear as subtle off-white paths. Tissot indicatrices remain exactly #009E73 — identical to the light render (only chrome flipped, data colors unchanged). Ellipse outline visible as a lighter border. - Legibility verdict: PASS — all text readable; chrome correctly flips; data colors identical to light render. + Background: Near-black #1A1A17 — correct anyplot dark surface + Chrome: Title and subtitle use light tokens (#F0EFE8 / #B8B7B0), clearly readable against dark background. No dark-on-dark failures detected. Graticule and ellipse boundary rendered in #B8B7B0. + Data: Ocean shifts to deep navy (#152030), land to dark olive green (#2A3020). Tissot indicatrices remain identical brand green #009E73 — data colors unchanged from light render as required. All data elements distinguishable against dark background. + Legibility verdict: PASS — all text and data elements readable in dark theme criteria_checklist: visual_quality: - score: 29 + score: 27 max: 30 items: - id: VQ-01 @@ -64,74 +59,78 @@ review: score: 7 max: 8 passed: true - comment: Title (12pt) and subtitle (8.5pt) explicitly set, readable in both - themes; subtitle is on the small side for mobile scale + comment: 'Title and subtitle readable in both themes. Minor: subtitle (~90 + chars at 9.5pt) spans ~90% of canvas width, approaching edges.' - id: VQ-02 name: No Overlap - score: 6 + score: 5 max: 6 passed: true - comment: No overlapping elements; Tissot ellipses well-spaced, no text collisions + comment: 'No text or data overlaps. Minor: some Tissot circles at high latitudes + are visually close in some longitude bands.' - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Tissot indicatrices clearly visible and well-sized; graticule subtle - but legible + comment: Tissot indicatrices, land, ocean, and graticule all clearly distinguishable + in both themes. - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Green (#009E73) on blue ocean has good contrast in both themes; CVD-safe + comment: Brand green against ocean blue/navy has adequate contrast; CVD-safe + palette. - id: VQ-05 name: Layout & Canvas - score: 4 + score: 3 max: 4 passed: true - comment: Map fills ~75% of canvas area, balanced margins, no content cut off + comment: 'Correct 3200x1800 canvas. Map ellipse well-centered. Minor: subtitle + text approaches canvas edges, limiting breathing room despite 50pt plot + margins.' - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Title descriptive; subtitle explains projection and Tissot purpose; - no axis labels appropriate for theme_void map + comment: theme_void() correct for a map. Subtitle provides projection type + and educational context. - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: 'Data elements use #009E73 (position 1); ocean blue is semantic (blue=water); - backgrounds correct #FAF8F1/#1A1A17; chrome flips correctly' + comment: 'Tissot circles use ANYPLOT_PALETTE[1] (#009E73). Backgrounds #FAF8F1 + (light) / #1A1A17 (dark). Data colors identical between themes.' design_excellence: - score: 15 + score: 12 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 6 + score: 5 max: 8 passed: true - comment: 'Strong design: custom dual-theme ocean colors, intentional graticule - hierarchy, harmonious green/blue composition — clearly above defaults' + comment: Theme-adaptive ocean/land colors (not grey default), intentional + visual hierarchy (equator/prime meridian bolder), good alpha usage. - id: DE-02 name: Visual Refinement - score: 5 + score: 3 max: 6 passed: true - comment: theme_void removes all extraneous chrome; graticule very subtle (alpha=0.45); - equator/prime meridian differentiated; clean ellipse outline + comment: theme_void() removes axes correctly for a map. Subtle graticule opacity. + Ellipse boundary clean. Subtitle near-edge is a refinement miss. - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: Subtitle explicitly explains what Tissot indicatrices demonstrate; - visual hierarchy guides eye from graticule to reference lines to data ellipses + comment: Tissot indicatrices demonstrate equal-area property effectively. + Subtitle explains what the circles prove. Clear educational narrative. spec_compliance: - score: 13 + score: 14 max: 15 items: - id: SC-01 @@ -139,119 +138,121 @@ review: score: 5 max: 5 passed: true - comment: 'Correct: Mollweide projection world map as specified; spec allows - single projection with clear title' + comment: Correct world map with Mollweide projection. Spec explicitly allows + single projection with clear title. - id: SC-02 name: Required Features - score: 2 + score: 3 max: 4 - passed: false - comment: Graticule at 30° intervals ✓, Tissot indicatrices ✓; but missing - country boundaries and coastlines which spec explicitly requires + passed: true + comment: 'Graticule at 30° ✓, coastlines/borders ✓, Tissot indicatrices ✓, + neutral land colors ✓. Minor deduction: spec description emphasizes multiple + projections for comparison; showing only Mollweide limits educational comparison.' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: Mollweide math correct; graticule correctly positioned; Tissot circles - at valid lat/lon centers + comment: Real world boundary data from maps package, correct Mollweide projection + math (Newton-Raphson), all country polygons displayed. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title exactly 'map-projections · r · ggplot2 · anyplot.ai'; no legend - needed for single-series map + comment: 'Title matches spec format exactly: ''map-projections · r · ggplot2 + · anyplot.ai''. No legend needed for single-series.' data_quality: - score: 13 + score: 15 max: 15 items: - id: DQ-01 name: Feature Coverage - score: 4 + score: 6 max: 6 - passed: false - comment: Shows projection math, graticule, Tissot indicatrices — but missing - the key world map feature (land polygons) + passed: true + comment: World boundaries, ocean fill, land fill, graticule, equator, prime + meridian, Tissot indicatrices, ellipse boundary all present. - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Real geographic context, Mollweide is a standard equal-area projection, - educational scenario, neutral content + comment: Real world boundary data. Mollweide is a genuine equal-area projection. + Tissot indicatrices are a standard cartographic tool. Neutral educational + context. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Projection math correct; Tissot radius (5°) is standard; lat centers - ±60° and lon spacing 60° are textbook positions + comment: 30° graticule standard. 5° Tissot radius at 5 lat-bands × 6 lon-positions + well-calibrated for canvas. code_quality: - score: 8 + score: 10 max: 10 items: - id: CQ-01 name: KISS Structure - score: 2 + score: 3 max: 3 - passed: false - comment: Has helper functions solve_theta() and mollweide() — not pure flat - KISS, though they are mathematically necessary + passed: true + comment: Helper functions solve_theta and mollweide are genuinely necessary + since sf/ggmap are out of scope per library rules. Flat, readable structure. - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: 'Fully deterministic: all data is computed mathematically, no random - elements' + comment: No random seed needed; all data is deterministic (real map data + + mathematical projection). - id: CQ-03 name: Clean Imports - score: 1 + score: 2 max: 2 - passed: false - comment: library(dplyr) is imported but never used anywhere in the code + passed: true + comment: ggplot2, maps, ragg — all three used. - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Well-organized; Newton-Raphson solver is correctly implemented; no - fake UI; appropriate complexity + comment: Appropriate ggplot2 layer composition. No fake UI elements. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves plot-{THEME}.png using ragg::agg_png at correct dimensions - (8x4.5in, dpi=400 = 3200x1800px) + comment: Saves as plot-{THEME}.png using ragg::agg_png. Correct dimensions + 8x4.5 in at 400dpi = 3200x1800. library_mastery: - score: 5 + score: 7 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 3 + score: 4 max: 5 passed: true - comment: Uses theme_void(), coord_fixed(), geom_polygon(), geom_path() idiomatically; - using ggplot2 as a drawing framework rather than exploiting grammar-of-graphics - statistical features + comment: theme_void() for a map, coord_fixed() for aspect ratio, multi-layer + geom composition via ggplot2 grammar. Good idiomatic patterns. - id: LM-02 name: Distinctive Features - score: 2 + score: 3 max: 5 - passed: false - comment: theme_void() + coord_fixed() are ggplot2-specific; layer composition - is idiomatic; but projection math is all manual — no ggplot2 geo projection - features leveraged - verdict: REJECTED + passed: true + comment: Mollweide projection implemented in pure R math layered through ggplot2 + geom system. Multi-layer grammar (ocean fill → land → borders → graticule + → equator/meridian → Tissot → boundary) showcases grammar of graphics well. + verdict: APPROVED impl_tags: - dependencies: [] + dependencies: + - maps techniques: - layer-composition + - patches patterns: - - data-generation + - dataset-loading - iteration-over-groups dataprep: [] styling: