Basic Stream Graph — D3.js

A streamgraph (also known as a stacked area chart with a centered baseline) displaying the composition of multiple categories over time with smooth, flowing curves. Unlike traditional stacked area charts, streamgraphs use a symmetric baseline centered around the x-axis, creating an organic, river-like appearance that emphasizes the overall shape and relative proportions of each category while minimizing the visual distortion of individual layers.

Basic Stream Graph rendered with D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// streamgraph-basic: Basic Stream Graph
// Library: d3 7.9.0 | JavaScript 22.23.1
// Quality: 90/100 | Created: 2026-08-05

const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;
const margin = { top: 110, right: 190, bottom: 60, left: 30 };
const iw = width - margin.left - margin.right;
const ih = height - margin.top - margin.bottom;

// --- Data: monthly streaming hours by music genre, 2024-2025 (deterministic) --
let seed = 42;
function rand() {
  seed = (seed * 1103515245 + 12345) & 0x7fffffff;
  return seed / 0x7fffffff;
}

const months = d3.range(24).map((i) => new Date(2024, i, 1));

const genreConfig = [
  { name: "Pop", base: 55, amp: 18, phase: 0.3, trend: 0 },
  { name: "Hip-Hop", base: 42, amp: 14, phase: 1.8, trend: 0.6 },
  { name: "Electronic", base: 30, amp: 16, phase: 2.6, trend: 0.5 },
  { name: "Rock", base: 34, amp: 9, phase: 0.9, trend: -0.35 },
  { name: "Jazz", base: 16, amp: 5, phase: 4.0, trend: 0.05 },
  { name: "Classical", base: 11, amp: 3, phase: 5.2, trend: -0.05 },
];
const genres = genreConfig.map((g) => g.name);
const freq = (2 * Math.PI) / 12;

const data = months.map((date, i) => {
  const row = { date };
  for (const g of genreConfig) {
    const seasonal = Math.sin(i * freq + g.phase) * g.amp;
    const noise = (rand() - 0.5) * 4;
    row[g.name] = Math.max(2, g.base + seasonal + g.trend * i + noise);
  }
  return row;
});

// --- Stack (wiggle offset = symmetric streamgraph baseline) ------------------
const stack = d3
  .stack()
  .keys(genres)
  .order(d3.stackOrderInsideOut)
  .offset(d3.stackOffsetWiggle);
const series = stack(data);

const x = d3.scaleTime().domain(d3.extent(months)).range([0, iw]);
const yExtent = d3.extent(series.flatMap((s) => s.flatMap((d) => [d[0], d[1]])));
const y = d3.scaleLinear().domain(yExtent).range([ih, 0]);
const color = d3.scaleOrdinal().domain(genres).range(t.palette.slice(0, genres.length));

const area = d3
  .area()
  .curve(d3.curveBasis)
  .x((d) => x(d.data.date))
  .y0((d) => y(d[0]))
  .y1((d) => y(d[1]));

// --- SVG mount ----------------------------------------------------------------
const svg = d3.select("#container").append("svg").attr("width", width).attr("height", height);
const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`);

// --- Streams --------------------------------------------------------------
g.selectAll("path.layer")
  .data(series)
  .join("path")
  .attr("class", "layer")
  .attr("fill", (d) => color(d.key))
  .attr("d", area);

// --- Time axis (no value axis — the wiggle baseline has no meaningful zero) --
const xAxis = g
  .append("g")
  .attr("transform", `translate(0,${ih + 16})`)
  .call(d3.axisBottom(x).ticks(d3.timeMonth.every(3)).tickFormat(d3.timeFormat("%b %Y")).tickSizeOuter(0));
xAxis.selectAll("text").attr("fill", t.inkSoft).style("font-size", "14px");
xAxis.selectAll("line").attr("stroke", t.grid);
xAxis.select(".domain").attr("stroke", t.inkSoft);

// --- Legend -----------------------------------------------------------------
const legend = svg
  .append("g")
  .attr("transform", `translate(${width - margin.right + 34}, ${margin.top + 20})`);
genres.forEach((name, i) => {
  const row = legend.append("g").attr("transform", `translate(0, ${i * 34})`);
  row.append("rect").attr("width", 18).attr("height", 18).attr("rx", 3).attr("fill", color(name));
  row
    .append("text")
    .attr("x", 26)
    .attr("y", 14)
    .attr("fill", t.ink)
    .style("font-size", "15px")
    .text(name);
});

// --- Title --------------------------------------------------------------------
const title = "Music Genre Streaming · streamgraph-basic · javascript · d3 · anyplot.ai";
const ratio = title.length > 67 ? 67 / title.length : 1.0;
const titleFontSize = Math.max(14, Math.round(22 * ratio));
svg
  .append("text")
  .attr("x", width / 2)
  .attr("y", 50)
  .attr("text-anchor", "middle")
  .attr("fill", t.ink)
  .style("font-size", `${titleFontSize}px`)
  .style("font-weight", "600")
  .text(title);

Part of Basic Stream Graph on anyplot.ai.

Other implementations