diff --git a/plots/line-yield-curve/implementations/r/ggplot2.R b/plots/line-yield-curve/implementations/r/ggplot2.R new file mode 100644 index 0000000000..fe11cbc06f --- /dev/null +++ b/plots/line-yield-curve/implementations/r/ggplot2.R @@ -0,0 +1,111 @@ +#' anyplot.ai +#' line-yield-curve: Yield Curve (Interest Rate Term Structure) +#' Library: ggplot2 3.5.1 | R 4.4.1 +#' Quality: 87/100 | Created: 2026-06-10 + +library(ggplot2) +library(scales) +library(ragg) + +# Theme tokens (Imprint palette + adaptive chrome) +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" +INK_MUTED <- if (THEME == "light") "#6B6A63" else "#A8A79F" +GRID_COLOR <- scales::alpha(INK, 0.15) + +# Imprint categorical palette — positions 1, 2, 3 for three yield curves +IMPRINT_PALETTE <- c("#009E73", "#C475FD", "#4467A3") + +# Data: U.S. Treasury yield curves on three historically significant dates +maturities <- c("1M", "3M", "6M", "1Y", "2Y", "3Y", "5Y", "7Y", "10Y", "20Y", "30Y") +maturity_years <- c(1/12, 0.25, 0.5, 1.0, 2.0, 3.0, 5.0, 7.0, 10.0, 20.0, 30.0) + +# Jan 2021: Normal steep curve (near-zero short rates, post-pandemic QE) +yields_jan2021 <- c(0.05, 0.06, 0.08, 0.09, 0.12, 0.20, 0.45, 0.80, 1.08, 1.57, 1.83) +# Jul 2023: Deeply inverted (peak of Fed hiking cycle, 5.25-5.50% fed funds) +yields_jul2023 <- c(5.30, 5.43, 5.52, 5.44, 4.87, 4.55, 4.28, 4.18, 3.97, 4.11, 3.96) +# Jan 2024: Still inverted, beginning to normalize +yields_jan2024 <- c(5.52, 5.43, 5.36, 5.12, 4.43, 4.17, 4.00, 4.01, 3.97, 4.35, 4.22) + +df <- data.frame( + maturity = rep(maturities, 3), + maturity_years = rep(maturity_years, 3), + yield_pct = c(yields_jan2021, yields_jul2023, yields_jan2024), + curve_date = factor( + rep(c("Jan 2021", "Jul 2023", "Jan 2024"), each = length(maturities)), + levels = c("Jan 2021", "Jul 2023", "Jan 2024") + ) +) + +y_top <- max(df$yield_pct) +y_annot <- y_top + 0.30 +plot_title <- "line-yield-curve · r · ggplot2 · anyplot.ai" + +# Data-driven ribbon: front-end region (1M–2Y) between Jan 2021 baseline and +# Jul 2023 peak — shows the full magnitude of the hiking cycle at the short end +front_end_mask <- maturity_years <= 2.0 +inversion_df <- data.frame( + maturity_years = maturity_years[front_end_mask], + ymin = yields_jan2021[front_end_mask], + ymax = yields_jul2023[front_end_mask] +) + +p <- ggplot(df, aes(x = maturity_years, y = yield_pct, color = curve_date)) + + geom_ribbon( + data = inversion_df, + aes(x = maturity_years, ymin = ymin, ymax = ymax), + fill = INK_MUTED, alpha = 0.10, inherit.aes = FALSE + ) + + geom_line(linewidth = 1.2, lineend = "round") + + geom_point(size = 3.5) + + annotate("text", + x = 0.7, y = y_annot, + label = "Inversion zone", + color = INK_MUTED, size = 3.5, hjust = 0.5, vjust = 0 + ) + + scale_color_manual(values = IMPRINT_PALETTE, name = NULL) + + scale_x_log10( + breaks = maturity_years, + labels = maturities + ) + + scale_y_continuous( + labels = function(x) sprintf("%.1f%%", x), + expand = expansion(mult = c(0.05, 0.22)) + ) + + labs( + title = plot_title, + x = "Maturity", + y = "Yield (%)" + ) + + 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 = GRID_COLOR, linewidth = 0.3), + panel.grid.minor = element_blank(), + panel.border = element_blank(), + axis.title = element_text(color = INK, size = 10), + axis.text = element_text(color = INK_SOFT, size = 8), + axis.line = element_line(color = INK_SOFT, linewidth = 0.5), + axis.ticks = element_blank(), + plot.title = element_text(color = INK, size = 12, margin = margin(b = 12)), + legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3), + legend.text = element_text(color = INK_SOFT, size = 9), + legend.title = element_blank(), + legend.key.width = unit(1.5, "cm"), + legend.position = "right", + plot.margin = margin(20, 20, 20, 20, "pt") + ) + +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/line-yield-curve/metadata/r/ggplot2.yaml b/plots/line-yield-curve/metadata/r/ggplot2.yaml new file mode 100644 index 0000000000..9582577050 --- /dev/null +++ b/plots/line-yield-curve/metadata/r/ggplot2.yaml @@ -0,0 +1,256 @@ +library: ggplot2 +language: r +specification_id: line-yield-curve +created: '2026-06-10T00:50:31Z' +updated: '2026-06-10T01:07:11Z' +generated_by: claude-sonnet +workflow_run: 27245270969 +issue: 4664 +language_version: 4.4.1 +library_version: 3.5.1 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-yield-curve/r/ggplot2/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-yield-curve/r/ggplot2/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: 87 +review: + strengths: + - Correct Imprint palette in canonical order with first series as brand green (#009E73) + - 'Full theme-adaptive chrome: all text, grid, legend, and background tokens correctly + flip between light and dark renders' + - Historically accurate and narratively meaningful data (QE era → hiking cycle peak + → normalization) + - Idiomatic ggplot2 grammar with appropriate geom layering (ribbon + line + point) + - 'Clean flat procedural script: all imports used, deterministic data, correct output + filenames' + - Correct log-scale x-axis with maturity string labels positioned at numeric maturity_years + values + - 'Good visual refinement: panel border removed, minor grid removed, ticks removed, + subtle major grid' + weaknesses: + - Ribbon alpha=0.10 is too subtle — nearly invisible in the dark render; raise to + alpha=0.18–0.22 so the inversion-zone highlight reads in both themes + - 'Ribbon semantics imprecise: currently spans from Jan 2021 baseline to Jul 2023 + at 1M–2Y, but the spec asks to highlight where short-term yields exceed long-term + yields within a single curve — consider shading the area below the inverted curve''s + long-end yield level in the front-end region' + - Legend border box adds visual noise; style guide recommends removing legend box + frames — set legend.background color=NA + - Title at size=12pt spans ~50% of plot width at the lower bound of the 50–70% guideline; + slightly larger (13pt) would give more visual authority + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct, not pure white + Chrome: Title "line-yield-curve · r · ggplot2 · anyplot.ai" in dark ink, clearly visible. X-axis label "Maturity" and Y-axis label "Yield (%)" readable. Tick labels (1M–30Y, 0.0%–6.0%) all readable in INK_SOFT. "Inversion zone" annotation visible in muted ink. + Data: Three curves — Jan 2021 in brand green (#009E73, first series correct), Jul 2023 in lavender (#C475FD), Jan 2024 in blue (#4467A3). Subtle gray ribbon fill in 1M–2Y region (alpha=0.10, barely visible). Lines at linewidth=1.2, points at size=3.5. + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct, not pure black + Chrome: Title in light ink (#F0EFE8), axis labels and tick labels in secondary light ink (#B8B7B0) — all readable against dark surface. No dark-on-dark failures. Grid lines subtle on dark background. Legend box uses elevated dark background (#242420). + Data: Data colors identical to light render — green, lavender, blue confirmed unchanged. Ribbon is nearly invisible at alpha=0.10 on dark surface (very weak contrast). "Inversion zone" annotation still legible. + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 28 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: Font sizes explicitly set (title=12, axis=10, tick=8); all text readable + in both themes. Title spans ~50% of plot width, at lower bound of 50-70% + guideline. + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No text/data collisions; Jan 2021 curve visually well-separated from + 2023/2024 inverted curves. + - id: VQ-03 + name: Element Visibility + score: 5 + max: 6 + passed: true + comment: Lines and points clearly visible. Ribbon fill at alpha=0.10 is very + subtle and nearly invisible in dark render. + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Imprint palette positions 1-3 are CVD-safe; no red-green sole-signal + issue. + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Canvas passes dimension gate (3200x1800). Good margins, proportions + balanced, no overflow. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: 'X: Maturity, Y: Yield (%) with unit. Both descriptive.' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73; positions 1-3 canonical order; backgrounds + #FAF8F1/#1A1A17; chrome correctly adapts.' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'Above-default: Imprint palette, ribbon+annotation add narrative + intent, intentional hierarchy. Not exceptional — no emphasis distinguishes + key curve, legend border noisy.' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: 'Above-default: panel border removed, minor grid removed, ticks removed, + subtle major grid 15% opacity. Legend border slightly noisy.' + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: 'Above-default: historically significant dates tell coherent macro + narrative. Ribbon+annotation guide viewer. Could be stronger with explicit + callout for most important curve.' + spec_compliance: + score: 14 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct multi-line yield curve on log-scale x-axis with all 11 maturity + tenors. + - id: SC-02 + name: Required Features + score: 3 + max: 4 + passed: true + comment: 'Three curves, legend, log-scale x-axis, maturity labels all present. + Minor: ribbon positioned between 2021 baseline and 2023 short-end, not directly + showing where short-end > long-end within a single curve.' + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: maturity_years drives x-axis; maturity strings as tick labels; yield_pct + on y-axis; all points shown. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title matches required format; legend labels Jan 2021/Jul 2023/Jan + 2024 correct. + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Normal upward-sloping, deeply inverted, and partially normalizing + curves all represented. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Real US Treasury yield data; accurate yield levels for each Fed policy + regime; neutral framing. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: All yield values consistent with historical reality for the given + dates. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Flat procedural script; no unnecessary functions or classes. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: All data hardcoded; deterministic output. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: ggplot2, scales, ragg — all imported and used. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean readable code; no fake UI; appropriate complexity. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png; current ggplot2 API (linewidth= not size=). + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: 'Good ggplot2 grammar: geom_ribbon+geom_line+geom_point, scale_color_manual, + scale_x_log10, expansion(), theme_minimal+theme layering.' + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: scale_x_log10 with explicit breaks/labels, geom_ribbon, factor() + with explicit levels, expansion(mult=), scales::alpha() for grid color. + verdict: APPROVED +impl_tags: + dependencies: + - scales + - ragg + techniques: + - annotations + - layer-composition + patterns: + - data-generation + dataprep: [] + styling: + - alpha-blending