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
111 changes: 111 additions & 0 deletions plots/line-yield-curve/implementations/r/ggplot2.R
Original file line number Diff line number Diff line change
@@ -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
)
256 changes: 256 additions & 0 deletions plots/line-yield-curve/metadata/r/ggplot2.yaml
Original file line number Diff line number Diff line change
@@ -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
Loading