Поле view.signal(): чтение и запись сигналов

Система сигналов в Vega построена на реактивной модели вычислений, где каждое изменение состояния автоматически приводит к пересчёту зависимых выражений, масштабов, фильтров и визуальных элементов. В этом контексте view.signal() является центральным API для чтения и изменения состояния визуализации на уровне экземпляра View.


Общая модель сигналов в Vega

Сигналы представляют собой именованные реактивные переменные, определённые в спецификации Vega. Они могут:

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

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


Поле view.signal(name) как механизм чтения состояния

Метод view.signal(name) без второго аргумента возвращает текущее значение сигнала из активного экземпляра визуализации.

Сигнатура

view.signal(name)

Поведение

  • возвращает текущее значение сигнала name;
  • не вызывает перерисовку;
  • работает синхронно;
  • обращается к внутреннему состоянию View.

Пример использования

const currentScale = view.signal("scaleFactor");

Если сигнал отсутствует в спецификации или ещё не инициализирован, результатом будет undefined.


Запись сигнала через view.signal(name, value)

При передаче второго аргумента метод становится сеттером и изменяет значение сигнала.

Сигнатура

view.signal(name, value)

Поведение записи

  • обновляет значение сигнала в рантайме;
  • инициирует пересчёт зависимых выражений;
  • не вызывает немедленную перерисовку без последующего .run() или .runAsync();
  • участвует в реактивной графовой модели зависимостей.

Пример

view.signal("threshold", 42);
view.run();

В этом примере изменение сигнала threshold влияет на все выражения, где он используется: фильтры, вычисляемые поля, условные стили.


Обязательный этап обновления: run() и runAsync()

Изменение сигнала само по себе не гарантирует немедленное обновление DOM или canvas. Для применения изменений используется запуск рендер-цикла:

view.signal("opacity", 0.5).run();

или асинхронный вариант:

await view.signal("opacity", 0.5).runAsync();

Различие режимов

  • run() — синхронное выполнение, блокирует до завершения рендера;
  • runAsync() — возвращает Promise, удобен для цепочек взаимодействий и анимаций.

Реактивная цепочка обновлений

При изменении сигнала происходит каскадное обновление:

  1. Обновляется значение сигнала в внутреннем store View.
  2. Пересчитываются выражения, зависящие от сигнала.
  3. Обновляются масштабные функции (если сигнал связан со scale/domain).
  4. Пересчитываются трансформации данных.
  5. Обновляются графические примитивы сцены.
  6. Выполняется рендер.

Ключевая особенность: пользователь работает не с отдельными объектами, а с графом зависимостей.


Использование сигналов в интерактивности

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

Пример: управление фильтром

view.signal("minValue", 10).run();

В Vega-спецификации:

"transform": [
  {
    "type": "filter",
    "expr": "datum.value > minValue"
  }
]

Изменение сигнала автоматически перестраивает фильтр.


Связь сигналов с событиями мыши

Сигналы часто обновляются через event listeners:

"signals": [
  {
    "name": "mouseX",
    "on": [
      {
        "events": "mousemove",
        "update": "x()"
      }
    ]
  }
]

На стороне Jav * aScript:

view.signal("mouseX");

При движении мыши Vega обновляет сигнал автоматически, а view.signal() позволяет считывать его текущее значение.


Батчинг изменений сигналов

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

view
  .signal("a", 1)
  .signal("b", 2)
  .signal("c", 3)
  .run();

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

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

Взаимодействие сигналов с вычисляемыми выражениями

Сигналы могут быть использованы внутри выражений Vega:

"expr": "width * scaleFactor"

или:

"expr": "datum.value > threshold"

При изменении scaleFactor или threshold автоматически пересчитываются все зависимые узлы.


Ограничения и особенности

1. Отсутствие мгновенного рендера

Изменение сигнала не приводит к обновлению сцены без run().

2. Потенциальные гонки состояния

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

3. Несуществующие сигналы

Чтение несуществующего сигнала возвращает undefined, что может привести к ошибкам выражений.

4. Типизация значений

Сигналы не строго типизированы, поэтому ответственность за согласованность лежит на разработчике.


Динамическое связывание UI и сигналов

Сигналы выступают в роли внутреннего состояния визуализации, аналогичного state в UI-фреймворках.

Пример: управление масштабом

view.signal("zoom", 1.5).run();

В Vega:

"scale": {
  "name": "xscale",
  "domain": {"signal": "zoomDomain"}
}

Изменение сигнала меняет отображаемый диапазон без пересборки спецификации.


Использование view.signal() в сложных сценариях

Интерактивные графики

  • zoom/pan через сигналы;
  • выделение диапазонов;
  • динамические фильтры;
  • переключение режимов отображения.

Управление анимациями

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

"expr": "sin(time * speed)"
view.signal("speed", 2).run();

Архитектурная роль view.signal()

Метод view.signal() фактически является:

  • точкой входа в runtime Vega;
  • интерфейсом управления реактивным графом;
  • механизмом двусторонней связи между JS и спецификацией;
  • основным API для runtime-интеракций.

Он объединяет декларативную модель Vega и императивное управление из JavaScript, сохраняя при этом целостность реактивной системы.