0
# Event Handling
1
2
Vega's event system provides comprehensive interaction support with event parsing, selection handling, listener management, and integration with the dataflow system for reactive visualizations.
3
4
## Capabilities
5
6
### Event Selector Parsing
7
8
Event selector string parsing for interaction specifications.
9
10
```typescript { .api }
11
/**
12
* Parse event selector strings into structured event selectors
13
* @param selector - Event selector string or array of strings
14
* @returns Array of parsed event selector objects
15
*/
16
function parseSelector(selector: string | string[]): EventSelector[];
17
18
interface EventSelector {
19
/** Event source (view, window, etc.) */
20
source?: EventSource;
21
22
/** Event type (click, mouseover, etc.) */
23
type: string;
24
25
/** Marks to target */
26
markname?: string;
27
28
/** Mark type to target */
29
marktype?: string;
30
31
/** CSS selector for DOM events */
32
selector?: string;
33
34
/** Event filter expression */
35
filter?: string;
36
37
/** Throttle delay in milliseconds */
38
throttle?: number;
39
40
/** Debounce delay in milliseconds */
41
debounce?: number;
42
43
/** Between event handling */
44
between?: EventSelector[];
45
46
/** Consume event flag */
47
consume?: boolean;
48
}
49
50
type EventSource = 'view' | 'scope' | 'window' | '*' | string;
51
```
52
53
### View Event Handling
54
55
View-level event listener management for scenegraph events.
56
57
```typescript { .api }
58
/**
59
* View event listener management interface
60
*/
61
interface ViewEventHandling {
62
/**
63
* Add event listener for scenegraph events
64
* @param type - Event type or selector
65
* @param handler - Event handler function
66
* @returns View instance for method chaining
67
*/
68
addEventListener(type: string | EventSelector, handler: EventListenerHandler): View;
69
70
/**
71
* Remove event listener
72
* @param type - Event type or selector
73
* @param handler - Event handler function to remove
74
* @returns View instance for method chaining
75
*/
76
removeEventListener(type: string | EventSelector, handler: EventListenerHandler): View;
77
78
/**
79
* Add signal change listener
80
* @param name - Signal name to watch
81
* @param handler - Signal change handler
82
* @returns View instance for method chaining
83
*/
84
addSignalListener(name: string, handler: SignalListenerHandler): View;
85
86
/**
87
* Remove signal change listener
88
* @param name - Signal name
89
* @param handler - Handler to remove
90
* @returns View instance for method chaining
91
*/
92
removeSignalListener(name: string, handler: SignalListenerHandler): View;
93
94
/**
95
* Add data change listener
96
* @param name - Dataset name to watch
97
* @param handler - Data change handler
98
* @returns View instance for method chaining
99
*/
100
addDataListener(name: string, handler: DataListenerHandler): View;
101
102
/**
103
* Remove data change listener
104
* @param name - Dataset name
105
* @param handler - Handler to remove
106
* @returns View instance for method chaining
107
*/
108
removeDataListener(name: string, handler: DataListenerHandler): View;
109
110
/**
111
* Add resize listener
112
* @param handler - Resize handler function
113
* @returns View instance for method chaining
114
*/
115
addResizeListener(handler: ResizeHandler): View;
116
117
/**
118
* Remove resize listener
119
* @param handler - Handler to remove
120
* @returns View instance for method chaining
121
*/
122
removeResizeListener(handler: ResizeHandler): View;
123
124
/**
125
* Get event stream for specified parameters
126
* @param source - Event source
127
* @param type - Event type
128
* @param filter - Optional event filter
129
* @returns Event stream
130
*/
131
events(source: EventSource, type: string, filter?: Function): EventStream;
132
}
133
134
type EventListenerHandler = (event: ScenegraphEvent, item?: Item | null) => void;
135
type SignalListenerHandler = (name: string, value: SignalValue) => void;
136
type DataListenerHandler = (name: string, changeset: Changeset) => void;
137
type ResizeHandler = (width: number, height: number) => void;
138
139
interface ScenegraphEvent extends Event {
140
/** Event type */
141
type: string;
142
143
/** Scene graph item */
144
item?: Item;
145
146
/** Vega event metadata */
147
vega?: EventMetadata;
148
}
149
150
interface EventMetadata {
151
/** View instance */
152
view: View;
153
154
/** Source item */
155
item: Item | null;
156
157
/** Event coordinates */
158
x?: number;
159
y?: number;
160
}
161
162
interface Item {
163
/** Item data */
164
datum: any;
165
166
/** Mark definition */
167
mark: RuntimeMark;
168
169
/** Item bounds */
170
bounds?: Bounds;
171
}
172
173
type SignalValue = any;
174
175
interface Changeset {
176
insert: any[];
177
remove: any[];
178
modify: any[];
179
}
180
```
181
182
### Event Stream System
183
184
Event stream management for reactive event handling.
185
186
```typescript { .api }
187
/**
188
* Event stream for managing event flow and transformations
189
*/
190
class EventStream {
191
/**
192
* Create new event stream
193
* @param source - Event source
194
* @param type - Event type
195
* @param filter - Optional event filter function
196
*/
197
constructor(source?: EventSource, type?: string, filter?: Function);
198
199
/** Event source */
200
source: EventSource;
201
202
/** Event type */
203
type: string;
204
205
/** Event filter */
206
filter: Function;
207
208
/** Stream targets */
209
targets: Set<any>;
210
211
/**
212
* Filter events with predicate
213
* @param predicate - Filter predicate function
214
* @returns New filtered event stream
215
*/
216
filter(predicate: (event: any) => boolean): EventStream;
217
218
/**
219
* Throttle event stream
220
* @param delay - Throttle delay in milliseconds
221
* @returns New throttled event stream
222
*/
223
throttle(delay: number): EventStream;
224
225
/**
226
* Debounce event stream
227
* @param delay - Debounce delay in milliseconds
228
* @returns New debounced event stream
229
*/
230
debounce(delay: number): EventStream;
231
232
/**
233
* Map events to new values
234
* @param mapper - Event mapping function
235
* @returns New mapped event stream
236
*/
237
map(mapper: (event: any) => any): EventStream;
238
239
/**
240
* Merge with another event stream
241
* @param stream - Stream to merge with
242
* @returns New merged event stream
243
*/
244
merge(stream: EventStream): EventStream;
245
246
/**
247
* Take events between start and end streams
248
* @param start - Start event stream
249
* @param end - End event stream
250
* @returns New between event stream
251
*/
252
between(start: EventStream, end: EventStream): EventStream;
253
254
/**
255
* Apply stream to target
256
* @param target - Target object or function
257
* @returns The event stream instance
258
*/
259
apply(target: any): EventStream;
260
}
261
```
262
263
### Event Types
264
265
Built-in event type definitions and constants.
266
267
```typescript { .api }
268
/** Mouse event types */
269
interface MouseEvents {
270
/** Mouse click */
271
click: 'click';
272
273
/** Mouse double click */
274
dblclick: 'dblclick';
275
276
/** Mouse down */
277
mousedown: 'mousedown';
278
279
/** Mouse up */
280
mouseup: 'mouseup';
281
282
/** Mouse move */
283
mousemove: 'mousemove';
284
285
/** Mouse enter */
286
mouseenter: 'mouseenter';
287
288
/** Mouse leave */
289
mouseleave: 'mouseleave';
290
291
/** Mouse over */
292
mouseover: 'mouseover';
293
294
/** Mouse out */
295
mouseout: 'mouseout';
296
297
/** Context menu */
298
contextmenu: 'contextmenu';
299
300
/** Mouse wheel */
301
wheel: 'wheel';
302
}
303
304
/** Touch event types */
305
interface TouchEvents {
306
/** Touch start */
307
touchstart: 'touchstart';
308
309
/** Touch end */
310
touchend: 'touchend';
311
312
/** Touch move */
313
touchmove: 'touchmove';
314
315
/** Touch cancel */
316
touchcancel: 'touchcancel';
317
}
318
319
/** Keyboard event types */
320
interface KeyboardEvents {
321
/** Key down */
322
keydown: 'keydown';
323
324
/** Key up */
325
keyup: 'keyup';
326
327
/** Key press */
328
keypress: 'keypress';
329
}
330
331
/** Window event types */
332
interface WindowEvents {
333
/** Window resize */
334
resize: 'resize';
335
336
/** Window scroll */
337
scroll: 'scroll';
338
339
/** Page load */
340
load: 'load';
341
342
/** Before unload */
343
beforeunload: 'beforeunload';
344
}
345
346
/** View event types */
347
interface ViewEvents {
348
/** View resize */
349
'view:resize': 'view:resize';
350
351
/** View render */
352
'view:render': 'view:render';
353
354
/** View update */
355
'view:update': 'view:update';
356
}
357
358
/** Timer event types */
359
interface TimerEvents {
360
/** Timer tick */
361
timer: 'timer';
362
}
363
```
364
365
### Interaction Utilities
366
367
Utility functions for common interaction patterns.
368
369
```typescript { .api }
370
/**
371
* Configure hover behavior for marks
372
* @param view - View instance
373
* @param hoverSet - Encoding set for hover state
374
* @param leaveSet - Encoding set for leave state
375
* @returns Configured view
376
*/
377
function configureHover(view: View, hoverSet?: string, leaveSet?: string): View;
378
379
/**
380
* Configure tooltip behavior
381
* @param view - View instance
382
* @param tooltipHandler - Custom tooltip handler
383
* @returns Configured view
384
*/
385
function configureTooltip(view: View, tooltipHandler?: TooltipHandler): View;
386
387
/**
388
* Configure selection behavior
389
* @param view - View instance
390
* @param selectionConfig - Selection configuration
391
* @returns Configured view
392
*/
393
function configureSelection(view: View, selectionConfig: SelectionConfig): View;
394
395
type TooltipHandler = (handler: any, event: MouseEvent, item: Item, value: any) => void;
396
397
interface SelectionConfig {
398
/** Selection type */
399
type: 'point' | 'interval' | 'multi';
400
401
/** Selection fields */
402
fields?: string[];
403
404
/** Selection encoding */
405
encodings?: string[];
406
407
/** Selection signal binding */
408
bind?: SelectionBinding;
409
410
/** Selection initialization */
411
init?: any;
412
413
/** Selection event trigger */
414
on?: string | EventSelector[];
415
416
/** Selection clear trigger */
417
clear?: string | EventSelector[];
418
419
/** Selection resolve mode */
420
resolve?: 'global' | 'union' | 'intersect';
421
}
422
423
interface SelectionBinding {
424
/** Input element type */
425
input: string;
426
427
/** Element binding properties */
428
[prop: string]: any;
429
}
430
```
431
432
### Event Debugging
433
434
Event debugging and inspection utilities.
435
436
```typescript { .api }
437
/**
438
* Enable event debugging for a view
439
* @param view - View instance
440
* @param options - Debug options
441
*/
442
function enableEventDebugging(view: View, options?: EventDebugOptions): void;
443
444
/**
445
* Log event information
446
* @param event - Event to log
447
* @param context - Additional context information
448
*/
449
function logEvent(event: Event, context?: any): void;
450
451
/**
452
* Trace event propagation
453
* @param view - View instance
454
* @param eventType - Event type to trace
455
*/
456
function traceEvents(view: View, eventType: string): void;
457
458
interface EventDebugOptions {
459
/** Log all events */
460
logAll?: boolean;
461
462
/** Event types to log */
463
types?: string[];
464
465
/** Include event data */
466
includeData?: boolean;
467
468
/** Custom log function */
469
logger?: (message: string, data?: any) => void;
470
}
471
```
472
473
## Usage Examples
474
475
### Basic Event Listeners
476
477
```typescript
478
import { View, parseSelector } from "vega";
479
480
// Add click listener
481
view.addEventListener('click', (event, item) => {
482
if (item && item.datum) {
483
console.log('Clicked on:', item.datum);
484
}
485
});
486
487
// Add hover listeners
488
view.addEventListener('mouseover', (event, item) => {
489
if (item) {
490
console.log('Hovering:', item.datum);
491
}
492
});
493
494
view.addEventListener('mouseout', (event, item) => {
495
console.log('Mouse left item');
496
});
497
```
498
499
### Signal Listeners
500
501
```typescript
502
// Listen to signal changes
503
view.addSignalListener('selectedCategory', (name, value) => {
504
console.log(`Signal ${name} changed to:`, value);
505
506
// Update other visualizations or UI
507
updateRelatedCharts(value);
508
});
509
510
// Listen to multiple signals
511
['filter', 'sort', 'groupBy'].forEach(signalName => {
512
view.addSignalListener(signalName, (name, value) => {
513
console.log(`Configuration ${name}:`, value);
514
});
515
});
516
```
517
518
### Data Change Listeners
519
520
```typescript
521
// Monitor data changes
522
view.addDataListener('sales', (name, changeset) => {
523
console.log(`Dataset ${name} changed:`, {
524
inserted: changeset.insert.length,
525
removed: changeset.remove.length,
526
modified: changeset.modify.length
527
});
528
529
// Trigger external updates
530
if (changeset.insert.length > 0) {
531
notifyNewData(changeset.insert);
532
}
533
});
534
```
535
536
### Complex Event Selectors
537
538
```typescript
539
import { parseSelector } from "vega";
540
541
// Parse complex selector
542
const selectors = parseSelector([
543
'rect:click',
544
'symbol:mouseover',
545
'@legend:click',
546
'window:resize'
547
]);
548
549
selectors.forEach(selector => {
550
console.log('Parsed selector:', selector);
551
});
552
553
// Use with view
554
view.addEventListener('rect:click', (event, item) => {
555
console.log('Rectangle clicked:', item.datum);
556
});
557
558
// Mark-specific events
559
view.addEventListener('symbol:dblclick', (event, item) => {
560
console.log('Symbol double-clicked:', item.datum);
561
});
562
```
563
564
### Event Streams
565
566
```typescript
567
import { EventStream } from "vega";
568
569
// Create event stream
570
const clickStream = view.events('view', 'click');
571
572
// Filter and transform events
573
const filteredClicks = clickStream
574
.filter(event => event.item && event.item.mark.marktype === 'rect')
575
.throttle(100); // Throttle to max 10 clicks per second
576
577
// Apply to signals
578
filteredClicks.apply((event) => {
579
view.signal('lastClicked', event.item.datum.id).run();
580
});
581
582
// Merge multiple streams
583
const mouseEvents = view.events('view', 'mousemove')
584
.merge(view.events('view', 'click'));
585
586
mouseEvents.apply((event) => {
587
view.signal('mouseActivity', Date.now()).run();
588
});
589
```
590
591
### Interaction Patterns
592
593
```typescript
594
// Hover highlighting
595
view.addEventListener('mouseover', (event, item) => {
596
if (item && item.mark.marktype === 'rect') {
597
view.signal('hoveredItem', item.datum.id).run();
598
}
599
});
600
601
view.addEventListener('mouseout', (event, item) => {
602
view.signal('hoveredItem', null).run();
603
});
604
605
// Click selection
606
let selectedItems = new Set();
607
608
view.addEventListener('click', (event, item) => {
609
if (item) {
610
const id = item.datum.id;
611
612
if (event.shiftKey) {
613
// Multi-select with Shift
614
if (selectedItems.has(id)) {
615
selectedItems.delete(id);
616
} else {
617
selectedItems.add(id);
618
}
619
} else {
620
// Single select
621
selectedItems.clear();
622
selectedItems.add(id);
623
}
624
625
view.signal('selectedItems', Array.from(selectedItems)).run();
626
}
627
});
628
```
629
630
### Custom Event Handling
631
632
```typescript
633
// Custom tooltip handler
634
const customTooltip = (handler, event, item, value) => {
635
if (item && item.datum) {
636
const tooltip = document.getElementById('custom-tooltip');
637
tooltip.innerHTML = `
638
<strong>${item.datum.name}</strong><br>
639
Value: ${item.datum.value}<br>
640
Category: ${item.datum.category}
641
`;
642
tooltip.style.left = event.pageX + 10 + 'px';
643
tooltip.style.top = event.pageY + 10 + 'px';
644
tooltip.style.display = 'block';
645
}
646
};
647
648
// Hide tooltip on mouse out
649
view.addEventListener('mouseout', () => {
650
document.getElementById('custom-tooltip').style.display = 'none';
651
});
652
653
view.tooltip(customTooltip);
654
```
655
656
### Keyboard Interactions
657
658
```typescript
659
// Keyboard navigation
660
view.addEventListener('keydown', (event) => {
661
const currentSelection = view.signal('selectedIndex');
662
const dataLength = view.data('dataset').length;
663
664
switch (event.key) {
665
case 'ArrowUp':
666
event.preventDefault();
667
view.signal('selectedIndex',
668
Math.max(0, currentSelection - 1)).run();
669
break;
670
671
case 'ArrowDown':
672
event.preventDefault();
673
view.signal('selectedIndex',
674
Math.min(dataLength - 1, currentSelection + 1)).run();
675
break;
676
677
case 'Enter':
678
event.preventDefault();
679
// Trigger selection action
680
const selectedData = view.data('dataset')[currentSelection];
681
console.log('Selected:', selectedData);
682
break;
683
}
684
});
685
```
686
687
### Event Throttling and Debouncing
688
689
```typescript
690
// Throttled mouse tracking
691
let lastMouseUpdate = 0;
692
const MOUSE_THROTTLE = 16; // ~60fps
693
694
view.addEventListener('mousemove', (event) => {
695
const now = Date.now();
696
if (now - lastMouseUpdate > MOUSE_THROTTLE) {
697
lastMouseUpdate = now;
698
699
view.signal('mouseX', event.x)
700
.signal('mouseY', event.y)
701
.run();
702
}
703
});
704
705
// Debounced search
706
let searchTimeout;
707
const SEARCH_DEBOUNCE = 300;
708
709
view.addSignalListener('searchQuery', (name, value) => {
710
clearTimeout(searchTimeout);
711
712
searchTimeout = setTimeout(() => {
713
// Perform search operation
714
const filtered = performSearch(value);
715
view.data('searchResults', filtered).run();
716
}, SEARCH_DEBOUNCE);
717
});
718
```
719
720
### Window and View Events
721
722
```typescript
723
// Responsive resize handling
724
view.addResizeListener((width, height) => {
725
console.log(`View resized to ${width}x${height}`);
726
727
// Update responsive breakpoints
728
const isSmall = width < 600;
729
view.signal('isSmallScreen', isSmall).run();
730
731
// Adjust layout parameters
732
if (isSmall) {
733
view.signal('fontSize', 10)
734
.signal('padding', 20)
735
.run();
736
} else {
737
view.signal('fontSize', 12)
738
.signal('padding', 40)
739
.run();
740
}
741
});
742
743
// Window event handling
744
window.addEventListener('beforeunload', (event) => {
745
// Save view state before page unload
746
const state = view.getState();
747
localStorage.setItem('viewState', JSON.stringify(state));
748
});
749
750
// Restore state on load
751
window.addEventListener('load', () => {
752
const savedState = localStorage.getItem('viewState');
753
if (savedState) {
754
view.setState(JSON.parse(savedState));
755
}
756
});
757
```
758
759
### Event Delegation
760
761
```typescript
762
// Event delegation for dynamic content
763
view.addEventListener('click', (event, item) => {
764
if (!item) return;
765
766
// Delegate based on mark type
767
switch (item.mark.marktype) {
768
case 'rect':
769
handleBarClick(item);
770
break;
771
772
case 'symbol':
773
handlePointClick(item);
774
break;
775
776
case 'text':
777
handleLabelClick(item);
778
break;
779
780
default:
781
console.log('Unhandled mark type:', item.mark.marktype);
782
}
783
});
784
785
function handleBarClick(item) {
786
console.log('Bar clicked:', item.datum);
787
// Drill down or filter logic
788
}
789
790
function handlePointClick(item) {
791
console.log('Point clicked:', item.datum);
792
// Detail view logic
793
}
794
795
function handleLabelClick(item) {
796
console.log('Label clicked:', item.datum);
797
// Edit or info logic
798
}
799
```