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
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
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
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.
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.
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:
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
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
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
viewer.on('viewpoint:changed', ({ viewpoint, position }) => {
console.log(`Viewpoint changed to ${viewpoint}`);
});Controls Events
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
// 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
viewer.once('surface:added', ({ surfaceId }) => {
console.log(`First surface added: ${surfaceId}`);
});Event Flow Example
// 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);
}
});