Question

I am new to D3.js, and I want to work with graph layouts. I have this demo set up with a very simple graph obeying the force-directed layout built into D3.

http://jsfiddle.net/ewG95/

What I want to do is be able to turn off the graph layout algorithm (say, when the user presses space) and then still be able to drag nodes around, and turn the force-directed layout back on later.

Right now I have it set up to stop the force-directed layout upon hitting space:

d3.select("body")
   .on("keydown", function() { 
     if (d3.event.keyCode == 32) { 
        if (forceActive) {force.stop();} else {force.resume();} 
     } 
   });

The problem is that every time you drag a node it restarts the force-layout. I've narrowed it down to (essentially) that the call method on a node is being bound to the force layout's drag method.

i.e. this piece of the linked code:

var node = svg.selectAll(".node")
     .data(graph.nodes)
   .enter().append("circle")
     ...
     .call(force.drag);

I imagine I may have to do two things:

  1. Write a custom "drag" method that moves a node and the edges associated to a single node.
  2. Rebind the node's call method to it as needed (does this even make sense? Would that replace the bound method? And what does force.drag really do?).

I want to know if this has been done before, and how to do it (at all, possibly not using my guess as to how to do it).

Was it helpful?

Solution

First, on your explicit question: what does the call() method do?

In d3, call is just a convenience function to turn other functions inside out. Doing

node.call( force.drag );

Is actually just the same as doing

force.drag( node );

The only difference is that the call method on a selection always returns the selection so you can chain other methods to it.

So now your question is, what does the force.drag( selection ) do? It creates the event listeners that handle the mouse and touch events for the drag behaviour. Those event listeners update the position of the node data object, but they also restart the force layout.

If you don't want the default force-layout drag behaviour, just don't call the force.drag method on your selection.

As far as being able to customize the drag behaviour so that it doesn't restart the force layout, it doesn't look like you can do it with force.drag (not without changing the source code, anyway). However, you can access most of the drag-related functionality by creating your own drag behaviour object and binding it to the nodes.

Then, in your drag event handler you would update the x and y positions of the node data object, and redraw the node and any links attached to it. If your graph isn't too big, you can do this by re-calling your tick function to redraw all the elements to match their data; if your graph is big, you might want to store references to the actual elements in the data objects so you can quickly find the correct links.

Some examples on using drag behaviours (without force layout) to get you started:

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top