Skip to content

D3.js - Dragging API

The D3 Dragging API (d3-drag) provides a convenient way to add drag-and-drop interactions to your SVG or HTML elements. It handles mouse and touch events to allow users to move elements around.

If you’re using the full D3 bundle, d3-drag is included. For modular projects, you’d install and import it:

// With npm or yarn
npm install d3-drag
// yarn add d3-drag
// In your JavaScript
import { drag } from 'd3-drag';
// d3-selection is also usually needed
import { select } from 'd3-selection';

If using D3 via a CDN:

<script src="https://d3js.org/d3.v7.min.js"></script>

Key methods related to the D3 Drag API:

This function creates a new drag behavior. This behavior is then applied to a D3 selection.

const dragBehavior = d3.drag();

Applies the drag behavior to the specified D3 selection. This is typically done using selection.call(dragBehavior).

// Example: Make all circles with class 'draggable' respond to dragging
d3.selectAll("circle.draggable")
.call(d3.drag().on("start drag end", handleDragEvent));
function handleDragEvent(event, d) {
// 'event' is the D3 drag event object
// 'd' is the datum bound to the dragged element
if (event.type === "start") {
// console.log("Drag started!", event.subject);
} else if (event.type === "drag") {
// d3.select(this).attr("cx", event.x).attr("cy", event.y);
} else if (event.type === "end") {
// console.log("Drag ended!");
}
}

Sets the container accessor, which determines the coordinate system for drag events (event.x, event.y). The container function is invoked for each event, passed the current event and datum, and this context is the dragged DOM element. It should return the DOM element whose coordinate system is used. Defaults to the parent of the dragged element for SVG, or the owner SVG element. For HTML elements, it’s often the parent. For dragging elements within a Canvas, you’d typically set the container to the canvas element itself.

dragBehavior.container(function() { return this.parentNode; });
// Or for canvas:
dragBehavior.container(function() { return canvasElement; });

Sets an event filter function. The drag behavior will only start if the filter function returns true. The filter is passed the current native event (e.g., mousedown, touchstart) and the datum d. this is the DOM element. The default filter is (event) => !event.ctrlKey && !event.button, which ignores drags initiated with a Ctrl key press or a non-primary mouse button (e.g., right-click).

dragBehavior.filter(function(event) {
// Only allow dragging with the primary mouse button (button 0)
return event.button === 0;
});

Sets the subject accessor. The subject represents the thing being dragged. This function is called when a drag gesture starts (on mousedown or touchstart). It’s passed the native event and the datum d, and this is the DOM element. It should return an object representing the subject. This subject object will be available as event.subject in subsequent drag events (start, drag, end). The default subject is the datum d of the dragged element: (event, d) => d. If the subject has x and y properties, these are used to calculate event.dx and event.dy (the change in position since the last drag event).

dragBehavior.subject(function(event, d) {
// If 'd' is the datum, it might already have x and y, or you can define them here
return d.dataForDragging || { x: event.x, y: event.y, originalData: d };
});

Sets the maximum distance (in pixels) a pointer can move between mousedown/touchstart and mouseup/touchend to be considered a click (and thus not initiate a drag or trigger a ‘drag’ event). Defaults to 0. This helps distinguish between clicks and drags.

Attaches an event listener for the specified typenames. typenames is a string of one or more event types, separated by spaces (e.g., “start drag end”). Valid event types are:

  • start: Fired when a drag gesture starts (after mousedown/touchstart and filter passes).
  • drag: Fired when the pointer moves during an active drag gesture.
  • end: Fired when the drag gesture ends (on mouseup/touchend).

The listener function is called with (event, d), where event is the D3 drag event object (see below) and d is the datum of the dragged element. this context within the listener is the dragged DOM element.

A utility function to prevent the default browser behavior for drag-and-drop and text selection on the specified window. Call this if native drag interactions interfere with D3 drag.

Re-enables default drag-and-drop and text selection if previously disabled by d3.dragDisable. If noclick is true, it also suppresses click events that might follow the mouseup.

When a drag event listener is called, it receives a D3 event object with the following useful properties:

  • target: The drag behavior instance that dispatched the event.
  • type: The type of drag event as a string: “start”, “drag”, or “end”.
  • subject: The current drag subject, as defined by drag.subject().
  • x: The new x-coordinate of the pointer, relative to the container.
  • y: The new y-coordinate of the pointer, relative to the container.
  • dx: The change in x-coordinate since the last drag event (for “drag” events). Calculated based on event.subject.x if available.
  • dy: The change in y-coordinate since the last drag event (for “drag” events). Calculated based on event.subject.y if available.
  • identifier: The string “mouse” for mouse events, or the touch identifier for touch events.
  • active: The number of active drag gestures (useful for multi-touch scenarios, though d3-drag primarily handles single pointer drags).
  • sourceEvent: The underlying native browser event (e.g., MouseEvent, TouchEvent).

Example of a simple draggable circle:

<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.draggable { cursor: grab; }
.dragging { cursor: grabbing; fill: steelblue; }
</style>
</head>
<body>
<svg width="400" height="300" style="border:1px solid #ccc;">
<circle class="draggable" cx="50" cy="50" r="20" fill="orange"></circle>
</svg>
<script>
function dragstarted(event, d) {
d3.select(this).raise().classed("dragging", true);
}
function dragged(event, d) {
// Update the circle's subject (its data object `d` if it has x,y)
// Or directly update the element's attributes
// If `d` is the subject and has x,y, D3 updates them internally for dx/dy calculation
// event.subject.x = event.x;
// event.subject.y = event.y;
d3.select(this).attr("cx", event.x).attr("cy", event.y);
}
function dragended(event, d) {
d3.select(this).classed("dragging", false);
}
const dragBehavior = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
d3.selectAll("circle.draggable").call(dragBehavior);
</script>
</body>
</html>

In this example, the circle’s cx and cy attributes are directly updated using event.x and event.y. For more complex scenarios, you might update the event.subject’s x and y properties if your rendering logic depends on the data object rather than direct attribute manipulation in the drag handler.