Модуль иерархий в D3 предназначен для работы с древовидными
структурами данных. Функция d3.hierarchy() преобразует
вложенный объект JavaScript в специальную структуру узлов D3, пригодную
для построения:
После преобразования каждый узел получает набор служебных свойств и методов, позволяющих обходить дерево, вычислять глубину, потомков, связи между элементами и выполнять визуальное размещение.
Иерархия представляет собой дерево объектов, где каждый элемент содержит дочерние узлы.
Простейший пример:
const data = {
name: "Компания",
children: [
{
name: "Отдел разработки",
children: [
{ name: "Frontend" },
{ name: "Backend" }
]
},
{
name: "Маркетинг"
}
]
};
Здесь:
"Компания";children;Для преобразования данных используется
d3.hierarchy():
const root = d3.hierarchy(data);
Результатом становится объект типа HierarchyNode.
HierarchyNodeПосле преобразования каждый узел содержит дополнительные свойства.
| Свойство | Описание |
|---|---|
data |
Исходный объект данных |
depth |
Глубина узла |
height |
Максимальная глубина потомков |
parent |
Родительский узел |
children |
Массив дочерних узлов |
value |
Числовое значение |
x, y |
Координаты после layout |
id |
Пользовательский идентификатор |
depthПоказывает уровень вложенности.
console.log(root.depth);
Корневой элемент всегда имеет:
0
Дочерние элементы:
1
Следующий уровень:
2
heightheight показывает расстояние до самого глубокого
потомка.
Пример:
console.log(root.height);
Если дерево имеет 3 уровня вложенности, значение будет:
2
Исходный объект хранится в node.data.
console.log(root.data.name);
Результат:
Компания
Свойство children содержит массив узлов.
console.log(root.children);
Каждый элемент массива — полноценный HierarchyNode.
Свойство parent позволяет подняться вверх по дереву.
const department = root.children[0];
console.log(department.parent.data.name);
Результат:
Компания
По умолчанию D3 ищет массив children.
При другой структуре данных можно указать собственный accessor.
Пример:
const data = {
title: "Root",
items: [
{
title: "Node A",
items: [
{ title: "Node B" }
]
}
]
};
const root = d3.hierarchy(data, d => d.items);
Второй аргумент — функция получения потомков.
descendants()Возвращает всех потомков дерева.
const nodes = root.descendants();
console.log(nodes);
Порядок:
ancestors()Возвращает цепочку родителей.
const node = root.children[0].children[0];
console.log(node.ancestors());
Результат:
[
Frontend,
Отдел разработки,
Компания
]
leaves()Возвращает только конечные узлы.
const leaves = root.leaves();
Листьями считаются элементы без children.
links()Создаёт массив связей между узлами.
const links = root.links();
Каждая связь имеет вид:
{
source: ...,
target: ...
}
Используется при построении линий дерева.
find()Позволяет найти элемент по условию.
const node = root.find(d => d.data.name === "Backend");
console.log(node);
each()Выполняет обход всех узлов.
root.each(node => {
console.log(node.data.name);
});
eachBefore()Обход сверху вниз.
root.eachBefore(node => {
console.log(node.data.name);
});
eachAfter()Обход снизу вверх.
root.eachAfter(node => {
console.log(node.data.name);
});
Метод sort() изменяет порядок дочерних элементов.
root.sort((a, b) => b.height - a.height);
Пример сортировки по имени:
root.sort((a, b) => {
return d3.ascending(a.data.name, b.data.name);
});
sum()Для визуализаций вроде treemap каждому узлу часто требуется числовое значение.
root.sum(d => d.value);
Пример данных:
const data = {
name: "Компания",
children: [
{
name: "Frontend",
value: 40
},
{
name: "Backend",
value: 60
}
]
};
После вызова:
root.value
получит сумму всех потомков:
100
count()Подсчитывает количество листьев.
root.count();
После этого:
console.log(root.value);
будет содержать число конечных узлов.
copy()Создаёт независимую копию иерархии.
const clone = root.copy();
Это полезно при разных вариантах layout для одного набора данных.
d3.tree()d3.hierarchy() часто используется совместно с
layout-функциями.
Пример:
const root = d3.hierarchy(data);
const treeLayout = d3.tree()
.size([500, 300]);
treeLayout(root);
После layout каждый узел получает координаты:
node.x
node.y
const svg = d3.select("svg");
svg.selectAll("line")
.data(root.links())
.enter()
.append("line")
.attr("x1", d => d.source.y)
.attr("y1", d => d.source.x)
.attr("x2", d => d.target.y)
.attr("y2", d => d.target.x)
.attr("stroke", "#999");
svg.selectAll("circle")
.data(root.descendants())
.enter()
.append("circle")
.attr("cx", d => d.y)
.attr("cy", d => d.x)
.attr("r", 6)
.attr("fill", "steelblue");
svg.selectAll("text")
.data(root.descendants())
.enter()
.append("text")
.attr("x", d => d.y + 10)
.attr("y", d => d.x)
.text(d => d.data.name);
d3.cluster()cluster() похож на tree(), но размещает
узлы равномерно.
const cluster = d3.cluster()
.size([400, 300]);
cluster(root);
Разница:
tree() минимизирует пересечения;cluster() создаёт более симметричную структуру.d3.treemap()Treemap отображает иерархию прямоугольниками.
const treemap = d3.treemap()
.size([800, 600])
.padding(2);
root.sum(d => d.value);
treemap(root);
После layout:
d.x0
d.y0
d.x1
d.y1
определяют границы прямоугольника.
d3.partition()Применяется для sunburst-диаграмм.
const partition = d3.partition()
.size([2 * Math.PI, 300]);
partition(root);
Узлы получают:
d.x0
d.x1
d.y0
d.y1
d3.pack()Circle packing строит вложенные окружности.
const pack = d3.pack()
.size([600, 600])
.padding(5);
root.sum(d => d.value);
pack(root);
Каждый узел получает:
d.x
d.y
d.r
Иногда данные находятся в плоском формате.
Пример:
const data = [
{ id: "A", parent: "" },
{ id: "B", parent: "A" },
{ id: "C", parent: "A" },
{ id: "D", parent: "B" }
];
Для таких случаев используется d3.stratify().
const stratify = d3.stratify()
.id(d => d.id)
.parentId(d => d.parent);
const root = stratify(data);
После этого создаётся полноценная иерархия D3.
d3.hierarchy() и d3.stratify()| Метод | Тип данных |
|---|---|
d3.hierarchy() |
Вложенные объекты |
d3.stratify() |
Плоские таблицы |
При визуализации крупных структур возникают проблемы:
const visible = root.descendants()
.filter(d => d.depth < 3);
Часть дерева временно скрывается:
node._children = node.children;
node.children = null;
Для тысяч элементов предпочтительнее Canvas.
Данные загружаются только при раскрытии ветви.
D3 поддерживает реактивное обновление.
Пример:
const nodes = svg.selectAll("circle")
.data(root.descendants(), d => d.data.name);
nodes.enter()
.append("circle");
nodes.exit().remove();
svg.selectAll("circle")
.transition()
.duration(500)
.attr("cx", d => d.y)
.attr("cy", d => d.x);
Пример обработчика:
function toggle(event, d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
childrenОшибка структуры:
{
name: "Root",
child: []
}
Правильно:
{
name: "Root",
children: []
}
или использование accessor-функции.
sum() для
treemapБез вычисления значений treemap не сможет определить размеры элементов.
root.sum(d => d.value);
Иерархия должна быть деревом.
Недопустимо:
A -> B -> C -> A
const data = {
name: "Root",
children: [
{
name: "A",
children: [
{ name: "A1" },
{ name: "A2" }
]
},
{
name: "B"
}
]
};
const width = 800;
const height = 500;
const svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
const root = d3.hierarchy(data);
const treeLayout = d3.tree()
.size([height, width - 200]);
treeLayout(root);
svg.selectAll("line")
.data(root.links())
.enter()
.append("line")
.attr("x1", d => d.source.y)
.attr("y1", d => d.source.x)
.attr("x2", d => d.target.y)
.attr("y2", d => d.target.x)
.attr("stroke", "#999");
svg.selectAll("circle")
.data(root.descendants())
.enter()
.append("circle")
.attr("cx", d => d.y)
.attr("cy", d => d.x)
.attr("r", 5);
svg.selectAll("text")
.data(root.descendants())
.enter()
.append("text")
.attr("x", d => d.y + 10)
.attr("y", d => d.x + 5)
.text(d => d.data.name);
d3.hierarchy()При создании иерархии D3:
HierarchyNode для каждого элемента;| Layout | Назначение |
|---|---|
tree() |
Классическое дерево |
cluster() |
Кластерная схема |
treemap() |
Прямоугольная карта |
partition() |
Sunburst |
pack() |
Вложенные окружности |
Обычно работа с иерархиями строится по этапам:
Данные
↓
d3.hierarchy()
↓
sum()/sort()
↓
layout
↓
SVG / Canvas
↓
анимация и интерактивность
d3.hierarchy() в экосистеме D3d3.hierarchy() является центральным механизмом для всех
древовидных layout-алгоритмов D3. Функция не выполняет визуализацию
самостоятельно, а формирует универсальную структуру данных, которая
затем используется различными алгоритмами размещения. Благодаря этому
одна и та же иерархия может быть визуализирована несколькими способами
без изменения исходных данных.