Streamgraph transitions

See also a more practical static streamgraph.

const height = 500;

const n = 20; // number of layers
const m = 200; // number of samples per layer
const k = 10; // number of bumps per layer

const x = d3.scaleLinear([0, m - 1], [0, width]);
const y = d3.scaleLinear([0, 1], [height, 0]);
const z = d3.interpolateCool;

const area = d3.area()
    .x((d, i) => x(i))
    .y0((d) => y(d[0]))
    .y1((d) => y(d[1]));

const stack = d3.stack()
    .keys(d3.range(n))
    .offset(offset)
    .order(d3.stackOrderNone);

function randomize() {
  const layers = stack(d3.transpose(Array.from({length: n}, () => bumps(m, k))));
  y.domain([
    d3.min(layers, (l) => d3.min(l, (d) => d[0])),
    d3.max(layers, (l) => d3.max(l, (d) => d[1]))
  ]);
  return layers;
}

const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("style", "max-width: 100%; height: auto;");

const path = svg.selectAll("path")
  .data(randomize)
  .join("path")
    .attr("d", area)
    .attr("fill", () => z(Math.random()));

const animation = (async function* () {
  while (true) {
    yield svg.node();

    await path
      .data(randomize)
      .transition()
        .delay(1000)
        .duration(1500)
        .attr("d", area)
      .end();
  }
})();

display(svg.node());

// Inspired by Lee Byron’s test data generator.
function bump(a, n) {
  const x = 1 / (0.1 + Math.random());
  const y = 2 * Math.random() - 0.5;
  const z = 10 / (0.1 + Math.random());
  for (let i = 0; i < n; ++i) {
    const w = (i / n - y) * z;
    a[i] += x * Math.exp(-w * w);
  }
}

function bumps(n, m) {
  const a = [];
  for (let i = 0; i < n; ++i) a[i] = 0;
  for (let i = 0; i < m; ++i) bump(a, n);
  return a;
}
✎ Suggest changes to this page