Treemap: d3.treemap()

Иерархические данные в визуализации часто представляются в виде вложенных структур, где каждый узел содержит дочерние элементы. Для компактного отображения таких структур используется treemap-раскладка, реализуемая в D3.js через d3.treemap().

Treemap преобразует дерево данных в набор прямоугольников, площадь которых пропорциональна значению узлов. Вся область визуализации последовательно делится между потомками, формируя плотную упаковку без пересечений.


Основа работы treemap — объект иерархии, создаваемый через d3.hierarchy. Данные должны иметь структуру вложенных объектов:

const data = {
  name: "root",
  children: [
    {
      name: "A",
      value: 10
    },
    {
      name: "B",
      children: [
        { name: "B1", value: 5 },
        { name: "B2", value: 15 }
      ]
    }
  ]
};

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


Построение иерархии

Перед применением treemap данные преобразуются:

const root = d3.hierarchy(data)
  .sum(d => d.value)
  .sort((a, b) => b.value - a.value);

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


Конфигурация treemap

Функция d3.treemap() создаёт layout-алгоритм, который настраивается параметрами:

const treemapLayout = d3.treemap()
  .size([width, height])
  .paddingInner(2)
  .paddingOuter(4)
  .paddingTop(10);

Основные параметры:

  • size([w, h]) — размер области отображения
  • paddingInner — отступы между соседними листьями
  • paddingOuter — отступы от границ контейнера
  • paddingTop — дополнительное пространство для заголовков

Вычисление раскладки

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

treemapLayout(root);

После вызова каждому узлу добавляются свойства:

  • x0, y0 — верхний левый угол
  • x1, y1 — нижний правый угол

Эти координаты определяют прямоугольники, которые затем отображаются в SVG или Canvas.


Геометрия прямоугольников

Каждый узел иерархии становится прямоугольником:

root.leaves().forEach(d => {
  console.log(d.data.name, d.x0, d.y0, d.x1, d.y1);
});

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

[ Area_{node} value_{node}]

Родительский узел представляет сумму всех потомков.


Отрисовка через SVG

Типичная реализация использует группы <g> и прямоугольники <rect>:

const svg = d3.select("svg")
  .attr("width", width)
  .attr("height", height);

const nodes = svg.selectAll("g")
  .data(root.leaves())
  .enter()
  .append("g")
  .attr("transform", d => `translate(${d.x0},${d.y0})`);

nodes.append("rect")
  .attr("width", d => d.x1 - d.x0)
  .attr("height", d => d.y1 - d.y0);

Каждый прямоугольник визуализирует листовой узел дерева.


Подписи и текстовая информация

Добавление текста требует учета размеров ячеек:

nodes.append("text")
  .text(d => d.data.name)
  .attr("x", 4)
  .attr("y", 14)
  .style("font-size", "12px");

Для предотвращения переполнения применяются проверки:

  • минимальная ширина блока
  • обрезка текста
  • перенос строк

Алгоритм упаковки (tiling)

d3.treemap() использует алгоритм рекурсивного разбиения пространства. Основная идея:

  1. Берётся корневой прямоугольник
  2. Потомки распределяются пропорционально значениям
  3. Каждый уровень рекурсивно делит свою область

Различные стратегии укладки:

  • binary
  • squarify (по умолчанию)
  • slice
  • dice
  • sliceDice

Алгоритм squarify минимизирует отклонение от квадратной формы, улучшая читаемость.


Настройка стратегии укладки

d3.treemap()
  .tile(d3.treemapSquarify);

Или альтернативные варианты:

d3.treemapBinary
d3.treemapSlice
d3.treemapDice
d3.treemapSliceDice

Выбор влияет на визуальную структуру и восприятие плотности данных.


Работа с глубиной и вложенностью

Каждый уровень иерархии может быть визуально различим:

nodes
  .style("fill", d => d.depth === 1 ? "#ccc" : "#888");

Глубина узла определяется свойством depth, начиная с 0 для корня.


Масштабирование и адаптация

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

treemapLayout.size([newWidth, newHeight])(root);

После перерасчёта обновляются координаты всех узлов, включая вложенные уровни.


Иерархические ограничения

Treemap имеет структурные особенности:

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

Цветовое кодирование

Цвет часто отражает дополнительную метрику:

const color = d3.scaleOrdinal(d3.schemeCategory10);

nodes.select("rect")
  .style("fill", d => color(d.parent.data.name));

Возможны схемы:

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

Агрегация значений

Если узел не содержит value, используется сумма потомков:

d3.hierarchy(data)
  .sum(d => d.value || 0);

Это обеспечивает автоматическую агрегацию вверх по дереву.


Фильтрация и выборка

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

root.each(d => {
  if (d.depth > 3) d.children = null;
});

Это уменьшает количество элементов и упрощает визуальную структуру.


Производительность

Treemap чувствителен к количеству узлов. При больших деревьях применяются:

  • предварительная агрегация данных
  • ограничение глубины
  • виртуализация отрисовки
  • переход на Canvas вместо SVG

SVG остаётся предпочтительным при умеренных объёмах данных.


Координатная система и трансформации

Каждый узел располагается в глобальной системе координат treemap:

  • x0, y0 — абсолютные координаты
  • размеры вычисляются через разность координат

SVG-группы используют трансформацию:

.attr("transform", d => `translate(${d.x0},${d.y0})`);

Это позволяет отделить геометрию от содержимого узла.


Вложенные подписи и интерактивность

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

  • скрытие при малой площади
  • масштабирование шрифта
  • динамическая обрезка

Интерактивные состояния часто зависят от событий:

nodes.on("mouseover", function(event, d) {
  d3.select(this).style("opacity", 0.7);
});

Связь с другими layout-алгоритмами

Treemap относится к семейству иерархических layout-методов наряду с:

  • dendrogram
  • partition layout
  • pack layout

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


Обработка обновлений данных

При изменении данных выполняется полный пересчёт:

const updatedRoot = d3.hierarchy(newData)
  .sum(d => d.value);

treemapLayout(updatedRoot);

После этого обновляются DOM-элементы через data join.


Масштабируемые структуры

Treemap эффективен для представления:

  • файловых систем
  • бюджетных распределений
  • категориальных иерархий
  • аналитических панелей

Каждый уровень отражает вложенность, а площадь — вклад в общий объём.