Поле view.change(): обновление данных

В Vega модель исполнения визуализации построена вокруг объекта View, который инкапсулирует состояние сцены, данные, масштабы и рендеринг. Любое динамическое изменение набора данных в уже созданной визуализации выполняется через механизм изменений (changeset), который применяется к конкретному источнику данных.


Объект View в Vega

Экземпляр View представляет запущенную визуализацию, созданную после компиляции спецификации:

  • хранит текущие данные и их состояния
  • управляет перерисовкой сцены
  • обеспечивает реактивные обновления
  • синхронизирует домены шкал и взаимодействия

После инициализации визуализации через view.runAsync() или view.initialize() дальнейшие изменения данных выполняются без пересоздания графа сцены.


Сигнатура view.change()

Метод используется для применения изменений к одному из наборов данных:

view.change(dataName, changeset)

Параметры:

  • dataName — строка, имя набора данных, определённого в спецификации Vega
  • changeset — объект типа changeset, описывающий операции вставки, удаления или обновления

Метод не изменяет данные напрямую, а формирует декларативное описание трансформации.


Changeset как основа мутации данных

Changeset — это структура, описывающая атомарные операции над данными. Создаётся через фабрику:

const cs = vega.changeset();

Основные операции:

Вставка данных

Добавление новых элементов в поток данных:

cs.insert([
  { category: "A", value: 10 },
  { category: "B", value: 20 }
]);

Удаление данных

Удаление по предикату или набору значений:

cs.remove(d => d.category === "A");

или точечное удаление:

cs.remove([{ category: "A", value: 10 }]);

Обновление данных (upsert)

Комбинация ins ert + update:

cs.upsert(
  { category: "A", val ue: 15 },
  d => d.category === "A"
);

Полная очистка

cs.remove(() => true);

Полезно при полной перезагрузке набора данных.


Применение изменений через view.change()

После формирования changeset он применяется к конкретному источнику:

view.change("table", cs).runAsync();

Где "table" — имя data-источника в Vega-spec:

{
  "data": [
    {
      "name": "table",
      "values": []
    }
  ]
}

Порядок обновления и реактивность

После вызова view.change() происходят следующие этапы:

  1. Изменение данных в внутреннем хранилище View
  2. Пересчёт производных трансформаций (filter, aggregate, bin и др.)
  3. Обновление доменов шкал при включённой зависимости
  4. Инвалидация сцены (scenegraph)
  5. Перерисовка визуальных элементов

Важно, что сам view.change() не запускает рендер автоматически в синхронном режиме — он лишь фиксирует изменения.


Асинхронное применение через view.runAsync()

Для гарантированной отрисовки используется:

view.change("table", cs).runAsync();

runAsync():

  • возвращает Promise
  • выполняет обновление сцены неблокирующим образом
  • позволяет объединять несколько изменений в один цикл рендера

Пример батчинга:

const cs = vega.changeset()
  .insert([{ category: "C", value: 30 }])
  .remove(d => d.category === "B");

view.change("table", cs);

view.change("otherTable", vega.changeset().insert([{ x: 1 }]));

view.runAsync();

Оба изменения будут применены в одном проходе.


Синхронный режим обновления

В более старых сценариях используется:

view.change("table", cs).run();

Особенности:

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

Инкрементальные обновления и производительность

Механизм changeset специально разработан для минимизации пересчётов:

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

Это делает возможным:

  • потоковые данные (streaming)
  • realtime-графики
  • интерактивные фильтры

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

Типичный сценарий взаимодействия:

function updateData(newPoint) {
  const cs = vega.changeset().insert([newPoint]);
  view.change("table", cs).runAsync();
}

Удаление устаревших значений:

function pruneOld() {
  const cs = vega.changeset()
    .remove(d => d.timestamp < Date.now() - 60000);

  view.change("table", cs).runAsync();
}

Связь с Vega-Lite

В Vega-Lite прямого аналога view.change() нет на уровне спецификации, но после компиляции Vega-Lite → Vega:

  • создаётся объект View
  • все динамические обновления выполняются через тот же API
  • dataName соответствует внутренним именам, сгенерированным компилятором

Пример:

const runtime = vl.compile(spec).spec;
const view = new vega.View(vega.parse(runtime));

Далее:

view.change("source_0", vega.changeset().insert(data)).runAsync();

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

  • нельзя напрямую мутировать массивы данных (view.data() возвращает копию)
  • порядок операций внутри changeset важен
  • удаление по предикату может быть затратным на больших датасетах
  • шкалы могут пересчитываться автоматически, если зависят от изменяемых данных

Типовые ошибки при использовании

Потеря ссылки на dataName

Несовпадение имени источника данных приводит к отсутствию обновления без ошибок выполнения.

Пропуск run / runAsync

view.change("table", cs); // изменения не отрисуются

Неконсистентные upsert-предикаты

cs.upsert(item, d => d.id); // ошибка: предикат должен возвращать boolean

Правильно:

cs.upsert(item, d => d.id === item.id);

Модель потоковых данных

view.change() фактически реализует потоковую модель:

  • каждое изменение — событие
  • changeset — пакет событий
  • View — реактивный исполнитель

Это позволяет строить архитектуры визуализации, близкие к event-driven системам обработки данных.