---
title: 'D3.js Tutorial: Drag and Drop'
url: https://blog.octoperf.com/d3js-tutorial-drag-and-drop/
date: '2018-04-18'
authors:
- gpereira
categories:
- Development
tags:
- D3.js
- SVG
- Drag and Drop
- Data
description: Learn how to handle drag and drop using D3-drag to manipulate SVG graphics. _d3.drag()_ explained via code samples and live examples.
---

# D3.js Tutorial: Drag and Drop

<link rel="stylesheet" href="https://blog.octoperf.com/img/blog/d3-js-drag-and-drop-tutorial/style.css">
<script src="https://d3js.org/d3.v5.min.js"></script>

In our previous tutorial we explained [how to handle click events with D3.js to add, remove and select shapes in a SVG chart](https://blog.octoperf.com/d3js-tutorial-mouse-events-handling/).

Using the same map example **this tutorial explains how to handle drag and drop with D3.js**. To follow this guide you must know about:

* D3.js installation,
* D3.js selectors,
* and D3.js data binding.

All these concepts are explained in our [D3 getting started guide](https://blog.octoperf.com/d3js-getting-started-a-first-tutorial/).


It uses the same SVG sample as our previous example:

```html
<div class="svg-container">
<svg id="click" xmlns="http://www.w3.org/2000/svg" width="600" height="300">
    <defs>
        <g id="pointer" transform="scale(0.8)">
            <path d="M0-1c-14.5-25.6-14.5-25.7-14.5-33.8c0-8.1,6.5-14.6,14.5-14.6s14.5,6.6,14.5,14.6C14.5-26.7,14.5-26.6,0-1z"></path>
            <path d="M0-49c7.7,0,14,6.3,14,14.1c0,8,0,8.1-14,32.8c-14-24.7-14-24.9-14-32.8C-14-42.7-7.7-49,0-49 M0-50c-8.3,0-15,6.8-15,15.1 S-15-26.5,0,0c15-26.5,15-26.5,15-34.9S8.3-50,0-50L0-50z"></path>
        </g>
    </defs>
</svg>
</div>
```

It's a simple SVG with a pointer shape defined. Drag and drop events will be added to `use` elements that reference this pre-defined shape (`<g id="pointer"/>`). If you need more information about this, please consult [our previous tutorial](https://blog.octoperf.com/d3js-tutorial-mouse-events-handling/#a-simple-svg-map).

## Drag and drop shapes

### The D3-drag library

To handle drag and drop events easily, D3.js comes with the D3-drag library. In the example bellow you can drag the pointer across the map:

<div class="svg-container">
    <svg id="dnd-no-data-fail" xmlns="http://www.w3.org/2000/svg" width="600" height="300">
        <defs>
            <g id="pointer" transform="scale(0.8)">
                <path d="M0-1c-14.5-25.6-14.5-25.7-14.5-33.8c0-8.1,6.5-14.6,14.5-14.6s14.5,6.6,14.5,14.6C14.5-26.7,14.5-26.6,0-1z"></path>
                <path d="M0-49c7.7,0,14,6.3,14,14.1c0,8,0,8.1-14,32.8c-14-24.7-14-24.9-14-32.8C-14-42.7-7.7-49,0-49 M0-50c-8.3,0-15,6.8-15,15.1 S-15-26.5,0,0c15-26.5,15-26.5,15-34.9S8.3-50,0-50L0-50z"></path>
            </g>
        </defs>
    </svg>
    <script src="https://blog.octoperf.com/img/blog/d3-js-drag-and-drop-tutorial/no-data-fail.js"></script>
</div>

For this first code sample, we first `append` a static pointer shape to the SVG:

```javascript
var svg = d3.select("svg");
svg.append("use")
    .attr("href", "#pointer")
    .attr("x", 50)
    .attr("y", 50)
    .attr("fill", "#039BE5")
    .attr("stroke", "#039BE5")
    .attr("stroke-width", "1px");

var dragHandler = d3.drag()
    .on("drag", function () {
        d3.select(this)
            .attr("x", d3.event.x)
            .attr("y", d3.event.y);
    });

dragHandler(svg.selectAll("use"));
```

Then we create a drag handler function using `d3.drag` and use it on our _use_ elements (`dragHandler(svg.selectAll("use"))`). **Handling drag and drop with D3.js is simple as that!**

_dragHandler_ describes the behavior on `drag` events. It takes a callback function, that should handle the transformation of the current selection.
In this example we simply update the current pointer coordinates (x,y) using the event position.

There is one minor flaw with this behavior: If the user grabs the pointer shape by somewhere else than the pointy end, it jumps to the mouse position.
That's because at the start of the drag motion, **the pointer origin (its lower tip) is moved to the mouse position. Not very user friendly!**

### Playing with delta

To fix this we must **compute the delta between the mouse position and the shape position when the drag motion starts**:

<div class="svg-container">
    <svg id="dnd-no-data" xmlns="http://www.w3.org/2000/svg" width="600" height="300">
        <defs>
            <g id="pointer" transform="scale(0.8)">
                <path d="M0-1c-14.5-25.6-14.5-25.7-14.5-33.8c0-8.1,6.5-14.6,14.5-14.6s14.5,6.6,14.5,14.6C14.5-26.7,14.5-26.6,0-1z"></path>
                <path d="M0-49c7.7,0,14,6.3,14,14.1c0,8,0,8.1-14,32.8c-14-24.7-14-24.9-14-32.8C-14-42.7-7.7-49,0-49 M0-50c-8.3,0-15,6.8-15,15.1 S-15-26.5,0,0c15-26.5,15-26.5,15-34.9S8.3-50,0-50L0-50z"></path>
            </g>
        </defs>
    </svg>
    <script src="https://blog.octoperf.com/img/blog/d3-js-drag-and-drop-tutorial/no-data.js"></script>
</div>

The drag behavior can handle both "drag" events and "start" events:

```javascript
var svg = d3.select("svg");
svg.append("use")
    .attr("href", "#pointer")
    .attr("x", 50)
    .attr("y", 50)
    .attr("fill", "#039BE5")
    .attr("stroke", "#039BE5")
    .attr("stroke-width", "1px");

var deltaX, deltaY;

var dragHandler = d3.drag()
    .on("start", function () {
        var current = d3.select(this);
        deltaX = current.attr("x") - d3.event.x;
        deltaY = current.attr("y") - d3.event.y;
    })
    .on("drag", function () {
        d3.select(this)
            .attr("x", d3.event.x + deltaX)
            .attr("y", d3.event.y + deltaY);
    });

dragHandler(svg.selectAll("use"));
```

Here, using the `.on("start", callback)` we compute the delta between the event position _d3.event.(x|y)_ and the origin of the shape.
The origin of our pointer shape is returned by `.attr("x|y")` method calls.

Then during the `drag` event, we can apply this delta to avoid the shape "jump": `d3.event.x + deltaX`.

It works pretty well, but that's a bit tedious. There might be a better way to do it?

## Drag and dropping data-bound elements

### A simpler use-case

Of course there is! Don't forget that **D3.js is a library for manipulating SVG and DOM elements based on data.** Here we were completely missing the data binding part!

Let's try again with (x,y) coordinated bound to the element data this time:

```javascript
var svg = d3.select("svg");
svg.selectAll("use")
    .data([{
        x: 50,
        y: 50
    }])
    .enter()
    .append("use")
    .attr("href", "#pointer")
    .attr("x", function (d) {
        return (d.x)
    })
    .attr("y", function (d) {
        return (d.y)
    })
    .attr("fill", "#039BE5")
    .attr("stroke", "#039BE5")
    .attr("stroke-width", "1px");

var dragHandler = d3.drag()
    .on("drag", function () {
        d3.select(this)
            .attr("x", d3.event.x)
            .attr("y", d3.event.y);
    });

dragHandler(svg.selectAll("use"));
```

Here when creating the pointer shape, we bind its coordinates `.data([{x: 50,y: 50}])` to affect it using attribute callbacks `.attr("x", function (d) {return (d.x)}).attr("y", function (d) {return (d.y)})`.

**When using data-binding, D3-drag automatically handle the drag events coordinates delta:**

<div class="svg-container">
    <svg id="dnd-data-fail" xmlns="http://www.w3.org/2000/svg" width="600" height="300">
        <defs>
            <g id="pointer" transform="scale(0.8)">
                <path d="M0-1c-14.5-25.6-14.5-25.7-14.5-33.8c0-8.1,6.5-14.6,14.5-14.6s14.5,6.6,14.5,14.6C14.5-26.7,14.5-26.6,0-1z"></path>
                <path d="M0-49c7.7,0,14,6.3,14,14.1c0,8,0,8.1-14,32.8c-14-24.7-14-24.9-14-32.8C-14-42.7-7.7-49,0-49 M0-50c-8.3,0-15,6.8-15,15.1 S-15-26.5,0,0c15-26.5,15-26.5,15-34.9S8.3-50,0-50L0-50z"></path>
            </g>
        </defs>
    </svg>
    <script src="https://blog.octoperf.com/img/blog/d3-js-drag-and-drop-tutorial/data-fail.js"></script>
</div>

Works like a charm, doesn't it? Well ... not that much. If you drag the shape more than once, it jumps back to its original position.

### Updating the data

Obviously that's because **we forgot to update the shape's data in the _drag_ event handler**:

```javascript
var svg = d3.select("#dnd-data-fail");
svg.selectAll("use")
[...]

var dragHandler = d3.drag()
    .on("drag", function (d) {
        d3.select(this)
            .attr("x", d.x = d3.event.x)
            .attr("y", d.y = d3.event.y);
    });

dragHandler(svg.selectAll("use"));
```

The `.on("drag")` callback function takes the parameter `d`: the selection data. We simply update it along with the shape (x,y) attributes: `d3.select(this).attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);`.

Now its perfect:

<div class="svg-container">
    <svg id="dnd-data" xmlns="http://www.w3.org/2000/svg" width="600" height="300">
        <defs>
            <g id="pointer" transform="scale(0.8)">
                <path d="M0-1c-14.5-25.6-14.5-25.7-14.5-33.8c0-8.1,6.5-14.6,14.5-14.6s14.5,6.6,14.5,14.6C14.5-26.7,14.5-26.6,0-1z"></path>
                <path d="M0-49c7.7,0,14,6.3,14,14.1c0,8,0,8.1-14,32.8c-14-24.7-14-24.9-14-32.8C-14-42.7-7.7-49,0-49 M0-50c-8.3,0-15,6.8-15,15.1 S-15-26.5,0,0c15-26.5,15-26.5,15-34.9S8.3-50,0-50L0-50z"></path>
            </g>
        </defs>
    </svg>
    <script src="https://blog.octoperf.com/img/blog/d3-js-drag-and-drop-tutorial/data.js"></script>
</div>

Good! But what happened to our `click` events?

## D3.js click and drag events combined

Now it's time to **combine our drag and drop example with our previous "mouse events" tutorial**. In the next live demo you can:

* Click on the map to create a new pointer,
* Click while CTRL is pressed on a pointer to remove it,
* Click on a pointer to (un)select it,
* And of course drag and drop pointers across the map.

<div class="svg-container">
    <svg id="dnd-final" xmlns="http://www.w3.org/2000/svg" width="600" height="300">
        <defs>
            <g id="pointer" transform="scale(0.8)">
                <path d="M0-1c-14.5-25.6-14.5-25.7-14.5-33.8c0-8.1,6.5-14.6,14.5-14.6s14.5,6.6,14.5,14.6C14.5-26.7,14.5-26.6,0-1z"></path>
                <path d="M0-49c7.7,0,14,6.3,14,14.1c0,8,0,8.1-14,32.8c-14-24.7-14-24.9-14-32.8C-14-42.7-7.7-49,0-49 M0-50c-8.3,0-15,6.8-15,15.1 S-15-26.5,0,0c15-26.5,15-26.5,15-34.9S8.3-50,0-50L0-50z"></path>
            </g>
        </defs>
    </svg>
    <script src="https://blog.octoperf.com/img/blog/d3-js-drag-and-drop-tutorial/final.js"></script>
</div>

Here we bind data to the shape using `datum()` instead of `data()`. But otherwise, it's the same as the previous code sample put together with a `click` handler:

```javascript
var svg = d3.select("svg");
svg.on("click", function () {
    var mouse = d3.mouse(this);

    var pointer = svg
        .append("use")
        .datum({x: mouse[0], y: mouse[1], selected: false})
        .attr("id", "currentPointer")
        .attr("href", "#pointer")
        .attr("transform", "translate(" + mouse[0] + "," + mouse[1] + ")scale(0)")
        .attr("fill", "#039BE5")
        .attr("stroke", "#039BE5")
        .attr("stroke-width", "1px");

    pointer
        .transition()
        .duration(500)
        .attr("transform", "translate(" + mouse[0] + "," + mouse[1] + ") scale(1)")
        .attr("x", mouse[0])
        .attr("y", mouse[1])
        .attr("transform", "scale(1)");

    pointer.on("click", function () {
        if (d3.event.ctrlKey) {
            pointer.transition()
                .duration(500)
                .attr("transform", "translate(" + pointer.attr("x") + "," + pointer.attr("y") + ") scale(0)")
                .remove();
        } else {
            var datum = pointer.datum();
            if (pointer.datum().selected) {
                datum.selected = false;
                pointer
                    .datum(datum)
                    .transition()
                    .duration(500)
                    .attr("stroke", "#039BE5")
                    .attr("stroke-width", "1px");
            } else {
                datum.selected = true;
                pointer
                    .datum(datum)
                    .transition()
                    .duration(500)
                    .attr("stroke", "#455A64")
                    .attr("stroke-width", "3px");
            }
        }
        d3.event.stopPropagation();
    });

    var dragHandler = d3.drag()
        .on("drag", function (d) {
            d3.select(this)
                .attr("x", d.x = d3.event.x)
                .attr("y", d.y = d3.event.y);
        });

    dragHandler(pointer);
});
```

**The data binding done during the shape creation `.datum({x: mouse[0], y: mouse[1], selected: false})` must be preserved** when it is (un)selected:

```javascript
 var datum = pointer.datum();
    if (pointer.datum().selected) {
        datum.selected = false;
        pointer
            .datum(datum)
```

We only update the `selected` state of the shape and keep its coordinates.

That's it for this tutorial. Feel free to check our other guides about D3.js!
