{"spec_id":"indicator-ema","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' indicator-ema: Exponential Moving Average (EMA) Indicator Chart\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 92/100 | Created: 2026-05-19\n\nlibrary(ggplot2)\nlibrary(scales)\nlibrary(tidyr)\nlibrary(ragg)\n\nset.seed(42)\n\n# Theme tokens\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nELEVATED_BG <- if (THEME == \"light\") \"#FFFDF6\" else \"#242420\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\n\nIMPRINT <- c(\n    \"Price\"    = \"#009E73\",\n    \"EMA (12)\" = \"#C475FD\",\n    \"EMA (26)\" = \"#4467A3\"\n)\n\n# Data\nn_days <- 180\ndates  <- seq.Date(as.Date(\"2024-01-02\"), by = \"day\", length.out = n_days)\nclose  <- 150 * cumprod(1 + rnorm(n_days, mean = 0.0005, sd = 0.018))\n\n# Inline EMA via Reduce — no helper function needed\nk12   <- 2 / (12 + 1)\nk26   <- 2 / (26 + 1)\nema12 <- Reduce(function(e, x) x * k12 + e * (1 - k12), close, accumulate = TRUE)\nema26 <- Reduce(function(e, x) x * k26 + e * (1 - k26), close, accumulate = TRUE)\n\ndf <- data.frame(date = dates, close = close, ema12 = ema12, ema26 = ema26)\n\n# Detect EMA crossovers (sign change in ema12 - ema26)\ndiff_ema   <- df$ema12 - df$ema26\ncross_idx  <- which(diff(sign(diff_ema)) != 0) + 1\ncrossovers <- df[cross_idx, , drop = FALSE]\n\n# Long form for idiomatic ggplot2 multi-series mapping\ndf_long <- pivot_longer(df, cols = c(close, ema12, ema26),\n                        names_to = \"series\", values_to = \"price\")\ndf_long$series <- factor(df_long$series,\n    levels = c(\"close\", \"ema12\", \"ema26\"),\n    labels = c(\"Price\", \"EMA (12)\", \"EMA (26)\"))\n\n# Plot\np <- ggplot(df_long, aes(x = date, y = price, color = series)) +\n    geom_vline(data = crossovers, aes(xintercept = date),\n               color = INK_SOFT, linetype = \"dashed\",\n               linewidth = 0.4, alpha = 0.7) +\n    geom_line(aes(linewidth = series, alpha = series)) +\n    geom_point(data = crossovers, aes(x = date, y = ema12),\n               shape = 21, size = 4, fill = PAGE_BG,\n               color = IMPRINT[[\"EMA (12)\"]], stroke = 1.5,\n               inherit.aes = FALSE) +\n    scale_color_manual(name = NULL, values = IMPRINT,\n                       breaks = c(\"Price\", \"EMA (12)\", \"EMA (26)\")) +\n    scale_linewidth_manual(\n        values = c(\"Price\" = 1.5, \"EMA (12)\" = 1.1, \"EMA (26)\" = 1.1),\n        guide  = \"none\") +\n    scale_alpha_manual(\n        values = c(\"Price\" = 0.80, \"EMA (12)\" = 1.0, \"EMA (26)\" = 1.0),\n        guide  = \"none\") +\n    scale_x_date(date_labels = \"%b %Y\", date_breaks = \"2 months\") +\n    scale_y_continuous(labels = dollar_format()) +\n    labs(\n        title = \"Tech Stock EMA · indicator-ema · r · ggplot2 · anyplot.ai\",\n        x     = \"Date\",\n        y     = \"Closing Price (USD)\"\n    ) +\n    theme_minimal(base_size = 14) +\n    theme(\n        plot.background    = element_rect(fill = PAGE_BG,     color = PAGE_BG),\n        panel.background   = element_rect(fill = PAGE_BG,     color = NA),\n        panel.grid.major.y = element_line(color = INK_SOFT,   linewidth = 0.3),\n        panel.grid.major.x = element_blank(),\n        panel.grid.minor   = element_blank(),\n        panel.border       = element_blank(),\n        axis.title         = element_text(color = INK,        size = 20),\n        axis.text          = element_text(color = INK_SOFT,   size = 16),\n        axis.line          = element_line(color = INK_SOFT,   linewidth = 0.5),\n        plot.title         = element_text(color = INK,        size = 24, face = \"bold\"),\n        legend.background  = element_rect(fill = ELEVATED_BG, color = INK_SOFT),\n        legend.text        = element_text(color = INK_SOFT,   size = 16),\n        legend.key         = element_rect(fill = ELEVATED_BG, color = NA),\n        legend.position    = \"bottom\",\n        plot.margin        = margin(20, 20, 20, 20, unit = \"pt\")\n    )\n\nggsave(\n    filename = sprintf(\"plot-%s.png\", THEME),\n    plot     = p,\n    device   = ragg::agg_png,\n    width    = 16,\n    height   = 9,\n    units    = \"in\",\n    dpi      = 300\n)\n"}