D3.js Collapsible Force Layout: Links are not being generated


I'm trying to generate a collapsible force layout similar to this example: http://bl.ocks.org/mbostock/1062288:

enter image description here

The data being used to generate my force layout is a JSON object being sourced from an API. I am successfully able to traverse the data structure, and generate nodes that are appended to the SVG (both root nodes and their children).

However, the issue comes with linking the parent nodes to their children nodes. Links are not being generated, and my links variable is returning an empty array. I'm pretty sure that the issue has something to do with how the data is structured in my app compared to the example, so I'll post both my JS code and the data structure of each node.

Any help would be much appreciated! Thanks in advance!!

Here's a JS Fiddle with my code: http://jsfiddle.net/tmzjW/

Here is my JS code:

//Force Layout Code
var w = 607,
    h = 500,
    node,
    link,
    root;

var force = d3.layout.force()
    .on("tick", tick)
    .size([w, h]);

var vis = d3.select("#chart").append("svg:svg")
    .attr("width", w)
    .attr("height", h);

function initiateForceJS(currentURL) {
    //Generate the URL
    forceURL = currentURL + ".json?jsonp=?";
    $('#commentArea').show();
    $.getJSON(forceURL,handleRequest2);
    function handleRequest2(json) {
        //Set the root as the first object returned
        root = json[1]['data']['children'][0];
        update();
    }
}

function update() {
  nodes = flatten(root),
  links = d3.layout.tree()
            .sort(null)
            .children(function(d) {return (!d['replies']['data']['children']|| d['replies']['data']['children'].length === 0) ? null : d['replies']['data']['children'];})
            .links(nodes);

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

  // Update the links…
  link = vis.selectAll("line.link")
      .data(links, function(d) { return d.target.id; });

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

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

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

  // Enter any new nodes.
  node.enter().append("svg:circle")
      .attr("class", "node")
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })
      .attr("r", function(d) { return Math.sqrt(d.ups) || 4.5; })
      .style("fill", color)
      //.on("click", click)
      .call(force.drag);

  // Exit any old nodes.
  node.exit().remove();
  //This will add the name of the character to the node

  node.append("comment").text(function(d) { return d.body });
  //This will put the comment in the Comment Area div
  node.on("mouseover", function() {
    var currentNode = d3.select(this);
    var currentTitle = currentNode.select("comment").text();
    $('#commentArea').html('<p>' + currentTitle + '</p>')  
  });
} 


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; });
}

// Color leaf nodes orange, and packages white or blue.
function color(d) {
  return d._children ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
}
// Returns a list of all nodes under the root.
function flatten(root) {
  var nodes = [], i = 0;
  function recurse(node) {

    if (node['data']['replies'] != "" && node['kind'] != "more") {
        node['data']['replies']['data']['children'].forEach(recurse);
    }
    if (node['kind'] !="more") {
        //Add an ID value to the node starting at 1
        node.data.id = ++i;
        var comment = node.data;
        nodes.push(comment);
    }
  }
  recurse(root);
  return nodes;
}

Answers:


The problem is that hierarchy.links function does not observe the children accessor you set; it relies solely on the children property of nodes. This works in the original example because that example uses the standard hierarchical node structure (which a children array on each node). It doesn’t work in your case because you’re using a nonstandard structure (defined by a third-party API).

The links method doesn't observe the children accessor because when you invoke a hierarchy layout (such as the tree layout) on data, it populates the children property for you based on the children accessor you define; it maps your nonstandard input data to the standard form automatically. However, since you’re not actually using the hierarchy layout here, it has no opportunity to map the nodes to the standard format, and thus hierarchy.links returns no results—no nodes have a children array.

The other problem here is that your root node doesn't actually have any replies, so there are no children of the root node. (There’s root.data.replies.data.children, but not root.replies.data.children as your children accessor is defined.)

I think the simplest thing to do here would be to map your data to a simpler hierarchical structure, where the children are defined as a children array. You might be able to use the hierarchy layout to help with that, but given that you've already written a flatten function (or at least modified one), you're most of the way there anyway.