Question

I have a d3 force-directed graph that contains a group of nodes:

var node = vis.selectAll("g.node")
              .data(json.nodes)
              .enter().append("svg:g")
              .attr("class", "node")
              .attr("id", function(d) { return "node" + d.index; })
              .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
              .call(force.drag);

This renders fine.

I'd now like to translate (move) these nodes 200 pixels to the right when I show a detail div with the id detail_container.

I tried the following, but nothing happens to the nodes when I show detail_container (other than showing the detail div):

$('#detail_container').fadeIn('slow', function() {
    d3.selectAll("g.node").attr("transform", function(d) { return "translate(200, 0)"; });
});

The d3.selectAll("g.node") statement contains the node data, which I can confirm by looking at the data in the console:

console.log(d3.selectAll("g.node"));

As another approach, I attached the zoom/pan behavior to my graph:

var vis = d3.select("#position")
            .append("svg:svg")
            .attr("width", w)
            .attr("height", h)
            .attr("pointer-events", "all")
            .append("svg:g")
            .call(d3.behavior.zoom().on("zoom", redraw))
            .append("svg:g");

This works fine. However, this interacts with the mouse, not with events that occur in my program, so is there a programmatic way to call the zoom/pan behavior, so that I can accomplish the translation I want?

Was it helpful?

Solution

If you just want to move the nodes to the right, then you can accomplish that by setting a transform; only use the zoom behavior if you want interactive panning and zooming.

It's not clear to me why nothing is happening when you select the nodes and set a transform, but one problem I see is that by setting the transform attribute on the nodes directly, you're overwriting the x & y position set by the force layout. So, you probably want to put all of the nodes inside another G element, so that you can offset all of them by changing the transform on the container rather than the individual nodes. The DOM would look like this:

<g class="nodes">
  <g class="node" transform="translate(42,128)">
    <circle r="2.5">
    <text>First Node</text>
  </g>
  <g class="node" transform="translate(64,501)">
    <circle r="2.5">
    <text>Second Node</text>
  </g>
  …
</g>

So then, if you want to shift all the nodes to the right by 200px, it's just:

d3.select(".nodes").attr("transform", "translate(200,0)");

And to revert the shift, remove the container's transform:

d3.select(".nodes").attr("transform", null);

If you wanted to be really fancy, another way of achieving this effect would be to change the center of gravity of the force layout, and then reheat the graph when you show or hide the details DIV. This will cause the nodes to smoothly shift into a new position to make way for the details container. You can see an example of this in Shan Carter's visualization, Four Ways to Slice Obama’s 2013 Budget Proposal: click the "Types of Spending" button and watch the circles recenter. Or, see this example on custom gravity.

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