or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdevent-handling.mdindex.mdtransform-operations.mdzoom-behavior.md
tile.json

event-handling.mddocs/

Event Handling

Comprehensive event system for handling zoom interactions and responding to zoom state changes.

Capabilities

Event Listener Registration

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

ZoomEvent Object

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

Event Types

Different types of zoom events and their timing.

type ZoomEventType = "start" | "zoom" | "end";

Start Events

Fired when a zoom gesture begins.

Triggers:

  • Mouse button press
  • First touch contact
  • First wheel event
  • Programmatic zoom start

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

Zoom Events

Fired during zoom transform changes.

Triggers:

  • Mouse movement during drag
  • Wheel events
  • Touch movement
  • Programmatic transform changes

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

End Events

Fired when a zoom gesture completes.

Triggers:

  • Mouse button release
  • Touch end
  • Wheel idle timeout (150ms)
  • Programmatic zoom completion

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

Event Context and Data

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

Advanced Event Patterns

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