{"spec_id":"funnel-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' funnel-basic: Basic Funnel Chart\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 88/100 | Created: 2026-09-05\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(tibble)\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(\n  \"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n  \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"\n)\n\n# --- Data: HR recruitment funnel ---------------------------------------------\nstage_names <- c(\"Applications\", \"Screened\", \"Interviewed\", \"Offer Extended\", \"Hired\")\nvalue <- c(4200, 2600, 1450, 680, 340)\nn <- length(stage_names)\nstage <- factor(stage_names, levels = stage_names)\n\n# Each trapezoid narrows from its own value at the top to the next stage's\n# value at the bottom; the final stage is a flat-bottomed rectangle. Widened\n# relative to attempt 1 so the funnel + labels use the full landscape canvas\n# instead of leaving the right side empty.\nhalf_width <- (value / value[1]) * 0.95\n\nsegments <- lapply(seq_len(n), function(i) {\n  top <- half_width[i]\n  bottom <- if (i < n) half_width[i + 1] else half_width[i]\n  y_top <- n - i + 1\n  y_bot <- n - i\n  tibble(\n    stage = stage[i],\n    x = c(-top, top, bottom, -bottom),\n    y = c(y_top, y_top, y_bot, y_bot)\n  )\n})\nfunnel_df <- bind_rows(segments)\n\nlabel_x <- max(half_width) + 0.35\n\n# Thin leader line from each trapezoid's right edge to its label block, so\n# the gap between funnel and text reads as an intentional connector rather\n# than dead space.\nleaders_df <- tibble(\n  stage = stage,\n  x_start = (half_width + c(half_width[-1], half_width[n])) / 2,\n  x_end = label_x - 0.06,\n  y = n - seq_len(n) + 0.5\n)\n\nlabels_df <- tibble(\n  stage = stage,\n  x = label_x,\n  y_name = n - seq_len(n) + 0.62,\n  y_value = n - seq_len(n) + 0.38,\n  name_text = stage_names,\n  value_text = sprintf(\"%s (%.0f%%)\", format(value, big.mark = \",\"), value / value[1] * 100)\n)\n\ntitle_text <- \"funnel-basic · r · ggplot2 · anyplot.ai\"\n\n# --- Plot ---------------------------------------------------------------------\np <- ggplot() +\n  geom_polygon(\n    data = funnel_df,\n    aes(x = x, y = y, group = stage, fill = stage),\n    color = PAGE_BG, linewidth = 0.6\n  ) +\n  geom_segment(\n    data = leaders_df,\n    aes(x = x_start, xend = x_end, y = y, yend = y),\n    color = INK_SOFT, linewidth = 0.35, alpha = 0.6\n  ) +\n  geom_text(\n    data = labels_df,\n    aes(x = x, y = y_name, label = name_text),\n    hjust = 0, size = 3.6, fontface = \"bold\", color = INK\n  ) +\n  geom_text(\n    data = labels_df,\n    aes(x = x, y = y_value, label = value_text),\n    hjust = 0, size = 3.2, color = INK_SOFT\n  ) +\n  scale_fill_manual(values = IMPRINT_PALETTE[1:n]) +\n  coord_cartesian(xlim = c(-1.0, label_x + 1.05), ylim = c(0, n), expand = FALSE) +\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    plot.title = element_text(color = INK, size = 12, hjust = 0.5, margin = margin(b = 14)),\n    legend.position = \"none\",\n    plot.margin = margin(t = 18, r = 24, b = 18, l = 24)\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"}