Overlapping histograms display multiple distributions on the same axes using semi-transparent bars, enabling direct visual comparison between groups. This technique reveals differences in central tendency, spread, and shape across categories while maintaining the familiar histogram format. The transparency allows viewers to see where distributions overlap and diverge.

#' anyplot.ai
#' histogram-overlapping: Overlapping Histograms
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 89/100 | Created: 2026-08-18
library(ggplot2)
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 -----------------------------------------------------------------
n <- 200
completion_time <- c(
rnorm(n, mean = 45, sd = 8),
rnorm(n, mean = 39, sd = 7),
rnorm(n, mean = 51, sd = 9)
)
design <- factor(
rep(c("Design A", "Design B", "Design C"), each = n),
levels = c("Design A", "Design B", "Design C")
)
df <- tibble::tibble(completion_time = completion_time, design = design)
binwidth <- diff(range(df$completion_time)) / 28
# Group means drive the dashed reference lines and the fastest-design callout
group_means <- tapply(df$completion_time, df$design, mean)
mean_df <- tibble::tibble(
design = factor(names(group_means), levels = levels(df$design)),
mean_time = as.numeric(group_means)
)
fastest <- mean_df[which.min(mean_df$mean_time), ]
# --- Plot -------------------------------------------------------------------
p <- ggplot(df, aes(x = completion_time, fill = design)) +
geom_histogram(
position = "identity",
binwidth = binwidth,
alpha = 0.5,
color = INK_SOFT,
linewidth = 0.15
) +
geom_vline(
xintercept = fastest$mean_time,
color = IMPRINT_PALETTE[which(levels(df$design) == fastest$design)],
linetype = "dashed",
linewidth = 0.6
) +
annotate(
"text",
x = fastest$mean_time,
y = Inf,
label = sprintf("%s: fastest avg (%.0fs)", fastest$design, fastest$mean_time),
hjust = -0.05,
vjust = 1.6,
size = 3,
fontface = "bold",
color = IMPRINT_PALETTE[which(levels(df$design) == fastest$design)]
) +
scale_fill_manual(values = IMPRINT_PALETTE[1:3], name = "UI design") +
scale_y_continuous(expand = expansion(mult = c(0, 0.08))) +
labs(
title = "histogram-overlapping · r · ggplot2 · anyplot.ai",
x = "Task Completion Time (seconds)",
y = "Number of Users"
) +
theme_minimal(base_size = 8) +
theme(
plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),
panel.background = element_rect(fill = PAGE_BG, color = NA),
panel.grid.major.x = element_blank(),
panel.grid.minor = element_blank(),
panel.grid.major.y = element_line(color = INK, linewidth = 0.15),
axis.title = element_text(color = INK, size = 10),
axis.text = element_text(color = INK_SOFT, size = 8),
axis.line = element_line(color = INK_SOFT),
plot.title = element_text(color = INK, size = 12),
legend.position = "right",
legend.title = element_text(color = INK, size = 10),
legend.text = element_text(color = INK_SOFT, size = 8),
legend.background = element_blank(),
legend.key = element_blank()
)
# --- Save -------------------------------------------------------------------
ggsave(
filename = sprintf("plot-%s.png", THEME),
plot = p,
device = ragg::agg_png,
width = 8,
height = 4.5,
units = "in",
dpi = 400
)
Part of Overlapping Histograms on anyplot.ai.