Chris Reichel
Chris Reichel

Reputation: 21

Visual Treelist with D3.js

I'm searching for a solution to make a D3.js hierarchical tree to a treelist, like in MindNode 2 (see: https://www.dropbox.com/s/6cjs1ma9n4yex3w/mindnode-treelist.png?dl=0).

As you see there are four different kinds of paths:

I found this example that seems like to be a good start to work on: http://bl.ocks.org/thehogfather/0e48ec486abbd5be17d7

My question is now: how should I continue now? I thought about appending a svg container for each node that gets the relevant paths based on the relevant conditions. Another solution would be one gigantic svg with all its paths - but that could be the wrong way, because - as you see in the image - some nodes have much more text that has to be wrapped inside of the li-element.

Any solutions? Open questions? Examples :-) ?

Upvotes: 1

Views: 1257

Answers (1)

Chris Reichel
Chris Reichel

Reputation: 21

After muddling through the code of this awesome example of @mbostck I found a solution and rewrote my code from treelist layout to the tree layout. But I've splitted the code in two parts: one is to build the list with ul and li HTML element. The other part is to draw the paths with just one big svg.

So the code of the first part was following:

  // This is my HTML container element
  var $treelistContainer = d3.select('#treelist');

  var tree = d3.layout.tree();

  var indent = 15,
      nodeTree = 0;
  var ul = $treelistContainer.append("ul").classed("treelist",true);

  var nodes = tree.nodes(data);

  var nodeEls = ul.selectAll("li.item").data(nodes);
  //list nodes
  var listEnter = nodeEls
    .enter()
      .append("li")
        .attr("class", function(d) {
          // set class to node and to leaf (for endpoints) or to root (for stem)
          var output = 'item'+(d.parent ? d.children ? '' : ' leaf' : ' root');

          // set class to even or to odd, based on its level;
          output += ((d.depth % 2) === 0 ? ' even' : ' odd');

          return output;
        })
        .attr("id", function(d,i){return "id"+i})
        .style("opacity", 1)
        .style("background-color", function(d) {
          return colorgrey(d.depth);
        })
        .append("span").attr("class", "value")
          .style("padding-left", function (d) {
            return 20 + d.depth * indent + "px";
          })
          .html(function (d) { return d.name; });

That builds the whole HTML list for the project. Now here comes the magic of the next part: you have to recalculate the x, y. I also used a value called nodeTree to give every hierarchy an own ID to choose a different color for it. So here's the code:

var nodeTree = 0;
var rootTop = d3.selectAll('li.item')
    .filter(function(d,i) {
      return i == 0;
    })
    .node()
      .getBoundingClientRect()
        .top;
nodes.forEach(function(n, i) {
  // Get position of li element
  var top = d3.selectAll('li.item')
    .filter(function(d2,i2) {
      return i2 == i;
    })
    .node()
      .getBoundingClientRect()
        .top;
  n.x = top - rootTop;//i * 38;
  n.y = n.depth * indent;
  if (n.depth == 1) nodeTree++;
  n.value = nodeTree;
});

Now I simply read out x, y and the value to calculate it's diagonal position. Before I have to build the container and calculate some other things

  var width = $treelistContainer.node().getBoundingClientRect().width,
      height = $treelistContainer.node().getBoundingClientRect().height,
      i = 0,
      id = 0,
      margin = {top: 20, right: 10, bottom: 10, left: 15};

  var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

  var svg = $treelistContainer
    .append("svg")
      .attr("width", width - margin.left - margin.right+"px")
      .attr("height", height+"px")
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var link = svg.selectAll("path.link")
    .data(tree.links(nodes))
      .enter()
        .insert("path", "g")
        .attr("class", "link")
        .attr("stroke", function(d) {

          // Setting the color based on the hierarchy
          console.log(d.target.value);
          return color(d.target.value);
        })
        .attr("d", function(d,i) {
          var source = {x: d.source.x, y: d.source.y};
          var target = {x: d.target.x, y: d.target.y};
          return diagonal({source: source, target: target});
        });

And here's how it looks like: https://www.dropbox.com/s/ysbszycoiost72t/result.png?dl=0

I will try to optimize it by changing the interpolation type, adding some little colorful node circles and so on.

Hope this will help someone.

Upvotes: 1

Related Questions