alexmloveless
alexmloveless

Reputation: 246

force graph in d3.js - disappearing nodes

I'm trying to construct a directed force graph in d3.js that has a click listener on it that changes the the underlying data and redraws the graph. I belive I'm following Mr. Bostock's update pattern, but the issue that I'm having is that when I run the update triggered by the click listener the nodes disappear off the bottom of the screen leaving the links and labels behind.

This update seems to run, updates the existing nodes (turns them green in this case) then ignores the "enter" and "exit" sections (which is the desired behaviour) then hits the tick() function which send the nodes south.

I can get this working by removing the "g" tag on the node and thus decoupling the labels and the node, which is obviously not desirable.

I can't help feeling I'm missing something obvious! Or perhaps I should be tacking this a different way?

Here's the code:

var width = 960,
    height = 500,
    links,
    nodes,
    root;

var force = d3.layout.force()
    .size([width, height])
    .charge(-200)
    .linkDistance(50)
    .on("tick", tick);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

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

d3.json("test.json", function(json) {
  root = json;
  update();
});

function update() {
  nodes = root.nodes
  links = root.links

  // Restart the force layout.
  force
      .nodes(nodes)
      .links(links)
      .start();

    svg.append("svg:defs").append("marker")
        .attr("id", "end")
        .attr("refX", 15)
        .attr("refY", 2)
        .attr("markerWidth", 6)
        .attr("markerHeight", 4)
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M 0,0 V 4 L8,2 Z");

  // Update the links…
  //link = link.data(links, function(d) { return d.target.name; });
  link = link.data(links)

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

  // Enter any new links.
  link.enter().insert("svg:line", ".node")
      .attr("class", "link")
      .attr("marker-end", "url(#end)")
      .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; });

  // Update the nodes…
  node = svg.selectAll("g").select(".node").data(nodes, function(d) { return d.name; });


  node.style("fill", "green")

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

  // Enter any new nodes.
  node.enter().append("g")
      .append("svg:circle")
      .attr("class", "node")
      .attr("id", function(d) {return "node" + d.index; })
      .attr("r", 12)
      .style("fill", "#BBB")
      .on("click", click)
      .call(force.drag);

  node.append("svg:text")
      .attr("dx", 16)
      .attr("dy", ".15em")
      .attr("class", "nodelabel")
      .text(function(d) { return d.name });
}

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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

}


function click(d) {
  if (!d3.event.defaultPrevented) {

    // DO ANYTHING
    update()
  }
}

and the contents of test.json is:

{
  "nodes": [
    {"name" : "John"},
    {"name" : "Alison"},
    {"name" : "Phil"},
    {"name" : "Jim"},
    {"name" : "Jane"},
    {"name" : "Mary"},
    {"name" : "Joe"}
  ],
  "links": [
    {"source":  1, "target":  0},
    {"source":  2, "target":  0},
    {"source":  3, "target":  0},
    {"source":  4, "target":  0},
    {"source":  5, "target":  1},
    {"source":  6, "target":  1}
  ]
}

Upvotes: 2

Views: 2756

Answers (1)

alexmloveless
alexmloveless

Reputation: 246

OK, so I figured out the problem. When I was selecting the nodes to update I was selecting the nodes (that is, the elements that had the class "node") and they were being updated:

node = svg.selectAll("g").select(".node").data(nodes, function(d) { return d.name; });

Then in the tick function I was updating those nodes:

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

so that was quite right, however, the nodes were encapsulated in the "g" tag along with the text label, but the tick() function was only acting on the node. The fix was to force the transform attribute in tick() to update the whole group rather than just the node:

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

All works now!

Upvotes: 1

Related Questions