Comprehensive event system for handling zoom interactions and responding to zoom state changes.
Registers event listeners for zoom events.
/**
* Gets or sets event listeners for zoom events
* @param typenames - Event type names (space-separated)
* @param listener - Event listener function or null to remove (optional)
* @returns Current listener or ZoomBehavior for chaining
*/
on(typenames: string, listener?: EventListener | null): ZoomBehavior | EventListener;
type EventListener = (this: Element, event: ZoomEvent, d?: any) => void;Usage Examples:
import { select } from "d3-selection";
import { zoom } from "d3-zoom";
const svg = select("svg");
const zoomBehavior = zoom()
.on("start", function(event) {
console.log("Zoom started");
this.style.cursor = "grabbing";
})
.on("zoom", function(event) {
const { transform } = event;
svg.select("g").attr("transform", transform);
})
.on("end", function(event) {
console.log("Zoom ended");
this.style.cursor = "grab";
});
svg.call(zoomBehavior);
// Multiple listeners for same event
zoomBehavior
.on("zoom.update", updateVisualization)
.on("zoom.debug", logTransform);
// Remove specific listener
zoomBehavior.on("zoom.debug", null);
// Get current listener
const currentListener = zoomBehavior.on("zoom");Event object passed to zoom event listeners.
/**
* Zoom event object containing event details
*/
interface ZoomEvent {
/** The zoom behavior that dispatched this event */
readonly target: ZoomBehavior;
/** Event type: "start", "zoom", or "end" */
readonly type: "start" | "zoom" | "end";
/** Current zoom transform */
readonly transform: ZoomTransform;
/** Original DOM event that triggered the zoom */
readonly sourceEvent: Event;
}Usage Examples:
import { zoom } from "d3-zoom";
const zoomBehavior = zoom()
.on("zoom", function(event) {
const { target, type, transform, sourceEvent } = event;
console.log("Event type:", type); // "zoom"
console.log("Transform:", transform.toString());
console.log("Source event:", sourceEvent.type); // "wheel", "mousemove", etc.
console.log("Zoom behavior:", target === zoomBehavior); // true
// Apply transform
svg.select("g").attr("transform", transform);
});Different types of zoom events and their timing.
type ZoomEventType = "start" | "zoom" | "end";Fired when a zoom gesture begins.
Triggers:
Usage Examples:
import { zoom } from "d3-zoom";
const zoomBehavior = zoom()
.on("start", function(event) {
// Visual feedback
this.classList.add("zooming");
// Disable other interactions
this.style.pointerEvents = "none";
// Log interaction type
const { sourceEvent } = event;
if (sourceEvent.type === "mousedown") {
console.log("Pan started");
} else if (sourceEvent.type === "wheel") {
console.log("Zoom started");
} else if (sourceEvent.type === "touchstart") {
console.log("Touch interaction started");
}
});Fired during zoom transform changes.
Triggers:
Usage Examples:
import { zoom } from "d3-zoom";
import { select } from "d3-selection";
const svg = select("svg");
const zoomBehavior = zoom()
.on("zoom", function(event) {
const { transform, sourceEvent } = event;
// Apply transform to content
svg.select(".content").attr("transform", transform);
// Update scale-dependent elements
svg.selectAll(".scale-dependent")
.attr("stroke-width", 1 / transform.k);
// Update info display
select("#zoom-info")
.text(`Scale: ${transform.k.toFixed(2)}, X: ${transform.x.toFixed(0)}, Y: ${transform.y.toFixed(0)}`);
// Throttle expensive operations
if (sourceEvent && sourceEvent.type === "wheel") {
clearTimeout(this._updateTimeout);
this._updateTimeout = setTimeout(() => {
updateExpensiveVisualization(transform);
}, 100);
} else {
updateExpensiveVisualization(transform);
}
});Fired when a zoom gesture completes.
Triggers:
Usage Examples:
import { zoom } from "d3-zoom";
const zoomBehavior = zoom()
.on("end", function(event) {
// Remove visual feedback
this.classList.remove("zooming");
this.style.pointerEvents = "";
// Save zoom state
const { transform } = event;
localStorage.setItem("zoomState", JSON.stringify({
k: transform.k,
x: transform.x,
y: transform.y
}));
// Trigger expensive post-zoom operations
updateDataLayers(transform);
recalculateVisibleItems(transform);
});Understanding the context and data available in event handlers.
Usage Examples:
import { zoom } from "d3-zoom";
import { select, selectAll } from "d3-selection";
// Bind data to elements
const nodes = selectAll(".node")
.data(nodeData)
.call(zoom().on("zoom", function(event, d) {
// 'this' is the DOM element
// 'event' is the ZoomEvent
// 'd' is the bound data for this element
console.log("Element:", this);
console.log("Data:", d);
console.log("Transform:", event.transform);
// Apply transform with data-specific scaling
select(this)
.attr("transform", event.transform)
.select("circle")
.attr("r", d.baseRadius / event.transform.k);
}));
// Global zoom behavior
const globalZoom = zoom()
.on("zoom", function(event) {
// 'this' is the element zoom was applied to
// No bound data ('d' is undefined)
selectAll(".zoomable")
.attr("transform", event.transform);
});
select("svg").call(globalZoom);Common patterns for complex zoom event handling.
Debounced Events:
import { zoom } from "d3-zoom";
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const zoomBehavior = zoom()
.on("zoom", function(event) {
// Immediate updates
svg.select("g").attr("transform", event.transform);
})
.on("zoom.debounced", debounce(function(event) {
// Expensive operations
updateComplexVisualization(event.transform);
}, 250));State Management:
import { zoom } from "d3-zoom";
let zoomState = {
isZooming: false,
startTransform: null,
currentTransform: null
};
const zoomBehavior = zoom()
.on("start", function(event) {
zoomState.isZooming = true;
zoomState.startTransform = event.transform;
})
.on("zoom", function(event) {
zoomState.currentTransform = event.transform;
// Calculate zoom delta
const deltaK = event.transform.k / zoomState.startTransform.k;
const deltaX = event.transform.x - zoomState.startTransform.x;
const deltaY = event.transform.y - zoomState.startTransform.y;
console.log("Zoom delta:", { deltaK, deltaX, deltaY });
})
.on("end", function(event) {
zoomState.isZooming = false;
// Determine interaction type
const transform = event.transform;
const startTransform = zoomState.startTransform;
if (Math.abs(transform.k - startTransform.k) > 0.1) {
console.log("User zoomed");
} else if (Math.abs(transform.x - startTransform.x) > 5 ||
Math.abs(transform.y - startTransform.y) > 5) {
console.log("User panned");
}
});Event Delegation:
import { zoom } from "d3-zoom";
import { select } from "d3-selection";
// Single zoom behavior for multiple elements
const sharedZoom = zoom()
.on("zoom", function(event) {
const targetClass = this.className;
// Different behavior based on element
if (targetClass.includes("main-view")) {
// Main visualization
select(this).select("g").attr("transform", event.transform);
} else if (targetClass.includes("mini-map")) {
// Mini-map with inverted behavior
const invertedTransform = event.transform.scale(0.1);
select(this).select("g").attr("transform", invertedTransform);
}
});
// Apply to multiple elements
select(".main-view").call(sharedZoom);
select(".mini-map").call(sharedZoom);