Сигналы в Vega представляют собой базовый механизм реактивного программирования, на котором строится вся система интерактивных визуализаций. Они выступают как именованные изменяемые значения, которые могут динамически пересчитываться в ответ на внешние события, изменения данных или вычисление выражений. Внутренняя архитектура Vega организована как направленный граф вычислений, где сигналы являются узлами состояния, а зависимости между ними формируют поток обновлений.
В основе Vega лежит декларативный подход, при котором визуализация описывается как набор взаимосвязанных компонентов: данных, шкал, осей, геометрических примитивов и интерактивных параметров. Сигналы в этой системе выполняют роль универсального механизма передачи изменяющегося состояния.
Ключевая особенность заключается в том, что сигнал не является просто переменной. Его значение может зависеть от:
При изменении любого источника зависимости автоматически инициируется пересчёт всех связанных компонентов графа.
Сигнал в Vega описывается как объект в JSON-спецификации. Основная форма включает имя, начальное значение и выражение вычисления.
{
"signals": [
{
"name": "width",
"value": 400
},
{
"name": "height",
"value": 200
}
]
}
В этом примере задаются статические сигналы, определяющие размеры визуализации. Несмотря на простоту, даже такие значения участвуют в реактивной системе и могут быть переопределены динамически.
Сигналы могут вычисляться на основе выражений, использующих встроенный язык Vega Expression Language. Этот язык поддерживает арифметику, логические операции, работу с функциями и доступ к другим сигналам.
{
"signals": [
{
"name": "padding",
"value": 10
},
{
"name": "width",
"update": "200 + padding * 2"
}
]
}
В данном случае width не является фиксированным
значением, а зависит от сигнала padding. Любое изменение
padding автоматически пересчитывает width.
Одной из ключевых возможностей сигналов является привязка к событиям пользовательского интерфейса. События описываются через селекторы DOM-элементов и типы действий.
{
"signals": [
{
"name": "mouseX",
"on": [
{
"events": "mousemove",
"update": "eventX()"
}
]
}
]
}
Сигнал mouseX обновляется при каждом движении мыши.
Функция eventX() возвращает координату курсора относительно
текущего контейнера визуализации.
Сигналы событийного типа формируют основу интерактивности: панорамирования, масштабирования, выделения областей и управления элементами визуализации.
Сигналы образуют ориентированный граф, в котором узлы пересчитываются при изменении зависимостей. Пример цепочки:
{
"signals": [
{ "name": "a", "value": 5 },
{ "name": "b", "update": "a * 2" },
{ "name": "c", "update": "b + 10" }
]
}
Изменение a приводит к пересчёту b, а затем
c. Такой механизм исключает необходимость ручного
управления состоянием и обеспечивает согласованность данных.
Сигналы активно используются при работе со шкалами и координатными системами. Они позволяют динамически адаптировать отображение данных.
{
"signals": [
{
"name": "chartWidth",
"value": 600
},
{
"name": "innerWidth",
"update": "chartWidth - 40"
}
]
}
Визуальные компоненты, такие как оси или столбцы диаграмм, используют сигналы для вычисления размеров, что обеспечивает адаптивность графиков.
В Vega-Lite концепция сигналов тесно связана с механизмом selections. После компиляции спецификации Vega-Lite в Vega, каждое выделение преобразуется в набор сигналов.
Пример в Vega-Lite:
{
"selection": {
"brush": {
"type": "interval"
}
}
}
После трансляции в Vega появляется набор сигналов, отслеживающих координаты выделенного диапазона. Эти сигналы используются для фильтрации данных, изменения масштаба или подсветки элементов.
Сигналы выполняют роль централизованного механизма хранения состояния визуализации. В отличие от императивных моделей, состояние не изменяется напрямую, а пересчитывается через выражения и события.
{
"signals": [
{
"name": "selected",
"value": null,
"on": [
{
"events": "rect:click",
"update": "datum.id"
}
]
}
]
}
В данном случае сигнал selected хранит идентификатор
выбранного элемента. Любой клик по прямоугольнику обновляет состояние,
которое может использоваться в фильтрах или стилях.
Язык выражений сигналов поддерживает широкий набор встроенных функций:
Пример условного сигнала:
{
"signals": [
{
"name": "opacity",
"update": "hover ? 1 : 0.3"
}
]
}
Здесь hover может быть сигналом события наведения
курсора, а opacity динамически изменяет прозрачность
элементов.
Любое изменение сигнала инициирует обновление зависимых компонентов сцены. Vega использует оптимизированный механизм дифференциального обновления, при котором пересчитываются только затронутые элементы графа.
Процесс обновления включает:
Это позволяет поддерживать высокую производительность даже при большом количестве интерактивных элементов.
Сигналы существуют в едином пространстве имён внутри спецификации визуализации. Конфликты имён приводят к переопределению значений, поэтому архитектура обычно предполагает строгую иерархию именования:
Vega-Lite представляет собой высокоуровневую декларативную оболочку над Vega. При компиляции спецификации Vega-Lite сигналы создаются автоматически для:
Например, простая спецификация:
{
"mark": "bar",
"encoding": {
"x": { "field": "category", "type": "nominal" },
"y": { "field": "value", "type": "quantitative" }
}
}
После компиляции в Vega появляются сигналы, управляющие доменами шкал, диапазонами осей и геометрией столбцов.
Архитектура сигналов оптимизирована для минимизации вычислений. Используется система отслеживания зависимостей, при которой обновление происходит только при изменении входных значений.
Ключевые принципы:
Эта модель позволяет масштабировать визуализации до тысяч элементов без значительных потерь производительности.
Сигналы формируют фундаментальную абстракцию Vega, объединяющую данные, поведение и представление в единую систему. Они обеспечивают:
Вся интерактивность Vega и Vega-Lite в конечном итоге сводится к сети сигналов, проходящих через вычислительный граф и определяющих текущее состояние визуализации.