Venn Diagram — ggplot2

A Venn diagram visualizes the logical relationships between two or three sets using overlapping circles. Each circle represents a set, and overlapping regions show elements shared between sets. This classic visualization is ideal for showing intersections, unions, and exclusive memberships, making abstract set relationships immediately intuitive.

Venn Diagram rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' venn-basic: Venn Diagram
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 89/100 | Created: 2026-09-09

library(ggplot2)
library(dplyr)
library(ragg)

# --- 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", # 1 - Python
  "#C475FD", # 2 - SQL
  "#4467A3"  # 3 - R
)

# --- Data: skill overlap across job candidates -------------------------------
python_total <- 120
sql_total    <- 95
r_total      <- 70
python_sql   <- 40
python_r     <- 25
sql_r        <- 30
all_three    <- 12

python_only     <- python_total - python_sql - python_r + all_three
sql_only        <- sql_total - python_sql - sql_r + all_three
r_only          <- r_total - python_r - sql_r + all_three
python_sql_only <- python_sql - all_three
python_r_only   <- python_r - all_three
sql_r_only      <- sql_r - all_three

circle_points <- function(cx, cy, radius, n = 200) {
  theta <- seq(0, 2 * pi, length.out = n)
  tibble::tibble(x = cx + radius * cos(theta), y = cy + radius * sin(theta))
}

# Radii scaled by sqrt(set_size) relative to the largest set, so circle area
# is proportional to set size while the three-circle triangular layout stays intact.
radius_python <- 2.2
radius_sql    <- radius_python * sqrt(sql_total / python_total)
radius_r      <- radius_python * sqrt(r_total / python_total)

center_python <- c(0, 1.27)
center_sql    <- c(-1.10, -0.635)
center_r      <- c(1.10, -0.635)

circles <- bind_rows(
  circle_points(center_python[1], center_python[2], radius_python) |> mutate(set = "Python"),
  circle_points(center_sql[1], center_sql[2], radius_sql) |> mutate(set = "SQL"),
  circle_points(center_r[1], center_r[2], radius_r) |> mutate(set = "R")
) |> mutate(set = factor(set, levels = c("Python", "SQL", "R")))

total_n <- python_only + sql_only + r_only + python_sql_only + python_r_only + sql_r_only + all_three

region_counts <- tibble::tibble(
  x = c(0, -1.81, 1.71, -0.88, 0.86, 0, 0),
  y = c(2.0, -1.27, -1.18, 0.87, 0.83, -1.06, 0.04),
  count = c(python_only, sql_only, r_only, python_sql_only, python_r_only, sql_r_only, all_three),
  focal = c(FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE)
) |> mutate(pct_label = paste0(round(100 * count / total_n), "%"))

set_name_labels <- tibble::tibble(
  x = c(0, -1.81, 1.71),
  y = c(3.15, -1.85, -1.90),
  label = c(
    paste0("Python (n=", python_total, ")"),
    paste0("SQL (n=", sql_total, ")"),
    paste0("R (n=", r_total, ")")
  )
)

# --- Plot ---------------------------------------------------------------------
p <- ggplot() +
  geom_polygon(
    data = circles, aes(x = x, y = y, fill = set, group = set),
    alpha = 0.5, color = INK_SOFT, linewidth = 0.6
  ) +
  geom_text(
    data = set_name_labels, aes(x = x, y = y, label = label),
    color = INK, size = 4.6, fontface = "bold"
  ) +
  geom_text(
    data = filter(region_counts, !focal), aes(x = x, y = y, label = count),
    color = INK, size = 5.6, fontface = "bold"
  ) +
  geom_text(
    data = filter(region_counts, focal), aes(x = x, y = y, label = count),
    color = INK, size = 7.0, fontface = "bold"
  ) +
  geom_text(
    data = region_counts, aes(x = x, y = y - 0.34, label = pct_label),
    color = INK_SOFT, size = 3.2, fontface = "plain"
  ) +
  scale_fill_manual(values = IMPRINT_PALETTE) +
  coord_fixed(xlim = c(-3.6, 3.6), ylim = c(-3.6, 3.6), expand = FALSE) +
  labs(title = "venn-basic · r · ggplot2 · anyplot.ai") +
  theme_void(base_size = 8) +
  theme(
    plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),
    plot.title = element_text(
      color = INK, size = 12, hjust = 0.5, margin = margin(b = 16)
    ),
    legend.position = "none",
    plot.margin = margin(24, 24, 24, 24)
  )

# --- Save ----------------------------------------------------------------------
ggsave(
  filename = sprintf("plot-%s.png", THEME),
  plot     = p,
  device   = ragg::agg_png,
  width    = 6,
  height   = 6,
  units    = "in",
  dpi      = 400
)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/venn-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": "venn-basic",
  "language": "r",
  "library": "ggplot2",
  "page": "https://anyplot.ai/venn-basic/r/ggplot2",
  "hub": "https://anyplot.ai/venn-basic",
  "code_json": "https://api.anyplot.ai/specs/venn-basic/ggplot2/code",
  "spec_json": "https://api.anyplot.ai/specs/venn-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/venn-basic/r/ggplot2/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/venn-basic/r/ggplot2/plot-dark.png",
  "quality_score": 89.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Venn Diagram on anyplot.ai.

Other implementations