Упакованные круги (circle packing) представляют собой способ визуализации иерархических данных, при котором элементы отображаются в виде окружностей, вложенных друг в друга без пересечений. Размер окружностей соответствует некоторой количественной метрике, а структура вложенности отражает иерархию данных. В экосистеме Vega и Vega-Lite данный тип визуализации реализуется через иерархические преобразования (hierarchy transforms) и алгоритмы упаковки (pack layout), основанные на вычислении оптимального размещения окружностей в ограниченном пространстве.
Основой является иерархическая структура данных в формате дерева. Каждый узел может содержать:
children)value) для листовых узлов или
агрегированное значениеname, id)Типичная структура:
{
"name": "root",
"children": [
{
"name": "A",
"children": [
{ "name": "A1", "value": 10 },
{ "name": "A2", "value": 20 }
]
},
{
"name": "B",
"children": [
{ "name": "B1", "value": 15 }
]
}
]
}
Ключевое условие: листья должны содержать числовые значения, которые определяют площадь окружности. Внутренние узлы вычисляются как сумма или агрегат дочерних значений.
Circle packing основан на вычислении радиусов окружностей и их плотной укладке без пересечений.
В Vega используется алгоритм, вдохновлённый работами Уотсона и Уолша по плотной упаковке кругов, а также модификации D3 hierarchy pack layout.
В Vega основным шагом является трансформация данных в иерархическую
структуру через stratify или hierarchy.
Пример:
{
"type": "stratify",
"key": "id",
"parentKey": "parent"
}
или прямое использование вложенных данных:
{
"type": "hierarchy",
"method": "sum",
"field": "value"
}
Параметр method: "sum" определяет агрегацию значений
снизу вверх.
Ключевым элементом является трансформация pack, которая
вычисляет позиции и радиусы окружностей.
{
"type": "pack",
"size": [800, 800],
"padding": 2
}
size — размер области визуализацииpadding — расстояние между окружностямиaspectRatio — соотношение сторон (опционально)sort — порядок размещения узловРезультатом является набор координат:
xyr (радиус)Радиус вычисляется на основе площади:
[ r = ]
В практической реализации используется нормализованный масштаб:
sqrt,
log)Это позволяет избежать доминирования крупных узлов.
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 600,
"height": 600,
"padding": 5,
"data": [
{
"name": "tree",
"values": [
{"id": "root.A.A1", "parent": "root.A", "value": 10},
{"id": "root.A.A2", "parent": "root.A", "value": 20},
{"id": "root.A", "parent": "root", "value": 0},
{"id": "root.B.B1", "parent": "root.B", "value": 15},
{"id": "root.B", "parent": "root", "value": 0},
{"id": "root", "parent": "", "value": 0}
],
"transform": [
{
"type": "stratify",
"key": "id",
"parentKey": "parent"
},
{
"type": "pack",
"field": "value",
"size": [{"signal": "width"}, {"signal": "height"}],
"padding": 4
}
]
}
],
"marks": [
{
"type": "symbol",
"from": {"data": "tree"},
"encode": {
"enter": {
"x": {"field": "x"},
"y": {"field": "y"},
"size": {"signal": "pow(datum.r, 2) * 10"},
"fill": {"value": "#4c78a8"},
"stroke": {"value": "#fff"}
}
}
}
]
}
Каждый узел дерева интерпретируется как окружность:
(x, y)rРодительская окружность полностью содержит дочерние, при этом:
Часто используется дополнительное визуальное кодирование:
Пример encode:
"fill": {
"scale": "color",
"field": "depth"
}
Или:
"fillOpacity": {
"scale": "opacityScale",
"field": "value"
}
В Vega-Lite отсутствует прямой низкоуровневый API упаковки кругов, однако поддержка достигается через:
aggregatewindowhierarchy (в расширенных версиях)Типичный подход:
Пример упрощённой структуры:
{
"data": {"values": [...]},
"mark": "circle",
"encoding": {
"x": {"field": "x", "type": "quantitative"},
"y": {"field": "y", "type": "quantitative"},
"size": {"field": "value"}
}
}
При этом реальные координаты чаще вычисляются через Vega runtime.
Circle packing имеет вычислительную сложность, зависящую от числа узлов:
Ограничения:
Агрегация уровней:
"sort": {
"field": "value",
"order": "descending"
}
Позволяет размещать крупные элементы в более центральных позициях.
В Vega circle packing часто используется:
Zoom реализуется через signal:
"signals": [
{
"name": "scale",
"value": 1
}
]
И трансформацию координат:
"x": {"signal": "datum.x * scale"},
"y": {"signal": "datum.y * scale"}
Circle packing может быть реализован в нескольких стилях:
Vega использует декларативный подход:
Это отличает систему от императивных библиотек, где размещение вычисляется вручную.
При увеличении количества узлов:
Circle packing выбирается, когда важна визуальная компактность и выразительность структуры, а не точное сравнение чисел.