A declarative visualization grammar for creating interactive data visualizations through JSON specifications.
—
Vega's event system provides comprehensive interaction support with event parsing, selection handling, listener management, and integration with the dataflow system for reactive visualizations.
Event selector string parsing for interaction specifications.
/**
* Parse event selector strings into structured event selectors
* @param selector - Event selector string or array of strings
* @returns Array of parsed event selector objects
*/
function parseSelector(selector: string | string[]): EventSelector[];
interface EventSelector {
/** Event source (view, window, etc.) */
source?: EventSource;
/** Event type (click, mouseover, etc.) */
type: string;
/** Marks to target */
markname?: string;
/** Mark type to target */
marktype?: string;
/** CSS selector for DOM events */
selector?: string;
/** Event filter expression */
filter?: string;
/** Throttle delay in milliseconds */
throttle?: number;
/** Debounce delay in milliseconds */
debounce?: number;
/** Between event handling */
between?: EventSelector[];
/** Consume event flag */
consume?: boolean;
}
type EventSource = 'view' | 'scope' | 'window' | '*' | string;View-level event listener management for scenegraph events.
/**
* View event listener management interface
*/
interface ViewEventHandling {
/**
* Add event listener for scenegraph events
* @param type - Event type or selector
* @param handler - Event handler function
* @returns View instance for method chaining
*/
addEventListener(type: string | EventSelector, handler: EventListenerHandler): View;
/**
* Remove event listener
* @param type - Event type or selector
* @param handler - Event handler function to remove
* @returns View instance for method chaining
*/
removeEventListener(type: string | EventSelector, handler: EventListenerHandler): View;
/**
* Add signal change listener
* @param name - Signal name to watch
* @param handler - Signal change handler
* @returns View instance for method chaining
*/
addSignalListener(name: string, handler: SignalListenerHandler): View;
/**
* Remove signal change listener
* @param name - Signal name
* @param handler - Handler to remove
* @returns View instance for method chaining
*/
removeSignalListener(name: string, handler: SignalListenerHandler): View;
/**
* Add data change listener
* @param name - Dataset name to watch
* @param handler - Data change handler
* @returns View instance for method chaining
*/
addDataListener(name: string, handler: DataListenerHandler): View;
/**
* Remove data change listener
* @param name - Dataset name
* @param handler - Handler to remove
* @returns View instance for method chaining
*/
removeDataListener(name: string, handler: DataListenerHandler): View;
/**
* Add resize listener
* @param handler - Resize handler function
* @returns View instance for method chaining
*/
addResizeListener(handler: ResizeHandler): View;
/**
* Remove resize listener
* @param handler - Handler to remove
* @returns View instance for method chaining
*/
removeResizeListener(handler: ResizeHandler): View;
/**
* Get event stream for specified parameters
* @param source - Event source
* @param type - Event type
* @param filter - Optional event filter
* @returns Event stream
*/
events(source: EventSource, type: string, filter?: Function): EventStream;
}
type EventListenerHandler = (event: ScenegraphEvent, item?: Item | null) => void;
type SignalListenerHandler = (name: string, value: SignalValue) => void;
type DataListenerHandler = (name: string, changeset: Changeset) => void;
type ResizeHandler = (width: number, height: number) => void;
interface ScenegraphEvent extends Event {
/** Event type */
type: string;
/** Scene graph item */
item?: Item;
/** Vega event metadata */
vega?: EventMetadata;
}
interface EventMetadata {
/** View instance */
view: View;
/** Source item */
item: Item | null;
/** Event coordinates */
x?: number;
y?: number;
}
interface Item {
/** Item data */
datum: any;
/** Mark definition */
mark: RuntimeMark;
/** Item bounds */
bounds?: Bounds;
}
type SignalValue = any;
interface Changeset {
insert: any[];
remove: any[];
modify: any[];
}Event stream management for reactive event handling.
/**
* Event stream for managing event flow and transformations
*/
class EventStream {
/**
* Create new event stream
* @param source - Event source
* @param type - Event type
* @param filter - Optional event filter function
*/
constructor(source?: EventSource, type?: string, filter?: Function);
/** Event source */
source: EventSource;
/** Event type */
type: string;
/** Event filter */
filter: Function;
/** Stream targets */
targets: Set<any>;
/**
* Filter events with predicate
* @param predicate - Filter predicate function
* @returns New filtered event stream
*/
filter(predicate: (event: any) => boolean): EventStream;
/**
* Throttle event stream
* @param delay - Throttle delay in milliseconds
* @returns New throttled event stream
*/
throttle(delay: number): EventStream;
/**
* Debounce event stream
* @param delay - Debounce delay in milliseconds
* @returns New debounced event stream
*/
debounce(delay: number): EventStream;
/**
* Map events to new values
* @param mapper - Event mapping function
* @returns New mapped event stream
*/
map(mapper: (event: any) => any): EventStream;
/**
* Merge with another event stream
* @param stream - Stream to merge with
* @returns New merged event stream
*/
merge(stream: EventStream): EventStream;
/**
* Take events between start and end streams
* @param start - Start event stream
* @param end - End event stream
* @returns New between event stream
*/
between(start: EventStream, end: EventStream): EventStream;
/**
* Apply stream to target
* @param target - Target object or function
* @returns The event stream instance
*/
apply(target: any): EventStream;
}Built-in event type definitions and constants.
/** Mouse event types */
interface MouseEvents {
/** Mouse click */
click: 'click';
/** Mouse double click */
dblclick: 'dblclick';
/** Mouse down */
mousedown: 'mousedown';
/** Mouse up */
mouseup: 'mouseup';
/** Mouse move */
mousemove: 'mousemove';
/** Mouse enter */
mouseenter: 'mouseenter';
/** Mouse leave */
mouseleave: 'mouseleave';
/** Mouse over */
mouseover: 'mouseover';
/** Mouse out */
mouseout: 'mouseout';
/** Context menu */
contextmenu: 'contextmenu';
/** Mouse wheel */
wheel: 'wheel';
}
/** Touch event types */
interface TouchEvents {
/** Touch start */
touchstart: 'touchstart';
/** Touch end */
touchend: 'touchend';
/** Touch move */
touchmove: 'touchmove';
/** Touch cancel */
touchcancel: 'touchcancel';
}
/** Keyboard event types */
interface KeyboardEvents {
/** Key down */
keydown: 'keydown';
/** Key up */
keyup: 'keyup';
/** Key press */
keypress: 'keypress';
}
/** Window event types */
interface WindowEvents {
/** Window resize */
resize: 'resize';
/** Window scroll */
scroll: 'scroll';
/** Page load */
load: 'load';
/** Before unload */
beforeunload: 'beforeunload';
}
/** View event types */
interface ViewEvents {
/** View resize */
'view:resize': 'view:resize';
/** View render */
'view:render': 'view:render';
/** View update */
'view:update': 'view:update';
}
/** Timer event types */
interface TimerEvents {
/** Timer tick */
timer: 'timer';
}Utility functions for common interaction patterns.
/**
* Configure hover behavior for marks
* @param view - View instance
* @param hoverSet - Encoding set for hover state
* @param leaveSet - Encoding set for leave state
* @returns Configured view
*/
function configureHover(view: View, hoverSet?: string, leaveSet?: string): View;
/**
* Configure tooltip behavior
* @param view - View instance
* @param tooltipHandler - Custom tooltip handler
* @returns Configured view
*/
function configureTooltip(view: View, tooltipHandler?: TooltipHandler): View;
/**
* Configure selection behavior
* @param view - View instance
* @param selectionConfig - Selection configuration
* @returns Configured view
*/
function configureSelection(view: View, selectionConfig: SelectionConfig): View;
type TooltipHandler = (handler: any, event: MouseEvent, item: Item, value: any) => void;
interface SelectionConfig {
/** Selection type */
type: 'point' | 'interval' | 'multi';
/** Selection fields */
fields?: string[];
/** Selection encoding */
encodings?: string[];
/** Selection signal binding */
bind?: SelectionBinding;
/** Selection initialization */
init?: any;
/** Selection event trigger */
on?: string | EventSelector[];
/** Selection clear trigger */
clear?: string | EventSelector[];
/** Selection resolve mode */
resolve?: 'global' | 'union' | 'intersect';
}
interface SelectionBinding {
/** Input element type */
input: string;
/** Element binding properties */
[prop: string]: any;
}Event debugging and inspection utilities.
/**
* Enable event debugging for a view
* @param view - View instance
* @param options - Debug options
*/
function enableEventDebugging(view: View, options?: EventDebugOptions): void;
/**
* Log event information
* @param event - Event to log
* @param context - Additional context information
*/
function logEvent(event: Event, context?: any): void;
/**
* Trace event propagation
* @param view - View instance
* @param eventType - Event type to trace
*/
function traceEvents(view: View, eventType: string): void;
interface EventDebugOptions {
/** Log all events */
logAll?: boolean;
/** Event types to log */
types?: string[];
/** Include event data */
includeData?: boolean;
/** Custom log function */
logger?: (message: string, data?: any) => void;
}import { View, parseSelector } from "vega";
// Add click listener
view.addEventListener('click', (event, item) => {
if (item && item.datum) {
console.log('Clicked on:', item.datum);
}
});
// Add hover listeners
view.addEventListener('mouseover', (event, item) => {
if (item) {
console.log('Hovering:', item.datum);
}
});
view.addEventListener('mouseout', (event, item) => {
console.log('Mouse left item');
});// Listen to signal changes
view.addSignalListener('selectedCategory', (name, value) => {
console.log(`Signal ${name} changed to:`, value);
// Update other visualizations or UI
updateRelatedCharts(value);
});
// Listen to multiple signals
['filter', 'sort', 'groupBy'].forEach(signalName => {
view.addSignalListener(signalName, (name, value) => {
console.log(`Configuration ${name}:`, value);
});
});// Monitor data changes
view.addDataListener('sales', (name, changeset) => {
console.log(`Dataset ${name} changed:`, {
inserted: changeset.insert.length,
removed: changeset.remove.length,
modified: changeset.modify.length
});
// Trigger external updates
if (changeset.insert.length > 0) {
notifyNewData(changeset.insert);
}
});import { parseSelector } from "vega";
// Parse complex selector
const selectors = parseSelector([
'rect:click',
'symbol:mouseover',
'@legend:click',
'window:resize'
]);
selectors.forEach(selector => {
console.log('Parsed selector:', selector);
});
// Use with view
view.addEventListener('rect:click', (event, item) => {
console.log('Rectangle clicked:', item.datum);
});
// Mark-specific events
view.addEventListener('symbol:dblclick', (event, item) => {
console.log('Symbol double-clicked:', item.datum);
});import { EventStream } from "vega";
// Create event stream
const clickStream = view.events('view', 'click');
// Filter and transform events
const filteredClicks = clickStream
.filter(event => event.item && event.item.mark.marktype === 'rect')
.throttle(100); // Throttle to max 10 clicks per second
// Apply to signals
filteredClicks.apply((event) => {
view.signal('lastClicked', event.item.datum.id).run();
});
// Merge multiple streams
const mouseEvents = view.events('view', 'mousemove')
.merge(view.events('view', 'click'));
mouseEvents.apply((event) => {
view.signal('mouseActivity', Date.now()).run();
});// Hover highlighting
view.addEventListener('mouseover', (event, item) => {
if (item && item.mark.marktype === 'rect') {
view.signal('hoveredItem', item.datum.id).run();
}
});
view.addEventListener('mouseout', (event, item) => {
view.signal('hoveredItem', null).run();
});
// Click selection
let selectedItems = new Set();
view.addEventListener('click', (event, item) => {
if (item) {
const id = item.datum.id;
if (event.shiftKey) {
// Multi-select with Shift
if (selectedItems.has(id)) {
selectedItems.delete(id);
} else {
selectedItems.add(id);
}
} else {
// Single select
selectedItems.clear();
selectedItems.add(id);
}
view.signal('selectedItems', Array.from(selectedItems)).run();
}
});// Custom tooltip handler
const customTooltip = (handler, event, item, value) => {
if (item && item.datum) {
const tooltip = document.getElementById('custom-tooltip');
tooltip.innerHTML = `
<strong>${item.datum.name}</strong><br>
Value: ${item.datum.value}<br>
Category: ${item.datum.category}
`;
tooltip.style.left = event.pageX + 10 + 'px';
tooltip.style.top = event.pageY + 10 + 'px';
tooltip.style.display = 'block';
}
};
// Hide tooltip on mouse out
view.addEventListener('mouseout', () => {
document.getElementById('custom-tooltip').style.display = 'none';
});
view.tooltip(customTooltip);// Keyboard navigation
view.addEventListener('keydown', (event) => {
const currentSelection = view.signal('selectedIndex');
const dataLength = view.data('dataset').length;
switch (event.key) {
case 'ArrowUp':
event.preventDefault();
view.signal('selectedIndex',
Math.max(0, currentSelection - 1)).run();
break;
case 'ArrowDown':
event.preventDefault();
view.signal('selectedIndex',
Math.min(dataLength - 1, currentSelection + 1)).run();
break;
case 'Enter':
event.preventDefault();
// Trigger selection action
const selectedData = view.data('dataset')[currentSelection];
console.log('Selected:', selectedData);
break;
}
});// Throttled mouse tracking
let lastMouseUpdate = 0;
const MOUSE_THROTTLE = 16; // ~60fps
view.addEventListener('mousemove', (event) => {
const now = Date.now();
if (now - lastMouseUpdate > MOUSE_THROTTLE) {
lastMouseUpdate = now;
view.signal('mouseX', event.x)
.signal('mouseY', event.y)
.run();
}
});
// Debounced search
let searchTimeout;
const SEARCH_DEBOUNCE = 300;
view.addSignalListener('searchQuery', (name, value) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
// Perform search operation
const filtered = performSearch(value);
view.data('searchResults', filtered).run();
}, SEARCH_DEBOUNCE);
});// Responsive resize handling
view.addResizeListener((width, height) => {
console.log(`View resized to ${width}x${height}`);
// Update responsive breakpoints
const isSmall = width < 600;
view.signal('isSmallScreen', isSmall).run();
// Adjust layout parameters
if (isSmall) {
view.signal('fontSize', 10)
.signal('padding', 20)
.run();
} else {
view.signal('fontSize', 12)
.signal('padding', 40)
.run();
}
});
// Window event handling
window.addEventListener('beforeunload', (event) => {
// Save view state before page unload
const state = view.getState();
localStorage.setItem('viewState', JSON.stringify(state));
});
// Restore state on load
window.addEventListener('load', () => {
const savedState = localStorage.getItem('viewState');
if (savedState) {
view.setState(JSON.parse(savedState));
}
});// Event delegation for dynamic content
view.addEventListener('click', (event, item) => {
if (!item) return;
// Delegate based on mark type
switch (item.mark.marktype) {
case 'rect':
handleBarClick(item);
break;
case 'symbol':
handlePointClick(item);
break;
case 'text':
handleLabelClick(item);
break;
default:
console.log('Unhandled mark type:', item.mark.marktype);
}
});
function handleBarClick(item) {
console.log('Bar clicked:', item.datum);
// Drill down or filter logic
}
function handlePointClick(item) {
console.log('Point clicked:', item.datum);
// Detail view logic
}
function handleLabelClick(item) {
console.log('Label clicked:', item.datum);
// Edit or info logic
}Install with Tessl CLI
npx tessl i tessl/npm-vega