Потоки событий

Потоки событий — основной способ описать действия пользователя для создания динамической интерактивной визуализации. Они собирают последовательности событий ввода: щелчки мышью, касания, срабатывания таймера или изменения сигналов. Когда возникает событие, подходящее под определение потока, запускаются соответствующие обработчики событий сигналов, которые могут обновить значение сигнала.

{
  "name": "signalName",
  "on": [
    {
      "events": <<event-stream-definition>>,
      "update": ...
    }
  ]
}

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

Поддерживаемые типы событий

Для элементов примитивов поддерживаются следующие типы событий DOM:

  • click
  • dblclick
  • dragenter
  • dragleave
  • dragover
  • keydown
  • keypress
  • keyup
  • mousedown
  • mousemove
  • mouseout
  • mouseover
  • mouseup
  • mousewheel
  • touchend
  • touchmove
  • touchstart
  • wheel

Другие события, поддерживаемые браузером, например resize у объекта окна, можно собирать с элементов DOM на той же странице, где находится визуализация Vega. Приведённый выше список относится только к элементам примитивов в графе сцены представления Vega.

Кроме того, Vega поддерживает событие timer. Оно возникает через интервалы времени в миллисекундах, заданные свойством потока throttle. ≥ 4.0

Vega также поддерживает событие resize для источника container. Оно возникает при любом изменении размера элемента DOM, содержащего представление: из-за изменения размера окна, CSS-разметки или работы скрипта. Используйте его вместе с функцией выражения containerSize, чтобы визуализация подстраивалась под размер контейнера. ≥ 6.4

{
  "name": "width",
  "update": "containerSize()[0]",
  "on": [{"events": "container:resize", "update": "containerSize()[0]"}]
}

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

Размер контейнера не должен зависеть от содержащегося в нём представления. Если сигнал размера определяется через контейнер, размеры которого зависят от содержимого, представление будет перерисовываться каждый раз, когда меняет размер собственного контейнера. Пример такой ситуации — сигнал height, получаемый из containerSize()[1] внутри элемента с height: auto.

Объекты потоков событий

Простой поток событий состоит из источника и типа события:

Свойство Тип Описание
source String Источник событий ввода. Для потоков верхнего уровня спецификации значение по умолчанию — "view": отслеживаются все события ввода в текущем представлении Vega, включая события самого элемента Canvas или SVG. Во вложенной области значение по умолчанию — "scope": учитываются только события внутри группы, в которой определён поток. Другие допустимые значения: "window" для окна браузера, "container" для элемента DOM с представлением (только события "resize") или CSS-селектор внешних элементов DOM. Если type равно "timer", свойство source игнорируется.
type String Обязательно. Тип отслеживаемого события, например "click", "keydown" или "timer". См. список поддерживаемых типов.

Объект потока событий также может содержать свойства для фильтрации и изменения потока:

Свойство Тип Описание
between EventStream[ ] Массив из двух объектов потоков, задающих начальное и конечное события. Собираются только события между ними.
consume Boolean Логический признак (по умолчанию false), определяющий, должен ли поток поглощать событие вызовом event.preventDefault(). Общие настройки см. в конфигурации обработки событий.
filter Выражение | Выражение[ ] Одно или несколько выражений фильтра. Событие собирается, только если каждое выражение возвращает истинное значение. Выражения могут ссылаться только на свойства события, но не на значения сигналов.
debounce Number Минимальная задержка между возникновением события и его обработкой. Если за время ожидания поступит новое событие, таймер запускается заново и будет собрано только последнее событие.
markname String Уникальное имя набора примитивов, события которого отслеживаются. События других примитивов игнорируются.
marktype String Тип примитивов (arc, rect и т. д.), события которого отслеживаются. События примитивов других типов игнорируются.
throttle Number Минимальный интервал между собираемыми событиями в миллисекундах (по умолчанию 0). События, поступившие до истечения интервала, игнорируются. Для timer это свойство задаёт интервал между срабатываниями.

Например, чтобы собирать события щелчка на примитивах rect:

{"type": "click", "marktype": "rect"}

Чтобы отслеживать изменение размера окна браузера:

{"source": "window", "type": "resize"}

Чтобы собирать события mousedown на примитивах image при нажатой клавише Ctrl и использовании левой кнопки мыши:

{
  "type": "mousedown",
  "marktype": "image",
  "filter": ["event.ctrlKey", "event.button === 0"]
}

Чтобы собирать события mousemove между событиями mousedown и mouseup:

{
  "type": "mousemove",
  "between": [
    {"type": "mousedown"},
    {"type": "mouseup"}
  ]
}

