Skip to contents

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), Date or POSIXct.

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 x and group are summed. NA values 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 the group column (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 uses colorPalette.

colorPalette

D3 categorical color scheme used when colors is NULL (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 to NULL, 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 NA or "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.

Value

An SVG streamgraph.

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" )