Signals в Vega и Vega-Lite: реактивная модель состояния
В архитектуре Vega signals представляют собой реактивные переменные, которые лежат в основе всей системы взаимодействия, вычислений и динамического обновления визуализаций. В отличие от классического подхода, где состояние управляется вручную через императивный код, signals формируют декларативную модель, в которой изменения данных, событий и параметров автоматически распространяются по графу зависимостей.
В Vega-Lite signals скрыты от пользователя на уровне спецификации, но активно генерируются компилятором при трансляции Vega-Lite в Vega, обеспечивая интерактивность, привязки и вычисляемые свойства визуализации.
Каждый signal в Vega представляет собой узел реактивного графа вычислений. Значение сигнала может зависеть от:
Изменение любого источника автоматически инициирует пересчёт всех зависимых сигналов.
Ключевая особенность: система не выполняет полный пересчёт визуализации, а обновляет только затронутые части графа.
Signals задаются в верхнем уровне Vega specification:
{
"signals": [
{
"name": "width",
"value": 400
},
{
"name": "height",
"value": 300
}
]
}
Signal всегда имеет имя и начальное значение. Дополнительно могут использоваться:
update — выражение пересчётаbind — привязка к UI элементамon — реакция на событияSignals могут вычисляться динамически через выражения:
{
"signals": [
{
"name": "radius",
"value": 10,
"update": "width / 20"
}
]
}
Здесь radius автоматически пересчитывается при изменении
width.
Выражения используют встроенный expression language Vega, поддерживающий:
Signals тесно связаны с event streams. Любое взаимодействие пользователя может быть источником обновления.
{
"signals": [
{
"name": "mouseX",
"value": 0,
"on": [
{
"events": "mousemove",
"update": "event.x"
}
]
}
]
}
Здесь event.x — текущее положение курсора.
Возможны сложные фильтры событий:
"events": "@plot:wheel"
или комбинации:
"events": [
{"events": "mousedown", "update": "x()"},
{"events": "mouseup", "update": "0"}
]
Signals могут быть связаны с HTML-элементами управления.
{
"signals": [
{
"name": "threshold",
"value": 50,
"bind": {
"input": "range",
"min": 0,
"max": 100
}
}
]
}
Такой signal становится двунаправленным:
Signals применяются в любом месте спецификации Vega:
{
"marks": [
{
"type": "circle",
"encode": {
"enter": {
"x": {"signal": "width / 2"},
"y": {"signal": "height / 2"},
"size": {"signal": "radius * radius"}
}
}
}
]
}
"fill": {"signal": "mouseX > 200 ? 'red' : 'blue'"}
Signals позволяют превращать визуализацию в динамическую систему правил.
Signals могут зависеть от данных:
{
"signals": [
{
"name": "maxValue",
"update": "max(data('table'), d => d.value)"
}
]
}
Таким образом, сигналы становятся мостом между data pipeline и визуальным представлением.
Signals часто управляют трансформациями данных:
{
"transform": [
{
"filter": "datum.value > threshold"
}
]
}
Здесь threshold — signal, изменяемый пользователем.
Это создаёт динамическую фильтрацию без пересборки графа.
Signals имеют область видимости:
Вложенные группы могут переопределять значения:
{
"signals": [
{"name": "scale", "value": 1}
],
"marks": [
{
"type": "group",
"signals": [
{"name": "scale", "value": 2}
]
}
]
}
Внутри группы используется локальная версия signal.
Signal dependency graph строится автоматически:
Пример цепочки:
mouseX → normalizedX → color → fill
Изменение одного входа распространяется каскадно.
Реактивная модель оптимизируется через:
Signals не пересчитываются глобально — только локально затронутые узлы.
В Vega-Lite signals не пишутся вручную, но генерируются автоматически:
Например, selection brush в Vega-Lite компилируется в:
Vega-Lite:
{
"mark": "point",
"selection": {
"brush": {"type": "interval"}
}
}
Компиляция в Vega включает signals:
brush_xbrush_ybrush_endи их event bindings:
"on": [
{"events": "mousedown", "update": "..."},
{"events": "mousemove", "update": "..."},
{"events": "mouseup", "update": "..."}
]
Vega runtime позволяет подключаться к signals через API:
view.signal("threshold", 30).run();
Изменение signal программно:
view.signal("mouseX", 120).run();
Получение значения:
view.signal("mouseX");
Signals становятся точками интеграции между декларативной визуализацией и внешним приложением.
Signals поддерживают композицию:
{
"name": "opacity",
"update": "clamp(mouseX / width, 0, 1)"
}
или логические цепочки:
"update": "selected && datum.value > threshold"
Это позволяет строить компактные реактивные правила без императивной логики.
Signals становятся универсальным механизмом управления состоянием визуализации, заменяя отдельные subsystems интерактивности единой реактивной моделью.