Вложенные группы и их шкалы

Иерархия групп в спецификациях визуализации

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

В Vega-Lite аналогом групп выступают операции компоновки: facet, repeat, concat, а также внутренние преобразования, которые при компиляции превращаются в Vega group marks. Именно на этом уровне возникает ключевая проблема — управление шкалами внутри вложенных областей визуализации.

Вложенные группы создают локальные контексты, в которых могут существовать собственные:

  • наборы данных (data scopes),
  • сигналы (signals),
  • шкалы (scales),
  • оси (axes),
  • легенды (legends).

При этом важнейшим аспектом становится поведение шкал при переходе между уровнями вложенности.


Локальные и глобальные шкалы

Шкала в Vega может быть определена либо на уровне всей сцены, либо внутри конкретной группы. Это определяет область её видимости и привязку к данным.

Глобальная шкала:

  • определяется в корневом объекте scales
  • используется всеми элементами сцены
  • имеет общий domain и range

Локальная шкала группы:

  • определяется внутри marks типа group
  • привязана к данным конкретного уровня
  • может переопределять domain независимо от родительского контекста

Простейшая структура Vega с вложенной группой:

{
  "type": "group",
  "marks": [
    {
      "type": "rect",
      "encode": {
        "enter": {
          "x": {"scale": "x", "field": "a"},
          "y": {"scale": "y", "field": "b"}
        }
      }
    }
  ],
  "scales": [
    {
      "name": "x",
      "type": "linear",
      "domain": {"data": "child", "field": "a"},
      "range": "width"
    }
  ]
}

Здесь шкала x существует только в рамках группы и не конфликтует с одноимёнными шкалами на других уровнях.


Наследование контекста и изоляция шкал

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

Ключевые свойства:

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

Пример вложенности:

{
  "type": "group",
  "scales": [
    {
      "name": "xGlobal",
      "type": "linear",
      "domain": [0, 100],
      "range": "width"
    }
  ],
  "marks": [
    {
      "type": "group",
      "from": {"data": "table"},
      "scales": [
        {
          "name": "xLocal",
          "type": "linear",
          "domain": {"data": "child", "field": "value"},
          "range": "width"
        }
      ],
      "marks": [
        {
          "type": "rect",
          "encode": {
            "enter": {
              "x": {"scale": "xLocal", "field": "value"},
              "width": {"value": 10}
            }
          }
        }
      ]
    }
  ]
}

Здесь xGlobal и xLocal существуют параллельно, но не пересекаются по области применения.


Вложенные группы в Vega-Lite и фасетирование

В Vega-Lite вложенные группы чаще всего возникают автоматически при использовании:

  • facet
  • repeat
  • concat
  • layer (в комбинации с facet)

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

Пример фасетирования:

{
  "data": {"url": "data.csv"},
  "facet": {
    "row": {"field": "category", "type": "nominal"}
  },
  "spec": {
    "mark": "bar",
    "encoding": {
      "x": {"field": "value", "type": "quantitative"},
      "y": {"field": "name", "type": "nominal"}
    }
  }
}

При компиляции каждая ячейка фасета превращается в отдельную группу Vega, содержащую:

  • собственные шкалы x и y
  • собственные оси
  • локальный domain данных

Проблема согласования шкал в вложенных группах

При наличии нескольких групп возникает задача согласования шкал между ними. В Vega-Lite для этого используется механизм resolve.

Основные стратегии:

  • shared — шкалы объединяются между группами
  • independent — каждая группа получает собственную шкалу
  • union (для domain) — объединение доменов

Пример:

{
  "facet": {
    "row": {"field": "category"}
  },
  "spec": {
    "mark": "line",
    "encoding": {
      "x": {"field": "date", "type": "temporal"},
      "y": {"field": "value", "type": "quantitative"}
    }
  },
  "resolve": {
    "scale": {
      "y": "shared",
      "x": "independent"
    }
  }
}

В этом случае:

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

Компиляция вложенных групп в Vega

Vega-Lite при трансляции в Vega формирует дерево групп, где каждая композиционная операция становится узлом group.

Типичная трансформация:

  • facet → group with from + nested group per facet cell
  • repeat → multiple child groups
  • concat → sequential group layout
  • layer → shared group with overlapping marks

Структура вложенности:

root group
 ├── facet group A
 │     ├── scales A
 │     └── marks
 ├── facet group B
 │     ├── scales B
 │     └── marks

Каждый узел содержит собственные шкалы, если они не объединены через resolve.


Область видимости шкал внутри групп

Шкалы в Vega имеют строго определённую область видимости:

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

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

Пример:

{
  "type": "group",
  "scales": [
    {"name": "x", "type": "linear"}
  ],
  "marks": [
    {
      "type": "group",
      "scales": [
        {"name": "x", "type": "band"}
      ]
    }
  ]
}

Здесь две шкалы x существуют в разных контекстах и имеют разные типы.


Динамика пересчёта шкал в вложенных структурах

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

  1. обновляется dataset группы
  2. пересчитывается domain локальных шкал
  3. обновляется range при изменении размеров
  4. перерисовываются marks

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

Особенно критично это при:

  • глубоком facet (много строк/столбцов)
  • динамических сигналах масштаба
  • изменении размеров контейнеров

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

Каждая группа формирует собственную систему координат:

  • origin (0,0) локален
  • размеры задаются через width и height группы
  • трансформации применяются на уровне контейнера

Это означает, что шкала всегда работает в пределах локального пространства группы, а не глобального canvas.


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

Вложенные группы применяются для построения:

  • small multiples (матричные графики)
  • иерархических диаграмм
  • комбинированных визуализаций (facet + layer)
  • интерактивных панелей с независимыми масштабами

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


Особенности поведения шкал при комбинированных компоновках

При использовании layer внутри facet возникает смешанная модель:

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

Пример комбинированной структуры:

{
  "facet": {
    "column": {"field": "group"}
  },
  "spec": {
    "layer": [
      {
        "mark": "line",
        "encoding": {
          "x": {"field": "x", "type": "quantitative"},
          "y": {"field": "y", "type": "quantitative"}
        }
      },
      {
        "mark": "point"
      }
    ]
  }
}

Здесь слой существует внутри фасетной группы, но использует её локальные шкалы.


Поведение шкал при изменении размеров вложенных групп

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

  • range всех шкал группы
  • позиции осей
  • трансформации marks

Вложенные группы добавляют каскадность: изменение родительского размера влияет на все дочерние шкалы через пересчёт range.

Особенно важно:

  • scale range привязан к width/height группы
  • дочерние группы получают пересчитанные размеры
  • масштабирование не влияет на domain, только на визуальное отображение