Трансформация pack: упакованные круги

В Vega трансформация pack реализует алгоритм круговой упаковки иерархических данных на основе подхода circle packing. Результат работы трансформации — набор узлов с вычисленными координатами x, y и радиусом r, которые затем используются для построения компактной вложенной структуры кругов без пересечений.

В контексте Vega-Lite данный механизм применяется как высокоуровневая абстракция для визуализаций, где требуется отобразить иерархию через вложенные окружности вместо прямоугольных областей (как в treemap).


Базовая идея circle packing

Алгоритм упакованных кругов решает задачу размещения иерархических узлов таким образом, чтобы:

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

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


Общая структура данных

Трансформация pack требует иерархических данных вида:

{
  "name": "root",
  "children": [
    {
      "name": "A",
      "value": 10
    },
    {
      "name": "B",
      "children": [
        { "name": "B1", "value": 5 },
        { "name": "B2", "value": 8 }
      ]
    }
  ]
}

Ключевые моменты:

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

Синтаксис трансформации pack

В Vega:

{
  "type": "pack",
  "field": "value",
  "size": [{"signal": "width"}, {"signal": "height"}],
  "padding": 2
}

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

field

Определяет поле, по которому вычисляется размер узла.

  • Используется только для листовых узлов
  • Если отсутствует, значение может вычисляться агрегированно

size

Определяет размеры области раскладки:

"size": [width, height]

или через сигналы:

"size": [{"signal": "width"}, {"signal": "height"}]

padding

Расстояние между кругами.

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

as

Переименование выходных полей:

"as": ["x", "y", "r", "depth", "children"]

По умолчанию создаются:

  • x
  • y
  • r
  • depth

sort

Контролирует порядок размещения узлов.

Пример:

"sort": {"field": "value", "order": "descending"}

Сортировка влияет на компактность и стабильность раскладки.


Выходные данные трансформации

После применения pack каждый узел получает:

  • x — координата центра по оси X
  • y — координата центра по оси Y
  • r — радиус круга
  • depth — уровень иерархии
  • children — вложенные узлы (внутреннее представление)

Эти значения используются mark-слоем circle:

{
  "mark": "circle",
  "encoding": {
    "x": {"field": "x"},
    "y": {"field": "y"},
    "size": {"field": "r"}
  }
}

Пример полной спецификации

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "width": 500,
  "height": 500,
  "data": [
    {
      "name": "tree",
      "values": [
        {
          "name": "root",
          "children": [
            {"name": "A", "value": 10},
            {
              "name": "B",
              "children": [
                {"name": "B1", "value": 5},
                {"name": "B2", "value": 8}
              ]
            }
          ]
        }
      ],
      "transform": [
        {
          "type": "stratify",
          "key": "name",
          "parentKey": "parent"
        },
        {
          "type": "pack",
          "field": "value",
          "size": [{"signal": "width"}, {"signal": "height"}],
          "padding": 3
        }
      ]
    }
  ],
  "marks": [
    {
      "type": "circle",
      "from": {"data": "tree"},
      "encode": {
        "enter": {
          "x": {"field": "x"},
          "y": {"field": "y"},
          "r": {"field": "r"},
          "fill": {"value": "steelblue"}
        }
      }
    }
  ]
}

Механика работы алгоритма

Трансформация основана на алгоритме d3-hierarchy pack, который реализует:

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

Данные преобразуются в дерево.

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

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

3. Вычисление радиусов

Радиус узла определяется как:

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

4. Упаковка кругов

Алгоритм:

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

Геометрические особенности

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

Каждый узел имеет центр (x, y), относительно которого строится окружность.

Радиусная иерархия

Если узел содержит потомков:

  • его радиус ≥ суммы радиусов детей с учётом padding
  • дети размещаются внутри границ родителя

Отличие от treemap

Характеристика pack treemap
Геометрия круги прямоугольники
Плотность ниже выше
Восприятие иерархии более визуально естественная более аналитическая
Заполнение пространства хуже лучше

Использование stratify вместе с pack

Часто pack требует предварительного преобразования:

{
  "type": "stratify",
  "key": "id",
  "parentKey": "parent"
}

Это позволяет строить дерево из плоских данных:

[
  {"id": "root"},
  {"id": "A", "parent": "root", "value": 10},
  {"id": "B", "parent": "root", "value": 20}
]

Масштабирование и стабильность

При изменении размеров контейнера:

  • координаты пересчитываются автоматически
  • радиусы масштабируются пропорционально
  • структура и относительные расстояния сохраняются

Практические особенности

Глубина вложенности

Глубокие иерархии приводят к:

  • уменьшению радиусов листьев
  • плотному центрированию внутренних узлов
  • визуальной перегрузке без фильтрации

Padding как инструмент визуальной структуры

Увеличение padding:

  • улучшает читаемость
  • снижает перекрытия визуального восприятия
  • уменьшает эффективное использование площади

Сортировка как фактор композиции

Без sort результат может меняться при каждом пересчёте.

Сортировка по убыванию:

  • делает крупные элементы центром композиции
  • повышает стабильность макета
  • улучшает визуальную интерпретацию структуры

Типичные ошибки использования

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

Взаимодействие с другими трансформациями

pack часто комбинируется с:

  • filter — для отбора узлов
  • formula — для вычисления значения узлов
  • lookup — для добавления атрибутов
  • collect — для управления порядком

Пример цепочки:

"transform": [
  {"type": "filter", "expr": "datum.value > 0"},
  {"type": "pack", "field": "value", "padding": 2}
]

Поведенческие нюансы алгоритма

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