Трансформация force: симуляция сил

Трансформация force в Vega используется для физического моделирования графов и наборов связанных объектов. Она реализует симуляцию сил, аналогичную движку D3 Force Simulation, позволяя автоматически располагать узлы на плоскости с учётом различных физических взаимодействий.

Наиболее распространённые сценарии применения:

  • визуализация графов;
  • отображение социальных сетей;
  • построение диаграмм связей;
  • моделирование кластеров;
  • автоматическая раскладка узлов;
  • предотвращение наложения объектов;
  • анимация динамических структур.

Трансформация доступна только в Vega. В Vega-Lite прямой поддержки force нет, поскольку Vega-Lite ориентирован на декларативные статистические визуализации, а не на физические симуляции.


Принцип работы force

Симуляция выполняется итеративно. На каждом шаге:

  1. рассчитываются силы между объектами;
  2. изменяются скорости узлов;
  3. обновляются координаты;
  4. система стремится к равновесию.

Каждый элемент набора данных обычно представляет отдельный узел графа.

После завершения симуляции данные получают вычисленные координаты:

  • x
  • y
  • vx
  • vy

Эти поля затем используются при отрисовке.


Базовая структура force

Минимальная конфигурация выглядит следующим образом:

{
  "transform": [
    {
      "type": "force",
      "iterations": 300,
      "forces": [
        {
          "force": "center",
          "x": 300,
          "y": 200
        }
      ]
    }
  ]
}

Поля трансформации

static

Определяет режим симуляции.

{
  "type": "force",
  "static": true
}

Если значение:

  • true — симуляция выполняется один раз;
  • false — симуляция остаётся активной.

Для большинства диаграмм используется static: true.


iterations

Количество итераций физического расчёта.

{
  "iterations": 500
}

Чем больше значение:

  • тем стабильнее результат;
  • тем выше вычислительная нагрузка.

Типичные значения:

Размер графа Итерации
Маленький 50–100
Средний 200–400
Большой 500–1000

restart

Перезапуск симуляции при изменении данных.

{
  "restart": true
}

signal

Позволяет управлять симуляцией через сигналы Vega.

{
  "signal": "forceSim"
}

Через сигнал можно:

  • запускать симуляцию;
  • останавливать;
  • отслеживать состояние;
  • обновлять силы динамически.

Массив forces

Ключевой элемент трансформации — массив forces.

{
  "forces": [
    { ... },
    { ... }
  ]
}

Каждый объект массива задаёт отдельную физическую силу.


Сила center

Назначение

Сила center притягивает все узлы к определённой точке.

{
  "force": "center",
  "x": 400,
  "y": 300
}

Без этой силы граф может смещаться за пределы области отображения.


Пример

{
  "transform": [
    {
      "type": "force",
      "static": true,
      "iterations": 300,
      "forces": [
        {
          "force": "center",
          "x": 300,
          "y": 200
        }
      ]
    }
  ]
}

Сила collide

Назначение

Предотвращает наложение узлов.

{
  "force": "collide",
  "radius": 20
}

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


radius

Радиус столкновения.

{
  "radius": 30
}

Можно использовать выражение:

{
  "radius": {"expr": "datum.size"}
}

strength

Интенсивность отталкивания.

{
  "strength": 1
}

Диапазон:

0 → 1

iterations

Количество внутренних итераций столкновения.

{
  "iterations": 4
}

Большие значения повышают точность.


Пример

{
  "forces": [
    {
      "force": "collide",
      "radius": 25,
      "strength": 1
    }
  ]
}

Сила nbody

Назначение

Моделирует взаимное притяжение или отталкивание между всеми узлами.

Это одна из важнейших сил в графовых визуализациях.


Отталкивание

{
  "force": "nbody",
  "strength": -50
}

Отрицательное значение создаёт отталкивание.


Притяжение

{
  "force": "nbody",
  "strength": 20
}

Положительное значение создаёт притяжение.


