Need help modifying d3.js Collapsible Force Layout


I am modifying the d3.js Collapsible Force Layout where the nodes are given as circles.

http://bl.ocks.org/mbostock/1062288

I changed it to a group g and attached the circle into the g. The problem is that in the tick function when I change from

node.attr("cx", function(d) { return d.x; })
  .attr("cy", function(d) { return d.y; });

to

node.attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });

the nodes are all displaced.

I have changed the reference to node so node refers to the group g in the second case.

I have created force layouts and have done this a lot of times but never faced this problem. Is it because of the d3.tree.layout? I don't know. Please help.

Initial nodes

Displaced nodes

I have tried a few more times with other svg elements like text and rect and I found out that the problem occurs only when there is circle involved and I need to give attributes like cx and cy to text and rect even though these elements don't have these attributes, to make them work.

So please help someone. Totally confused.

Code::

    var link = svg.selectAll(".link"),
        node = svg.selectAll(".node");

    link = link.data(force.links());

    // Exit any old links.
    link.exit().remove();

    // Enter any new links.
    link.enter().insert("line",".node")
      .attr("class", "link")
      .attr("x1", function(d,i) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });

    // Update the nodes…
    node = node.data(force.nodes(), function(d) { return d.id; }).style("fill", color);

    // Exit any old nodes.
    node.exit().remove();

    // Enter any new nodes.
    node.enter().append("g")
      .attr("class", "node")
      .attr("title",function(d) { return d.name || d.layout; })
      .on("click", click)
      .call(force.drag);

      node.append("circle")
        .attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; })
        .attr("r", function(d) { return 4.5; })
        .style("fill", color);

      node.append("text")
        .attr("x", function(d) { return d.x; })
        .attr("y", function(d) { return d.y; })
        .attr("fill","red") 
        .attr("stroke","red")           
        .style("font-size","9px")
        .on("click",click)
        .text(function(d){return d.name})
        .call(force.drag);


function tick() {
  link.attr("x1", function(d) { return d.source.x; })
      .attr("y1", function(d) { return d.source.y; })
      .attr("x2", function(d) { return d.target.x; })
      .attr("y2", function(d) { return d.target.y; });

  // node.attr("cx", function(d) { return d.x; })
  //     .attr("cy", function(d) { return d.y; });

  node.attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });
}

Here's the code that generates the faulty graph.


Answers:


The problem is that you're setting the coordinates twice; on the actual elements and on the g containing them:

node.append("circle")
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", function(d) { return 4.5; })
    .style("fill", color);

(similarly for the text elements) and

node.attr("transform", function(d) {
  return "translate(" + d.x + "," + d.y + ")";
});

This causes the offset. To fix, just set one of them (the translation on the g elements):

node.append("circle")
    .attr("r", function(d) { return 4.5; })
    .style("fill", color);
node.append("text")
    .attr("fill","red") 
    .attr("stroke","red")
    // etc