{"spec_id":"mosaic-categorical","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' mosaic-categorical: Mosaic Plot for Categorical Association Analysis\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 86/100 | Created: 2026-05-19\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(scales)\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   <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                 \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Data: product category vs customer satisfaction (retail survey, n = 500)\ncategory_levels     <- c(\"Electronics\", \"Clothing\", \"Food\", \"Books\")\nsatisfaction_levels <- c(\"Satisfied\", \"Neutral\", \"Unsatisfied\")\n\nfreq_data <- data.frame(\n  category     = factor(rep(category_levels, each = 3), levels = category_levels),\n  satisfaction = factor(rep(satisfaction_levels, times = 4), levels = satisfaction_levels),\n  freq         = c(\n    80, 50, 20,  # Electronics: n = 150\n    60, 55, 25,  # Clothing:    n = 140\n    65, 35, 20,  # Food:        n = 120\n    70, 15,  5   # Books:       n = 90\n  )\n)\n\n# Marginal totals per category — determines column widths\ncategory_totals <- freq_data %>%\n  group_by(category) %>%\n  summarize(total = sum(freq), .groups = \"drop\") %>%\n  arrange(category) %>%\n  mutate(\n    width_prop = total / sum(total),\n    xmax       = cumsum(width_prop),\n    xmin       = lag(xmax, default = 0),\n    xmin_plot  = xmin + 0.005,\n    xmax_plot  = xmax - 0.005,\n    x_center   = (xmin_plot + xmax_plot) / 2\n  )\n\n# Conditional heights within each column\nplot_data <- freq_data %>%\n  left_join(category_totals, by = \"category\") %>%\n  arrange(category, satisfaction) %>%\n  group_by(category) %>%\n  mutate(\n    height_prop = freq / total,\n    ymax        = cumsum(height_prop)\n  ) %>%\n  mutate(\n    ymin      = lag(ymax, default = 0),\n    ymin_plot = ymin + 0.003,\n    ymax_plot = ymax - 0.003\n  ) %>%\n  ungroup()\n\n# Plot\np <- ggplot(plot_data) +\n  geom_rect(\n    aes(\n      xmin = xmin_plot, xmax = xmax_plot,\n      ymin = ymin_plot, ymax = ymax_plot,\n      fill = satisfaction\n    ),\n    color = PAGE_BG, linewidth = 0.6\n  ) +\n  scale_fill_manual(\n    values = setNames(IMPRINT[1:3], satisfaction_levels),\n    name   = \"Customer\\nSatisfaction\"\n  ) +\n  scale_x_continuous(\n    breaks = category_totals$x_center,\n    labels = paste0(category_totals$category, \"\\n(n = \", category_totals$total, \")\"),\n    expand = c(0.01, 0.01)\n  ) +\n  scale_y_continuous(\n    labels = percent_format(accuracy = 1),\n    expand = c(0.01, 0.01)\n  ) +\n  labs(\n    title = \"Customer Satisfaction by Product Category · mosaic-categorical · r · ggplot2 · anyplot.ai\",\n    x     = \"Product Category  (column width proportional to group size)\",\n    y     = \"Proportion of Respondents\"\n  ) +\n  theme_minimal(base_size = 14) +\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_text(color = INK,      size = 18),\n    axis.text.x       = element_text(color = INK_SOFT, size = 14),\n    axis.text.y       = element_text(color = INK_SOFT, size = 14),\n    axis.ticks        = element_blank(),\n    plot.title        = element_text(color = INK,      size = 18, hjust = 0.5),\n    legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT),\n    legend.text       = element_text(color = INK_SOFT, size = 14),\n    legend.title      = element_text(color = INK,      size = 16),\n    plot.margin       = margin(40, 60, 40, 40)\n  )\n\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 16,\n  height   = 9,\n  units    = \"in\",\n  dpi      = 300\n)\n"}