Иерархические данные в визуализации часто требуют компактного, но выразительного представления структуры вложенности. Для этого используется радиальная форма диаграмм, где каждый уровень иерархии раскладывается по концентрическим окружностям, а сегменты распределяются по углу пропорционально значению. Такой подход реализуется через разбиение пространства с помощью алгоритма partition, который формирует координаты узлов дерева в полярной системе.
Работа начинается с подготовки структуры данных, пригодной для обработки в иерархическом формате. D3 ожидает вложенный объект, где каждый узел может содержать дочерние элементы и числовое значение на листьях.
const data = {
name: "root",
children: [
{
name: "A",
children: [
{ name: "A1", value: 100 },
{ name: "A2", value: 300 }
]
},
{
name: "B",
children: [
{ name: "B1", value: 200 },
{ name: "B2", value: 150 }
]
}
]
};
Для дальнейшей обработки структура преобразуется в иерархический формат:
const root = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
Метод sum агрегирует значения снизу вверх, формируя вес
внутренних узлов. Сортировка обеспечивает стабильное расположение
элементов.
Функция d3.partition() преобразует иерархию в набор
прямоугольных координат, которые затем интерпретируются в полярной
системе.
const partition = d3.partition()
.size([2 * Math.PI, radius]);
Параметр size задаёт область распределения:
После применения алгоритма каждый узел получает:
x0, x1 — границы углового сегмента,y0, y1 — радиальные границы.root = partition(root);
Эта операция полностью подготавливает данные для построения sunburst-диаграммы.
Sunburst-диаграмма опирается на преобразование декартовых координат partition в полярные.
Основные параметры:
x0 и
x1,y0 и y1.Для отрисовки используется генератор дуг:
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.innerRadius(d => d.y0)
.outerRadius(d => d.y1);
Каждый узел иерархии преобразуется в сегмент кольца.
Визуализация размещается в SVG-элементе с центровкой координатной системы.
const width = 800;
const radius = width / 2;
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", width)
.append("g")
.attr("transform", `translate(${radius},${radius})`);
Перенос центра координат позволяет строить диаграмму от центра наружу.
После подготовки иерархии выполняется привязка узлов к SVG-путям.
svg.selectAll("path")
.data(root.descendants())
.enter()
.append("path")
.attr("d", arc)
.style("fill", d => color((d.children ? d : d.parent).data.name))
.style("stroke", "#fff");
Ключевые моменты:
descendants() возвращает все узлы дерева,Для различения ветвей используется категориальная шкала.
const color = d3.scaleOrdinal()
.domain(root.children.map(d => d.data.name))
.range(d3.schemeCategory10);
Цвет назначается не на каждый узел, а на уровень выше, что сохраняет визуальную целостность веток.
Радиальная ось определяется значениями глубины. Для управления плотностью используется масштабирование:
const radiusScale = d3.scaleLinear()
.domain([0, root.height])
.range([0, radius]);
Хотя partition уже распределяет узлы, дополнительное масштабирование позволяет адаптировать диаграмму под разные размеры данных.
Добавление текста требует вычисления середины дуги и радиуса.
svg.selectAll("text")
.data(root.descendants().filter(d => d.depth))
.enter()
.append("text")
.attr("transform", d => {
const angle = (d.x0 + d.x1) / 2;
const r = (d.y0 + d.y1) / 2;
return `
rotate(${angle * 180 / Math.PI - 90})
translate(${r},0)
`;
})
.text(d => d.data.name)
.style("font-size", "10px");
Преобразование включает:
В плотных диаграммах подписи часто перекрываются. Применяются фильтры:
.filter(d => d.x1 - d.x0 > 0.03)
или условие по глубине:
.filter(d => d.depth < 4)
Это ограничивает отображение мелких сегментов.
Sunburst-диаграммы часто используются в аналитике, поэтому важна интерактивность.
svg.selectAll("path")
.on("mouseover", function(event, d) {
d3.select(this)
.style("opacity", 0.7);
})
.on("mouseout", function(event, d) {
d3.select(this)
.style("opacity", 1);
});
Дополнительно можно выделять ветвь целиком:
.on("click", (event, d) => {
svg.selectAll("path")
.style("opacity", node => node.ancestors().includes(d) ? 1 : 0.2);
});
Изменение данных или состояния диаграммы сопровождается плавными переходами.
svg.selectAll("path")
.transition()
.duration(750)
.attrTween("d", d => {
const interpolate = d3.interpolate(d.current, d);
return t => arc(interpolate(t));
});
Для корректной анимации требуется сохранение текущего состояния узлов.
При динамических изменениях структура пересчитывается:
function update(newData) {
const newRoot = d3.hierarchy(newData)
.sum(d => d.value);
const newPartition = d3.partition().size([2 * Math.PI, radius]);
newPartition(newRoot);
svg.selectAll("path")
.data(newRoot.descendants())
.join("path")
.transition()
.attr("d", arc);
}
Метод join упрощает синхронизацию DOM и данных.
При больших объёмах данных возникают проблемы производительности:
Используются подходы:
Алгоритм partition сам по себе не создаёт радиальную структуру. Он формирует прямоугольные координаты, которые затем интерпретируются через arc-генератор. Это разделение логики позволяет:
Глубина узла напрямую влияет на радиус:
depth = 0 — центр,Каждый уровень равномерно распределяется по радиальной оси, если не задана альтернативная шкала.
Каждый сегмент представляет собой кольцевой сектор, ограниченный:
Площадь сегмента пропорциональна значению узла, что делает диаграмму интуитивно понятной для анализа распределений.
Sunburst-структуры применяются для:
Их преимущество заключается в одновременном отображении структуры и веса данных в одном пространстве.
D3 позволяет комбинировать partition с масштабами:
const x = d3.scaleLinear().range([0, 2 * Math.PI]);
const y = d3.scaleSqrt().range([0, radius]);
Такая комбинация изменяет восприятие пропорций, усиливая или сглаживая различия между сегментами.
Sunburst-диаграмма является радиальным аналогом treemap. Обе
используют d3.hierarchy, но различаются способом
отображения:
Partition выступает общей основой для обоих подходов, обеспечивая единый механизм разбиения иерархии.