{"spec_id":"subplot-mosaic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' subplot-mosaic: Mosaic Subplot Layout with Varying Sizes\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 88/100 | Created: 2026-09-09\n\nlibrary(ggplot2)\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\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                     \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\nBRAND <- IMPRINT_PALETTE[1]\n\n# --- Data ----------------------------------------------------------------\n# Website analytics dashboard, mosaic layout \"AAA;BBC;DEF\"\ndates <- seq(as.Date(\"2024-06-01\"), by = \"day\", length.out = 30)\npage_views <- pmax(500, round(3000 + cumsum(rnorm(30, mean = 10, sd = 120))))\noverview_df <- data.frame(date = dates, page_views = page_views)\n\ndevices <- factor(c(\"Desktop\", \"Mobile\", \"Tablet\"), levels = c(\"Desktop\", \"Mobile\", \"Tablet\"))\ndevice_visits <- c(12500, 8700, 2100)\ndevice_df <- data.frame(device = devices, visits = device_visits)\n\npages <- c(\"Home\", \"Blog\", \"Product\", \"Pricing\", \"Docs\", \"Support\")\navg_session_sec <- c(145, 210, 95, 130, 260, 175) + rnorm(6, 0, 10)\nbounce_rate_pct <- c(38, 22, 55, 47, 18, 33) + rnorm(6, 0, 3)\npage_pageviews <- c(9800, 4200, 3100, 2600, 2000, 1400)\npages_df <- data.frame(\n  page = pages,\n  avg_session_sec = avg_session_sec,\n  bounce_rate_pct = bounce_rate_pct,\n  pageviews = page_pageviews\n)\n\nrecent_days <- dates[17:30]\nbounce_trend <- pmax(10, 45 - seq(0, 13) * 0.6 + rnorm(14, 0, 2))\nsession_trend <- 150 + seq(0, 13) * 3 + rnorm(14, 0, 8)\nconversion_trend <- pmax(0, 2.1 + seq(0, 13) * 0.05 + rnorm(14, 0, 0.15))\nbounce_df <- data.frame(date = recent_days, value = bounce_trend)\nsession_df <- data.frame(date = recent_days, value = session_trend)\nconversion_df <- data.frame(date = recent_days, value = conversion_trend)\n\n# --- Shared chrome -----------------------------------------------------------\nbase_chrome <- 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.minor  = element_blank(),\n    panel.grid.major  = element_line(color = INK_SOFT, linewidth = 0.15),\n    axis.title        = element_text(color = INK),\n    axis.text         = element_text(color = INK_SOFT),\n    plot.title        = element_text(color = INK, face = \"plain\", size = 9, hjust = 0),\n    legend.position   = \"none\",\n    plot.margin       = margin(8, 8, 8, 8, unit = \"pt\")\n  )\n\n# --- Panel A: overview (wide, top row) ---------------------------------------\npanel_a <- ggplot(overview_df, aes(date, page_views)) +\n  geom_area(fill = BRAND, alpha = 0.15) +\n  geom_line(color = BRAND, linewidth = 1.1) +\n  labs(title = \"Daily page views\", x = NULL, y = \"Views\") +\n  scale_y_continuous(labels = scales::comma) +\n  base_chrome +\n  theme(\n    panel.grid.major.x = element_blank(),\n    axis.title.y = element_text(size = 9), axis.text = element_text(size = 8)\n  )\n\n# --- Panel B: device breakdown (medium, spans 2 cols) -------------------------\npanel_b <- ggplot(device_df, aes(device, visits)) +\n  geom_col(fill = BRAND, width = 0.6) +\n  geom_text(\n    aes(label = scales::comma(visits)), vjust = -0.4, size = 2.6, color = INK\n  ) +\n  labs(title = \"Traffic by device\", x = NULL, y = \"Visits\") +\n  scale_y_continuous(labels = scales::comma, expand = expansion(mult = c(0, 0.3))) +\n  coord_cartesian(clip = \"off\") +\n  base_chrome +\n  theme(\n    panel.grid.major.x = element_blank(),\n    axis.title.y = element_text(size = 9), axis.text = element_text(size = 8)\n  )\n\n# --- Panel C: page engagement (medium) ----------------------------------------\npanel_c <- ggplot(pages_df, aes(avg_session_sec, bounce_rate_pct)) +\n  geom_point(aes(size = pageviews), color = BRAND, alpha = 0.75) +\n  labs(title = \"Page engagement\", x = \"Avg session (s)\", y = \"Bounce (%)\") +\n  scale_size_area(\n    name = \"Pageviews\", max_size = 8,\n    breaks = c(2000, 5000, 9000), labels = scales::comma\n  ) +\n  scale_y_continuous(expand = expansion(mult = c(0.05, 0.15))) +\n  coord_cartesian(clip = \"off\") +\n  base_chrome +\n  theme(\n    axis.title         = element_text(size = 8),\n    axis.text          = element_text(size = 7),\n    legend.position    = \"right\",\n    legend.background  = element_rect(fill = PAGE_BG, color = NA),\n    legend.text        = element_text(size = 6, color = INK_SOFT),\n    legend.title        = element_text(size = 6.5, color = INK),\n    legend.key.size     = unit(8, \"pt\"),\n    legend.margin       = margin(0, 0, 0, 0)\n  )\n\n# --- Panels D/E/F: small metric trends (bottom row) ---------------------------\nsmall_chrome <- base_chrome +\n  theme(\n    panel.grid.major.x = element_blank(),\n    axis.title  = element_blank(),\n    axis.text.y = element_text(size = 7.5),\n    axis.text.x = element_text(size = 7.5),\n    plot.title  = element_text(size = 8.5)\n  )\n\npeak_label <- function(df) {\n  df[which.max(df$value), , drop = FALSE]\n}\n\npanel_d <- ggplot(bounce_df, aes(date, value)) +\n  geom_line(color = BRAND, linewidth = 1.0) +\n  geom_point(color = BRAND, size = 2.2) +\n  labs(title = \"Bounce rate (%)\") +\n  small_chrome\n\npanel_e <- ggplot(session_df, aes(date, value)) +\n  geom_line(color = BRAND, linewidth = 1.0) +\n  geom_point(color = BRAND, size = 2.2) +\n  labs(title = \"Avg session (s)\") +\n  small_chrome\n\nconversion_peak <- peak_label(conversion_df)\npanel_f <- ggplot(conversion_df, aes(date, value)) +\n  geom_line(color = BRAND, linewidth = 1.0) +\n  geom_point(color = BRAND, size = 2.2) +\n  geom_point(data = conversion_peak, color = BRAND, size = 3.6) +\n  geom_text(\n    data = conversion_peak, aes(label = sprintf(\"%.1f%%\", value)),\n    vjust = 2.4, size = 2.4, color = INK, fontface = \"bold\"\n  ) +\n  labs(title = \"Conversion rate (%)\") +\n  scale_y_continuous(expand = expansion(mult = c(0.1, 0.2))) +\n  coord_cartesian(clip = \"off\") +\n  small_chrome\n\n# --- Mosaic assembly -----------------------------------------------------\n# Layout string:  \"AAA\n#                  BBC\n#                  DEF\"\n# patchwork::wrap_plots() composites full plot grobs (including each panel's\n# own legend, and any coord_cartesian(clip = \"off\") overflow) instead of\n# gridExtra::arrangeGrob(), which drops per-panel legends and re-clips\n# overflow at each fixed grid-cell boundary.\ntitle_text <- \"subplot-mosaic · r · ggplot2 · anyplot.ai\"\n\nmosaic <- wrap_plots(\n  A = panel_a, B = panel_b, C = panel_c,\n  D = panel_d, E = panel_e, F = panel_f,\n  design = \"AAA\\nBBC\\nDEF\",\n  heights = c(1.8, 1.3, 1)\n) +\n  plot_annotation(\n    title = title_text,\n    theme = theme(\n      plot.title      = element_text(size = 12, face = \"bold\", color = INK, hjust = 0),\n      plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG)\n    )\n  )\n\n# --- Save ----------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = mosaic,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400,\n  bg       = PAGE_BG\n)\n"}