Streamgraph представляет собой разновидность многослойной диаграммы площадей, в которой слои смещаются относительно центральной оси, формируя плавный «текущий» силуэт. В отличие от классического stacked area chart, где базовая линия фиксирована, streamgraph минимизирует визуальную «жёсткость» за счёт центрирования и сглаживания слоёв.
Визуальная модель строится на идее отображения изменений нескольких временных рядов таким образом, чтобы общая форма оставалась непрерывной и органичной. Каждый слой представляет отдельную категорию данных, а его толщина соответствует величине значения в конкретный момент времени.
Ключевым аспектом реализации в D3.js является использование стекового преобразования данных и специализированного алгоритма смещения baseline, чаще всего известного как wiggle offset.
Основой streamgraph выступает преобразование исходных данных в
стековую структуру. В D3.js для этого используется механизм
d3.stack, который преобразует таблицу значений в массив
слоёв.
Каждый слой содержит пары координат:
Структура данных после стекового преобразования:
Пример подготовки данных:
const data = [
{ year: 2000, a: 30, b: 20, c: 10 },
{ year: 2001, a: 40, b: 25, c: 15 },
{ year: 2002, a: 35, b: 30, c: 20 }
];
const stack = d3.stack()
.keys(["a", "b", "c"]);
const series = stack(data);
Результат series становится основой для построения
геометрии streamgraph.
Ключевая особенность streamgraph заключается в выборе смещения слоёв. В отличие от стандартного накопления от нуля, используется алгоритм минимизации «дрожания» общей формы.
Wiggle-алгоритм стремится минимизировать сумму квадратичных отклонений между соседними слоями, распределяя их относительно центральной линии.
В D3.js это реализуется через:
d3.stack()
.offset(d3.stackOffsetWiggle)
Другие варианты смещения:
stackOffsetNone — базовое накопление от нуляstackOffsetExpand — нормализация до 1stackOffsetSilhouette — центрирование вокруг нуляStreamgraph обычно использует именно wiggle или его
вариации.
Графическая форма streamgraph формируется с помощью генератора
областей. В D3.js используется d3.area, который определяет
верхнюю и нижнюю границы каждого слоя.
Базовая конфигурация:
const area = d3.area()
.x(d => xScale(d.data.year))
.y0(d => yScale(d[0]))
.y1(d => yScale(d[1]))
.curve(d3.curveBasis);
Сглаживание играет критическую роль: без интерполяции линии streamgraph становятся угловатыми и теряют характерную «текучесть». Чаще всего применяются:
d3.curveBasisd3.curveCatmullRomd3.curveCardinalОси в streamgraph не всегда выражены явно. Чаще всего горизонтальная ось соответствует времени или последовательному индексу.
Пример настройки шкал:
const xScale = d3.scaleLinear()
.domain([2000, 2002])
.range([0, width]);
const yScale = d3.scaleLinear()
.range([height, 0]);
Вертикальная шкала формируется на основе максимальной суммарной высоты слоёв после стекового преобразования.
Каждый слой streamgraph визуально отделяется цветом. В D3.js обычно используется категориальная шкала:
const color = d3.scaleOrdinal()
.domain(["a", "b", "c"])
.range(["#4e79a7", "#f28e2c", "#e15759"]);
Цвет применяется к каждому path слоя:
svg.selectAll("path")
.data(series)
.join("path")
.attr("fill", (d, i) => color(i));
Для повышения читаемости часто добавляется прозрачность слоёв, чтобы визуально проявлялись пересечения и общая структура потоков.
Streamgraph в интерактивной среде часто дополняется динамическими эффектами:
Пример базового hover-эффекта:
svg.selectAll("path")
.on("mouseover", function() {
d3.selectAll("path").style("opacity", 0.3);
d3.select(this).style("opacity", 1);
})
.on("mouseout", function() {
d3.selectAll("path").style("opacity", 1);
});
В D3.js такие взаимодействия тесно связаны с выборками (selections), что позволяет изменять DOM без пересоздания всей сцены.
Одним из ключевых преимуществ подхода является декларативное обновление. При изменении данных:
d3.stacksvg.selectAll("path")
.data(series)
.join("path")
.transition()
.duration(750)
.attr("d", area);
Плавные переходы обеспечивают сохранение когнитивной связи между состояниями графика.
При использовании временных рядов применяется шкала времени:
const xScale = d3.scaleTime()
.domain(d3.extent(data, d => new Date(d.year)))
.range([0, width]);
Это позволяет корректно распределять точки по оси X независимо от равномерности данных.
Streamgraph чувствителен к масштабу значений. При большом разбросе категорий применяются нормализации:
В D3.js часто используется комбинация stackOffsetExpand,
приводящая сумму слоёв к единице на каждом шаге времени.
Типовая последовательность построения:
d3.stackd3.areaЭта структура позволяет масштабировать визуализацию на большие наборы данных без изменения логики отображения.
При работе с большими массивами данных в D3.js важны следующие подходы:
stackrequestAnimationFrame при анимацияхОсобенно критично избегать повторного вызова генератора area для каждого кадра без необходимости.
Streamgraph требует согласованности между математической моделью данных и визуальной интерпретацией, иначе теряется смысл «потока» как формы представления.