strength

Определяет мощность взаимодействия.

{
  "strength": -200
}

Типичные значения:

Эффект Значение
Слабое -10
Среднее -50
Сильное -300

theta

Параметр оптимизации Barnes–Hut.

{
  "theta": 0.9
}

Меньшие значения:

  • повышают точность;
  • увеличивают нагрузку.

distanceMin

Минимальная дистанция.

{
  "distanceMin": 10
}

distanceMax

Максимальная дистанция действия силы.

{
  "distanceMax": 300
}

Пример

{
  "forces": [
    {
      "force": "nbody",
      "strength": -80,
      "distanceMax": 500
    }
  ]
}

Сила link

Назначение

Создаёт связи между узлами графа.

Используется совместно с отдельным набором данных рёбер.


Структура данных

Узлы

{
  "name": "nodes",
  "values": [
    {"id": "A"},
    {"id": "B"},
    {"id": "C"}
  ]
}

Связи

{
  "name": "links",
  "values": [
    {"source": "A", "target": "B"},
    {"source": "B", "target": "C"}
  ]
}

{
  "force": "link",
  "links": "links"
}

distance

Желаемая длина связи.

{
  "distance": 100
}

strength

Жёсткость связи.

{
  "strength": 0.7
}

id

Поле идентификатора узла.

{
  "id": "id"
}

Пример

{
  "forces": [
    {
      "force": "link",
      "links": "links",
      "id": "id",
      "distance": 120,
      "strength": 0.5
    }
  ]
}

Сила x

Назначение

Притягивает узлы к определённой координате по оси X.

{
  "force": "x",
  "x": 300
}

Использование для группировки

{
  "force": "x",
  "x": {
    "expr": "datum.group === 'A' ? 200 : 500"
  }
}

strength

{
  "strength": 0.2
}

Пример кластеризации

{
  "forces": [
    {
      "force": "x",
      "x": {
        "expr": "datum.type === 'server' ? 200 : 600"
      },
      "strength": 0.3
    }
  ]
}

Сила y

Работает аналогично x, но по вертикальной оси.

{
  "force": "y",
  "y": 250,
  "strength": 0.1
}

Сила radial

Назначение

Располагает узлы по окружности.


Пример

{
  "force": "radial",
  "radius": 150,
  "x": 300,
  "y": 300
}

strength

{
  "strength": 0.5
}

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

{
  "force": "radial",
  "radius": {
    "expr": "datum.level * 80"
  }
}

Полный пример force-графа

Узлы и связи

{
  "data": [
    {
      "name": "nodes",
      "values": [
        {"id": "A"},
        {"id": "B"},
        {"id": "C"},
        {"id": "D"}
      ],
      "transform": [
        {
          "type": "force",
          "static": true,
          "iterations": 300,
          "forces": [
            {
              "force": "center",
              "x": 400,
              "y": 300
            },
            {
              "force": "nbody",
              "strength": -100
            },
            {
              "force": "collide",
              "radius": 20
            },
            {
              "force": "link",
              "links": "links",
              "id": "id",
              "distance": 120
            }
          ]
        }
      ]
    },
    {
      "name": "links",
      "values": [
        {"source": "A", "target": "B"},
        {"source": "A", "target": "C"},
        {"source": "B", "target": "D"}
      ]
    }
  ]
}

Отрисовка линий

{
  "type": "path",
  "from": {"data": "links"},
  "encode": {
    "update": {
      "stroke": {"value": "#999"}
    }
  },
  "transform": [
    {
      "type": "linkpath",
      "shape": "line"
    }
  ]
}

Отрисовка узлов

{
  "type": "symbol",
  "from": {"data": "nodes"},
  "encode": {
    "enter": {
      "size": {"value": 400},
      "fill": {"value": "steelblue"}
    },
    "update": {
      "x": {"field": "x"},
      "y": {"field": "y"}
    }
  }
}

