{"spec_id":"stock-event-flags","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' stock-event-flags: Stock Chart with Event Flags\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 93/100 | Created: 2026-05-27\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(scales)\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\"\nGRID_COLOR  <- adjustcolor(INK, alpha.f = 0.12)\n\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                     \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\nEVENT_COLORS <- c(\n  \"Earnings\" = IMPRINT_PALETTE[3],  # blue — financial results\n  \"Dividend\" = IMPRINT_PALETTE[4],  # ochre — income\n  \"News\"     = IMPRINT_PALETTE[2],  # lavender — analyst / market news\n  \"Launch\"   = IMPRINT_PALETTE[6]   # cyan — product / tech\n)\n\n# Generate 190 trading days (weekdays only, Jan–Sep 2023)\nall_days     <- seq(as.Date(\"2023-01-03\"), as.Date(\"2023-12-29\"), by = \"day\")\ntrading_days <- all_days[!weekdays(all_days) %in% c(\"Saturday\", \"Sunday\")]\ntrading_days <- trading_days[seq_len(190)]\n\n# Geometric random walk starting at $150 (fictional tech company)\nreturns      <- rnorm(190, mean = 0.0005, sd = 0.015)\nclose_prices <- 150 * cumprod(1 + returns)\n\nprice_df <- data.frame(\n  date  = trading_days,\n  close = close_prices\n)\n\n# Seven corporate events across the period\nevent_df <- data.frame(\n  date       = as.Date(c(\n    \"2023-02-01\", \"2023-03-15\", \"2023-05-02\",\n    \"2023-06-20\", \"2023-07-27\", \"2023-08-15\",\n    \"2023-09-06\"\n  )),\n  event_type = c(\n    \"Earnings\", \"Dividend\", \"Earnings\",\n    \"News\",     \"Earnings\", \"Launch\",\n    \"Dividend\"\n  ),\n  label = c(\n    \"Q4 Beat\", \"Div $0.25\", \"Q1 Beat\",\n    \"Upgrade\", \"Q2 Miss\",   \"New Model\",\n    \"Div $0.28\"\n  ),\n  stringsAsFactors = FALSE\n)\n\n# Attach closing price at each event date\nevent_df <- event_df |>\n  left_join(price_df, by = \"date\")\n\n# Stagger flag heights in two rows to prevent overlap\nprice_max   <- max(price_df$close)\nprice_range <- price_max - min(price_df$close)\n\nevent_df <- event_df |>\n  mutate(\n    row_alt = row_number() %% 2,\n    flag_y  = price_max + price_range * (0.11 + row_alt * 0.09),\n    is_miss = label == \"Q2 Miss\"\n  )\n\ny_ceiling <- price_max + price_range * 0.36\n\n# Extract Q2 Miss row for targeted emphasis\nmiss_row   <- event_df[event_df$is_miss, ]\nmiss_date  <- miss_row$date\nmiss_close <- miss_row$close\n\n# Earnings-season shading bands (±8 calendar days around each earnings release)\nearnings_dates <- event_df$date[event_df$event_type == \"Earnings\"]\nearnings_bands <- data.frame(\n  xmin = earnings_dates - 8,\n  xmax = earnings_dates + 8\n)\n\ntitle_str <- \"stock-event-flags · r · ggplot2 · anyplot.ai\"\n\np <- ggplot() +\n  # Subtle earnings-window shading — highlights the quarterly reporting cadence\n  geom_rect(\n    data = earnings_bands,\n    aes(xmin = xmin, xmax = xmax, ymin = -Inf, ymax = Inf),\n    fill = IMPRINT_PALETTE[3], alpha = 0.06, inherit.aes = FALSE\n  ) +\n  # Price line (first categorical series = brand green)\n  geom_line(\n    data      = price_df,\n    aes(x = date, y = close),\n    color     = IMPRINT_PALETTE[1],\n    linewidth = 1.0\n  ) +\n  # Dashed connectors from price level to flag\n  geom_segment(\n    data      = event_df,\n    aes(x = date, xend = date, y = close, yend = flag_y, color = event_type),\n    linetype  = \"dashed\",\n    linewidth = 0.45,\n    alpha     = 0.75,\n    show.legend = FALSE\n  ) +\n  # Flag markers (shape encodes event type)\n  geom_point(\n    data = event_df,\n    aes(x = date, y = flag_y, color = event_type, shape = event_type),\n    size = 3.5\n  ) +\n  # Emphasis ring on Q2 Miss marker — matte red (semantic \"loss\") signals the miss\n  geom_point(\n    data = miss_row,\n    aes(x = date, y = flag_y),\n    shape = 1, size = 6.5, color = IMPRINT_PALETTE[5], stroke = 1.2,\n    inherit.aes = FALSE\n  ) +\n  # Short labels above flags (size 2.7 for mobile readability)\n  geom_text(\n    data  = event_df,\n    aes(x = date, y = flag_y, label = label, color = event_type),\n    size  = 2.7,\n    vjust = -0.65,\n    fontface    = \"bold\",\n    show.legend = FALSE\n  ) +\n  # Curved arrow callout: points to the price level at Q2 Miss, narrates the decline\n  annotate(\n    \"curve\",\n    x    = miss_date + 6,\n    xend = miss_date + 1,\n    y    = miss_close - price_range * 0.08,\n    yend = miss_close - price_range * 0.01,\n    color     = IMPRINT_PALETTE[5],\n    linewidth = 0.5,\n    curvature = -0.3,\n    arrow = arrow(length = unit(0.06, \"inches\"), type = \"closed\")\n  ) +\n  annotate(\n    \"text\",\n    x     = miss_date + 6,\n    y     = miss_close - price_range * 0.09,\n    label = \"Decline\\nfollows miss\",\n    color = IMPRINT_PALETTE[5],\n    size  = 2.2,\n    hjust = 0,\n    vjust = 1,\n    fontface = \"italic\"\n  ) +\n  scale_color_manual(values = EVENT_COLORS, name = \"Event\") +\n  scale_shape_manual(\n    values = c(\"Earnings\" = 17L, \"Dividend\" = 15L, \"News\" = 19L, \"Launch\" = 18L),\n    name   = \"Event\"\n  ) +\n  scale_x_date(date_breaks = \"2 months\", date_labels = \"%b '%y\") +\n  scale_y_continuous(\n    labels = scales::dollar_format(),\n    limits = c(NA_real_, y_ceiling),\n    expand = expansion(mult = c(0.04, 0.01))\n  ) +\n  labs(\n    title = title_str,\n    x     = \"Date\",\n    y     = \"Close Price (USD)\"\n  ) +\n  theme_minimal(base_size = 8) +\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 = GRID_COLOR,  linewidth = 0.5),\n    panel.grid.major.x = element_blank(),\n    panel.grid.minor   = element_blank(),\n    panel.border       = element_blank(),\n    axis.line          = element_line(color = INK_SOFT,    linewidth = 0.4),\n    axis.title         = element_text(color = INK,         size = 10),\n    axis.text          = element_text(color = INK_SOFT,    size = 8),\n    plot.title         = element_text(color = INK,         size = 12,\n                                      face = \"bold\"),\n    legend.background  = element_rect(fill = ELEVATED_BG,  color = INK_SOFT,\n                                      linewidth = 0.3),\n    legend.key         = element_rect(fill = PAGE_BG,      color = NA),\n    legend.text        = element_text(color = INK_SOFT,    size = 8),\n    legend.title       = element_text(color = INK,         size = 10),\n    legend.position    = \"bottom\",\n    legend.direction   = \"horizontal\",\n    plot.margin        = margin(15, 30, 10, 15)\n  )\n\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400\n)\n"}