{"spec_id":"streamgraph-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' streamgraph-basic: Basic Stream Graph\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 88/100 | Created: 2026-08-05\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(tidyr)\nlibrary(tibble)\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\"\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\")\n\n# --- Data: monthly streaming hours (thousands) by music genre over 2 years --\nmonths      <- seq(as.Date(\"2022-01-01\"), by = \"month\", length.out = 24)\ngenre_params <- tibble::tribble(\n  ~genre,        ~level, ~trend, ~amp, ~phase,\n  \"Pop\",          420,     2.5,   80,  0.3,\n  \"Hip-Hop\",      310,     4.0,   65,  1.8,\n  \"Electronic\",   220,     5.5,   50,  3.4,\n  \"Rock\",         180,    -1.5,   35,  0.9,\n  \"Jazz\",          90,     0.8,   20,  5.1\n)\n\nraw_data <- genre_params %>%\n  tidyr::crossing(month_idx = 0:23) %>%\n  mutate(\n    month = months[month_idx + 1],\n    value = pmax(\n      level + trend * month_idx + amp * sin(2 * pi * month_idx / 12 + phase) +\n        rnorm(n(), 0, level * 0.04),\n      5\n    )\n  ) %>%\n  select(genre, month, value)\n\n# Stack order + palette assignment: largest total genre gets the brand color\ngenre_order <- raw_data %>%\n  group_by(genre) %>%\n  summarise(total = sum(value), .groups = \"drop\") %>%\n  arrange(desc(total)) %>%\n  pull(genre)\n\nraw_data <- raw_data %>% mutate(genre = factor(genre, levels = genre_order))\n\n# --- Smooth interpolation (natural cubic spline) for flowing curves --------\ntime_numeric <- as.numeric(months)\nfine_time    <- seq(min(time_numeric), max(time_numeric), length.out = 200)\n\ninterp_data <- lapply(genre_order, function(g) {\n  sub <- raw_data %>% filter(genre == g) %>% arrange(month)\n  fit <- spline(x = as.numeric(sub$month), y = sub$value, xout = fine_time, method = \"natural\")\n  tibble(genre = g, time_num = fit$x, value = pmax(fit$y, 0))\n}) %>%\n  bind_rows() %>%\n  mutate(genre = factor(genre, levels = genre_order))\n\n# --- Symmetric (silhouette) baseline: centered around the x-axis -----------\nstream_data <- interp_data %>%\n  group_by(time_num) %>%\n  arrange(genre, .by_group = TRUE) %>%\n  mutate(\n    ymax = cumsum(value) - sum(value) / 2,\n    ymin = ymax - value\n  ) %>%\n  ungroup() %>%\n  mutate(time = as.Date(time_num, origin = \"1970-01-01\"))\n\n# --- Plot ---------------------------------------------------------------\np <- ggplot(stream_data, aes(x = time, ymin = ymin, ymax = ymax, fill = genre)) +\n  geom_ribbon(color = PAGE_BG, linewidth = 0.15, alpha = 0.92) +\n  scale_fill_manual(values = setNames(IMPRINT_PALETTE, genre_order), name = \"Genre\") +\n  scale_x_date(date_labels = \"%b %Y\", date_breaks = \"4 months\",\n               expand = expansion(mult = c(0.01, 0.05))) +\n  labs(\n    title    = \"streamgraph-basic · r · ggplot2 · anyplot.ai\",\n    subtitle = \"Electronic and Hip-Hop trend upward while Rock cools off\",\n    x        = \"Month\",\n    y        = \"Streaming hours (thousands)\"\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        = element_blank(),\n    axis.title.x      = element_text(color = INK, size = 10),\n    axis.title.y      = element_text(color = INK, size = 8),\n    axis.text.x       = element_text(color = INK_SOFT, size = 8),\n    axis.text.y       = element_blank(),\n    axis.ticks        = element_blank(),\n    axis.line.x       = element_line(color = INK_SOFT, linewidth = 0.3),\n    plot.title        = element_text(color = INK, size = 12),\n    plot.subtitle     = element_text(color = INK_SOFT, size = 9),\n    legend.position   = \"bottom\",\n    legend.background = element_rect(fill = ELEVATED_BG, color = NA),\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.title      = element_text(color = INK, size = 10)\n  )\n\n# --- Save --------------------------------------------------------------\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"}