Skip to content

Events

SurfView.js uses an event system for communication between components. Both the viewer and surfaces emit events you can listen to.

Viewer Events

Surface Events

javascript
viewer.on('surface:added', ({ surfaceId, surface }) => {
  console.log(`Surface ${surfaceId} added`);
});

viewer.on('surface:removed', ({ surfaceId }) => {
  console.log(`Surface ${surfaceId} removed`);
});

viewer.on('surface:variant', ({ surfaceId, variant }) => {
  console.log(`Surface ${surfaceId} switched to variant ${variant}`);
});

Layer Events

javascript
viewer.on('layer:added', ({ surfaceId, layerId }) => {
  console.log(`Layer ${layerId} added to ${surfaceId}`);
});

viewer.on('layer:removed', ({ surfaceId, layerId }) => {
  console.log(`Layer ${layerId} removed from ${surfaceId}`);
});

viewer.on('layer:updated', ({ surfaceId, layerId, changes }) => {
  console.log(`Layer ${layerId} updated`, changes);
});

viewer.on('layer:colormap', ({ layerId, colormap }) => {
  console.log(`Colormap changed to ${colormap}`);
});

viewer.on('layer:intensity', ({ layerId, range }) => {
  console.log(`Intensity range changed to`, range);
});

viewer.on('layer:threshold', ({ layerId, threshold }) => {
  console.log(`Threshold changed to`, threshold);
});

viewer.on('layer:opacity', ({ layerId, opacity }) => {
  console.log(`Opacity changed to ${opacity}`);
});

Interaction Events

javascript
viewer.on('surface:click', (hit) => {
  if (hit.surfaceId && hit.vertexIndex !== null) {
    console.log(`Clicked ${hit.surfaceId} at vertex ${hit.vertexIndex}`);
    console.log(`Position:`, hit.point);
    console.log(`Normal:`, hit.normal);
  }
});

Hover Events

Emitted when the mouse hovers over a surface vertex. The visual hover crosshair is optional; the events themselves are emitted on mouse move.

javascript
viewer.on('vertex:hover', ({ surfaceId, vertexIndex, screenX, screenY }) => {
  if (surfaceId && vertexIndex !== null) {
    console.log(`Hovering ${surfaceId} vertex ${vertexIndex} at (${screenX}, ${screenY})`);
  } else {
    console.log('Hover left surface');
  }
});

This event is useful for wiring up sparkline tooltips with SparklineOverlay. See the Temporal Playback guide.

Parcel Interaction Events

When the hovered or clicked surface provides parcel metadata, the viewer emits parcel-native interaction events. These are intended for synchronizing external views such as parcel heatmaps, tables, or connectivity matrices.

javascript
viewer.on('parcel:hover', ({ parcelId, parcelLabel, atlasId, surfaceId }) => {
  if (parcelId === null) {
    heatmap.clearHover();
    return;
  }

  heatmap.setHoverParcel(parcelId);
  console.log(`Hover parcel ${parcelLabel} on ${surfaceId} (${atlasId})`);
});

viewer.on('parcel:click', ({ parcelId }) => {
  if (parcelId === null) {
    return;
  }

  heatmap.setSelectedParcel(parcelId);
});

parcel:hover is emitted with parcelId: null when the pointer leaves parcelized geometry, so external views can clear their hover state without listening to lower-level vertex events.

External views can also drive the viewer back through the same parcel state:

javascript
heatmap.onHoverParcel((parcelId) => {
  if (parcelId === null) {
    viewer.clearParcelHover();
    return;
  }

  viewer.setParcelHover('parcel-connectivity', parcelId);
});

heatmap.onSelectParcel((parcelId) => {
  if (parcelId === null) {
    viewer.clearParcelSelection();
    return;
  }

  viewer.setParcelSelection('parcel-connectivity', parcelId);
});

These methods use a parcelized surface's representative vertex internally, so external tools can synchronize hover and selection without synthesizing mouse events.

Annotation Events

javascript
viewer.on('annotation:added', ({ id, surfaceId, vertexIndex }) => {
  console.log(`Annotation ${id} added`);
});

viewer.on('annotation:moved', ({ id, vertexIndex }) => {
  console.log(`Annotation ${id} moved to vertex ${vertexIndex}`);
});

viewer.on('annotation:removed', ({ id }) => {
  console.log(`Annotation ${id} removed`);
});

viewer.on('annotation:activated', ({ id }) => {
  console.log(`Annotation ${id} activated`);
});

viewer.on('annotation:reset', () => {
  console.log('All annotations cleared');
});

Render Events

javascript
viewer.on('render:before', () => {
  // Called before each render
});

viewer.on('render:after', () => {
  // Called after each render
});

viewer.on('render:needed', () => {
  // Called when a render is requested
});

Viewpoint Events

javascript
viewer.on('viewpoint:changed', ({ viewpoint, position }) => {
  console.log(`Viewpoint changed to ${viewpoint}`);
});

Controls Events

javascript
viewer.on('controls:changed', ({ name, value }) => {
  console.log(`Control ${name} changed to ${value}`);
});

viewer.on('controls:error', ({ error }) => {
  console.error('Controls error:', error);
});

Removing Listeners

javascript
// Store the handler
const handler = (event) => console.log(event);

// Add listener
viewer.on('surface:click', handler);

// Remove listener
viewer.off('surface:click', handler);

One-time Listeners

javascript
viewer.once('surface:added', ({ surfaceId }) => {
  console.log(`First surface added: ${surfaceId}`);
});

Event Flow Example

javascript
// Track all layer changes
viewer.on('layer:updated', ({ surfaceId, layerId, changes }) => {
  // Log to analytics
  analytics.track('layer_updated', { surfaceId, layerId, ...changes });
});

// Sync state with React
viewer.on('layer:opacity', ({ layerId, opacity }) => {
  setLayerOpacity(prev => ({ ...prev, [layerId]: opacity }));
});

// Handle picking
viewer.on('surface:click', (hit) => {
  if (hit.vertexIndex !== null) {
    // Show data at clicked vertex
    const data = getDataAtVertex(hit.surfaceId, hit.vertexIndex);
    showTooltip(hit.point, data);
  }
});

Released under the MIT License.