! [image-20220823162654765](. /image/image-20220823162654765. png)
Как использовать D3.js для визуализации иерархических данных (деревьев). В этой статье показано, как создать иерархическую структуру данных из массива данных. Затем используется D3 для визуализации, с использованием различных форматов отображения, включая Tree,Cluster,Treemap,Pack,Partition
.
Одним из важных шагов при анализе или визуализации данных является их предварительная обработка, например: группировка
Предположим, что у нас есть данные о структуре управления в компании (очень простой набор данных):
name | boss |
---|---|
Подчиненный 1 у Лao Wang | Лao Wang |
Подчиненный 2 у Лao Wang | Лao Wang |
Подчиненный 3 у Лao Wang | Лao Wang |
Подчиненный 1 у Лao Li | Лao Li |
Подчиненный 2 у Лao Li | Лao Li |
Подчиненный 3 у Лao Li | Лao Li |
Подчиненный 4 у Лao Li | Лao Li |
Например, вот данные о фильмах: | |
Можно группировать данные по boss и подсчитывать количество подчиненных у каждого босса: | |
boss | Количество подчиненных |
:--- | :------- |
Лao Li | 4 |
Лao Wang | 3 |
Данные могут иметь иерархическую структуру. Верхний уровень (top level ) — это boss (в данном случае Лao Wang и Лao Li); следующий уровень — это их подчиненные. |
|
Иерархическую структуру можно рассматривать как дерево, где корневой узел (здесь определен как CEO), разделяется на верхний уровень (в данном случае боссы).Каждый верхний узел разделяется на группу второго уровня (в данном случае подчинённые), и так далее: | |
! [image-20220824113057496](. /image/image-20220824113057496. png) |
Верхний уровень элементов (узлов) называется
root node
(корневым узлом). Самый нижний элемент называется листом или листовым узлом. Разумеется, для разных данных можно использовать различные способы группировки (агрегации). В этой статье будут рассмотрены различные способы визуализации иерархических данных, включая Tree, Cluster, Treemap, Pack, Partition
Возьмём, например, данные из приведённого выше примера:
let Structure = [
{
"name":"Подчинённый 1 у Лao Wang",
"boss":"Лao Wang",
},
{
"name":"Подчинённый 2 у Лao Wang",
"boss":"Лao Wang",
},
{
"name":"Подчинённый 3 у Лao Wang",
"boss":"Лao Wang",
},
{
"name":"Подчинённый 1 у Лao Li",
"boss":"Лao Li",
},
{
"name":"Подчинённый 2 у Лao Li",
"boss":"Лao Li",
},
{
"name":"Подчинённый 3 у Лao Li",
"boss":"Лao Li",
},
]
Можно использовать ! [image-20220824113524559](. /image/image-20220824113524559. png) функцию для группировки данных по любому классификационному атрибуту. Первый параметр
. rollup
— это данные, которые нужно группировать (итерируемый массив). Вторым параметром является функция reduce. Это функция, которая принимает массив значений и выводит одно значение. Она проходит по массиву и суммирует один из его атрибутов.Дальнейшие параметры — это функции, которые указывают атрибуты для группировки. (последовательная группировка) Давайте сгруппируем элементы по группамboss
иname
; суммируемlength
let groups = d3.rollup(Structure,
function(d) { return d.length; }, // здесь length равно 1;
function(d) { return d.boss; },
function(d) { return d.name; },
);

В приведенном выше примере данные группируются и суммируются с помощью d3.rollup
.
d3.rollup
возвращает вложенный объект Map
.
Map
можно получить с помощью .get
:
groups.get("Старший");
// Map(3) {'Старший подчиненный 1' => 1, 'Старший подчиненный 2' => 1, 'Старший подчиненный 3' => 1}
groups.get("Старший").get("Старший подчиненный 1")
// 1 возвращает сумму
Внутри D3 есть функция для создания структуры иерархических данных.
Создается путем вызова d3.hierarchy
и передачи в него объекта Map
, созданного с помощью d3.rollup
:
let groups = d3.rollup(Structure,
function(d) { return d.length; },
function(d) { return d.boss; },
function(d) { return d.name; },
);
let root = d3.hierarchy(groups);

