Get drag and drop working with d3

This is the first of two posts explaining the basics of drag and drop.

Want the second post? Here it is!.


In this post we’ll start to become familiar with the module d3-drag.

This module is a useful and flexible one that has many applications. Spending some time understanding the basics of this module can pay large benefits.

The best way to learn is through examples - so we’re going to create a simple example to see what exactly is happening.

This simple example is just creating a bunch of circles and then adding code to drag them around.

The end product is based on this example and can be found here. It’s a bit less schmick than the original but hopefully a bit simpler to pick up.

Setting up Link to heading

Most of our d3 examples start the same way. We create a basic HTML page and create a SVG element inside it.

<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

//d3 code goes here 

</script>

Next we create some circles to play with. We’re going to create 50 green circles at random positions.

Creating these is a three step process:

  • First use the d3.range() function to create a vector of integers from 0 to 49. This is done through d3.range(50)
  • Use JavaScript’s map function to call a function on each of these integers. For each value in the array the function will return an object with a random x attribute and a random y attribute. These are our circle centres.
  • Create SVG circles with the circle centres from the previous step by taking advantage of data binding.

One thing to note is that we should really make sure that the circles instantiate fully inside the svg element we’ve created for them, and not halfway off the border. This will require a bit of fiddling around with the position code until we’re confident it’s right.

Here’s one solution:

//create some circles at random points on the screen

//create 50 circles of radius 20
//specify centre points randomly through the map function 
radius = 20;
var circle_data = d3.range(50).map(function() {
    return{
        x : Math.round(Math.random() * (width - radius * 2 ) + radius),
        y : Math.round(Math.random() * (height - radius * 2 ) + radius)
    }; 
}); 

//add svg circles 
var circles = d3.select("svg")
	.append("g")
	.attr("class", "circles")
	.selectAll("circle")
        .data(circle_data)
        .enter()
        .append("circle")
        .attr("cx", function(d) {return(d.x)})
        .attr("cy", function(d) {return(d.y)})
        .attr("r", radius)
        .attr("fill", "green");

Adding drag capabilities Link to heading

There’s a few things involved in dragging objects around.

First we have to create a “drag handler”, a function that handles the logistics of dragging objects. This is sometimes called a drag behaviour function and is created by using the d3.drag() function.

You then apply the drag handler to objects. These objects will then have drag behaviour functions attached to them that the drag handler defines.

I’ll explain further.

The drag handler has a number of functions contained within it that you have to supply.

Each one of these functions triggers when certain conditions are met or when certain events occur. Event listeners look out for these events and trigger the associated functions when the events occur.

The drag handler has three event listeners associated with it. These are as follows:

  • start: triggered when you click on the object
  • drag: triggered while you hold down the mouse button
  • end: triggered when you release the mouse

So since all we’re interested in is making circles move when you drag them, then the only function we need to provide to the drag handler is something for the “drag” event listener.

For our example we don’t care about the start or end drag listeners. Normally you do, but here we don’t.

So what goes in our function? Remember, this function gets called when we’re dragging something around. We’ll put our function inside the drag.on() function to run whenever the drag event listener is triggered.

Well, we’d want something to update the position of the object so that it gets dragged around with the cursor. How do we do this?

Turns out when you start dragging the circles around, a d3.event object is created. Or more specifically, when any event listener is triggered a d3.event object is created.

The d3.event object has x and y attributes attached to it that represent where the cursor is at that point. So by setting the centre of the circle we’re dragging to where the d3.event object’s x and y coordinates are, we can make the circle follow around the cursor.

//create drag handler with d3.drag()
//only interested in "drag" event listener, not "start" or "end"        
var drag_handler = d3.drag()
    .on("drag", function(d) {
          d3.select(this)
            .attr("cx", d.x = d3.event.x  )
            .attr("cy", d.y = d3.event.y  );
            });

Anyway, that’s done.

All we need to do now is apply the drag handler to the circles:

//apply the drag_handler to our circles 
drag_handler(circles);

All finished! You can check out the working link here.


Hope you found that useful! Click here to view to the rest of the force directed graph series.