{"spec_id":"chernoff-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' chernoff-basic: Chernoff Faces for Multivariate Data\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 93/100 | Created: 2026-09-02\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(tibble)\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\"\nIMPRINT_PALETTE <- c(\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n                     \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# --- Data: mtcars performance metrics per model -------------------------------\ncars <- c(\n  \"Mazda RX4\", \"Datsun 710\", \"Hornet Sportabout\", \"Duster 360\",\n  \"Merc 240D\", \"Merc 280\", \"Cadillac Fleetwood\", \"Fiat 128\",\n  \"Honda Civic\", \"Toyota Corolla\", \"Dodge Challenger\", \"Camaro Z28\",\n  \"Porsche 914-2\", \"Ferrari Dino\", \"Volvo 142E\"\n)\n\nfaces <- rownames_to_column(mtcars, \"car\") |>\n  filter(car %in% cars) |>\n  mutate(\n    car       = factor(car, levels = cars),\n    cyl_group = factor(cyl, levels = c(4, 6, 8),\n                        labels = c(\"4 cyl\", \"6 cyl\", \"8 cyl\")),\n    # Each performance metric is rescaled onto its own facial-feature range\n    face_width  = rescale(mpg,  to = c(0.65, 1.05)),\n    eye_size    = rescale(qsec, to = c(0.05, 0.12)),\n    eye_spacing = rescale(hp,   to = c(0.22, 0.42)),\n    brow_slant  = rescale(wt,   to = c(20, -20)),\n    nose_length = rescale(disp, to = c(0.14, 0.32)),\n    mouth_curve = rescale(drat, to = c(-1, 1)),\n    mouth_width = rescale(carb, to = c(0.30, 0.50))\n  ) |>\n  arrange(car)\n\n# --- Facial-feature geometry helpers ------------------------------------------\nellipse_pts <- function(cx, cy, rx, ry, n = 60) {\n  t <- seq(0, 2 * pi, length.out = n)\n  tibble(x = cx + rx * cos(t), y = cy + ry * sin(t))\n}\n\nmouth_pts <- function(cx, cy, half_width, curvature, amp = 0.14, n = 24) {\n  t <- seq(-1, 1, length.out = n)\n  tibble(x = cx + t * half_width, y = cy + curvature * amp * (t^2 - 1))\n}\n\n# --- Build one face's geometry per row, then stack into shared layers ---------\nn_faces   <- nrow(faces)\nbrow_half <- 0.11\neye_y     <- 0.18\n\noutline_list  <- vector(\"list\", n_faces)\neyes_list     <- vector(\"list\", n_faces)\npupils_list   <- vector(\"list\", n_faces)\neyebrows_list <- vector(\"list\", n_faces)\nnose_list     <- vector(\"list\", n_faces)\nmouth_list    <- vector(\"list\", n_faces)\n\nfor (i in seq_len(n_faces)) {\n  row <- faces[i, ]\n\n  outline_list[[i]] <- bind_cols(\n    car = row$car, cyl_group = row$cyl_group,\n    ellipse_pts(0, 0, row$face_width, 0.85)\n  )\n\n  eyes_list[[i]] <- bind_rows(\n    bind_cols(car = row$car, side = \"left\",\n              ellipse_pts(-row$eye_spacing, eye_y, row$eye_size, row$eye_size, n = 28)),\n    bind_cols(car = row$car, side = \"right\",\n              ellipse_pts( row$eye_spacing, eye_y, row$eye_size, row$eye_size, n = 28))\n  )\n\n  pupils_list[[i]] <- tibble(\n    car = row$car,\n    x   = c(-row$eye_spacing, row$eye_spacing),\n    y   = c(eye_y, eye_y)\n  )\n\n  slant  <- row$brow_slant * pi / 180\n  dy     <- brow_half * tan(slant)\n  brow_y <- eye_y + row$eye_size + 0.07\n  eyebrows_list[[i]] <- tibble(\n    car  = row$car,\n    x    = c(-row$eye_spacing - brow_half, row$eye_spacing - brow_half),\n    xend = c(-row$eye_spacing + brow_half, row$eye_spacing + brow_half),\n    y    = c(brow_y - dy, brow_y + dy),\n    yend = c(brow_y + dy, brow_y - dy)\n  )\n\n  nose_list[[i]] <- tibble(\n    car = row$car, x = 0, xend = 0,\n    y = -0.02, yend = -0.02 - row$nose_length\n  )\n\n  mouth_list[[i]] <- bind_cols(\n    car = row$car,\n    mouth_pts(0, -0.55, row$mouth_width / 2, row$mouth_curve)\n  )\n}\n\noutline_df  <- bind_rows(outline_list)\neyes_df     <- bind_rows(eyes_list)\npupils_df   <- bind_rows(pupils_list)\neyebrows_df <- bind_rows(eyebrows_list)\nnose_df     <- bind_rows(nose_list)\nmouth_df    <- bind_rows(mouth_list)\n\n# --- Plot ----------------------------------------------------------------------\ntitle_text     <- \"Car Performance Profiles · chernoff-basic · r · ggplot2 · anyplot.ai\"\ntitle_fontsize <- round(12 * min(1, 67 / nchar(title_text)))\n\np <- ggplot() +\n  geom_polygon(data = outline_df, aes(x, y, group = car, fill = cyl_group),\n               color = INK_SOFT, linewidth = 0.35, alpha = 0.9) +\n  geom_polygon(data = eyes_df, aes(x, y, group = interaction(car, side)),\n               fill = PAGE_BG, color = INK, linewidth = 0.35) +\n  geom_point(data = pupils_df, aes(x, y), color = INK, size = 1.6) +\n  geom_segment(data = eyebrows_df, aes(x = x, xend = xend, y = y, yend = yend),\n               color = INK, linewidth = 0.9, lineend = \"round\") +\n  geom_segment(data = nose_df, aes(x = x, xend = xend, y = y, yend = yend),\n               color = INK, linewidth = 0.7, lineend = \"round\") +\n  geom_path(data = mouth_df, aes(x, y, group = car),\n            color = INK, linewidth = 0.9, lineend = \"round\") +\n  scale_fill_manual(values = IMPRINT_PALETTE[1:3], name = \"Cylinders\") +\n  coord_equal(xlim = c(-1.15, 1.15), ylim = c(-1.05, 1.05), expand = FALSE) +\n  facet_wrap(~ car, ncol = 5, labeller = label_wrap_gen(width = 11)) +\n  labs(title = title_text) +\n  theme_void(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.spacing     = unit(1.2, \"lines\"),\n    plot.title        = element_text(color = INK, size = title_fontsize, hjust = 0.5,\n                                      margin = margin(b = 14)),\n    strip.text        = element_text(color = INK_SOFT, size = 9, lineheight = 0.9,\n                                      margin = margin(b = 4)),\n    legend.position    = \"bottom\",\n    legend.title       = element_text(color = INK, size = 10),\n    legend.text        = element_text(color = INK_SOFT, size = 9),\n    legend.background  = element_rect(fill = PAGE_BG, color = NA),\n    legend.key         = element_rect(fill = PAGE_BG, color = NA),\n    plot.margin        = margin(20, 24, 20, 24)\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"}