CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vega

A declarative visualization grammar for creating interactive data visualizations through JSON specifications.

Pending
Overview
Eval results
Files

events.mddocs/

Event Handling

Vega's event system provides comprehensive interaction support with event parsing, selection handling, listener management, and integration with the dataflow system for reactive visualizations.

Capabilities

Event Selector Parsing

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 Event Handling

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 System

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;
}

Event Types

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';
}

Interaction Utilities

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

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;
}

Usage Examples

Basic Event Listeners

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');
});

Signal Listeners

// 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);
  });
});

Data Change Listeners

// 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);
  }
});

Complex Event Selectors

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);
});

Event Streams

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();
});

Interaction Patterns

// 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 Event Handling

// 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 Interactions

// 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;
  }
});

Event Throttling and Debouncing

// 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);
});

Window and View Events

// 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

// 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

docs

data-loading.md

dataflow.md

events.md

expressions.md

index.md

parsing.md

scales.md

scenegraph.md

statistics.md

time.md

utilities.md

view.md

tile.json