Дендрограмма в контексте визуализации данных представляет собой способ отображения иерархической структуры, где элементы объединяются в группы по принципу вложенности. Такая структура возникает в задачах кластеризации, анализа родства данных, построения таксономий и организационных схем.
В основе работы лежит преобразование исходного набора данных в иерархический формат, который обычно выражается в виде дерева. Каждый узел содержит ссылку на потомков, а листья дерева представляют конечные элементы.
В экосистеме D3.js работа с иерархиями строится вокруг объекта
d3.hierarchy, который преобразует обычные JSON-структуры в
формат, пригодный для дальнейшей визуализации.
Пример исходной структуры:
const data = {
name: "root",
children: [
{
name: "A",
children: [
{ name: "A1" },
{ name: "A2" }
]
},
{
name: "B",
children: [
{ name: "B1" },
{ name: "B2" }
]
}
]
};
Преобразование в иерархию:
const root = d3.hierarchy(data);
После этого объект root содержит дополнительные
вычисляемые свойства: глубину узла, количество потомков, суммарные
значения и другие метаданные, необходимые для построения
дендрограммы.
Для построения дендрограммы используется один из двух основных
алгоритмов компоновки: d3.tree и
d3.cluster.
d3.tree равномерно распределяет узлы по глубине и
ширинеd3.cluster выравнивает листья по одному уровню,
формируя более компактную структуруКлассическая дендрограмма чаще строится с использованием
cluster, поскольку она визуально подчёркивает группировку
элементов.
const width = 800;
const height = 600;
const cluster = d3.cluster()
.size([height, width - 200]);
const root = d3.hierarchy(data);
cluster(root);
После вызова cluster(root) каждый узел получает
координаты x и y, определяющие его положение в
двумерном пространстве.
Связи между узлами формируются на основе родительско-дочерних
отношений. В SVG это обычно реализуется через элементы
path, которые соединяют координаты родителя и ребёнка.
D3 предоставляет генератор кривых, например
d3.linkHorizontal, который упрощает создание плавных
соединений.
const link = d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x);
svg.selectAll("path")
.data(root.links())
.enter()
.append("path")
.attr("d", link)
.attr("fill", "none")
.attr("stroke", "#555");
Метод root.links() возвращает массив связей в формате
{ source, target }, что упрощает построение рёбер
графа.
Визуализация дендрограммы обычно выполняется в SVG, поскольку он обеспечивает точное позиционирование элементов и масштабируемость.
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(100,0)");
Смещение по оси X позволяет оставить пространство для подписей узлов и предотвратить обрезание элементов на границе контейнера.
Каждый узел дерева представляется кругом или другим графическим
примитивом. В D3 узлы доступны через
root.descendants().
const nodes = svg.selectAll("g.node")
.data(root.descendants())
.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.y},${d.x})`);
Добавление визуального представления узлов:
nodes.append("circle")
.attr("r", 4)
.attr("fill", d => d.children ? "#555" : "#999");
Текстовые подписи:
nodes.append("text")
.attr("dy", 3)
.attr("x", d => d.children ? -10 : 10)
.style("text-anchor", d => d.children ? "end" : "start")
.text(d => d.data.name);
Иерархические структуры могут содержать десятки уровней вложенности, что требует контроля над масштабом визуализации.
Масштабирование часто реализуется через линейное отображение координат:
const scale = d3.scaleLinear()
.domain([0, root.height])
.range([0, width - 200]);
Глубина узла доступна через d.depth, что позволяет
управлять визуальной иерархией:
.attr("opacity", d => 1 - d.depth * 0.1)
Такое отображение подчёркивает структуру дерева и снижает визуальную перегрузку.
Помимо классической горизонтальной ориентации, дендрограмма может быть представлена в радиальной форме. В этом случае координаты преобразуются из декартовой системы в полярную.
const radius = width / 2;
const cluster = d3.cluster()
.size([360, radius]);
Преобразование координат:
function project(x, y) {
const angle = (x - 90) / 180 * Math.PI;
return [
y * Math.cos(angle),
y * Math.sin(angle)
];
}
Линии связей:
const link = d3.linkRadial()
.angle(d => d.x / 180 * Math.PI)
.radius(d => d.y);
Радиальная форма используется для компактного отображения больших иерархий, где линейное представление становится слишком вытянутым.
Иерархические структуры часто содержат числовые значения, которые
агрегируются вверх по дереву. В D3 это реализуется через метод
sum.
const root = d3.hierarchy(data)
.sum(d => d.value || 0);
Агрегация позволяет вычислять суммарные показатели на уровне поддеревьев, что используется для изменения размеров узлов или толщины связей.
nodes.selectAll("circle")
.attr("r", d => Math.sqrt(d.value));
Дендрограммы часто используются в интерактивных интерфейсах, где узлы
могут сворачиваться и разворачиваться. Это требует управления состоянием
children.
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
Обновление визуализации после изменения структуры:
function update(source) {
const root = d3.hierarchy(data);
cluster(root);
const nodes = root.descendants();
svg.selectAll("g.node")
.data(nodes)
.join(
enter => enter.append("g"),
update => update,
exit => exit.remove()
);
}
Визуальное различие уровней иерархии достигается через изменение цвета, прозрачности и толщины линий.
.attr("stroke-width", d => 2 - d.depth * 0.2)
Цветовая дифференциация:
.attr("fill", d => d.depth === 0 ? "#000"
: d.depth === 1 ? "#444"
: "#888")
Такие приёмы позволяют визуально отделить корневые элементы от периферийных.
При увеличении числа узлов до тысяч элементов основным ограничением становится производительность DOM-рендеринга SVG. Оптимизация достигается через:
transform вместо пересчёта координатhierarchyВ некоторых случаях применяется виртуализация уровней, при которой отображаются только видимые части дерева.
Дендрограммы часто используются совместно с диаграммами связей и тепловыми картами. Иерархическая структура может служить основой для:
Использование общей иерархической модели в D3.js позволяет унифицировать обработку данных и переиспользовать алгоритмы компоновки.