Потоки событий
Потоки событий — основной способ описать действия пользователя для создания динамической интерактивной визуализации. Они собирают последовательности событий ввода: щелчки мышью, касания, срабатывания таймера или изменения сигналов. Когда возникает событие, подходящее под определение потока, запускаются соответствующие обработчики событий сигналов, которые могут обновить значение сигнала.
{
"name": "signalName",
"on": [
{
"events": <<event-stream-definition>>,
"update": ...
}
]
}
Поток событий можно определить несколькими способами:
- Объекты потоков событий указывают, какие события нужно собирать.
- Строковые селекторы потоков событий позволяют задать поток в сокращённой форме. Например,
"rect:mouseover","click[event.shiftKey]"или"timer{1000}". - Ссылки на сигналы отслеживают их изменения. Например,
{"signal": "name"}. - Ссылки на шкалы отслеживают их изменения. Например,
{"scale": "xscale"}. - Массив из нескольких объектов потоков событий, ссылок на сигналы или ссылок на шкалы, события которых нужно собирать.
Поддерживаемые типы событий
Для элементов примитивов поддерживаются следующие типы событий DOM:
clickdblclickdragenterdragleavedragoverkeydownkeypresskeyupmousedownmousemovemouseoutmouseovermouseupmousewheeltouchendtouchmovetouchstartwheel
Другие события, поддерживаемые браузером, например 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": "..."
}
]