События мыши и клавиатуры

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

Событийная модель опирается на несколько уровней абстракции: view events, item events, signal events, а также интеграцию с selection-механизмами Vega-Lite. Это позволяет строить сложные интерактивные визуализации без ручного управления DOM-слушателями.


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

  • события мыши (mousemove, click, mouseover, mouseout, mousedown, mouseup, wheel)
  • события клавиатуры (keydown, keyup)
  • системные события (resize, timer, load)
  • внутренние события рендеринга и изменения сигналов

В Vega эти события не обрабатываются напрямую, а используются в event streams, которые привязываются к сигналам:

{
  "signals": [
    {
      "name": "cursor",
      "on": [
        {
          "events": "view:mousemove",
          "update": "xy()"
        }
      ]
    }
  ]
}

Здесь view:mousemove — поток событий от всей сцены визуализации, а xy() — функция, возвращающая координаты курсора.


События мыши в Vega

Мышь является основным источником интерактивности. Vega различает события:

Базовые мышиные события

  • view:click
  • view:mousemove
  • view:mouseover
  • view:mouseout
  • view:mousedown
  • view:mouseup
  • view:wheel

Каждое событие может быть привязано к сигналу:

{
  "signals": [
    {
      "name": "lastClick",
      "on": [
        {
          "events": "view:click",
          "update": "datum"
        }
      ]
    }
  ]
}

Здесь datum — данные элемента, по которому произошёл клик. Если клик произошёл по фону, datum будет null.


События на уровне элементов

Vega позволяет различать события не только по сцене, но и по конкретным графическим примитивам:

  • rect:click
  • symbol:mouseover
  • text:mouseout

Пример:

{
  "signals": [
    {
      "name": "hoveredPoint",
      "on": [
        {
          "events": "symbol:mouseover",
          "update": "datum.id"
        },
        {
          "events": "symbol:mouseout",
          "update": "null"
        }
      ]
    }
  ]
}

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


Фильтрация и условия событий

События можно фильтровать с помощью выражений:

{
  "events": {
    "source": "view",
    "type": "click",
    "filter": "event.shiftKey === true"
  }
}

Это позволяет обрабатывать, например, только клики с зажатым Shift.

В выражениях доступны:

  • event — нативное DOM-событие
  • item — визуальный элемент
  • datum — данные элемента
  • xy() — координаты курсора

Клавиатурные события в Vega

Клавиатура обрабатывается через глобальные события окна или представления:

  • window:keydown
  • window:keyup
  • view:keydown

Базовое использование

{
  "signals": [
    {
      "name": "pressedKey",
      "on": [
        {
          "events": "window:keydown",
          "update": "event.key"
        }
      ]
    }
  ]
}

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


Комбинирование клавиш

Можно реагировать на сочетания:

{
  "signals": [
    {
      "name": "resetZoom",
      "on": [
        {
          "events": {
            "source": "window",
            "type": "keydown",
            "filter": "event.key === 'r' && event.ctrlKey"
          },
          "update": "true"
        }
      ]
    }
  ]
}

События в Vega-Lite: selection как основа интерактивности

Vega-Lite абстрагирует низкоуровневые события через механизм selection. Вместо прямого описания event streams используется декларация взаимодействий.

Point selection (выбор точек)

{
  "selection": {
    "highlight": {
      "type": "point",
      "on": "click"
    }
  }
}

Здесь:

  • type: "point" — одиночный выбор
  • on: "click" — событие активации

Hover-взаимодействие

{
  "selection": {
    "hover": {
      "type": "single",
      "on": "mouseover",
      "empty": "none"
    }
  }
}

Такой механизм часто используется для подсветки элементов без клика.


Interval selection (выделение области)

{
  "selection": {
    "brush": {
      "type": "interval",
      "on": "[mousedown, mouseup]"
    }
  }
}

Это создаёт прямоугольное выделение области с помощью мыши.


Связь событий с данными

События в Vega и Vega-Lite всегда связаны с данными через datum. Это ключевой механизм реактивной визуализации.

Пример реакции на клик:

{
  "signals": [
    {
      "name": "selectedValue",
      "on": [
        {
          "events": "rect:click",
          "update": "datum.value"
        }
      ]
    }
  ]
}

Здесь событие напрямую изменяет состояние визуализации.


Drag, pan и zoom как событийные композиции

Интерактивные жесты строятся как комбинации базовых событий.

Drag

{
  "signals": [
    {
      "name": "dragX",
      "on": [
        {
          "events": "symbol:mousedown",
          "update": "x()"
        },
        {
          "events": "window:mousemove",
          "update": "x()"
        }
      ]
    }
  ]
}

Zoom через колесо мыши

{
  "signals": [
    {
      "name": "zoom",
      "on": [
        {
          "events": "view:wheel",
          "update": "pow(1.001, -event.deltaY)"
        }
      ]
    }
  ]
}

Контекст событий: item, view, window

Каждое событие привязано к источнику:

  • item — конкретный графический элемент
  • view — вся визуализация
  • window — глобальное окно браузера

Пример различия:

"events": "item:click"

и

"events": "view:click"

Первое срабатывает только по элементу, второе — по любому месту сцены.


Приоритеты и конфликт событий

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

  1. item-события (более специфичные)
  2. view-события
  3. window-события

Также можно контролировать распространение через фильтрацию:

"filter": "event.defaultPrevented === false"

Производительность событийной системы

Система Vega оптимизирована для большого количества событий за счёт:

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

Особенно важно при работе с mousemove и wheel, где поток событий может быть очень интенсивным.


Комбинирование мыши и клавиатуры

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

Пример: перемещение объекта с зажатым Shift:

{
  "signals": [
    {
      "name": "moveEnabled",
      "on": [
        {
          "events": {
            "source": "window",
            "type": "keydown",
            "filter": "event.shiftKey"
          },
          "update": "true"
        },
        {
          "events": {
            "source": "window",
            "type": "keyup",
            "filter": "!event.shiftKey"
          },
          "update": "false"
        }
      ]
    }
  ]
}

Событийные выражения и вычисления

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

  • математические операции
  • доступ к event, datum, item
  • функции координат (x(), y(), xy())

Пример:

"update": "clamp(x(), 0, width)"

Это позволяет ограничивать движение курсора в пределах графика.


Интеграция событий с визуальными состояниями

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

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

Пример подсветки при hover:

{
  "encode": {
    "enter": {
      "fill": {"value": "steelblue"}
    },
    "hover": {
      "fill": {"value": "orange"}
    }
  }
}

Хотя это не прямое событие, оно логически связано с symbol:mouseover.


Сложные цепочки событий

В Vega можно строить цепочки реакций:

  1. click → сохраняет datum
  2. mousemove → обновляет позицию
  3. keydown → фиксирует режим
  4. wheel → изменяет масштаб

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