Взаимодействие сигналов: чтение из других сигналов

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

Сигналы в Vega-Lite существуют опосредованно: они компилируются в низкоуровневые сигналы Vega, однако логика взаимодействия сохраняет ту же модель — вычисление значений на основе других реактивных источников.


Базовая модель чтения сигналов

Чтение одного сигнала из другого реализуется через выражения сигнального языка. В Vega сигналы доступны по имени в контексте выражения или через функцию доступа:

{
  "signals": [
    { "name": "a", "value": 10 },
    { "name": "b", "update": "a * 2" }
  ]
}

Здесь сигнал b зависит от a. При изменении a автоматически пересчитывается b.

Другой вариант явного обращения:

{
  "name": "c",
  "update": "signal('a') + 5"
}

Функция signal('a') делает зависимость явной на уровне выражения, что полезно при динамическом доступе по имени.


Выражения как контекст зависимостей

Сигналы читаются внутри выражений, которые интерпретируются движком Vega Expression Grammar. Внутри таких выражений доступно прямое обращение по имени:

{
  "signals": [
    { "name": "widthFactor", "value": 2 },
    { "name": "height", "update": "widthFactor * 100" }
  ]
}

При этом система автоматически строит граф зависимостей:

  • height зависит от widthFactor
  • любые изменения widthFactor инициируют пересчёт height

Подобная модель исключает необходимость ручного управления порядком вычислений.


Явные и неявные зависимости

Существует два способа чтения сигналов:

Неявное чтение по имени

"update": "a + b"

Зависимости выводятся анализом выражения. Такой подход компактный, но требует корректного статического анализа строки выражения.

Явное чтение через функции

"update": "signal('a') + signal('b')"

Этот способ снижает неоднозначность при сложных сценариях, особенно когда имя сигнала вычисляется динамически.


Зависимые цепочки сигналов

Сигналы могут образовывать цепочки произвольной глубины:

{
  "signals": [
    { "name": "a", "value": 1 },
    { "name": "b", "update": "a + 1" },
    { "name": "c", "update": "b + 1" },
    { "name": "d", "update": "c + 1" }
  ]
}

Здесь формируется направленный ациклический граф:

a → b → c → d

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


Контекст сигналов в интерактивных выражениях

Сигналы часто используются внутри интерактивных обработчиков событий:

{
  "signals": [
    {
      "name": "mouseX",
      "value": 0,
      "on": [
        {
          "events": "mousemove",
          "update": "x()"
        }
      ]
    },
    {
      "name": "normalized",
      "update": "mouseX / width"
    }
  ]
}

Здесь normalized читает значение mouseX, которое обновляется событиями мыши. Это создаёт реактивный поток данных, в котором события преобразуются в сигналы, а сигналы — в вычисляемые значения.


Доступ к сигналам внутри scale и transform

Сигналы активно используются внутри шкал и трансформаций:

{
  "signals": [
    { "name": "padding", "value": 20 }
  ],
  "scales": [
    {
      "name": "x",
      "padding": { "signal": "padding" }
    }
  ]
}

Здесь сигнал padding используется как реактивный параметр масштаба. Изменение сигнала приводит к перерасчёту шкалы и всей визуализации.


Роль сигналов в параметрических зависимостях Vega-Lite

В Vega-Lite прямое управление сигналами ограничено, однако через параметры (params) создаются аналогичные зависимости:

{
  "params": [
    {
      "name": "threshold",
      "value": 50
    }
  ],
  "mark": "bar",
  "encoding": {
    "opacity": {
      "condition": {
        "test": "datum.value > threshold",
        "value": 1
      },
      "value": 0.3
    }
  }
}

После компиляции threshold превращается в сигнал Vega, который может быть использован в выражениях и взаимодействиях.


Динамическое чтение сигналов по имени

Одной из наиболее мощных возможностей является доступ к сигналам через динамические ключи:

{
  "signals": [
    { "name": "x0", "value": 10 },
    { "name": "x1", "value": 20 },
    { "name": "index", "value": 1 },
    { "name": "selected", "update": "signal('x' + index)" }
  ]
}

Здесь selected вычисляется на основе конкатенации строки и имени сигнала. Такая конструкция позволяет реализовывать:

  • переключаемые параметры
  • наборы состояний
  • динамическое связывание интерфейсных элементов

Приоритеты пересчёта и реактивность

При построении графа зависимостей система учитывает порядок:

  1. источники значений (value)
  2. вычисляемые сигналы (update)
  3. сигналы, зависящие от событий

Если сигнал зависит от другого сигнала, пересчёт всегда происходит снизу вверх по графу зависимостей.

Циклические зависимости запрещены:

{
  "name": "a",
  "update": "b + 1"
},
{
  "name": "b",
  "update": "a + 1"
}

Такая конструкция приводит к ошибке вычисления графа.


Сигналы внутри выражений трансформаций

В transform-слое сигналы часто используются для фильтрации и вычислений:

{
  "transform": [
    {
      "type": "filter",
      "expr": "datum.value > threshold"
    }
  ],
  "signals": [
    { "name": "threshold", "value": 100 }
  ]
}

Выражение expr получает доступ к сигналам как к глобальным переменным контекста.


Композиция сигналов и переиспользование логики

Сигналы часто выступают как промежуточный слой абстракции:

{
  "signals": [
    { "name": "base", "value": 10 },
    { "name": "scale", "value": 2 },
    { "name": "result", "update": "base * scale" },
    { "name": "adjusted", "update": "result + base" }
  ]
}

Такая композиция позволяет:

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

Особенности взаимодействия сигналов с layout-системой

Сигналы активно применяются для управления геометрией визуализации:

{
  "signals": [
    { "name": "margin", "value": 30 },
    { "name": "innerWidth", "update": "width - margin * 2" },
    { "name": "innerHeight", "update": "height - margin * 2" }
  ]
}

Любое изменение размеров контейнера автоматически приводит к пересчёту всех производных сигналов.


Ограничения модели чтения сигналов

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

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

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