Icicle-диаграмма

Icicle-диаграмма представляет собой способ визуализации иерархических данных в виде вложенных прямоугольников, расположенных друг над другом или последовательно вдоль оси. Каждый уровень иерархии отображается как горизонтальная полоса, внутри которой размещаются дочерние элементы. Площадь или длина каждого прямоугольника пропорциональна значению узла, что позволяет быстро оценивать распределение данных в структуре.

Основой построения таких диаграмм в веб-разработке выступает D3.js, предоставляющая инструменты для работы с иерархическими данными, вычисления раскладок и управления SVG-элементами.


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

{
  "name": "root",
  "value": 100,
  "children": [
    {
      "name": "A",
      "value": 60,
      "children": [
        { "name": "A1", "value": 30 },
        { "name": "A2", "value": 30 }
      ]
    },
    {
      "name": "B",
      "value": 40
    }
  ]
}

Важные характеристики структуры:

  • Корневой узел — отправная точка визуализации
  • Дочерние узлы — вложенные элементы
  • Листовые узлы — конечные элементы без потомков
  • Значение (value) — определяет размер элемента

Принцип раскладки icicle

Для построения используется алгоритм partition layout, который преобразует дерево в координаты прямоугольников.

Основная идея:

  • ось X отвечает за ширину сегмента

  • ось Y отвечает за уровень глубины

  • каждый узел получает координаты:

    • x0, x1 — горизонтальные границы
    • y0, y1 — вертикальные границы

Формально распределение базируется на вычислении суммы значений потомков и пропорциональном делении пространства.


Базовая реализация в D3.js

D3.js предоставляет модуль d3.hierarchy и d3.partition, которые используются совместно.

const width = 900;
const height = 500;

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

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

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

partition(root);

svg.selectAll("rect")
  .data(root.descendants())
  .enter()
  .append("rect")
  .attr("x", d => d.x0)
  .attr("y", d => d.y0)
  .attr("width", d => d.x1 - d.x0)
  .attr("height", d => d.y1 - d.y0)
  .attr("fill", d => d.depth === 0 ? "#ccc" : "#69b3a2")
  .attr("stroke", "#fff");

Построение иерархии через d3.hierarchy

d3.hierarchy преобразует обычный JSON в структуру узлов с дополнительными метаданными:

  • depth — глубина узла
  • height — расстояние до листьев
  • parent — ссылка на родителя
  • children — массив потомков

Функция .sum() задаёт правило вычисления значений листьев, а .sort() определяет порядок отображения.


Алгоритм partition

Partition-алгоритм делит пространство следующим образом:

  1. Вычисляется суммарное значение каждого поддерева
  2. Родитель получает диапазон координат
  3. Потомки делят диапазон пропорционально своим значениям
  4. Глубина узла определяет вертикальный уровень

Этот механизм обеспечивает строгую вложенность без пересечений.


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

Цвет используется для различения уровней или категорий:

const color = d3.scaleOrdinal()
  .domain([0, 1, 2, 3])
  .range(["#e0f3db", "#a8ddb5", "#43a2ca", "#0868ac"]);

svg.selectAll("rect")
  .attr("fill", d => color(d.depth));

Варианты применения:

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

Подписи узлов

Добавление текстовых меток требует учета размеров прямоугольников:

svg.selectAll("text")
  .data(root.descendants())
  .enter()
  .append("text")
  .attr("x", d => d.x0 + 4)
  .attr("y", d => d.y0 + 15)
  .text(d => d.data.name)
  .style("font-size", "12px")
  .style("fill", "#000");

Ограничение:

  • подписи отображаются только при достаточной ширине узла
  • часто применяется фильтрация по d.x1 - d.x0

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

Размеры icicle-диаграммы зависят от контейнера. Используются:

  • viewBox для масштабируемости SVG
  • динамический расчёт ширины и высоты
  • пересчёт layout при resize
const partition = d3.partition()
  .size([width, height - 20]);

При изменении размеров требуется повторный вызов layout.


Интерактивность

Поведение диаграммы расширяется через события:

Hover-эффект

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

Подсветка поддерева

rect.on("mouseover", function(event, d) {
  svg.selectAll("rect")
    .attr("opacity", node => node.ancestors().includes(d) ? 1 : 0.3);
});

Tooltip

const tooltip = d3.select("body")
  .append("div")
  .style("position", "absolute")
  .style("background", "#fff");

rect.on("mousemove", (event, d) => {
  tooltip
    .style("left", event.pageX + 10 + "px")
    .style("top", event.pageY + 10 + "px")
    .html(`${d.data.name}: ${d.value}`);
});

Обработка больших данных

При работе с глубокими деревьями возникают ограничения:

  • перегрузка DOM
  • высокая стоимость пересчёта layout
  • сложность визуального восприятия

Оптимизации:

  • виртуализация уровней
  • ограничение глубины отображения
  • агрегация малых узлов
  • кэширование hierarchy.sum()

Сравнение с другими иерархическими диаграммами

Icicle-диаграмма часто сопоставляется с:

  • Treemap — использует вложенные прямоугольники
  • Sunburst — круговая иерархия
  • Flame graph — профилирование исполнения

Особенность icicle:

  • линейная структура удобна для чтения слева направо
  • сохраняет ясную вертикальную иерархию
  • проще интерпретируется при глубокой вложенности

Стилизация SVG-элементов

CSS применяется для улучшения визуального восприятия:

rect {
  shape-rendering: crispEdges;
}

text {
  pointer-events: none;
}

Дополнительно:

  • границы узлов через stroke
  • скругление через rx, ry
  • градиенты через <defs>

Разбиение данных по уровням

Для сложных систем применяется логика агрегации:

  • уровень 0: система
  • уровень 1: модули
  • уровень 2: компоненты
  • уровень 3: элементы

Каждый уровень может иметь собственную цветовую схему и правила отображения.


Поведение при изменении структуры

При динамическом обновлении данных:

  1. обновляется hierarchy
  2. пересчитывается partition
  3. выполняется data join
  4. обновляются DOM-элементы
const newRoot = d3.hierarchy(newData).sum(d => d.value);
partition(newRoot);

svg.selectAll("rect")
  .data(newRoot.descendants())
  .join("rect")
  .transition()
  .attr("x", d => d.x0)
  .attr("width", d => d.x1 - d.x0);

Глубина визуального восприятия

Icicle-диаграмма создаёт линейное ощущение иерархии, где:

  • верхние уровни воспринимаются как обобщение
  • нижние уровни — детализация
  • ширина отражает вклад в общее значение

Такое представление эффективно для анализа распределённых систем, структур каталогов, финансовых моделей и аналитических деревьев.