В базовом обычном JavaScript объекте определены различные свойства и методы, предоставляющие дополнительные функции. Каждый узел имеет свойства:
data
,children
,depth
иheight
.parent
data
— этоMap
, переданный вd3.hierarchy
. Обычно значение этого свойства не требуется, так как иерархияhierarchy
делает данные доступными через свойстваchildren
иvalue
.children
— это массив, содержащий подузлы.depth
иheight
указывают глубину и высоту узла в иерархии. (Глубина корневого узла равна нулю, высота листового узла равна нулю.)parent
— это родительский узел текущего узла. Листовые узлы выглядят так:Можно видеть, что свойство
data
содержит сводные значения. Если сводное значение является суммой или подсчетом, можно использовать метод.sum
иерархии для возврата значения вroot
:
let groups = d3.rollup(Structure,
function(d) { return d.length; },
function(d) { return d.boss; },
function(d) { return d.name; },
);
let root = d3.hierarchy(groups);
root.sum(function(d) {
return d[1];
});
Метод
.sum
принимает функцию-акцессор, первый аргумент которой является свойствомdata
узла. Функция-акцессор возвращает значение, которое требуется сложить.Обратите внимание, что каждый листовой узел теперь имеет свойство
value
, равное сумме значений. Например:
Нелистовые узлы также имеют свойство
value
, равное сумме значений их дочерних узлов.
Каждый узел в D3-иерархии имеет удобные методы, такие как
.descendants
,.ancestors
и.links
.
.descendants
возвращает массив, содержащий узел и его дочерние узлы.
.ancestors
возвращает массив, содержащий узел и его родителей (до корневого узла).
.links
возвращает массив, содержащий пары связей между узлами.links` возвращает массив объектов, представляющих соединения между узлами и их дочерними узлами, до листовых узлов.Визуализация иерархии
Существует несколько методов размещения для визуализации иерархии, включая:
Tree
! [image-20220824141237713](. /image/image-20220824141237713. png)
https://codepen. io/wantnocode/pen/BarEdxY
Treemap
! [image-20220824154122632](. /image/image-20220824154122632. png)
https://codepen. io/wantnocode/pen/gOeyxJe? editors=1111
Pack
! [image-20220824151902200](. /image/image-20220824151902200. png)
https://codepen. io/wantnocode/pen/dymLVaj
partition
! [image-20220824153940570](. /image/image-20220824153940570. png)
https://codepen. io/wantnocode/pen/MWVROgJ
D3 использует функции размещения для выполнения вышеупомянутых форм визуализации. Эти функции принимают структуру
d3. hierarchy
и добавляют визуальные переменные отображения, такие как положение и размер.Например, размещение дерева добавляет значения
x
иy
к каждому узлу, чтобы узлы образовывали форму дерева.В этой главе мы рассмотрим размещения
tree
,cluster
,treemap
,pack
иpartition
.Tree Layout (Размещение дерева)
Размещение узлов иерархии в виде дерева.
! [image-20220824141237713](. /image/image-20220824141237713. png)
https://codepen. io/wantnocode/pen/BarEdxY
Сначала создайте функцию размещения дерева с помощью
d3. tree()
:var treeLayout = d3. tree();
d3. tree() возвращает функцию размещения, которую можно передать объекту иерархии.
Размер дерева можно настроить с помощью команды
. size
:treeLayout. size([700, 200]); ```> ```Затем можно вызвать `treeLayout`, передав в него определенный выше объект иерархии `root`:
treeLayout(root);
Это запишет значения x
и y
на каждом узле.
Рисование узлов:
root.descendants()
для получения массива всех узловx
и y
для позиционирования кругов
Для рисования связей:root.links()
для получения массива всех связейline
(или path
)x
и y
атрибуты source
и target
для позиционирования line
root.links()
возвращает массив, каждый элемент которого представляет объект с двумя свойствами:source
иtarget
, представляющими источник и целевую точку связи соответственно.
// Связи
d3.select('svg g')
.selectAll('line')
.data(root.links())
.join('line')
.attr('x1', function(d) {return d.source.x;})
.attr('y1', function(d) {return d.source.y;})
.attr('x2', function(d) {return d.target.x;})
.attr('y2', function(d) {return d.target.y;});
// Узлы
d3.select('svg g')
.selectAll('circle')
.data(root.descendants())
.join('circle')
.attr('cx', function(d) {return d.x;})
.attr('cy', function(d) {return d.y;})
.attr('r', 4);
### Кластерная разметка (Cluster Layout)
Разметка очень похожа на разметку cluster
, но основное отличие заключается в том, что все листовые узлы располагаются на одной глубине.
var clusterLayout = d3.cluster()
.size([400, 200]);
var root = d3.hierarchy(data);
clusterLayout(root);
Для создания функции разметки treemap вызовите
d3.treemap()
:
var treemapLayout = d3.treemap();
Как и раньше, вы можете настроить разметку:
treemapLayout
.size([400, 200])
.paddingOuter(10);
Перед применением этой разметки к вашей иерархии вам необходимо вызвать .sum()
на иерархии. Это пройдет по дереву и установит .value
для каждого узла как сумму его подузлов:
root.sum(function(d) {
return d.value;
});
Обратите внимание, что в
.sum()
передается функция-посетитель для указания свойства, которое требуется суммировать. Теперь вы можете вызватьtreemapLayout
, передав в него определенный выше объект иерархииroot
:
treemapLayout(root);
Функция размещения дерева добавляет 4 атрибута x0
, y0
, x1
и y1
к каждому узлу, которые определяют размер каждого прямоугольника в дереве.
Теперь вы можете связать узлы с элементами rect
и обновить их атрибуты x
, y
, width
и height
:
d3.select('svg g')
.selectAll('rect')
.data(root.descendants())
.join('rect')
.attr('x', function(d) { return d.x0; })
.attr('y', function(d) { return d.y0; })
.attr('width', function(d) { return d.x1 - d.x0; })
.attr('height', function(d) { return d.y1 - d.y0; })
Если вы хотите добавить метки в каждый прямоугольник, вы можете добавить элементы g
в массив и добавить элементы rect
и text
к каждому g
:
var nodes = d3.select('svg g')
.selectAll('g')
.data(rootNode.descendants())
.join('g')
.attr('transform', function(d) {return 'translate(' + [d.x0, d.y0] + ')'})
nodes
.append('rect')
.attr('width', function(d) { return d.x1 - d.x0; })
.attr('height', function(d) { return d.y1 - d.y0; })
nodes
.append('text')
.attr('dx', 4)
.attr('dy', 4)
.text(function(d) { return d.data.name; })
``````javascript
.attr('dy', 14)
.text(function(d) {
return d.data.name;
})
`treemap` может быть настроен несколькими способами:
- Полярность между узлами и их дочерними узлами может быть установлена с помощью `.paddingOuter`
- Полярность между братскими узлами может быть установлена с помощью `.paddingInner`
- Оба полярности могут быть установлены одновременно с помощью `.padding`
- Внешняя полярность может быть установлена с помощью `.paddingTop`, `.paddingBottom`, `.paddingLeft` и `.paddingRight`.
Треемап имеет не одну, а несколько стратегий размещения прямоугольников. D3 имеет встроенные стратегии, такие как `treemapBinary`, `treemapDice` и `treemapSquarify`.
`treemapBinary` стремится к балансу между горизонтальными и вертикальными разделами, `treemapDice` чередует горизонтальные и вертикальные разделы, а `treemapSquarify` позволяет аспектному соотношению прямоугольников изменяться.
Вы можете выбрать стратегию размещения с помощью метода `.tile`:
treemapLayout.tile(d3.treemapDice)
### Обертка размещения (Pack Layout)
`Pack`-размещение похоже на `Tree`-размещение, но **круги** используются для представления узлов.

Функция размещения `Pack` создается с помощью команды `d3.pack()`:
var packLayout = d3.pack();
Как и раньше, размер можно настроить, передавая массив `[width, height]` методу `.size`:
packLayout.size([300, 300]);
Как и в случае с `treemap`, необходимо вызвать `.sum()` **до применения размещения**:
rootNode.sum(function(d) { return d.value; }); packLayout(rootNode);
``` Размещение `pack` добавляет атрибуты `x` и `y` для каждого узла.
Теперь можно связать элементы `circle` с дочерними узлами `root`:
d3.select('svg g') .selectAll('circle') .data(rootNode.descendants()) .join('circle') .attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }) .attr('r', function(d) { return d.r; })
Можно создать элементы для каждого дочернего узла, добавив метки:
var nodes = d3.select('svg g') .selectAll('g') .data(rootNode.descendants()) .join('g') .attr('transform', function(d) {return 'translate(' + [d.x, d.y] + ')'}); nodes .append('circle') .attr('r', function(d) { return d.r; }) nodes .append('text') .attr('dy', 4) .text(function(d) { return d.children === undefined ? d.data.name : ''; })
! [image-20220824151749759](. /image/image-20220824151749759. png)
Размер пустого пространства вокруг каждого круга можно настроить с помощью метода `.padding()`:
packLayout.padding(20);
> Обратите внимание, что настройка должна быть выполнена до применения размещения `packLayout()`;
! [image-20220824151902200](. /image/image-20220824151902200. png)
> https://codepen.io/wantnocode/pen/dymLVaj
### Partition layout (размещение разделения)
Размещение `partition` делит прямоугольное пространство на слои, каждый из которых представляет собой уровень в иерархии. Для каждого узла в слое каждый слой делится дальше:
! [img](https://www.d3indepth.com/img/layouts/partition.png)
Функция размещения разделения создается с помощью команды `d3.partition()`:
var partitionLayout = d3.partition();
Как и раньше, размер можно настроить, передавая массив `[width, height]` методу `.size`:
partitionLayout.size([400, 200]);
``` Как и в случае с treemap
, необходимо вызвать `.sum()` до применения размещения:
rootNode.sum(function(d) {
return d.value;
});
partitionLayout(rootNode);
```Теперь можно соединить элемент `rect` с каждым подузлом `root`:
d3.select('svg g') .selectAll('rect') .data(rootNode.descendants()) .join('rect') .attr('x', function(d) { return d.x0; }) .attr('y', function(d) { return d.y0; }) .attr('width', function(d) { return d.x1 - d.x0; }) .attr('height', function(d) { return d.y1 - d.y0; });

Для добавления отступа между узлами можно использовать метод `.padding()`:
partitionLayout.padding(2);

> https://codepen.io/wantnocode/pen/WNzWZWv
Если нужно изменить направление разбиения, чтобы слои шли слева направо, можно поменять местами `x0` и `y0` с `x1` и `y1` в элементе `rect`:
.attr('x', function(d) { return d.y0; }) .attr('y', function(d) { return d.x0; }) .attr('width', function(d) { return d.y1 - d.y0; }) .attr('height', function(d) { return d.x1 - d.x0; });

> https://codepen.io/wantnocode/pen/ZExZXde
Также можно отобразить `x` как угол поворота, а `y` как радиус для создания солнечной диаграммы разбиения:
```
d3.arc()
.startAngle(function(d) { return d.x0; })
.endAngle(function(d) { return d.x1; })
.innerRadius(function(d) { return d.y0; })
.outerRadius(function(d) { return d.y1; });

Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )