{"spec_id":"upset-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' upset-basic: UpSet Plot for Multi-Set Intersection Analysis\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 93/100 | Created: 2026-09-09\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(patchwork)\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\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\n\nBRAND <- \"#009E73\"  # Imprint palette position 1 — always first series\nBLUE  <- \"#4467A3\"  # Imprint palette position 3 — high end of imprint_seq\n\n# --- Data: differential-expression gene sets across genomic assays -----\n# A shared \"true DE signal\" liability per gene correlates detection across\n# assays, so genes with strong signal tend to surface in several assays at\n# once — the realistic pattern that gives an UpSet plot higher-degree bars.\nn_genes  <- 2500\nset_names_all <- c(\"RNA-seq\", \"ChIP-seq\", \"ATAC-seq\", \"Proteomics\", \"Methylation\", \"SNP-array\")\nset_intercepts <- c(-0.9, -1.3, -1.0, -1.7, -1.9, -2.2)\nset_slopes     <- c(1.7, 1.4, 1.6, 1.3, 1.2, 1.0)\n\nliability <- rnorm(n_genes)\nmembership <- vapply(seq_along(set_names_all), function(i) {\n  rbinom(n_genes, 1, plogis(set_intercepts[i] + set_slopes[i] * liability))\n}, integer(n_genes))\ncolnames(membership) <- set_names_all\nmembership <- membership[rowSums(membership) > 0, ]\n\ncombo_key <- apply(membership, 1, function(row) paste(set_names_all[row == 1], collapse = \"|\"))\n\nintersections <- tibble(combo = combo_key) %>%\n  count(combo, name = \"size\") %>%\n  arrange(desc(size)) %>%\n  slice_head(n = 15) %>%\n  mutate(\n    intersection_id = row_number(),\n    degree = sapply(strsplit(combo, \"\\\\|\"), length)\n  )\nn_int <- nrow(intersections)\nmember_sets <- strsplit(intersections$combo, \"\\\\|\")\n\nset_sizes <- tibble(set = set_names_all, size = colSums(membership)) %>%\n  arrange(desc(size))\nset_order <- set_sizes$set\nset_sizes$set <- factor(set_sizes$set, levels = rev(set_order))\n\nmatrix_df <- expand.grid(\n  intersection_id = intersections$intersection_id,\n  set = set_names_all,\n  stringsAsFactors = FALSE\n) %>%\n  as_tibble() %>%\n  rowwise() %>%\n  mutate(member = set %in% member_sets[[intersection_id]]) %>%\n  ungroup() %>%\n  left_join(intersections %>% select(intersection_id, degree), by = \"intersection_id\") %>%\n  mutate(set = factor(set, levels = rev(set_order)))\n\nrow_stripes <- tibble(set = factor(set_order, levels = rev(set_order))) %>%\n  mutate(y_pos = as.integer(set)) %>%\n  filter(y_pos %% 2 == 0)\n\ndegree_breaks <- seq(min(intersections$degree), max(intersections$degree), by = 1)\n\n# --- Panel A: horizontal set-size bars (left) ---------------------------\np_left <- ggplot(set_sizes, aes(x = size, y = set)) +\n  geom_col(fill = BRAND, width = 0.65) +\n  scale_x_reverse(expand = expansion(mult = c(0.08, 0))) +\n  labs(x = \"Set Size\") +\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_blank(),\n    panel.grid.minor  = element_blank(),\n    panel.grid.major.x = element_line(color = INK, linewidth = 0.2),\n    axis.title.y      = element_blank(),\n    axis.text.y       = element_text(color = INK, size = 9, hjust = 1),\n    axis.ticks.y      = element_blank(),\n    axis.title.x      = element_text(color = INK, size = 10),\n    axis.text.x       = element_text(color = INK_SOFT, size = 8),\n    axis.ticks.x      = element_blank(),\n    plot.margin       = margin(t = 2, r = 2, b = 6, l = 6)\n  )\n\n# --- Panel B: intersection-size bars (top) ------------------------------\np_top <- ggplot(intersections, aes(x = factor(intersection_id, levels = seq_len(n_int)), y = size)) +\n  geom_col(fill = BRAND, width = 0.65) +\n  scale_y_continuous(expand = expansion(mult = c(0, 0.08))) +\n  labs(y = \"Elements\") +\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.x = element_blank(),\n    panel.grid.minor  = element_blank(),\n    panel.grid.major.y = element_line(color = INK, linewidth = 0.2),\n    axis.title.x      = element_blank(),\n    axis.text.x       = element_blank(),\n    axis.ticks.x      = element_blank(),\n    axis.title.y      = element_text(color = INK, size = 10),\n    axis.text.y       = element_text(color = INK_SOFT, size = 8),\n    axis.ticks.y      = element_blank(),\n    plot.margin       = margin(t = 6, r = 2, b = 2, l = 6)\n  )\n\n# --- Panel C: dot matrix (set membership per intersection) -------------\np_matrix <- ggplot() +\n  geom_rect(\n    data = row_stripes,\n    aes(ymin = y_pos - 0.5, ymax = y_pos + 0.5, xmin = -Inf, xmax = Inf),\n    inherit.aes = FALSE, fill = ELEVATED_BG, color = NA\n  ) +\n  geom_point(\n    data = matrix_df,\n    aes(x = factor(intersection_id, levels = seq_len(n_int)), y = set),\n    color = INK_MUTED, alpha = 0.28, size = 2.6\n  ) +\n  geom_line(\n    data = filter(matrix_df, member),\n    aes(x = factor(intersection_id, levels = seq_len(n_int)), y = set, group = intersection_id, color = degree),\n    linewidth = 1.1\n  ) +\n  geom_point(\n    data = filter(matrix_df, member),\n    aes(x = factor(intersection_id, levels = seq_len(n_int)), y = set, color = degree),\n    size = 3.4\n  ) +\n  scale_color_gradient(low = BRAND, high = BLUE, name = \"Sets combined\", breaks = degree_breaks) +\n  scale_x_discrete(expand = expansion(add = 0.6)) +\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        = element_blank(),\n    axis.text         = element_blank(),\n    axis.ticks        = element_blank(),\n    legend.position    = \"right\",\n    legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT),\n    legend.text        = element_text(color = INK_SOFT, size = 8),\n    legend.title       = element_text(color = INK, size = 9),\n    plot.margin        = margin(t = 2, r = 6, b = 6, l = 2)\n  )\n\n# --- Compose: blank corner, top bars, left bars, matrix -----------------\ndesign <- \"\n#BBBBB\n#BBBBB\nACCCCC\nACCCCC\nACCCCC\nACCCCC\nACCCCC\n\"\n\ncombined <- p_left + p_top + p_matrix +\n  patchwork::plot_layout(design = design) +\n  patchwork::plot_annotation(\n    title = \"upset-basic · r · ggplot2 · anyplot.ai\",\n    theme = theme(\n      plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),\n      plot.title       = element_text(color = INK, size = 12, margin = margin(b = 8))\n    )\n  )\n\n# --- Save -----------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = combined,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400,\n  bg       = PAGE_BG\n)\n"}