2015-09-25 151 views
0

我想知道是否有一种方法让页面加载后有孩子的节点自动打开。我做了一些挖掘,但无法找到任何有关这方面的信息。我的样本树代码如下:有没有办法按节点自动打开D3折叠树节点(对于有子节点的节点)?

<!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'> 
 

 
\t \t \t <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 for drag/drop 
 
    var selectedNode = null; 
 
    var draggingNode = null; 
 
    // panning variables 
 
    var panSpeed = 200; 
 
    var panBoundary = 20; // Within 20px from edges will pan when dragging. 
 
    // Misc. variables 
 
    var i = 0; 
 
    var duration = 750; 
 
    var root; 
 

 
    // size of the diagram 
 
    var viewerWidth = $(document).width(); 
 
    var viewerHeight = $(document).height(); 
 

 
    var tree = d3.layout.tree() 
 
     .size([viewerHeight, viewerWidth]); 
 

 
    // define a d3 diagonal projection for use by the node paths later on. 
 
    var diagonal = d3.svg.diagonal() 
 
     .projection(function(d) { 
 
      return [d.y, d.x]; 
 
     }); 
 

 
    // A recursive helper function for performing some setup by walking through all nodes 
 

 
    function visit(parent, visitFn, childrenFn) { 
 
     if (!parent) return; 
 

 
     visitFn(parent); 
 

 
     var children = childrenFn(parent); 
 
     if (children) { 
 
      var count = children.length; 
 
      for (var i = 0; i < count; i++) { 
 
       visit(children[i], visitFn, childrenFn); 
 
      } 
 
     } 
 
    } 
 

 
    // Call visit function to 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 the tree according to the node names 
 

 
    function sortTree() { 
 
     tree.sort(function(a, b) { 
 
      return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1; 
 
     }); 
 
    } 
 
    // Sort the tree initially incase the JSON isn't in a 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 the zoom function for the zoomable tree 
 

 
    function zoom() { 
 
     svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); 
 
    } 
 
    // define the zoomListener which calls the zoom function on the "zoom" event constrained within the 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 the parent and sort the path's 
 
      if (a.id != draggingNode.id) return 1; // a is not the hovered element, send "a" to the back 
 
      else return -1; // a is the hovered element, bring "a" to the front 
 
     }); 
 
     // if nodes has children, remove the links and 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 the baseSvg, attaching a class for styling and the zoomListener 
 
    var baseSvg = d3.select("#tree-container").append("svg") 
 
     .attr("width", viewerWidth) 
 
     .attr("height", viewerHeight) 
 
     .attr("class", "overlay") 
 
     .call(zoomListener); 
 
    // Define the drag listeners for 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 that we suppress the mouseover event on the node being dragged. Otherwise it will absorb the mouseover event and the underlying node will not detect it d3.select(this).attr('pointer-events', 'none'); 
 
     }) 
 
     .on("drag", function(d) { 
 
      if (d == root) { 
 
       return; 
 
      } 
 
      if (dragStarted) { 
 
       domNode = this; 
 
       initiateDrag(d, domNode); 
 
      } 
 

 
      // get coords of mouseEvent relative to svg container to allow for 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) { 
 
       // now remove the element from the parent, and insert it into the 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 that the node being added to is expanded so user can see added node is correctly moved 
 
       expand(selectedNode); 
 
       sortTree(); 
 
       endDrag(); 
 
      } else { 
 
       endDrag(); 
 
      } 
 
     }); 
 

 
    function endDrag() { 
 
     selectedNode = null; 
 
     d3.selectAll('.ghostCircle').attr('class', 'ghostCircle'); 
 
     d3.select(domNode).attr('class', 'node'); 
 
     // now restore the mouseover event or we won't be able to drag a 2nd time 
 
     d3.select(domNode).select('.ghostCircle').attr('pointer-events', ''); 
 
     updateTempConnector(); 
 
     if (draggingNode !== null) { 
 
      update(root); 
 
      centerNode(draggingNode); 
 
      draggingNode = null; 
 
     } 
 
    } 
 

 
    // Helper functions for collapsing and 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 to update the temporary connector indicating dragging affiliation 
 
    var updateTempConnector = function() { 
 
     var data = []; 
 
     if (draggingNode !== null && selectedNode !== null) { 
 
      // have to flip the source coordinates since we did this for the existing connectors on the 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 to center node when clicked/dropped so node doesn't get lost when collapsing/moving with 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 the new height, function counts total children of root node and sets tree height accordingly. 
 
     // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed 
 
     // This makes the 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 the 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 to keep a fixed scale one can set a fixed depth per level 
 
      // Normalize for fixed-depth by commenting out below line 
 
      // d.y = (d.depth * 500); //500px per level. 
 
     }); 
 

 
     // Update the nodes… 
 
     node = svgGroup.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") 
 
      .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 to give us mouseover in a radius around it 
 
     nodeEnter.append("circle") 
 
      .attr("class", "ghostCircle") 
 
      .attr("r",30) 
 
      .attr("opacity", 0.2) // change this to zero to hide the 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(this) 
 
          .transition() 
 
          .duration(100) 
 
          .attr("fill", "red"); 
 
         }) 
 
         .on("mouseout", function(d){ 
 
          d3.select(this) 
 
          .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 the text to 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 the circle fill depending on whether it has children and is collapsed 
 
     node.select("circle.nodeCircle") 
 
      .attr("r", 4.5) 
 
      .style("fill", function(d) { 
 
       return d._children ? "#EEB4B4" : "#FFF"; 
 
      }); 
 

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

 
     // Fade the text in 
 
     nodeUpdate.select("text") 
 
      .style("fill-opacity", 1); 
 
     nodeUpdate.select("rect") 
 
      .style("fill-opacity", 0.9); 
 

 
     // 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", 0); 
 

 
     nodeExit.select("text") 
 
      .style("fill-opacity", 0); 
 
     nodeExit.select("rect") 
 
      .style("fill-opacity", 0); 
 

 
     // Update the links… 
 
     var link = svgGroup.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", "link") 
 
      .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; 
 
     }); 
 
    } 
 

 
    // Append a group which holds all nodes and which the zoom Listener can act upon. 
 
    var svgGroup = baseSvg.append("g"); 
 

 
    // Define the root 
 
    root = treeData; 
 
    root.x0 = viewerHeight/2; 
 
    root.y0 = 0; 
 

 
    // Layout the tree initially and center on the root node. 
 
    update(root); 
 
    centerNode(root); 
 
//}); 
 

 
</script> </body> 
 
</html>

回答

2

是的!在你的小提琴中,我添加了以下函数来遍历子元素,并调用父子元素递归地递归。

function open(myNode) { 
    toggleChildren(myNode); 
    if (myNode.children) { 
     myNode.children.forEach(function (j) { 
      open(j);//open the child node recursively 
     }) 
    } 
} 
open(treeData); 

Full working code here

+0

谢谢西里尔。我想知道每个节点是否有办法轮流打开它的分支。例如,父节点首先打开,然后打开子1,等等。换句话说,我正在寻找渐进的迭代动画效果。谢谢! – Alex

+0

@Alex你可以在设置的超时函数中移动函数open(j),像这样... setTimeout(function(){doOpen(j)},3000 * nodeLevel); – Cyril

相关问题