From 6c7fd73027d118b7fcde3794bf44f941c2255bb3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 19 May 2026 05:00:14 +0000 Subject: [PATCH 1/4] chore(ggplot2): add metadata for indicator-ema --- .../indicator-ema/implementations/r/ggplot2.R | 97 +++++++++++++++++++ plots/indicator-ema/metadata/r/ggplot2.yaml | 21 ++++ 2 files changed, 118 insertions(+) create mode 100644 plots/indicator-ema/implementations/r/ggplot2.R create mode 100644 plots/indicator-ema/metadata/r/ggplot2.yaml diff --git a/plots/indicator-ema/implementations/r/ggplot2.R b/plots/indicator-ema/implementations/r/ggplot2.R new file mode 100644 index 0000000000..eed514daa7 --- /dev/null +++ b/plots/indicator-ema/implementations/r/ggplot2.R @@ -0,0 +1,97 @@ +#' anyplot.ai +#' indicator-ema: Exponential Moving Average (EMA) Indicator Chart +#' Library: ggplot2 | R +#' Quality: pending | Created: 2026-05-19 + +library(ggplot2) +library(scales) +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" + +OKABE_ITO <- c( + "#009E73", # 1 - brand green (price) + "#D55E00", # 2 - vermillion (EMA 12) + "#0072B2" # 3 - blue (EMA 26) +) + +# Data - synthetic daily prices for a tech stock over ~180 trading days +n_days <- 180 +dates <- seq.Date(as.Date("2024-01-02"), by = "day", length.out = n_days) +returns <- rnorm(n_days, mean = 0.0005, sd = 0.018) +close <- 150 * cumprod(1 + returns) + +calc_ema <- function(prices, period) { + k <- 2 / (period + 1) + ema <- numeric(length(prices)) + ema[1] <- prices[1] + for (i in seq(2, length(prices))) { + ema[i] <- prices[i] * k + ema[i - 1] * (1 - k) + } + ema +} + +df <- data.frame( + date = dates, + close = close, + ema12 = calc_ema(close, 12), + ema26 = calc_ema(close, 26) +) + +# Plot +p <- ggplot(df, aes(x = date)) + + geom_line(aes(y = close, color = "Price"), linewidth = 1.5, alpha = 0.80) + + geom_line(aes(y = ema12, color = "EMA (12)"), linewidth = 1.1) + + geom_line(aes(y = ema26, color = "EMA (26)"), linewidth = 1.1) + + scale_color_manual( + name = NULL, + values = c( + "Price" = OKABE_ITO[1], + "EMA (12)" = OKABE_ITO[2], + "EMA (26)" = OKABE_ITO[3] + ), + breaks = c("Price", "EMA (12)", "EMA (26)") + ) + + scale_x_date(date_labels = "%b %Y", date_breaks = "2 months") + + scale_y_continuous(labels = dollar_format()) + + labs( + title = "Tech Stock EMA · indicator-ema · r · ggplot2 · anyplot.ai", + x = "Date", + y = "Closing Price (USD)" + ) + + theme_minimal(base_size = 14) + + theme( + plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), + panel.background = element_rect(fill = PAGE_BG, color = NA), + panel.grid.major.y = element_line(color = INK_SOFT, linewidth = 0.3), + panel.grid.major.x = element_blank(), + panel.grid.minor = element_blank(), + panel.border = element_blank(), + axis.title = element_text(color = INK, size = 20), + axis.text = element_text(color = INK_SOFT, size = 16), + axis.line = element_line(color = INK_SOFT, linewidth = 0.5), + plot.title = element_text(color = INK, size = 22, face = "bold"), + legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT), + legend.text = element_text(color = INK_SOFT, size = 16), + legend.key = element_rect(fill = ELEVATED_BG, color = NA), + legend.position = "bottom", + plot.margin = margin(20, 20, 20, 20, unit = "pt") + ) + +# Save +ggsave( + filename = sprintf("plot-%s.png", THEME), + plot = p, + device = ragg::agg_png, + width = 16, + height = 9, + units = "in", + dpi = 300 +) diff --git a/plots/indicator-ema/metadata/r/ggplot2.yaml b/plots/indicator-ema/metadata/r/ggplot2.yaml new file mode 100644 index 0000000000..1b81853e74 --- /dev/null +++ b/plots/indicator-ema/metadata/r/ggplot2.yaml @@ -0,0 +1,21 @@ +# Per-library metadata for ggplot2 implementation of indicator-ema +# Auto-generated by impl-generate.yml + +library: ggplot2 +language: r +specification_id: indicator-ema +created: '2026-05-19T05:00:14Z' +updated: '2026-05-19T05:00:14Z' +generated_by: claude-sonnet +workflow_run: 26077083850 +issue: 3652 +language_version: 4.4.1 +library_version: 3.5.1 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/indicator-ema/r/ggplot2/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/indicator-ema/r/ggplot2/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: null +review: + strengths: [] + weaknesses: [] From 71804fb94a58c078d922db5c3a898c45478fdc16 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 19 May 2026 05:06:35 +0000 Subject: [PATCH 2/4] chore(ggplot2): update quality score 84 and review feedback for indicator-ema --- .../indicator-ema/implementations/r/ggplot2.R | 4 +- plots/indicator-ema/metadata/r/ggplot2.yaml | 231 +++++++++++++++++- 2 files changed, 226 insertions(+), 9 deletions(-) diff --git a/plots/indicator-ema/implementations/r/ggplot2.R b/plots/indicator-ema/implementations/r/ggplot2.R index eed514daa7..45b3ea9d90 100644 --- a/plots/indicator-ema/implementations/r/ggplot2.R +++ b/plots/indicator-ema/implementations/r/ggplot2.R @@ -1,7 +1,7 @@ #' anyplot.ai #' indicator-ema: Exponential Moving Average (EMA) Indicator Chart -#' Library: ggplot2 | R -#' Quality: pending | Created: 2026-05-19 +#' Library: ggplot2 3.5.1 | R 4.4.1 +#' Quality: 84/100 | Created: 2026-05-19 library(ggplot2) library(scales) diff --git a/plots/indicator-ema/metadata/r/ggplot2.yaml b/plots/indicator-ema/metadata/r/ggplot2.yaml index 1b81853e74..64495b29ee 100644 --- a/plots/indicator-ema/metadata/r/ggplot2.yaml +++ b/plots/indicator-ema/metadata/r/ggplot2.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for ggplot2 implementation of indicator-ema -# Auto-generated by impl-generate.yml - library: ggplot2 language: r specification_id: indicator-ema created: '2026-05-19T05:00:14Z' -updated: '2026-05-19T05:00:14Z' +updated: '2026-05-19T05:06:35Z' generated_by: claude-sonnet workflow_run: 26077083850 issue: 3652 @@ -15,7 +12,227 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/indicator preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/indicator-ema/r/ggplot2/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: null +quality_score: 84 review: - strengths: [] - weaknesses: [] + strengths: + - 'Perfect spec compliance: all required EMA features present with correct line + hierarchy' + - 'Solid theme adaptation: both renders fully legible with correct Okabe-Ito colors + and theme-adaptive chrome' + - 'Correct palette order: Price=#009E73, EMA12=#D55E00, EMA26=#0072B2 (canonical + positions 1-3)' + - 'Good visual refinement: Y-only subtle grid, no panel border, generous margins' + weaknesses: + - Plot title size is 22pt — raise to 24pt per style guide + - 'No visual storytelling: crossover points between EMA12 and EMA26 are not highlighted + despite spec mentioning this as a key feature' + - Design is clean but generic — needs visual emphasis (crossover markers, shaded + region, or trend annotation) to reach DE-01 score above 4 + - calc_ema() helper function breaks KISS flat structure — replace with inline Reduce() + or stats::filter() + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) - correct, not pure white + Chrome: Title "Tech Stock EMA · indicator-ema · r · ggplot2 · anyplot.ai" in dark bold ink, clearly readable. Axis labels "Date" and "Closing Price (USD)" in dark ink. Tick labels (Feb/Apr/Jun 2024 dates, $140-$175 dollar values) in dark gray — all readable. + Data: Three lines — green (#009E73) price line (lw=1.5, alpha=0.80), orange (#D55E00) EMA(12) line (lw=1.1), blue (#0072B2) EMA(26) line (lw=1.1). First series correctly uses brand green. Subtle horizontal grid lines visible. + Legibility verdict: PASS + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) - correct, not pure black + Chrome: Title and axis labels in near-white (#F0EFE8), tick labels and legend text in lighter gray (#B8B7B0) — all clearly readable against dark surface. No dark-on-dark failures. Legend box uses elevated dark (#242420) with subtle border. + Data: Identical colors to light render — green (#009E73) price, orange (#D55E00) EMA(12), blue (#0072B2) EMA(26). Data colors unchanged as required. + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: Sizes explicitly set but plot.title=22pt is below the >=24pt guideline + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping text elements + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Price line (lw=1.5) and EMA lines (lw=1.1) clearly visible + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Okabe-Ito palette is CVD-safe; green/orange/blue distinguishable + under deuteranopia/protanopia + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: 16x9 at 300dpi fills canvas well; bottom legend placement works + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Y-axis includes units (USD); X-axis descriptive + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: Price=#009E73 (pos 1), EMA12=#D55E00 (pos 2), EMA26=#0072B2 (pos + 3); backgrounds correct; chrome theme-correct in both renders + design_excellence: + score: 10 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 4 + max: 8 + passed: false + comment: Correctly configured with Okabe-Ito and good typography, but reads + as a well-tuned library default + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Y-only grid (lw=0.3), no minor grid, no panel border, theme_minimal + removes top/right spines, generous 20pt margins + - id: DE-03 + name: Data Storytelling + score: 2 + max: 6 + passed: false + comment: Mild hierarchy (price line thicker/translucent) but no visual emphasis + on crossover points or trend events + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: EMA overlay chart with price line and two EMA lines + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Price prominent (thicker), two EMA periods (12 and 26), EMA lines + thinner than price, legend labels each EMA + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Date on X, closing price (USD) on Y, all 180 points visible + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title matches {Descriptive} · {spec-id} · {lang} · {lib} · anyplot.ai + format; legend labels correct + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Shows short and long EMAs, smoothing lag, uptrend and downtrend phases + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Tech stock at $150 with plausible drift (0.05%) and volatility (1.8%) + — neutral financial scenario + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Price range $135-$180 over 180 trading days is realistic; volatility + parameters factually appropriate + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 2 + max: 3 + passed: false + comment: calc_ema() helper function defined; departs from flat Imports->Data->Plot->Save + structure + - 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, scales, ragg all used; no dead imports + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Compact, readable, no fake functionality + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png via ragg::agg_png with correct dimensions + library_mastery: + score: 6 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Good use of scale_x_date(), dollar_format(), scale_color_manual() + with named breaks; could use pivot_longer + single aesthetic for more idiomatic + multi-series + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: Uses scale_x_date() and dollar_format() (ggplot2-ecosystem), but + chart structure not deeply ggplot2-distinctive + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - layer-composition + - custom-legend + patterns: + - data-generation + dataprep: + - time-series + styling: + - alpha-blending From 8a3ed1a3f3ea6afd900990034d0b789300c36828 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 05:15:36 +0000 Subject: [PATCH 3/4] fix(ggplot2): address review feedback for indicator-ema Attempt 1/3 - fixes based on AI review --- .../indicator-ema/implementations/r/ggplot2.R | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/plots/indicator-ema/implementations/r/ggplot2.R b/plots/indicator-ema/implementations/r/ggplot2.R index 45b3ea9d90..243e2e9e14 100644 --- a/plots/indicator-ema/implementations/r/ggplot2.R +++ b/plots/indicator-ema/implementations/r/ggplot2.R @@ -1,10 +1,10 @@ #' anyplot.ai #' indicator-ema: Exponential Moving Average (EMA) Indicator Chart #' Library: ggplot2 3.5.1 | R 4.4.1 -#' Quality: 84/100 | Created: 2026-05-19 library(ggplot2) library(scales) +library(tidyr) library(ragg) set.seed(42) @@ -17,48 +17,54 @@ INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" OKABE_ITO <- c( - "#009E73", # 1 - brand green (price) - "#D55E00", # 2 - vermillion (EMA 12) - "#0072B2" # 3 - blue (EMA 26) + "Price" = "#009E73", + "EMA (12)" = "#D55E00", + "EMA (26)" = "#0072B2" ) -# Data - synthetic daily prices for a tech stock over ~180 trading days -n_days <- 180 -dates <- seq.Date(as.Date("2024-01-02"), by = "day", length.out = n_days) -returns <- rnorm(n_days, mean = 0.0005, sd = 0.018) -close <- 150 * cumprod(1 + returns) +# Data +n_days <- 180 +dates <- seq.Date(as.Date("2024-01-02"), by = "day", length.out = n_days) +close <- 150 * cumprod(1 + rnorm(n_days, mean = 0.0005, sd = 0.018)) -calc_ema <- function(prices, period) { - k <- 2 / (period + 1) - ema <- numeric(length(prices)) - ema[1] <- prices[1] - for (i in seq(2, length(prices))) { - ema[i] <- prices[i] * k + ema[i - 1] * (1 - k) - } - ema -} +# Inline EMA via Reduce — no helper function needed +k12 <- 2 / (12 + 1) +k26 <- 2 / (26 + 1) +ema12 <- Reduce(function(e, x) x * k12 + e * (1 - k12), close, accumulate = TRUE) +ema26 <- Reduce(function(e, x) x * k26 + e * (1 - k26), close, accumulate = TRUE) -df <- data.frame( - date = dates, - close = close, - ema12 = calc_ema(close, 12), - ema26 = calc_ema(close, 26) -) +df <- data.frame(date = dates, close = close, ema12 = ema12, ema26 = ema26) + +# Detect EMA crossovers (sign change in ema12 - ema26) +diff_ema <- df$ema12 - df$ema26 +cross_idx <- which(diff(sign(diff_ema)) != 0) + 1 +crossovers <- df[cross_idx, , drop = FALSE] + +# Long form for idiomatic ggplot2 multi-series mapping +df_long <- pivot_longer(df, cols = c(close, ema12, ema26), + names_to = "series", values_to = "price") +df_long$series <- factor(df_long$series, + levels = c("close", "ema12", "ema26"), + labels = c("Price", "EMA (12)", "EMA (26)")) # Plot -p <- ggplot(df, aes(x = date)) + - geom_line(aes(y = close, color = "Price"), linewidth = 1.5, alpha = 0.80) + - geom_line(aes(y = ema12, color = "EMA (12)"), linewidth = 1.1) + - geom_line(aes(y = ema26, color = "EMA (26)"), linewidth = 1.1) + - scale_color_manual( - name = NULL, - values = c( - "Price" = OKABE_ITO[1], - "EMA (12)" = OKABE_ITO[2], - "EMA (26)" = OKABE_ITO[3] - ), - breaks = c("Price", "EMA (12)", "EMA (26)") - ) + +p <- ggplot(df_long, aes(x = date, y = price, color = series)) + + geom_vline(data = crossovers, aes(xintercept = date), + color = INK_SOFT, linetype = "dashed", + linewidth = 0.4, alpha = 0.7) + + geom_line(aes(linewidth = series, alpha = series)) + + geom_point(data = crossovers, aes(x = date, y = ema12), + shape = 21, size = 4, fill = PAGE_BG, + color = OKABE_ITO[["EMA (12)"]], stroke = 1.5, + inherit.aes = FALSE) + + scale_color_manual(name = NULL, values = OKABE_ITO, + breaks = c("Price", "EMA (12)", "EMA (26)")) + + scale_linewidth_manual( + values = c("Price" = 1.5, "EMA (12)" = 1.1, "EMA (26)" = 1.1), + guide = "none") + + scale_alpha_manual( + values = c("Price" = 0.80, "EMA (12)" = 1.0, "EMA (26)" = 1.0), + guide = "none") + scale_x_date(date_labels = "%b %Y", date_breaks = "2 months") + scale_y_continuous(labels = dollar_format()) + labs( @@ -77,7 +83,7 @@ p <- ggplot(df, aes(x = date)) + axis.title = element_text(color = INK, size = 20), axis.text = element_text(color = INK_SOFT, size = 16), axis.line = element_line(color = INK_SOFT, linewidth = 0.5), - plot.title = element_text(color = INK, size = 22, face = "bold"), + plot.title = element_text(color = INK, size = 24, face = "bold"), legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT), legend.text = element_text(color = INK_SOFT, size = 16), legend.key = element_rect(fill = ELEVATED_BG, color = NA), @@ -85,7 +91,6 @@ p <- ggplot(df, aes(x = date)) + plot.margin = margin(20, 20, 20, 20, unit = "pt") ) -# Save ggsave( filename = sprintf("plot-%s.png", THEME), plot = p, From 34a0a3c1e3fe5a4753c9d1060cd331184c06821b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 19 May 2026 05:20:45 +0000 Subject: [PATCH 4/4] chore(ggplot2): update quality score 92 and review feedback for indicator-ema --- .../indicator-ema/implementations/r/ggplot2.R | 1 + plots/indicator-ema/metadata/r/ggplot2.yaml | 158 +++++++++--------- 2 files changed, 82 insertions(+), 77 deletions(-) diff --git a/plots/indicator-ema/implementations/r/ggplot2.R b/plots/indicator-ema/implementations/r/ggplot2.R index 243e2e9e14..32bcebb959 100644 --- a/plots/indicator-ema/implementations/r/ggplot2.R +++ b/plots/indicator-ema/implementations/r/ggplot2.R @@ -1,6 +1,7 @@ #' anyplot.ai #' indicator-ema: Exponential Moving Average (EMA) Indicator Chart #' Library: ggplot2 3.5.1 | R 4.4.1 +#' Quality: 92/100 | Created: 2026-05-19 library(ggplot2) library(scales) diff --git a/plots/indicator-ema/metadata/r/ggplot2.yaml b/plots/indicator-ema/metadata/r/ggplot2.yaml index 64495b29ee..78f362c11b 100644 --- a/plots/indicator-ema/metadata/r/ggplot2.yaml +++ b/plots/indicator-ema/metadata/r/ggplot2.yaml @@ -2,7 +2,7 @@ library: ggplot2 language: r specification_id: indicator-ema created: '2026-05-19T05:00:14Z' -updated: '2026-05-19T05:06:35Z' +updated: '2026-05-19T05:20:44Z' generated_by: claude-sonnet workflow_run: 26077083850 issue: 3652 @@ -12,110 +12,109 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/indicator preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/indicator-ema/r/ggplot2/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: 84 +quality_score: 92 review: strengths: - - 'Perfect spec compliance: all required EMA features present with correct line - hierarchy' - - 'Solid theme adaptation: both renders fully legible with correct Okabe-Ito colors - and theme-adaptive chrome' - - 'Correct palette order: Price=#009E73, EMA12=#D55E00, EMA26=#0072B2 (canonical - positions 1-3)' - - 'Good visual refinement: Y-only subtle grid, no panel border, generous margins' + - Complete theme-adaptive chrome with zero dark-on-dark failures + - Idiomatic ggplot2 long-format pattern for clean multi-series mapping + - Technically correct EMA via Reduce with proper exponential smoothing factor + - Crossover detection and visualization adds real chart value + - Alpha and linewidth differentiation creates visual hierarchy between price and + EMA lines weaknesses: - - Plot title size is 22pt — raise to 24pt per style guide - - 'No visual storytelling: crossover points between EMA12 and EMA26 are not highlighted - despite spec mentioning this as a key feature' - - Design is clean but generic — needs visual emphasis (crossover markers, shaded - region, or trend annotation) to reach DE-01 score above 4 - - calc_ema() helper function breaks KISS flat structure — replace with inline Reduce() - or stats::filter() + - DE-01 could be elevated with typography polish or a focal annotation at the most + significant crossover + - Frequent crossover dashed verticals add visual noise; limiting to significant + crossovers would improve storytelling image_description: |- Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) - correct, not pure white - Chrome: Title "Tech Stock EMA · indicator-ema · r · ggplot2 · anyplot.ai" in dark bold ink, clearly readable. Axis labels "Date" and "Closing Price (USD)" in dark ink. Tick labels (Feb/Apr/Jun 2024 dates, $140-$175 dollar values) in dark gray — all readable. - Data: Three lines — green (#009E73) price line (lw=1.5, alpha=0.80), orange (#D55E00) EMA(12) line (lw=1.1), blue (#0072B2) EMA(26) line (lw=1.1). First series correctly uses brand green. Subtle horizontal grid lines visible. - Legibility verdict: PASS + Background: Warm off-white #FAF8F1 — correct light surface + Chrome: Title "Tech Stock EMA · indicator-ema · r · ggplot2 · anyplot.ai" in bold dark text clearly readable; axis labels "Closing Price (USD)" and "Date" readable; tick labels ($140-$180, Feb/Apr/Jun 2024) readable in INK_SOFT gray; legend box with subtle border at bottom showing Price/EMA(12)/EMA(26) + Data: Three lines — Price in teal #009E73 (slightly transparent), EMA(12) in orange #D55E00, EMA(26) in blue #0072B2; dashed vertical gray crossover lines; open circles at crossover points on EMA(12) + Legibility verdict: PASS — all text clearly readable against light background Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) - correct, not pure black - Chrome: Title and axis labels in near-white (#F0EFE8), tick labels and legend text in lighter gray (#B8B7B0) — all clearly readable against dark surface. No dark-on-dark failures. Legend box uses elevated dark (#242420) with subtle border. - Data: Identical colors to light render — green (#009E73) price, orange (#D55E00) EMA(12), blue (#0072B2) EMA(26). Data colors unchanged as required. - Legibility verdict: PASS + Background: Warm near-black #1A1A17 — correct dark surface + Chrome: Title and axis labels rendered in light off-white (#F0EFE8); tick labels in soft light gray (#B8B7B0); legend box using elevated dark background #242420 with light text — all readable + Data: Data line colors identical to light render (teal #009E73, orange #D55E00, blue #0072B2); crossover markers and dashed verticals adapt to lighter chrome + Legibility verdict: PASS — no dark-on-dark failures; all text clearly legible against dark background criteria_checklist: visual_quality: - score: 29 + score: 30 max: 30 items: - id: VQ-01 name: Text Legibility - score: 7 + score: 8 max: 8 passed: true - comment: Sizes explicitly set but plot.title=22pt is below the >=24pt guideline + comment: Title 24pt, axis labels 20pt, tick labels 16pt, legend 16pt all explicitly + set; readable in both themes - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No overlapping text elements + comment: No text collisions; line overlaps are inherent to EMA overlay chart + type - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Price line (lw=1.5) and EMA lines (lw=1.1) clearly visible + comment: Lines well-sized at linewidth 1.1-1.5; crossover markers visible + at size=4 - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Okabe-Ito palette is CVD-safe; green/orange/blue distinguishable - under deuteranopia/protanopia + comment: Okabe-Ito teal/orange/blue is CVD-safe; adequate contrast in both + themes - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: 16x9 at 300dpi fills canvas well; bottom legend placement works + comment: Plot fills canvas well; 20pt margins; legend well-positioned at bottom - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Y-axis includes units (USD); X-axis descriptive + comment: Closing Price (USD) with units; Date appropriate for x-axis - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: Price=#009E73 (pos 1), EMA12=#D55E00 (pos 2), EMA26=#0072B2 (pos - 3); backgrounds correct; chrome theme-correct in both renders + comment: 'Price=#009E73, EMA(12)=#D55E00, EMA(26)=#0072B2; backgrounds #FAF8F1/#1A1A17; + data colors identical across themes' design_excellence: - score: 10 + score: 13 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 4 + score: 5 max: 8 - passed: false - comment: Correctly configured with Okabe-Ito and good typography, but reads - as a well-tuned library default + passed: true + comment: 'Above generic defaults: alpha differentiation, hollow crossover + markers; intentional design choices but not publication-grade' - id: DE-02 name: Visual Refinement score: 4 max: 6 passed: true - comment: Y-only grid (lw=0.3), no minor grid, no panel border, theme_minimal - removes top/right spines, generous 20pt margins + comment: L-shaped frame via axis.line + panel.border blank; y-only major grid; + no minor grid; semantic crossover verticals - id: DE-03 name: Data Storytelling - score: 2 + score: 4 max: 6 - passed: false - comment: Mild hierarchy (price line thicker/translucent) but no visual emphasis - on crossover points or trend events + passed: true + comment: Crossover highlights create clear focal points; alpha/linewidth hierarchy + keeps EMAs prominent spec_compliance: score: 15 max: 15 @@ -125,27 +124,28 @@ review: score: 5 max: 5 passed: true - comment: EMA overlay chart with price line and two EMA lines + comment: Correct EMA overlay chart with price + two EMA periods - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: Price prominent (thicker), two EMA periods (12 and 26), EMA lines - thinner than price, legend labels each EMA + comment: Price line prominent; EMAs overlaid; distinct colors; thinner EMAs; + legend labels; crossover highlighting - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: Date on X, closing price (USD) on Y, all 180 points visible + comment: Date on x, closing price on y; all series visible across 180 trading + days - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title matches {Descriptive} · {spec-id} · {lang} · {lib} · anyplot.ai - format; legend labels correct + comment: Title matches {Descriptive}·{spec-id}·r·ggplot2·anyplot.ai format; + legend labels correct data_quality: score: 15 max: 15 @@ -155,32 +155,33 @@ review: score: 6 max: 6 passed: true - comment: Shows short and long EMAs, smoothing lag, uptrend and downtrend phases + comment: Price volatility, EMA smoothing, crossovers, uptrend and downtrend + phases all present - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Tech stock at $150 with plausible drift (0.05%) and volatility (1.8%) - — neutral financial scenario + comment: Tech Stock EMA neutral trading scenario; standard periods 12/26; + plausible price action - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Price range $135-$180 over 180 trading days is realistic; volatility - parameters factually appropriate + comment: Starting price $150, range $135-$180; daily drift/volatility realistic + for tech stocks code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 name: KISS Structure - score: 2 + score: 3 max: 3 - passed: false - comment: calc_ema() helper function defined; departs from flat Imports->Data->Plot->Save - structure + passed: true + comment: 'Linear flow: imports → tokens → data → EMA → crossovers → reshape + → plot → save' - id: CQ-02 name: Reproducibility score: 2 @@ -192,46 +193,49 @@ review: score: 2 max: 2 passed: true - comment: ggplot2, scales, ragg all used; no dead imports + comment: ggplot2, scales, tidyr, ragg all actively used - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Compact, readable, no fake functionality + comment: Idiomatic Reduce for inline EMA; pivot_longer for reshaping; named + color vector - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves plot-{THEME}.png via ragg::agg_png with correct dimensions + comment: sprintf plot-%s.png → plot-light/dark.png; ragg::agg_png device library_mastery: - score: 6 + score: 9 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 4 + score: 5 max: 5 passed: true - comment: Good use of scale_x_date(), dollar_format(), scale_color_manual() - with named breaks; could use pivot_longer + single aesthetic for more idiomatic - multi-series + comment: 'Exemplary: long-format data, named aesthetic vectors, scale_linewidth/alpha_manual + with guide=none, multi-dataset geoms with inherit.aes=FALSE' - id: LM-02 name: Distinctive Features - score: 2 + score: 4 max: 5 - passed: false - comment: Uses scale_x_date() and dollar_format() (ggplot2-ecosystem), but - chart structure not deeply ggplot2-distinctive - verdict: REJECTED + passed: true + comment: Grammar-driven multi-aesthetic scaling, multi-dataset geom layering, + factor levels for legend ordering — distinctively ggplot2 + verdict: APPROVED impl_tags: - dependencies: [] + dependencies: + - scales + - tidyr + - ragg techniques: - layer-composition - - custom-legend patterns: - data-generation + - wide-to-long dataprep: - time-series styling: