Создание радиального дерева с использованием D3.js для JavaScript

В этой статье я опишу вам, как вы можете закодировать радиальное дерево на JavaScript, используя библиотеку D3.js (версия 6). Попутно я также рассмотрю ряд концепций, используемых в D3.js и SVG для HTML. Так что, даже если радиальные деревья вас не интересуют, вы все равно можете найти здесь полезную информацию.

Причина написания этого заключается в том, что я искал простое и понятное решение, но не нашел ни одного, которое работало бы.

Код, который вы найдете ниже, представляет собой синтез кода из статей [1], [2] и [3], которые вы можете найти в Справочниках в конце этой статьи. Код должен работать сразу.

По сути, цель состоит в том, чтобы создать радиальное дерево на основе некоторых данных, которые у нас есть, и которые уже имеют какую-то древовидную структуру.

Структурированные данные

Ниже приведен пример данных в формате JSON, который я буду использовать в этой статье. Вы можете видеть, что он имеет четкую древовидную структуру. Это взято из [2].

let root = {"name" : "A", "info" : "tst", "children" : [
                        {"name" : "A1", "children" : [
                                {"name" : "A12" },
                                {"name" : "A13" },
                                {"name" : "A14" },
                                {"name" : "A15" },
                                {"name" : "A16" }
                            ] },
                        {"name" : "A2", "children" : [
                                {"name" : "A21" },
                                {"name" : "A22", "children" : [
                                {"name" : "A221" },
                                {"name" : "A222" },
                                {"name" : "A223" },
                                {"name" : "A224" }
                            ]},
                                {"name" : "A23" },
                                {"name" : "A24" },
                                {"name" : "A25" }] },
                        {"name" : "A3", "children": [
                                {"name" : "A31", "children" :[
                                        {"name" : "A311" },
                                        {"name" : "A312" },
                                        {"name" : "A313" },
                                        {"name" : "A314" },
                                        {"name" : "A315" }
                                    ]}] }
                    ]};

Обзор Кодекса

Перед тем, как начать кодировать радиальное дерево, вам необходимо импортировать библиотеку D3.js. В этой статье мы будем использовать версию 6.

Импорт библиотеки D3.js

Разместите следующий код под элементом head своей HTML-страницы. Это позволит импортировать библиотеку D3.js для использования на вашей веб-странице.

<head>
...
    <script src="https://d3js.org/d3.v6.min.js"></script>
...
</head>

Теперь мы можем приступить к работе с JavaScript.

Создайте элемент SVG, содержащий радиальное дерево

Сначала установите высоту и ширину. На мой взгляд, квадрат имеет наибольший смысл, учитывая, что радиальное дерево лучше всего подходит для квадратной формы.

let height = 600;
let width = 600;

Затем мы позволяем D3 создать элемент SVG с указанными выше высотой и шириной для нас под элементом body.

let svg = d3.select('body')
        .append('svg')
        .attr('width', width)
        .attr('height', height);

Элемент SVG также может быть создан под другим элементом, например, div с определенным id, если необходимо.

Фактически, также возможно стилизовать элемент SVG с помощью CSS, включенного в элемент head.

Здесь мы придаем элементу SVG красивую рамку.

<head>
...
    <style>
        svg {
            border: solid 1px #ccc;
        }    
    </style>
...
</head>

Если вы запустите этот код, вы увидите пустое изображение SVG размером 600x600 пикселей с красивой рамкой вокруг него.

Создайте древовидную иерархию на основе данных

D3 требует, чтобы вы создали так называемый «корневой узел», прежде чем вы сможете создать иерархический макет - для дерева. Учитывая, что наши данные JSON уже находятся в иерархическом формате, мы можем просто использовать следующий код:

let input = root //our JSON data from the beginning of the article
let data = d3.hierarchy(input)

Если наши данные не были в иерархическом формате, как, например, табличные данные, то вместо этого можно использовать функцию d3.stratify (). См. [6].

Создайте дерево D3, содержащее наши данные

Приступим к созданию нашего дерева.

Мы можем установить диаметр на 3/4 высоты. Это простое удобство. Вы можете использовать любое соотношение, подходящее для конкретного дерева. Может потребоваться некоторая настройка, чтобы ваше радиальное дерево правильно поместилось в поле SVG.

Учитывая, что мы создаем радиальное дерево, размер дерева определяется общими градусами, которые оно охватывает - в данном случае 2 * PI - и радиусом.

Если поставить здесь PI вместо 2 * PI, то получится половина радиального дерева.

Аксессор separa () необходим для установки расстояния между соседними узлами в дереве. При необходимости см. Ссылку здесь [5].

let diameter = height * 0.75;
let radius = diameter / 2;
let tree = d3.tree()
    .size([2 * Math.PI, radius])
    .separation(function(a, b) { 
        return (a.parent == b.parent ? 1 : 2) / a.depth; 
    });