Комбинирование сил

На практике почти всегда используется комбинация нескольких сил.

Типичная конфигурация:

{
  "forces": [
    {
      "force": "center",
      "x": 400,
      "y": 300
    },
    {
      "force": "nbody",
      "strength": -80
    },
    {
      "force": "collide",
      "radius": 15
    },
    {
      "force": "link",
      "links": "links",
      "id": "id",
      "distance": 100
    }
  ]
}

Каждая сила отвечает за отдельный аспект поведения:

Сила Назначение
center Центрирование
nbody Притяжение/отталкивание
collide Исключение наложений
link Связи между узлами
x/y Позиционные ограничения
radial Круговая компоновка

Динамические симуляции

static: false

{
  "static": false
}

В этом режиме:

  • узлы продолжают двигаться;
  • возможно перетаскивание;
  • симуляция обновляется в реальном времени.

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

Пример сигнала:

{
  "signals": [
    {
      "name": "drag",
      "value": {},
      "on": [
        {
          "events": "symbol:mousedown",
          "update": "datum"
        }
      ]
    }
  ]
}

Производительность

Основные источники нагрузки

Наибольшие вычисления создают:

  • nbody;
  • большое число связей;
  • тысячи узлов;
  • высокая точность столкновений.

Практические рекомендации

Ограничение итераций

{
  "iterations": 100
}

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

{
  "distanceMax": 200
}

Уменьшение collide iterations

{
  "iterations": 1
}

Использование static

{
  "static": true
}

Это значительно снижает нагрузку.


Отладка force-графов

Узлы разлетаются

Причина:

  • слишком сильное отталкивание.

Решение:

{
  "strength": -30
}

Узлы слипаются

Причина:

  • слабый collide;
  • недостаточный nbody.

Решение:

{
  "radius": 20
}

Граф дрожит

Причина:

  • нестабильный баланс сил.

Решение:

  • уменьшить силы;
  • увеличить iterations;
  • включить static.

Узлы выходят за пределы экрана

Решение:

{
  "force": "center"
}

или:

{
  "force": "x"
}

и

{
  "force": "y"
}

Архитектура force в Vega

Трансформация force выполняется:

  1. после загрузки данных;
  2. до рендеринга marks;
  3. до вычисления координат визуальных элементов.

Симуляция изменяет сами объекты данных, добавляя:

x
y
vx
vy
index

Эти поля становятся частью потока данных Vega.


Отличия force от обычных layout

Обычные layout

Статически вычисляют положение:

  • treemap;
  • stack;
  • pie;
  • pack.

force

Вычисляет позиции через физическое моделирование:

  • итерации;
  • силы;
  • ускорения;
  • скорости;
  • столкновения.

Когда использовать force

Трансформация особенно эффективна для:

  • социальных графов;
  • сетевых топологий;
  • dependency-графов;
  • knowledge graph;
  • визуализации API-связей;
  • отображения микросервисов;
  • биологических сетей;
  • recommendation graph.

Когда force использовать нежелательно

Симуляция может быть избыточной для:

  • простых bar chart;
  • line chart;
  • табличных данных;
  • жёстко фиксированных схем;
  • больших графов без оптимизации.

Связь force и D3

Внутри Vega используется механизм, концептуально близкий к d3-force.

Многие параметры совпадают:

Vega force D3 equivalent
center forceCenter
collide forceCollide
nbody forceManyBody
link forceLink
radial forceRadial
x forceX
y forceY

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


Типичный шаблон force-графа

{
  "transform": [
    {
      "type": "force",
      "static": true,
      "iterations": 300,
      "forces": [
        {
          "force": "center",
          "x": 400,
          "y": 300
        },
        {
          "force": "nbody",
          "strength": -80
        },
        {
          "force": "collide",
          "radius": 20
        },
        {
          "force": "link",
          "links": "links",
          "id": "id",
          "distance": 100
        }
      ]
    }
  ]
}