javascript - Is there a way to automatically open a D3 collapsed tree node by node (for nodes that have children)? -


i know if there way let nodes children automatically open once page loaded. did digging, wasn't able find information on that. sample tree code below:

<!doctype html>  <meta charset="utf-8">  <style>   .node {    cursor: pointer;  }   .overlay{    background-color:#fff;  }   .node text {    font-size: 0.85em;  font-family: 'roboto condensed', sans-serif;    font-weight: 500;}     .link {    fill: none;    stroke:#bcbcbc;    stroke-width:1px;  }     .templink {    fill: none;    stroke: red;    stroke-width: 3px;  }    .ghostcircle.show{    display:block;  }    .ghostcircle, .activedrag .ghostcircle{    display: none;  }  </style> <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>  <script src="http://d3js.org/d3.v3.min.js"></script>   <head>                  <link href='https://fonts.googleapis.com/css?family=amaranth' rel='stylesheet' type='text/css'>                  <link href='https://fonts.googleapis.com/css?family=playfair+display' rel='stylesheet' type='text/css'>    			  <link href='https://fonts.googleapis.com/css?family=josefin+sans' rel='stylesheet' type='text/css'>                  <link href='https://fonts.googleapis.com/css?family=fjord+one' rel='stylesheet' type='text/css'>                  <link href='https://fonts.googleapis.com/css?family=lateef' rel='stylesheet' type='text/css'>                  <link href='https://fonts.googleapis.com/css?family=open+sans' rel='stylesheet' type='text/css'>                  <link href='https://fonts.googleapis.com/css?family=lato' rel='stylesheet' type='text/css'>                  <link href='https://fonts.googleapis.com/css?family=roboto+condensed' rel='stylesheet' type='text/css'>                  <link href='https://fonts.googleapis.com/css?family=source+sans+pro' rel='stylesheet' type='text/css'>                  <link href='https://fonts.googleapis.com/css?family=ubuntu' rel='stylesheet' type='text/css'>                   </head>   <body>    <div id="tree-container"></div>    <script>   var treedata = {"name": "parent", "_children": [{"name": "child 1", "_children": [{"name": "grandchild 1"}, {"name": "grandchild 2"}]}, {"name": "child 2", "_children": [{"name": "grandchild 3"}, {"name": "grandchild 4"}]}]};   // calculate total nodes, max label length      var totalnodes = 0;      var maxlabellength = 0;      // variables drag/drop      var selectednode = null;      var draggingnode = null;      // panning variables      var panspeed = 200;      var panboundary = 20; // within 20px edges pan when dragging.      // misc. variables      var = 0;      var duration = 750;      var root;        // size of diagram      var viewerwidth = $(document).width();      var viewerheight = $(document).height();        var tree = d3.layout.tree()          .size([viewerheight, viewerwidth]);        // define d3 diagonal projection use node paths later on.      var diagonal = d3.svg.diagonal()          .projection(function(d) {              return [d.y, d.x];          });        // recursive helper function performing setup walking through nodes        function visit(parent, visitfn, childrenfn) {          if (!parent) return;            visitfn(parent);            var children = childrenfn(parent);          if (children) {              var count = children.length;              (var = 0; < count; i++) {                  visit(children[i], visitfn, childrenfn);              }          }      }        // call visit function establish maxlabellength      visit(treedata, function(d) {          totalnodes++;          maxlabellength = math.max(d.name.length, maxlabellength);        }, function(d) {          return d.children && d.children.length > 0 ? d.children : null;      });          // sort tree according node names        function sorttree() {          tree.sort(function(a, b) {              return b.name.tolowercase() < a.name.tolowercase() ? 1 : -1;          });      }      // sort tree incase json isn't in sorted order.      sorttree();      function pan(domnode, direction) {          var speed = panspeed;          if (pantimer) {              cleartimeout(pantimer);              translatecoords = d3.transform(svggroup.attr("transform"));              if (direction == 'left' || direction == 'right') {                  translatex = direction == 'left' ? translatecoords.translate[0] + speed : translatecoords.translate[0] - speed;                  translatey = translatecoords.translate[1];              } else if (direction == 'up' || direction == 'down') {                  translatex = translatecoords.translate[0];                  translatey = direction == 'up' ? translatecoords.translate[1] + speed : translatecoords.translate[1] - speed;              }              scalex = translatecoords.scale[0];              scaley = translatecoords.scale[1];              scale = zoomlistener.scale();              svggroup.transition().attr("transform", "translate(" + translatex + "," + translatey + ")scale(" + scale + ")");              d3.select(domnode).select('g.node').attr("transform", "translate(" + translatex + "," + translatey + ")");              zoomlistener.scale(zoomlistener.scale());              zoomlistener.translate([translatex, translatey]);              pantimer = settimeout(function() {                  pan(domnode, speed, direction);              }, 50);          }      }        // define zoom function zoomable tree        function zoom() {          svggroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");      }      // define zoomlistener calls zoom function on "zoom" event constrained within scaleextents      var zoomlistener = d3.behavior.zoom().scaleextent([0.1, 3]).on("zoom", zoom);        function initiatedrag(d, domnode) {          draggingnode = d;          d3.select(domnode).select('.ghostcircle').attr('pointer-events', 'none');          d3.selectall('.ghostcircle').attr('class', 'ghostcircle show');          d3.select(domnode).attr('class', 'node activedrag');            svggroup.selectall("g.node").sort(function(a, b) { // select parent , sort path's              if (a.id != draggingnode.id) return 1; // not hovered element, send "a"              else return -1; // hovered element, bring "a" front          });          // if nodes has children, remove links , nodes          if (nodes.length > 1) {              // remove link paths              links = tree.links(nodes);              nodepaths = svggroup.selectall("path.link")                  .data(links, function(d) {                      return d.target.id;                  }).remove();              // remove child nodes              nodesexit = svggroup.selectall("g.node")                  .data(nodes, function(d) {                      return d.id;                  }).filter(function(d, i) {                      if (d.id == draggingnode.id) {                          return false;                      }                      return true;                  }).remove();          }            // remove parent link          parentlink = tree.links(tree.nodes(draggingnode.parent));          svggroup.selectall('path.link').filter(function(d, i) {              if (d.target.id == draggingnode.id) {                  return true;              }              return false;          }).remove();            dragstarted = null;      }        // define basesvg, attaching class styling , zoomlistener      var basesvg = d3.select("#tree-container").append("svg")          .attr("width", viewerwidth)          .attr("height", viewerheight)          .attr("class", "overlay")          .call(zoomlistener);      // define drag listeners drag/drop behaviour of nodes.      draglistener = d3.behavior.drag()          .on("dragstart", function(d) {              if (d == root) {                  return;              }              dragstarted = true;              nodes = tree.nodes(d);              d3.event.sourceevent.stoppropagation();              // it's important suppress mouseover event on node being dragged. otherwise absorb mouseover event , underlying node not detect d3.select(this).attr('pointer-events', 'none');          })          .on("drag", function(d) {              if (d == root) {                  return;              }              if (dragstarted) {                  domnode = this;                  initiatedrag(d, domnode);              }                // coords of mouseevent relative svg container allow panning              relcoords = d3.mouse($('svg').get(0));              if (relcoords[0] < panboundary) {                  pantimer = true;                  pan(this, 'left');              } else if (relcoords[0] > ($('svg').width() - panboundary)) {                    pantimer = true;                  pan(this, 'right');              } else if (relcoords[1] < panboundary) {                  pantimer = true;                  pan(this, 'up');              } else if (relcoords[1] > ($('svg').height() - panboundary)) {                  pantimer = true;                  pan(this, 'down');              } else {                  try {                      cleartimeout(pantimer);                  } catch (e) {                    }              }                d.x0 += d3.event.dy;              d.y0 += d3.event.dx;              var node = d3.select(this);              node.attr("transform", "translate(" + d.y0 + "," + d.x0 + ")");              updatetempconnector();          }).on("dragend", function(d) {              if (d == root) {                  return;              }              domnode = this;              if (selectednode) {                  // remove element parent, , insert new elements children                  var index = draggingnode.parent.children.indexof(draggingnode);                  if (index > -1) {                      draggingnode.parent.children.splice(index, 1);                  }                  if (typeof selectednode.children !== 'undefined' || typeof selectednode._children !== 'undefined') {                      if (typeof selectednode.children !== 'undefined') {                          selectednode.children.push(draggingnode);                      } else {                          selectednode._children.push(draggingnode);                      }                  } else {                      selectednode.children = [];                      selectednode.children.push(draggingnode);                  }                  // make sure node being added expanded user can see added node correctly moved                  expand(selectednode);                  sorttree();                  enddrag();              } else {                  enddrag();              }          });        function enddrag() {          selectednode = null;          d3.selectall('.ghostcircle').attr('class', 'ghostcircle');          d3.select(domnode).attr('class', 'node');          // restore mouseover event or won't able drag 2nd time          d3.select(domnode).select('.ghostcircle').attr('pointer-events', '');          updatetempconnector();          if (draggingnode !== null) {              update(root);              centernode(draggingnode);              draggingnode = null;          }      }        // helper functions collapsing , expanding nodes.        function collapse(d) {          if (d.children) {              d._children = d.children;              d._children.foreach(collapse);              d.children = null;          }      }        function expand(d) {          if (d._children) {              d.children = d._children;              d.children.foreach(expand);              d._children = null;          }      }        var overcircle = function(d) {          selectednode = d;          updatetempconnector();      };      var outcircle = function(d) {          selectednode = null;          updatetempconnector();      };        // function update temporary connector indicating dragging affiliation      var updatetempconnector = function() {          var data = [];          if (draggingnode !== null && selectednode !== null) {              // have flip source coordinates since did existing connectors on original tree              data = [{                  source: {                      x: selectednode.y0,                      y: selectednode.x0                  },                  target: {                      x: draggingnode.y0,                      y: draggingnode.x0                  }              }];          }          var link = svggroup.selectall(".templink").data(data);            link.enter().append("path")              .attr("class", "templink")              .attr("d", d3.svg.diagonal())              .attr('pointer-events', 'none');            link.attr("d", d3.svg.diagonal());            link.exit().remove();      };        // function center node when clicked/dropped node doesn't lost when collapsing/moving large amount of children.        function centernode(source) {          scale = zoomlistener.scale();          x = -source.y0;          y = -source.x0;          x = x * scale + viewerwidth / 2;          y = y * scale + viewerheight / 2;          d3.select('g').transition()              .duration(duration)              .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");          zoomlistener.scale(scale);          zoomlistener.translate([x, y]);      }        // toggle children function        function togglechildren(d) {          if (d.children) {              d._children = d.children;              d.children = null;          } else if (d._children) {              d.children = d._children;              d._children = null;          }          return d;      }        // toggle children on click.        function click(d) {          if (d3.event.defaultprevented) return; // click suppressed          d = togglechildren(d);          update(d);          centernode(d);      }     function update(source) {          // compute new height, function counts total children of root node , sets tree height accordingly.          // prevents layout looking squashed when new nodes made visible or looking sparse when nodes removed          // makes layout more consistent.          var levelwidth = [1];          var childcount = function(level, n) {                if (n.children && n.children.length > 0) {                  if (levelwidth.length <= level + 1) levelwidth.push(0);                    levelwidth[level + 1] += n.children.length;                  n.children.foreach(function(d) {                      childcount(level + 1, d);                  });              }          };         childcount(0, root);          var newheight = d3.max(levelwidth) * 40;          tree = tree.size([newheight, viewerwidth]);            // compute new tree layout.          var nodes = tree.nodes(root).reverse(),              links = tree.links(nodes);            // set widths between levels based on maxlabellength.          nodes.foreach(function(d) {              d.y = (d.depth * (maxlabellength * 30)); //maxlabellength * 10px              // alternatively keep fixed scale 1 can set fixed depth per level              // normalize fixed-depth commenting out below line              // d.y = (d.depth * 500); //500px per level.          });            // update nodes…          node = svggroup.selectall("g.node")              .data(nodes, function(d) {                  return d.id || (d.id = ++i);              });            // enter new nodes @ parent's previous position.          var nodeenter = node.enter().append("g")              .call(draglistener)              .attr("class", "node")              .attr("transform", function(d) {                  return "translate(" + source.y0 + "," + source.x0 + ")";              })              .on('click', click);            nodeenter.append("circle")              .attr('class', 'nodecircle')              .attr("r", 4.5)              .style("stroke-width", "1")              .style("stroke", "red")              .style("fill", function(d) {                  return d._children ? "#eeb4b4" : "#fff";              });                    // phantom node give mouseover in radius around          nodeenter.append("circle")              .attr("class", "ghostcircle")              .attr("r",30)              .attr("opacity", 0.2) // change 0 hide target area              .style("fill", "#eeb4b4")              .attr("pointer-events", "mouseover")              .on("mouseover", function(node) {                  overcircle(node);              })              .on("mouseout", function(node) {                  outcircle(node);              });   var textgroup = nodeenter.append( "g" );    var rects = textgroup.append( "rect" );    var texts = textgroup.append( "text" )                         .text( function( d ) { return d.name; } )                         .on( "mouseover", function( d ){                              d3.select( )                              .transition( )                              .duration( 100 )                              .attr( "fill", "red" );                         })                         .on( "mouseout", function( d ){                            d3.select( )                              .transition( )                              .duration( 100 )                              .attr( "fill", "black"  );                          })                         .each( function( d ) { d.width = this.getbbox( ).width; } )                         .attr( "text-anchor", function( d ) { return d.children || d._children ? "end" : "start"; })                         .attr( "x", function( d ) { return d.children || d._children ? -10 : 10; })                         .attr( "dy", "0.30em" )                         .style( "fill-opacity", 1e-6 );      rects.attr( "x" , function( d ) { return d.children || d._children ? -10 - d.width: 10; })         .attr( "y", "-0.5em" )         .attr( "height" , "1em" )         .style( "fill", "#fff")         .style( "fill-opacity", 1e-6  )         .attr( "width" , function( d ) { return d.width; });              // update text reflect whether node has children or not.          node.select("text")              .attr("x", function(d) {                  return d.children || d._children ? -10 : 10;              })              .attr("text-anchor", function(d) {                  return d.children || d._children ? "end" : "start";              })              .text(function(d) {                  return d.name;              });            // change circle fill depending on whether has children , collapsed          node.select("circle.nodecircle")              .attr("r", 4.5)              .style("fill", function(d) {                  return d._children ? "#eeb4b4" : "#fff";              });               // transition nodes new position.          var nodeupdate = node.transition()              .duration(duration)              .attr("transform", function(d) {                  return "translate(" + d.y + "," + d.x + ")";              });            // fade text in          nodeupdate.select("text")              .style("fill-opacity", 1);          nodeupdate.select("rect")              .style("fill-opacity", 0.9);            // transition exiting nodes 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", 0);            nodeexit.select("text")              .style("fill-opacity", 0);          nodeexit.select("rect")              .style("fill-opacity", 0);            // update links…          var link = svggroup.selectall("path.link")              .data(links, function(d) {                  return d.target.id;              });            // enter new links @ parent"s previous position.          link.enter().insert("path", "g")              .attr("class", "link")              .attr("d", function(d) {                  var o = {                      x: source.x0,                      y: source.y0                  };                  return diagonal({                      source: o,                      target: o                  });              });            // transition links new position.          link.transition()              .duration(duration)              .attr("d", diagonal);            // transition exiting nodes 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 old positions transition.          nodes.foreach(function(d) {              d.x0 = d.x;              d.y0 = d.y;          });      }        // append group holds nodes , zoom listener can act upon.      var svggroup = basesvg.append("g");        // define root      root = treedata;      root.x0 = viewerheight / 2;      root.y0 = 0;        // layout tree , center on root node.      update(root);      centernode(root);  //});    </script> </body>  </html>

yes! in fiddle added following function iterate on children , call toggle on parent has children recursively.

function open(mynode) {     togglechildren(mynode);     if (mynode.children) {         mynode.children.foreach(function (j) {             open(j);//open child node recursively         })     } } open(treedata); 

full working code here.


Comments