Теперь, когда у нас есть пустое дерево, можно добавлять в него данные.

let treeData = tree(data);

Мы также можем получить узлы и ссылки из дерева.

let nodes = treeData.descendants();
let links = treeData.links();

Создание элемента SVG, содержащего радиальное дерево

Следующее, что мы можем сделать, это создать элемент g под элементом SVG. Этот элемент позволяет нам группировать различные элементы SVG вместе. Как [4] заявляет:

Элемент группы SVG, ‹g›, отлично подходит для логической группировки наборов связанных графических объектов. Эта групповая возможность позволяет легко добавлять стили, преобразования, интерактивность и даже анимацию для целых групп объектов.

Элемент g размещается в середине изображения SVG. Размещение элемента g можно выполнить только с помощью преобразования, поскольку он не имеет координат x и y.

Мы будем использовать имя переменной graphGroup, чтобы ссылаться на нее.

let graphGroup = svg.append('g')
    .attr('transform', "translate("+(width/2)+","+(height/2)+")");

Создание путей SVG для соединения узлов в дереве

Теперь мы можем создавать пути между всеми узлами в дереве. Они представлены переменной links.

Используя selectAll (), вы можете создать пустой выбор. С помощью data () каждая из ссылок объединяется с пустым выделенным фрагментом. В результате получается массив элементов, длина которого равна количеству ссылок. Однако все эти элементы пусты.

Для заполнения пустых элементов используется функция join (). Таким образом, пустые элементы преобразуются в элементы path (SVG).

Затем для атрибута class устанавливается значение link, а атрибут d заполняется с помощью функции d3.linkRadial (). . Последняя функция по существу создает радиальную структуру дерева.

graphGroup.selectAll(".link")
    .data(links)
    .join("path")
    .attr("class", "link")
    .attr("d", d3.linkRadial()
        .angle(d => d.x)
        .radius(d => d.y));

В случае, если мы хотим создать вертикальное или горизонтальное дерево вместо радиального, мы могли бы рассмотреть возможность использования d3.linkVertical () и d3.linkHorizontal () соответственно. См. [7].

Ссылки также можно стилизовать с помощью CSS, чтобы они были видны и выглядели правильно.

<head>
...
    <style>
        ...
        .link {
            fill: none;
            stroke: #ccc;
            stroke-width: 1.5px;
        }    
    </style>
...
</head>

Создание кругов и текста SVG для представления узлов дерева

Процесс работает так же, как и для ссылок, но немного сложнее.

Вместо использования join () с элементом SVG path мы используем элемент SVG g. Элемент g используется для группировки кругов и текста для узлов.

На этот раз атрибут class является узлом.

Кроме того, элемент g преобразуется таким образом, что он помещается в дерево.

let node = graphGroup
        .selectAll(".node")
        .data(nodes)
        .join("g")
        .attr("class", "node")
        .attr("transform", function(d){
            return `rotate(${d.x * 180 / Math.PI - 90})` 
                + `translate(${d.y}, 0)`;
        });

Теперь мы можем добавить кружок к элементу g. Этот круг будет представлять сам узел. Атрибут r используется для определения радиуса круга.

node.append("circle").attr("r", 1);

И, наконец, можно также добавить текст к каждому из узлов.

Здесь можно добавить различные текстовые атрибуты, такие как font-size и font-family.

Атрибуты dx и dy определяют положение текста по отношению к самому узлу.

text-anchor и преобразование используются вместе для правильного выравнивания текста по узлам, чтобы он оставался читаемым.

Наконец, к узлу добавляется фактический текст.

node.append("text")
    .attr("font-family", "sans-serif")
    .attr("font-size", 12)
    .attr("dx", function(d) { return d.x < Math.PI ? 8 : -8; })
    .attr("dy", ".31em")
    .attr("text-anchor", function(d) { 
        return d.x < Math.PI ? "start" : "end"; 
    })
    .attr("transform", function(d) { 
        return d.x < Math.PI ? null : "rotate(180)"; 
    })
    .text(function(d) { return d.data.name; });

Полученное радиальное дерево

Когда вы запустите код, вы получите веб-страницу, содержащую радиальное дерево, подобное приведенному ниже:

Полный код

использованная литература

[1] « Радиальное аккуратное дерево Майка Бостока»

[2] « d3.js - От дерева к кластеру и радиальная проекция Свена JavaDude Хафнера»

[3] Тема« Радиальное дерево / кластер D3 v5 в StackOverflow»

[4] « Создание веб-приложений с SVG Дэвидом Дейли, Джоном Фростом и Доменико Страцзулло из Microsoft Press» (партнерская ссылка)

[5] Дерево D3.js с GitHub

[6] Иерархия D3.js с GitHub

[7] Ссылки на D3.js с GitHub