{"spec_id":"volcano-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' volcano-basic: Volcano Plot for Statistical Significance\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-09-09\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(ragg)\nlibrary(scales)\n\nset.seed(42)\n\n# --- Theme tokens -----------------------------------------------------------\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\nIMPRINT_UP   <- \"#AE3030\"  # Imprint semantic anchor for bad/loss/error — up-regulated\nIMPRINT_DOWN <- \"#4467A3\"  # Imprint blue — down-regulated\nIMPRINT_NS   <- INK_MUTED  # Imprint muted anchor — not significant\n\n# --- Data ---------------------------------------------------------------\n# Simulated differential gene expression results (RNA-seq treatment vs control)\nn_genes <- 2400\n\nlog2_fold_change <- c(\n  rnorm(n_genes * 0.88, mean = 0, sd = 0.6),\n  rnorm(n_genes * 0.06, mean = 2.4, sd = 0.9),\n  rnorm(n_genes * 0.06, mean = -2.4, sd = 0.9)\n)\nn_total <- length(log2_fold_change)\n\nneg_log10_pvalue <- pmax(\n  0,\n  0.55 * abs(log2_fold_change) + rgamma(n_total, shape = 1.6, rate = 1.4)\n)\n\ngenes <- data.frame(\n  gene             = paste0(\"GENE\", seq_len(n_total)),\n  log2_fold_change = log2_fold_change,\n  neg_log10_pvalue = neg_log10_pvalue\n)\n\nfc_cutoff <- 1\nsig_cutoff <- -log10(0.05)\n\ngenes <- genes %>%\n  mutate(\n    status = case_when(\n      neg_log10_pvalue >= sig_cutoff & log2_fold_change >= fc_cutoff  ~ \"Up-regulated\",\n      neg_log10_pvalue >= sig_cutoff & log2_fold_change <= -fc_cutoff ~ \"Down-regulated\",\n      TRUE                                                            ~ \"Not significant\"\n    ),\n    status = factor(status, levels = c(\"Not significant\", \"Up-regulated\", \"Down-regulated\"))\n  )\n\n# --- Plot -----------------------------------------------------------------\ntitle_text <- \"volcano-basic · r · ggplot2 · anyplot.ai\"\n\n# ggrepel is not installed in the CI R environment (see .github/actions/setup-r),\n# so the top-hit gene labels below use a manual rank-based x/y nudge fan-out\n# instead of geom_text_repel() collision avoidance.\ntop_labels <- genes %>%\n  filter(status != \"Not significant\") %>%\n  group_by(status) %>%\n  slice_max(order_by = neg_log10_pvalue, n = 3) %>%\n  arrange(status, desc(neg_log10_pvalue)) %>%\n  mutate(\n    rank_in_group = row_number(),\n    label_x = log2_fold_change + if_else(log2_fold_change > 0, 0.35, -0.35),\n    label_y = neg_log10_pvalue + 0.7 * rank_in_group\n  ) %>%\n  ungroup()\n\nn_up   <- sum(genes$status == \"Up-regulated\")\nn_down <- sum(genes$status == \"Down-regulated\")\n\np <- ggplot(genes, aes(x = log2_fold_change, y = neg_log10_pvalue, color = status)) +\n  geom_vline(xintercept = c(-fc_cutoff, fc_cutoff), color = INK_SOFT,\n             linewidth = 0.4, linetype = \"dashed\") +\n  geom_hline(yintercept = sig_cutoff, color = INK_SOFT,\n             linewidth = 0.4, linetype = \"dashed\") +\n  geom_point(data = filter(genes, status == \"Not significant\"),\n             size = 1.0, alpha = 0.22) +\n  geom_point(data = filter(genes, status != \"Not significant\"),\n             size = 2.2, alpha = 0.8) +\n  geom_text(\n    data = top_labels,\n    aes(label = gene, x = label_x, y = label_y),\n    hjust = 0.5,\n    vjust = 0,\n    size = 2.6,\n    fontface = \"italic\",\n    show.legend = FALSE\n  ) +\n  annotate(\"text\", x = Inf, y = Inf, label = paste0(n_up, \" up\"),\n           hjust = 1.15, vjust = 1.8, size = 2.8, fontface = \"bold\",\n           color = IMPRINT_UP) +\n  annotate(\"text\", x = -Inf, y = Inf, label = paste0(n_down, \" down\"),\n           hjust = -0.15, vjust = 1.8, size = 2.8, fontface = \"bold\",\n           color = IMPRINT_DOWN) +\n  scale_color_manual(\n    values = c(\n      \"Not significant\" = IMPRINT_NS,\n      \"Up-regulated\"     = IMPRINT_UP,\n      \"Down-regulated\"   = IMPRINT_DOWN\n    ),\n    name = \"Status\"\n  ) +\n  labs(\n    title = title_text,\n    x = \"Log2 Fold Change\",\n    y = expression(-Log[10] * \" (p-value)\")\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  = element_line(color = alpha(INK, 0.15), linewidth = 0.3),\n    panel.grid.minor  = element_blank(),\n    axis.title        = element_text(color = INK, size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    axis.ticks        = element_blank(),\n    axis.line         = element_line(color = INK_SOFT, linewidth = 0.3),\n    plot.title        = element_text(color = INK, size = 12),\n    legend.position   = \"top\",\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.title      = element_text(color = INK, size = 10),\n    legend.background = element_blank(),\n    legend.key        = element_blank()\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"}