A funnel chart visualizes sequential stages of a process where values progressively decrease from one stage to the next. Each stage is represented as a trapezoidal segment that narrows from top to bottom, making it easy to identify drop-offs between stages. This chart is ideal for tracking conversion rates, sales pipelines, and multi-step processes where understanding stage-to-stage transitions is critical.

#' anyplot.ai
#' funnel-basic: Basic Funnel Chart
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 88/100 | Created: 2026-09-05
library(ggplot2)
library(dplyr)
library(tibble)
library(ragg)
set.seed(42)
# --- Theme tokens ------------------------------------------------------------
THEME <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
IMPRINT_PALETTE <- c(
"#009E73", "#C475FD", "#4467A3", "#BD8233",
"#AE3030", "#2ABCCD", "#954477", "#99B314"
)
# --- Data: HR recruitment funnel ---------------------------------------------
stage_names <- c("Applications", "Screened", "Interviewed", "Offer Extended", "Hired")
value <- c(4200, 2600, 1450, 680, 340)
n <- length(stage_names)
stage <- factor(stage_names, levels = stage_names)
# Each trapezoid narrows from its own value at the top to the next stage's
# value at the bottom; the final stage is a flat-bottomed rectangle. Widened
# relative to attempt 1 so the funnel + labels use the full landscape canvas
# instead of leaving the right side empty.
half_width <- (value / value[1]) * 0.95
segments <- lapply(seq_len(n), function(i) {
top <- half_width[i]
bottom <- if (i < n) half_width[i + 1] else half_width[i]
y_top <- n - i + 1
y_bot <- n - i
tibble(
stage = stage[i],
x = c(-top, top, bottom, -bottom),
y = c(y_top, y_top, y_bot, y_bot)
)
})
funnel_df <- bind_rows(segments)
label_x <- max(half_width) + 0.35
# Thin leader line from each trapezoid's right edge to its label block, so
# the gap between funnel and text reads as an intentional connector rather
# than dead space.
leaders_df <- tibble(
stage = stage,
x_start = (half_width + c(half_width[-1], half_width[n])) / 2,
x_end = label_x - 0.06,
y = n - seq_len(n) + 0.5
)
labels_df <- tibble(
stage = stage,
x = label_x,
y_name = n - seq_len(n) + 0.62,
y_value = n - seq_len(n) + 0.38,
name_text = stage_names,
value_text = sprintf("%s (%.0f%%)", format(value, big.mark = ","), value / value[1] * 100)
)
title_text <- "funnel-basic · r · ggplot2 · anyplot.ai"
# --- Plot ---------------------------------------------------------------------
p <- ggplot() +
geom_polygon(
data = funnel_df,
aes(x = x, y = y, group = stage, fill = stage),
color = PAGE_BG, linewidth = 0.6
) +
geom_segment(
data = leaders_df,
aes(x = x_start, xend = x_end, y = y, yend = y),
color = INK_SOFT, linewidth = 0.35, alpha = 0.6
) +
geom_text(
data = labels_df,
aes(x = x, y = y_name, label = name_text),
hjust = 0, size = 3.6, fontface = "bold", color = INK
) +
geom_text(
data = labels_df,
aes(x = x, y = y_value, label = value_text),
hjust = 0, size = 3.2, color = INK_SOFT
) +
scale_fill_manual(values = IMPRINT_PALETTE[1:n]) +
coord_cartesian(xlim = c(-1.0, label_x + 1.05), ylim = c(0, n), expand = FALSE) +
labs(title = title_text) +
theme_void(base_size = 8) +
theme(
plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),
panel.background = element_rect(fill = PAGE_BG, color = NA),
plot.title = element_text(color = INK, size = 12, hjust = 0.5, margin = margin(b = 14)),
legend.position = "none",
plot.margin = margin(t = 18, r = 24, b = 18, l = 24)
)
# --- Save -----------------------------------------------------------------
ggsave(
filename = sprintf("plot-%s.png", THEME),
plot = p,
device = ragg::agg_png,
width = 8,
height = 4.5,
units = "in",
dpi = 400
)
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/funnel-basic/ggplot2/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.
{
"spec_id": "funnel-basic",
"language": "r",
"library": "ggplot2",
"page": "https://anyplot.ai/funnel-basic/r/ggplot2",
"hub": "https://anyplot.ai/funnel-basic",
"code_json": "https://api.anyplot.ai/specs/funnel-basic/ggplot2/code",
"spec_json": "https://api.anyplot.ai/specs/funnel-basic",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/funnel-basic/r/ggplot2/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/funnel-basic/r/ggplot2/plot-dark.png",
"quality_score": 88.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Basic Funnel Chart on anyplot.ai.