A streamgraph is a stacked area chart whose baseline is not fixed at zero but displaced around a central axis, so the layers flow like a river. It shows how the composition of a total changes over time while keeping attention on the shape of each layer rather than on exact values. The form was popularised by Lee Byron and Martin Wattenberg's "Stacked Graphs: Geometry & Aesthetics" (2008); this implementation reproduces the look and feel of the Highcharts streamgraph in plain D3.
Unlike stacked_area_chart(), which takes wide data and stacks
areas on a zero baseline, streamgraph() takes long (tidy) data, uses
a centred baseline (offset), orders layers inside out so the largest
layers sit in the middle, and labels each layer inline at its thickest
point instead of relying on a separate legend.
The chart is interactive: hovering a layer highlights it while the others
dim, a vertical crosshair snaps to the nearest x value and shows a tooltip
with the value of every layer at that x, and (when legend = TRUE)
hovering a legend item focuses its layer while clicking it hides or shows
the layer, re-stacking the stream with an animation. Missing
x/group combinations are treated as zero.
Usage
streamgraph(
data,
x,
group,
value,
offset = c("wiggle", "silhouette", "expand"),
order = c("insideOut", "none", "reverse", "ascending", "descending"),
curve = c("basis", "catmullRom", "monotoneX", "linear", "step"),
colors = NULL,
colorPalette = "Tableau10",
opacity = 0.9,
strokeCol = "white",
strokeWidth = 0.5,
labels = TRUE,
labelFontSize = 11,
labelCol = NULL,
tooltip = TRUE,
hover = TRUE,
animate = TRUE,
legend = TRUE,
legendFontSize = 11,
valueFormat = ",.2~f",
xFormat = NULL,
xticks = NULL,
xFontSize = 11,
gridCol = "#e6e6e6",
xtitle = NULL,
xtitleFontSize = 13,
title = NULL,
titleFontSize = 16,
font = "Verdana, Geneva, Tahoma, sans-serif",
bgcol = "white",
axisCol = "black",
width = NULL,
height = NULL
)Arguments
- data
The data frame containing the variables to consider, in long format (one row per x value and group).
- x
The name of the column holding the x values. Must be numeric (e.g. years),
DateorPOSIXct.- group
The name of the column holding the layer (series) names. If it is a factor, its level order is used as the layer order when
order = "none"and in the legend.- value
The name of the numeric, non-negative column holding the thickness of each layer. Rows sharing the same
xandgroupare summed.NAvalues are treated as zero.- offset
The baseline of the stream.
"wiggle"(default) minimises the weighted change in slope of the layers (the classic streamgraph),"silhouette"centres the stream symmetrically around zero, and"expand"normalises every x to 100% (a percent axis is then shown on the left).- order
The order in which layers are stacked.
"insideOut"(default) puts the layers that peak earliest in the middle and the later ones on the outside, which works best with"wiggle"."none"keeps the order of thegroupcolumn (factor levels or first appearance),"reverse"reverses it, and"ascending"/"descending"sort the layers by their total.- curve
The curve used to interpolate the layers:
"basis"(default, smooth B-spline that never lets layers cross),"catmullRom","monotoneX","linear"or"step".- colors
Optional. A vector of colors, one per layer (recycled if shorter). Defaults to
NULL, which usescolorPalette.- colorPalette
D3 categorical color scheme used when
colorsisNULL(e.g."Tableau10","Category10","Set2","Paired"). Defaults to"Tableau10".- opacity
Fill opacity of the layers (0 to 1). Defaults to
0.9.- strokeCol
Color of the thin line separating the layers. Defaults to
"white".- strokeWidth
Width of the separating line in pixels. Defaults to
0.5.- labels
Whether to write each layer's name inside the layer, at its thickest point. Labels that do not fit their layer are hidden. Defaults to
TRUE.- labelFontSize
Font size of the inline layer labels. Defaults to
11.- labelCol
Optional. Color of the inline labels. Defaults to
NULL, which picks white or near-black automatically for contrast with each layer's fill.- tooltip
Whether to show the vertical crosshair and the tooltip listing every layer's value at the hovered x. Defaults to
TRUE.- hover
Whether hovering a layer (or a legend item) highlights it and dims the others. Defaults to
TRUE.- animate
Whether the stream is revealed from left to right on first render. Defaults to
TRUE.- legend
Whether to show an interactive legend under the chart (hover to focus a layer, click to hide or show it). Defaults to
TRUE.- legendFontSize
Font size of the legend text. Defaults to
11.- valueFormat
A d3-format specifier used to display the values in the tooltip (e.g.
",.0f","$,.2s",".1f"). Defaults to",.2~f".- xFormat
Optional. Format of the x values on the axis and in the tooltip header: a d3-format specifier for numeric
x(e.g."d") or a d3-time-format specifier for dates (e.g."%b %Y"). Defaults toNULL, which picks a sensible format automatically.- xticks
Optional. Approximate number of x-axis ticks. Defaults to
NULL(chosen from the chart width).- xFontSize
Font size of the x-axis tick labels. Defaults to
11.- gridCol
Color of the vertical grid lines drawn at each x tick. Use
NAor"transparent"to hide them. Defaults to"#e6e6e6".- xtitle
Optional. Title of the x-axis.
- xtitleFontSize
Font size of the x-axis title. Defaults to
13.- title
Optional. Title of the chart.
- titleFontSize
Font size of the chart title. Defaults to
16.- font
The font family used for all text. Defaults to
"Verdana, Geneva, Tahoma, sans-serif".- bgcol
Background color of the SVG. Defaults to
"white".- axisCol
Color of the x-axis, the titles, the crosshair and the legend text. Defaults to
"black".- width
Optional. The width of the SVG output.
- height
Optional. The height of the SVG output.
Examples
# Popularity of music genres over four decades (simulated)
set.seed(2026)
years <- 1980:2020
genres <- c("Rock", "Pop", "Hip hop", "Electronic", "Country",
"R&B", "Jazz", "Metal", "Reggae", "Latin")
peaks <- c(1984, 1999, 2012, 2008, 1993, 2003, 1981, 1990, 1986, 2018)
sizes <- c(60, 55, 70, 40, 30, 35, 18, 25, 15, 45)
music <- expand.grid(year = years, genre = genres)
idx <- match(music$genre, genres)
music$listeners <- round(
sizes[idx] * exp(-((music$year - peaks[idx]) / 9)^2) + runif(nrow(music), 1, 6)
)
streamgraph(
data = music,
x = "year",
group = "genre",
value = "listeners",
valueFormat = ",.0f",
xtitle = "Year",
title = "Music genres, 1980-2020 (millions of listeners)"
)
# Share of the world's telephones by region (base R data), 100% stream
phones <- data.frame(
year = rep(as.numeric(rownames(WorldPhones)), ncol(WorldPhones)),
region = factor(rep(colnames(WorldPhones), each = nrow(WorldPhones)),
levels = colnames(WorldPhones)),
phones = as.vector(WorldPhones)
)
streamgraph(
data = phones,
x = "year",
group = "region",
value = "phones",
offset = "expand",
order = "descending",
curve = "monotoneX",
colorPalette = "Set2",
valueFormat = ",.0f",
title = "Telephones by region, 1951-1961 (thousands)"
)
# Date x-axis with a silhouette baseline: weekly sign-ups per channel
set.seed(7)
weeks <- seq(as.Date("2026-01-01"), by = "week", length.out = 52)
channels <- c("Organic", "Paid search", "Social", "Referral", "Email")
signups <- expand.grid(week = weeks, channel = channels)
t <- as.numeric(signups$week - min(weeks)) / 7
k <- as.integer(signups$channel)
signups$n <- round(
(60 + 25 * sin(t / 6 + k)) * c(1.4, 1, 0.8, 0.5, 0.6)[k] + rpois(length(t), 6)
)
streamgraph(
data = signups,
x = "week",
group = "channel",
value = "n",
offset = "silhouette",
xFormat = "%b %Y",
colors = c("#2caffe", "#544fc5", "#00e272", "#fe6a35", "#6b8abc"),
legend = FALSE,
title = "Weekly sign-ups by channel, 2026"
)