Force directed graph: dragable
This is the second of two posts explaining the basics of drag and drop. You can find the first post here.
Lets take the minimal working example as a template.
We’re going to make the same graph dragable so that we can interact with it. Click on the picture above to see what this looks like!
You also need to know how drag and drop works before diving into this article. Check out this post if you need a refresher.
We’re starting with a template from here. Ready?
Drag Link to heading
First thing to do is create a drag handler using d3.drag()
.
There’s three event listeners that we’re looking out for: start, drag and end.
Start triggers when you click on the circle. Drag triggers when you move your mouse while clicked on the circle. End triggers when you let go of your mouse.
Start and end event listeners will trigger only once. Drag will trigger many times and for as long as you’re dragging for.
Each of these will fire of a d3.event. These have some interesting fields:
d3.event.active
: takes a value of 1 when the circle is being dragged and 0 otherwised3.event.type
: string field that indicates what kind of event it is. Can bestart
,drag
orend
d3.event.dx
: change in x-position the circle has moved since the last event was fired. Makes most sense fordrag
.d3.event.dy
: change in y-position the circle has moved since the last event was fired. Makes most sense fordrag
.
Anyway, lets create our drag handler. We’ll tell it to look out for functions drag_start
, drag_drag
and drag_end
when the respective event listeners are triggered.
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
Lets create these functions now:
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
Each of these functions takes in an input d
, which is the node that’s being dragged. These look like this.
Questions remain. What do these functions do?
drag_start
Link to heading
Honestly I’m not sure why we check the if statement regarding d3.event.active
. It seems to work without it as well, if we took away the if statement and just had simulation.alphaTarget
. (UPDATE: see https://stackoverflow.com/questions/42605261/d3-event-active-purpose-in-drag-dropping-circles for why).
We set the fixed position of the dragged node to be wherever it was when we’ve clicked the mouse.
If we don’t do this, the node will “float off” when we click on it. Any previous velocities the node had will still be there. Not good.
We also need to “reheat” the simulation. We do this by setting an alphaTarget
and then restarting the simulation.
The alphaTarget
controls how quickly the simulation returns to equilibrium. Lower values means that the simulation returns slower and higher values means that it returns quicker. Setting it below the minimum alpha of 0.01 means that the graph gets “stuck” and the nodes don’t update further. Not ideal.
drag_drag
Link to heading
Here we set the fixed position of the node to wherever the mouse currently is. Quite simple really.
drag_end
Link to heading
We set the fixed position of the node to null. This will allow the node to “spring back” to wherever the simulation puts it to achieve a stable state. You can set this to the current position of the mouse to allow “sticky nodes” - where your nodes stay stuck where you left them.
We also set the targetAlpha to 0 of the simulation. Since this is less than the default minimum alpha of 0.01 it means that the simulation will eventually stop. Setting this to a value higher than 0.01 ensures that the simulation will keep slowly ticking away.
One more thing Link to heading
We need to add the drag handler on. Do that with
drag_handler(node)
and then sit back and admire your finished work.
Hope you found that useful! Click here to view to the rest of the force directed graph series.