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
Post a Comment