Чтобы получать срабатывания таймера каждые 2 секунды (2000 миллисекунд):

{
  "type": "timer",
  "throttle": 2000
}

Производные потоки событий

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

Свойство Тип Описание
stream Поток событий Обязательно. Входной поток событий, изменяемый дополнительными параметрами.

Например:

{
  "stream": {"marktype": "rect", "type": "click"},
  "filter": "event.shiftKey",
  "debounce": 500
}

Объединённые потоки событий

Несколько потоков событий можно объединить в один.

Свойство Тип Описание
merge EventStream[ ] Обязательно. Массив потоков событий, объединяемых в один поток.

Например:

{
  "merge": [
    {"marktype": "symbol", "type": "mousedown"},
    {"marktype": "symbol", "type": "touchstart"}
  ]
}

Селекторы потоков событий

Селектор событий — удобная сокращённая запись для определения потока по образцу CSS-селекторов.

Простые селекторы

Простой селектор задаёт тип события, необязательный источник и необязательные выражения фильтрации.

Общий вид простого селектора (в записи, похожей на регулярное выражение):

(source:)?type([filter])*({throttle(,debounce)?})?

Для свойства source доступны следующие варианты:

  • Если не задано, используется значение source по умолчанию (view или scope), как описано в разделе о свойстве source.
  • Допустимый тип примитива, например arc, image или rect.
  • Имя примитива с префиксом @, например @cell. У соответствующих элементов свойство name должно быть задано в определении примитива.
  • Строка * означает примитивы любого типа, но исключает само представление.
  • Строки view и scope задают области отслеживания событий, описанные в разделе о свойстве source.
  • Строка window означает объект окна браузера.
  • Строка container означает элемент DOM, содержащий представление. Поддерживается только событие resize. ≥ 6.4
  • Иное значение source интерпретируется как CSS-селектор отслеживаемых элементов DOM.

Значение type должно быть одним из поддерживаемых типов событий. Чтобы поглотить событие ввода, то есть вызвать event.preventDefault(), добавьте восклицательный знак (!) после типа события.

Одно или несколько выражений filter можно добавить в квадратных скобках ([]). Если фильтров несколько, они объединяются логическим «И». Выражения фильтра могут ссылаться только на объект события, но не на сигналы. Подробнее о синтаксисе см. раздел «Выражения».

Чтобы ограничить частоту событий (throttle) или задать задержку обработки (debounce), добавьте интервалы в миллисекундах в фигурных скобках ({}) в конце селектора.

Примеры простых селекторов событий:

mousedown           // все события mousedown независимо от источника
*:mousedown         // события mousedown на примитивах, но не на самом представлении
rect:mousedown      // события mousedown на любых примитивах rect
@foo:mousedown      // события mousedown на примитивах с именем 'foo'
symbol:mousedown!   // сбор и поглощение событий mousedown на примитивах symbol
window:mousemove    // события mousemove в окне браузера
container:resize    // изменение размера контейнера представления
mousemove{100}      // не чаще одного события каждые 100 мс
mousemove{100, 200} // дополнительно задержка обработки на 200 мс
mousemove{0, 200}   // задержка на 200 мс без ограничения частоты
timer{1000}         // срабатывание таймера каждые 1000 мс
mousemove[event.buttons] // mousemove при нажатой кнопке мыши
click[event.shiftKey]    // click при нажатой клавише Shift

Объединение селекторов

Чтобы объединить потоки событий, перечислите селекторы через запятую.

selector1, selector2, ...

Например, чтобы собирать события mousedown и touchstart на примитивах path:

path:mousedown, path:touchstart

Фильтры между событиями

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

[startSelector, stopSelector] > selector

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

[rect:mousedown, window:mouseup] > window:mousemove

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

Ссылки на сигналы

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

"on": [
  {
    "events": {"signal": "foo"},
    "update": "..."
  }
]

В этом случае обновление запускается при каждом изменении сигнала foo. Чтобы объединить ссылки на сигналы с объектами потоков событий, передайте массив в свойстве events:

"on": [
  {
    "events": [{"signal": "foo"}, {"type": "click", "marktype": "rect"}],
    "update": "..."
  }
]

Ссылки на шкалы

Потоки событий также могут отслеживать изменения шкал:

"on": [
  {
    "events": {"scale": "xscale"},
    "update": "..."
  }
]

Здесь обновление запускается при каждом изменении шкалы xscale. Ссылки на шкалы можно объединить со ссылками на сигналы и объектами потоков событий, передав массив в свойстве events:

"on": [
  {
    "events": [{"scale": "xscale"}, {"signal": "foo"}],
    "update": "..."
  }
]