{"spec_id":"scatter-matrix","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' scatter-matrix: Scatter Plot Matrix\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 88/100 | Created: 2026-09-09\n\nlibrary(ggplot2)\nlibrary(dplyr)\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(\n  \"#009E73\", # 1 - first categorical series (brand green)\n  \"#C475FD\", # 2 - lavender\n  \"#4467A3\", # 3 - blue\n  \"#BD8233\", # 4 - ochre\n  \"#AE3030\", # 5 - matte red\n  \"#2ABCCD\", # 6 - cyan\n  \"#954477\", # 7 - rose\n  \"#99B314\"  # 8 - lime\n)\n\n# --- Data ---------------------------------------------------------------\n# Four flower measurements from the classic iris dataset, compared pairwise\n# across the three species so correlations and cluster separation both show.\nvars <- c(\"Sepal.Length\", \"Sepal.Width\", \"Petal.Length\", \"Petal.Width\")\nvar_labels <- c(\n  \"Sepal.Length\" = \"Sepal Length (cm)\",\n  \"Sepal.Width\"  = \"Sepal Width (cm)\",\n  \"Petal.Length\" = \"Petal Length (cm)\",\n  \"Petal.Width\"  = \"Petal Width (cm)\"\n)\n\npairs_list <- list()\nidx <- 1\nfor (rv in vars) {\n  for (cv in vars) {\n    if (rv == cv) next\n    pairs_list[[idx]] <- tibble::tibble(\n      row_var = factor(rv, levels = vars),\n      col_var = factor(cv, levels = vars),\n      x       = iris[[cv]],\n      y       = iris[[rv]],\n      species = iris$Species\n    )\n    idx <- idx + 1\n  }\n}\npairs_df <- bind_rows(pairs_list)\n\n# Diagonal density curves. Each species' KDE is (1) estimated only over that\n# variable's own data range (`from`/`to`), so the shared free x-scale for the\n# column is never pulled wider than the real min/max, and (2) normalized to\n# its OWN peak (not a global max across species) before being rescaled into\n# the row's data-value range, so every species curve reaches the same\n# relative height regardless of how much taller one species' peak density is\n# than another's (e.g. Petal.Width: setosa's peak is ~6x virginica's).\ndiag_list <- list()\nidx <- 1\nfor (v in vars) {\n  var_data  <- iris[[v]]\n  var_range <- range(var_data)\n  dens_df <- bind_rows(lapply(levels(iris$Species), function(sp) {\n    d <- density(var_data[iris$Species == sp], from = var_range[1], to = var_range[2])\n    tibble::tibble(x = d$x, dens = d$y / max(d$y), species = sp)\n  }))\n  diag_list[[idx]] <- dens_df %>%\n    mutate(\n      row_var = factor(v, levels = vars),\n      col_var = factor(v, levels = vars),\n      ymin    = var_range[1],\n      ymax    = var_range[1] + dens * diff(var_range) * 0.9\n    )\n  idx <- idx + 1\n}\ndiag_df <- bind_rows(diag_list)\n\n# --- Plot -----------------------------------------------------------------\np <- ggplot() +\n  geom_point(\n    data  = pairs_df,\n    aes(x = x, y = y, color = species),\n    size = 1.3, alpha = 0.6\n  ) +\n  geom_ribbon(\n    data  = diag_df,\n    aes(x = x, ymin = ymin, ymax = ymax, fill = species, color = species, group = species),\n    alpha = 0.35, linewidth = 0.5\n  ) +\n  facet_grid(\n    row_var ~ col_var,\n    scales   = \"free\",\n    switch   = \"both\",\n    labeller = labeller(row_var = as_labeller(var_labels), col_var = as_labeller(var_labels))\n  ) +\n  scale_color_manual(values = IMPRINT_PALETTE, name = \"Species\") +\n  scale_fill_manual(values = IMPRINT_PALETTE, name = \"Species\") +\n  labs(\n    title = \"scatter-matrix · r · ggplot2 · anyplot.ai\",\n    x = NULL, y = NULL\n  ) +\n  theme_minimal(base_size = 7) +\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 = INK, linewidth = 0.15),\n    panel.grid.minor   = element_blank(),\n    panel.spacing      = unit(0.4, \"lines\"),\n    axis.title         = element_blank(),\n    axis.text          = element_text(color = INK_SOFT, size = 6.5),\n    axis.ticks         = element_line(color = INK_SOFT, linewidth = 0.2),\n    strip.placement    = \"outside\",\n    strip.background   = element_rect(fill = ELEVATED_BG, color = NA),\n    strip.text         = element_text(color = INK, size = 8, face = \"plain\"),\n    plot.title         = element_text(color = INK, size = 12, face = \"bold\", hjust = 0.5),\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 = 9)\n  )\n\n# --- Save -------------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 6,\n  height   = 6,\n  units    = \"in\",\n  dpi      = 400\n)\n"}