Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collapsible graph in D3.JS: Multiple root points

I am looking to create a Collapsible graph using d3js as this one http://bl.ocks.org/d3noob/8375092 but I would like to add many root points (Top Level_1 and Top Level_2 in the example below)

var treeData = [
  {
    "name": "Top Level_1",
    "parent": "null",
    "children": [
      {
        "name": "Level 2: A",
        "parent": "Top Level",
        "children": [
          {
            "name": "Son of A",
            "parent": "Level 2: A"
          },
          {
            "name": "Daughter of A",
            "parent": "Level 2: A"
          }
        ]
      },
      {
        "name": "Level 2: B",
        "parent": "Top Level"
      }
    ],
"name": "Top Level_2",
        "parent": "null",
        "children": [
          {
            "name": "Level 2_2: A",
            "parent": "Top Level",
            "children": [
              {
                "name": "Son of A_2",
                "parent": "Level 2_2: A"
              },
              {
                "name": "Daughter of A_2",
                "parent": "Level 2_2: A"
              }
            ]
          },
          {
            "name": "Level 2_2: B",
            "parent": "Top Level_2"
          }
        ]

      }
    ];

This example does not work. How could it be possible to create many seperate collapsible trees in the same graph?

like image 257
Jack Lehman Avatar asked Sep 05 '18 15:09

Jack Lehman


1 Answers

Hierarchical graphs can only have a single root; there have been requests for d3 to allow multiply-rooted trees, but so far all have been denied. To create a tree with multiple roots, then, we can either create two separate trees, or we can fake having two separate trees by hiding the root node.

Your data structure was invalid (it's a good idea to use a code formatter to standardise the indentation make sure that you don't make errors with complex data structures like this), so I added a root node and edited the existing nodes to correctly represent a hierarchical structure.

The simplest way to hide the root node is to use CSS. To do this, I gave each node, link, and text item a class, level-n, based on its depth in the tree (e.g. level-0, level-1, etc.). Hiding the root and the links from the root is then a matter of setting the elements with class level-0 to have opacity: 0.

Using the code from the d3noob collapsible tree, we can find the relevant parts of the update function code and add in the new css class:

  // add the class to the whole `g` element
  var nodeEnter = node.enter().append("g")
    .attr("class", function(d){
      return 'node level-'+d.depth; // add the node depth
    })
    .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
    .on("click", click);

Do the same with the links:

  // each link has two nodes, the source and the target; we want to
  // label our links with the source node (the parent node)
  link.enter().insert("path", "g")
      .attr("class", function(d){
        return 'link level-' + d.source.depth})
      .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
      });

And in the page CSS, set the css class level-0 to have opacity 0:

  .level-0 {
    opacity: 0;
  }

Since we now have an empty space on the left where the missing node was, the whole chart can be shifted over to the left. You may want to alter the tree dimensions accordingly.

The code now looks like this:

var treeData = [{
  name: "null",
  children: [{
    "name": "Top Level_1",
    "parent": "null",
    "children": [{
      "name": "Level 2: A",
      "parent": "Top Level_1",
      "children": [{
        "name": "Son of A",
        "parent": "Level 2: A"
      }, {
        "name": "Daughter of A",
        "parent": "Level 2: A"
      }]
    },
    {
      "name": "Level 2: B",
      "parent": "Top Level"
    }],
  }, {
    "name": "Top Level_2",
    "parent": "null",
    "children": [
    {
      "name": "Level 2_2: A",
      "parent": "Top Level_2",
      "children": [
      {
        "name": "Son of A_2",
        "parent": "Level 2_2: A"
      },
      {
        "name": "Daughter of A_2",
        "parent": "Level 2_2: A"
      }]
    },
    {
      "name": "Level 2_2: B",
      "parent": "Top Level_2"
    }]
  }]
}];

// ************** Generate the tree diagram  *****************
var margin = {
    top: 20,
    right: 120,
    bottom: 20,
    left: 120
  },
  width = 960 - margin.right - margin.left,
  height = 500 - margin.top - margin.bottom;

var i = 0,
  duration = 750,
  root;

var tree = d3.layout.tree()
  .size([height, width]);

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

var svg = d3.select("body").append("svg")
  .attr("width", width + margin.right + margin.left)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + (margin.left - 120) + "," + margin.top + ")");

root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;

update(root);

d3.select(self.frameElement).style("height", "500px");

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root).reverse(),
    links = tree.links(nodes);

  // Normalize for fixed-depth.
  nodes.forEach(function(d) {
    d.y = d.depth * 120;
  });

  // Update the nodes…
  var node = svg.selectAll("g.node")
    .data(nodes, function(d) {
      return d.id || (d.id = ++i);
    });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
    .attr("class", function(d) {
      return 'node level-' + d.depth;
    })
    .attr("transform", function(d) {
      return "translate(" + source.y0 + "," + source.x0 + ")";
    })
    .on("click", click);

  nodeEnter.append("circle")
    .attr("r", 1e-6)
    .style("fill", function(d) {
      return d._children ? "lightsteelblue" : "#fff";
    });

  nodeEnter.append("text")
    .attr('class', function(d) {
      return 'node-text level-' + d.level
    })
    .attr("x", function(d) {
      return d.children || d._children ? -13 : 13;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", function(d) {
      return d.children || d._children ? "end" : "start";
    })
    .text(function(d) {
      return d.name;
    })
    .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + d.y + "," + d.x + ")";
    });

  nodeUpdate.select("circle")
    .attr("r", 10)
    .style("fill", function(d) {
      return d._children ? "lightsteelblue" : "#fff";
    });

  nodeUpdate.select("text")
    .style("fill-opacity", 1);

  // Transition exiting nodes to the parent's new position.
  var nodeExit = node.exit().transition()
    .duration(duration)
    .attr("transform", function(d) {
      return "translate(" + source.y + "," + source.x + ")";
    })
    .remove();

  nodeExit.select("circle")
    .attr("r", 1e-6);

  nodeExit.select("text")
    .style("fill-opacity", 1e-6);

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

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
    .attr("class", function(d) {
      return 'link level-' + d.source.depth
    })
    .attr("d", function(d) {
      var o = {
        x: source.x0,
        y: source.y0
      };
      return diagonal({
        source: o,
        target: o
      });
    });

  // Transition links to their new position.
  link.transition()
    .duration(duration)
    .attr("d", diagonal);

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
    .duration(duration)
    .attr("d", function(d) {
      var o = {
        x: source.x,
        y: source.y
      };
      return diagonal({
        source: o,
        target: o
      });
    })
    .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  update(d);
}

View on bl.ocks.org

like image 141
i alarmed alien Avatar answered Nov 02 '22 22:11

i alarmed alien