Диаграмма Сэнки в Vega

Диаграмма Сэнки в Vega строится на представлении потоков как направленных связей между узлами, где ширина ребра пропорциональна величине потока. В экосистеме Vega это реализуется через набор преобразований данных и специализированный layout-алгоритм, чаще всего предоставляемый расширением vega-sankey, которое добавляет вычисление координат узлов и изгибов связей.

Базовая модель включает два типа сущностей:

  • узлы (nodes) — категории или состояния системы
  • связи (links) — направленные потоки между узлами с числовым весом

Типичная структура исходного набора данных:

{
  "nodes": [
    { "name": "A" },
    { "name": "B" },
    { "name": "C" }
  ],
  "links": [
    { "source": "A", "target": "B", "value": 10 },
    { "source": "A", "target": "C", "value": 5 },
    { "source": "B", "target": "C", "value": 3 }
  ]
}

В Vega эта структура может использоваться напрямую или нормализоваться через трансформации lookup и fold, если данные поступают в разрозненном виде.

Базовый принцип layout Sankey

Алгоритм размещения в Sankey-графах решает две ключевые задачи:

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

В Vega используется итеративная оптимизация:

  • минимизация пересечений связей
  • выравнивание узлов по “уровням” (depth)
  • балансировка входящих и исходящих потоков

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

  • x — уровень (шаг по оси направления потока)
  • y — позиция внутри уровня

Толщина ребра вычисляется как функция value.

Подключение Sankey layout в Vega

Vega не включает Sankey как базовый mark, поэтому применяется внешний трансформ:

{
  "type": "sankey",
  "nodeWidth": 15,
  "nodePadding": 10,
  "extent": [[1, 1], [500, 300]]
}

Этот трансформ:

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

В зависимости от версии Vega используется пакет vega-transforms или отдельный модуль vega-sankey.

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

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "width": 600,
  "height": 300,
  "padding": 5,

  "data": [
    {
      "name": "sankey",
      "values": {
        "nodes": [
          { "name": "Источник" },
          { "name": "Промежуточный" },
          { "name": "Сток" }
        ],
        "links": [
          { "source": "Источник", "target": "Промежуточный", "value": 12 },
          { "source": "Промежуточный", "target": "Сток", "value": 7 },
          { "source": "Источник", "target": "Сток", "value": 5 }
        ]
      },
      "transform": [
        {
          "type": "sankey",
          "nodeWidth": 20,
          "nodePadding": 12,
          "iterations": 32
        }
      ]
    }
  ],

  "marks": [
    {
      "type": "path",
      "from": { "data": "sankey" },
      "encode": {
        "enter": {
          "path": { "field": "path" },
          "stroke": { "value": "#888" },
          "strokeOpacity": { "value": 0.4 }
        },
        "update": {
          "strokeWidth": { "field": "value" }
        }
      }
    },
    {
      "type": "rect",
      "from": { "data": "sankey.nodes" },
      "encode": {
        "enter": {
          "x": { "field": "x0" },
          "x2": { "field": "x1" },
          "y": { "field": "y0" },
          "y2": { "field": "y1" },
          "fill": { "value": "#4c78a8" }
        }
      }
    }
  ]
}

В данной схеме:

  • sankey трансформирует входные данные в геометрию
  • path хранит кривые потоков
  • rect визуализирует узлы

Геометрия потоков

Связи в Sankey представляются кубическими кривыми Безье. Каждая связь определяется четырьмя точками:

  • стартовая точка (выход узла-источника)
  • контрольная точка 1
  • контрольная точка 2
  • конечная точка (вход узла-назначения)

Форма кривой формируется так, чтобы:

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

Толщина ребра:

[ w_i = k value_i]

где коэффициент масштабирования k зависит от общей высоты слоя.

Иерархия и уровни узлов

Распределение по уровням строится на основе направленного графа:

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

Каждому узлу присваивается глубина depth, которая затем преобразуется в координату x.

Нормализация потоков

Для корректного отображения применяется балансировка:

  • сумма входящих потоков узла равна сумме исходящих
  • при несоответствии вводятся фиктивные узлы или корректирующие веса

Это важно для сохранения визуальной пропорциональности.

Управление плотностью и отступами

Параметры layout-а определяют читаемость диаграммы:

  • nodePadding задаёт расстояние между узлами
  • nodeWidth определяет толщину блока
  • iterations влияет на стабильность размещения

Увеличение числа итераций:

  • улучшает минимизацию пересечений
  • увеличивает время построения

Работа с Vega-Lite и ограничения

Vega-Lite не имеет нативного Sankey mark, поэтому используются подходы:

  • переход в чистую Vega через toVega
  • использование кастомных transforms
  • подключение внешнего vega-sankey через расширение

Типичный сценарий:

  • Vega-Lite используется для подготовки данных
  • Vega отвечает за финальную геометрию Sankey

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

Цвета применяются для:

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

Пример:

"encode": {
  "update": {
    "stroke": { "scale": "color", "field": "source" }
  }
}

Шкала цветов обычно дискретная (ordinal), что позволяет различать категории без смешивания оттенков.

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

Sankey-диаграммы в Vega поддерживают интерактивные преобразования:

  • hover-подсветка связей
  • фильтрация потоков
  • динамическое изменение значений

Добавление сигналов:

"signals": [
  {
    "name": "highlight",
    "value": null,
    "on": [
      { "events": "rect:mouseover", "update": "datum.name" },
      { "events": "rect:mouseout", "update": "null" }
    ]
  }
]

Это позволяет изменять визуальное представление без пересборки графа.

Оптимизация больших графов

При увеличении количества узлов и связей возникают ограничения:

  • рост количества пересечений
  • падение производительности layout-а
  • перегрузка визуального пространства

Используются методы:

  • агрегация узлов
  • предварительная фильтрация
  • иерархическое группирование

В некоторых случаях применяется двухуровневая Sankey-схема, где верхний уровень агрегирует категории, а нижний раскрывает детали.