Загрузка стратифицированных данных: d3.stratify()

Библиотека D3.js предоставляет механизм преобразования плоских табличных данных в иерархические структуры через d3.stratify(), что критически важно для построения деревьев, кластеров и диаграмм вложенности.

Функция d3.stratify() выполняет трансформацию набора объектов, содержащих идентификаторы и ссылки на родительские узлы, в полноценную иерархию в формате d3.hierarchy. Входные данные при этом остаются плоскими, но приобретают структуру связей «родитель–потомок».


d3.stratify() строит дерево на основе двух ключевых правил:

  • каждый узел должен иметь уникальный идентификатор (id)
  • каждый узел (кроме корневого) должен ссылаться на родителя через parentId

На выходе формируется объект иерархии, совместимый с алгоритмами раскладки деревьев (d3.tree, d3.cluster, d3.pack).


Базовая структура входных данных

Типичный набор данных:

const data = [
  { id: "root", parentId: "" },
  { id: "a", parentId: "root" },
  { id: "b", parentId: "root" },
  { id: "a1", parentId: "a" },
  { id: "a2", parentId: "a" },
  { id: "b1", parentId: "b" }
];

Особенности:

  • root не имеет родителя
  • остальные элементы ссылаются на существующие узлы
  • структура может быть произвольной глубины

Создание стратификатора

Функция d3.stratify() возвращает конфигурируемый конструктор иерархии.

const stratify = d3.stratify()
  .id(d => d.id)
  .parentId(d => d.parentId);

После настройки выполняется преобразование:

const root = stratify(data);

Результат — объект типа d3.hierarchy.


Внутренняя модель результата

Каждый узел в иерархии содержит:

  • data — исходный объект
  • parent — ссылка на родителя
  • children — массив потомков
  • depth — глубина узла
  • height — высота поддерева
  • id — идентификатор (если задан)

Пример обхода:

console.log(root.children);
console.log(root.depth);

Обязательные условия корректности

При работе d3.stratify() строго проверяются ограничения:

1. Уникальность идентификаторов

Если два узла имеют одинаковый id, возникает ошибка.

2. Существование родителя

Каждый parentId должен ссылаться на существующий id.

3. Единственный корень

Только один узел может иметь пустой parentId.


Обработка ошибок

При нарушении структуры возникают исключения:

  • циклические зависимости
  • отсутствующие родительские узлы
  • множественные корни

Типичный случай ошибки:

Error: multiple roots

Настройка доступа к полям

d3.stratify() не требует фиксированных названий полей:

const stratify = d3.stratify()
  .id(d => d.key)
  .parentId(d => d.parent);

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


Преобразование в иерархию d3.hierarchy

После вызова:

const root = stratify(data);

объект автоматически совместим с алгоритмами раскладки:

const treeLayout = d3.tree().size([width, height]);

treeLayout(root);

Результат содержит координаты x и y для визуализации.


Использование в визуализациях деревьев

Типичный сценарий — построение древовидной схемы:

const link = svg.selectAll("path")
  .data(root.links())
  .enter()
  .append("path")
  .attr("d", d3.linkHorizontal()
    .x(d => d.y)
    .y(d => d.x));

Узел-структура, полученная через stratify, напрямую поддерживает:

  • root.descendants()
  • root.links()
  • root.each()

Разница между stratify и hierarchy

Метод Источник структуры Назначение
d3.hierarchy() вложенные объекты JSON-деревья
d3.stratify() плоские таблицы parentId-связи

d3.stratify() применяется, когда данные приходят из:

  • CSV
  • баз данных
  • API с реляционной моделью

Работа с CSV данными

Частый сценарий — загрузка через d3.csv:

d3.csv("data.csv").then(data => {
  const root = stratify(data);
});

CSV формат:

id,parentId
root,
a,root
b,root
a1,a
a2,a

Фильтрация и подготовка данных

Перед стратификацией часто выполняется предобработка:

data = data.filter(d => d.id && d.id !== "null");

или нормализация:

data.forEach(d => {
  d.parentId = d.parentId || "";
});

Глубина и вычисляемые свойства

После построения иерархии доступны вычисления:

console.log(root.height);
console.log(root.count());

Методы:

  • count() — количество всех потомков
  • each() — обход всех узлов
  • sort() — сортировка дерева

Сортировка узлов

root.sort((a, b) => b.height - a.height);

Сортировка влияет на:

  • порядок визуальных элементов
  • расположение ветвей
  • структуру обхода

Оптимизация больших деревьев

При работе с тысячами узлов важны:

  • минимизация повторных вызовов stratify
  • кеширование результата
  • предварительная нормализация данных

Пример:

const cached = new Map();

function getHierarchy(data) {
  const key = JSON.stringify(data);
  if (cached.has(key)) return cached.get(key);

  const result = stratify(data);
  cached.set(key, result);
  return result;
}

Взаимодействие с layout-алгоритмами

После стратификации данные используются в:

  • d3.tree() — классическое дерево
  • d3.cluster() — компактная кластеризация
  • d3.pack() — вложенные круги
const pack = d3.pack().size([500, 500]);
const packed = pack(root);

Работа с ссылочной целостностью

Важно поддерживать согласованность:

  • отсутствие «висячих» ссылок
  • отсутствие циклов
  • единая корневая точка

Циклы нарушают модель дерева и приводят к ошибкам исполнения.


Расширенные сценарии использования

Стратификация применяется не только для деревьев, но и для:

  • организационных структур
  • файловых систем
  • категорий товаров
  • зависимостей задач
  • графов с ограничением на дерево

Комбинирование с масштабированием

После построения иерархии часто применяется масштабирование координат:

const x = d3.scaleLinear().domain([0, root.height]).range([0, width]);

Работа с динамическими данными

При обновлении данных:

root = stratify(newData);
update(root);

Важно полностью пересчитывать структуру, так как изменения в parentId могут перераспределить дерево.


Взаимодействие с DOM

Привязка узлов:

const nodes = svg.selectAll("circle")
  .data(root.descendants());

Удаление устаревших:

nodes.exit().remove();

Итоговая модель применения

d3.stratify() выступает как мост между:

  • реляционными данными
  • иерархическими структурами визуализации

и формирует основу для всех последующих этапов построения дерева внутри D3.js.