HAHS.
Back to Catalog

Alluvial Diagram

chart

Also known as: alluvial chart, alluvial plot, parallel sets, flow diagram

Show flowShow change over timeShow composition CategoricalNumerical Node-Link

Description

An alluvial diagram is a type of flow visualization that shows how items are distributed among categories across multiple dimensions (or time periods), and how those memberships shift. Vertical columns of stacked blocks represent categorical distributions at each stage, and smooth, curved bands flow between blocks to show how the population redistributes. The width of each band is proportional to the number (or magnitude) of items following that path.

While often confused with Sankey diagrams, alluvial diagrams have a specific focus: the same set of entities is tracked as their categorical memberships change across axes. In a Sankey diagram, the nodes at different stages may represent entirely different entities (e.g., energy sources vs. consumption sectors). In an alluvial diagram, the same group of people, countries, or items appears at every axis, and the chart shows how they regroup.

The flowing bands create a visual metaphor of rivers or streams — hence the geological term “alluvial” — making the chart aesthetically striking and intuitive. The chart excels at revealing which transitions are common, which groups remain stable, and where unexpected cross-overs occur. Interactive highlighting of individual bands is nearly essential for dense diagrams, as crossing flows can otherwise obscure the paths of specific groups.

Alluvial Diagram — interactive example

When to Use

  • Tracking how the same set of entities redistributes across categorical groupings over time or conditions
  • Showing voter migration between parties across elections
  • Visualizing student flows between majors, schools, or graduation outcomes
  • Displaying how survey respondents change their answers across waves

When NOT to Use

  • When the nodes at different stages represent fundamentally different entities — use a Sankey diagram instead
  • When there are many categories at each stage (>8) — bands become thin and crossings unreadable
  • When the temporal order or direction of flow is not meaningful — use a parallel coordinates plot for multivariate exploration
  • When precise quantitative comparison is needed — the curved bands make exact size comparison difficult; use a stacked bar chart
  • When there is no meaningful grouping dimension — a simple line graph showing totals may suffice

Anatomy

  • Axes (columns): Vertical columns representing dimensions or time periods, with stacked blocks at each axis
  • Strata (blocks): Stacked rectangles at each axis representing categories, sized by total count or value
  • Alluvia (bands/streams): Curved flows connecting blocks across axes, with width proportional to the subgroup size
  • Color encoding: Applied to bands to trace a particular category from the first axis through all subsequent axes
  • Labels: Category names on each block, sometimes with counts
  • Crossings: Where bands intersect, indicating that entities from one category at one axis join a different category at the next

Variations

  • Parallel sets: A closely related form where bands represent exact categorical combinations (no aggregation into blocks)
  • Temporal alluvial: Axes represent consecutive time periods, showing category migration over time
  • Weighted alluvial: Band width encodes a quantitative weight beyond simple counts
  • Highlighted alluvial: Only selected flows are colored; the rest are grayed out for focused storytelling
  • Alluvial with mini-bars: Small bar charts at each axis show the total for each category alongside the flows

Code Reference

// D3-based alluvial diagram (using d3-sankey with aligned columns)
import {sankey, sankeyLinkHorizontal} from "d3-sankey";

const graph = sankey()
  .nodeWidth(20)
  .nodePadding(8)
  .extent([[0, 0], [width, height]])({
    nodes: data.nodes.map(d => ({...d})),
    links: data.links.map(d => ({...d}))
  });

svg.selectAll("path")
  .data(graph.links).join("path")
  .attr("d", sankeyLinkHorizontal())
  .attr("stroke-width", d => d.width)
  .attr("stroke", d => color(d.source.category))
  .attr("fill", "none").attr("opacity", 0.4);