{"spec_id":"andrews-curves","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' andrews-curves: Andrews Curves for Multivariate Data\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-09-02\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(tidyr)\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\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nGRID_COLOR  <- scales::alpha(INK, 0.15)\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                     \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# --- Data -----------------------------------------------------------------\n# Iris sepal/petal measurements, z-score normalized so no single dimension\n# dominates the Fourier expansion (see specification \"Notes\").\nmeasurements <- iris %>%\n  select(Sepal.Length, Sepal.Width, Petal.Length, Petal.Width) %>%\n  scale() %>%\n  as.matrix()\n\nspecies <- iris$Species\nn_vars  <- ncol(measurements)\n\n# Andrews curve Fourier basis:\n# f(t) = x1/sqrt(2) + x2 sin(t) + x3 cos(t) + x4 sin(2t) + x5 cos(2t) + ...\nt_vals <- seq(-pi, pi, length.out = 200)\nbasis  <- matrix(0, nrow = length(t_vals), ncol = n_vars)\nbasis[, 1] <- 1 / sqrt(2)\nfor (k in 2:n_vars) {\n  harmonic   <- k %/% 2\n  basis[, k] <- if (k %% 2 == 0) sin(harmonic * t_vals) else cos(harmonic * t_vals)\n}\n\ncurve_values <- measurements %*% t(basis) # observations x t_vals\n\ncurves_df <- as.data.frame(curve_values) %>%\n  setNames(as.character(t_vals)) %>%\n  mutate(obs_id = row_number(), species = species) %>%\n  pivot_longer(-c(obs_id, species), names_to = \"t\", values_to = \"f_t\") %>%\n  mutate(t = as.numeric(t))\n\n# --- Plot -------------------------------------------------------------------\n# facet_wrap(~species) splits the overlaid Fourier bands into per-species\n# small multiples, so cluster shape is legible even where the pooled overlay\n# is densest (t ~= 0) — a ggplot2-idiomatic alternative to a single hairball.\np <- ggplot(curves_df, aes(x = t, y = f_t, group = obs_id, color = species)) +\n  geom_line(linewidth = 0.5, alpha = 0.3) +\n  facet_wrap(~species, nrow = 1) +\n  scale_color_manual(values = IMPRINT_PALETTE[1:3], name = \"Species\") +\n  scale_x_continuous(breaks = c(-pi, -pi / 2, 0, pi / 2, pi),\n                      labels = c(\"-π\", \"-π/2\", \"0\", \"π/2\", \"π\")) +\n  labs(title = \"andrews-curves · r · ggplot2 · anyplot.ai\",\n       x = \"t\", y = \"f(t)\") +\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 = GRID_COLOR, linewidth = 0.3),\n    panel.grid.minor  = element_blank(),\n    panel.border      = element_blank(),\n    panel.spacing     = unit(1.2, \"lines\"),\n    strip.background  = element_blank(),\n    strip.text        = element_text(color = INK, size = 9, face = \"bold\"),\n    axis.title        = element_text(color = INK, size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    axis.line         = element_line(color = INK_SOFT),\n    plot.title        = element_text(color = INK, size = 12),\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"}