将 d3js 树折叠到指定深度

haw*_*him 5 tree depth d3.js

我是 d3js 和 javascript 的新手。我正在尝试创建一个包含 8000 多个节点的树,并使用可用的基本树示例并进行修改以垂直显示。

示例数据是硬编码的,但是,在我的本地服务器上,我从外部 JSON 文件中读取数据。

我希望能够指定开始的深度级别(折叠所有深度超过 2 的节点)并允许用户进一步展开或折叠节点。

我尝试修改“折叠”功能以检查深度级别,但是逻辑不正确。

感谢这方面的任何帮助。

<!-- load the d3.js library --> 
<script src="http://d3js.org/d3.v3.min.js"></script>
    
<script>

//------------------
var data =[{"sid":"1","parent":"null","name_ar":"Hashim"},{"sid":"2","parent":"1","name_ar":"wahb"},{"sid":"3","parent":"1","name_ar":"Abdulmuttaleb"},{"sid":"4","parent":"2","name_ar":"Amina"},{"sid":"5","parent":"3","name_ar":"Abutaleb"},{"sid":"6","parent":"3","name_ar":"Abdulla"},{"sid":"7","parent":"3","name_ar":"Abbas"},{"sid":"8","parent":"3","name_ar":"Hamza"},{"sid":"9","parent":"6","name_ar":"Mohammed (Prophet)"},{"sid":"10","parent":"9","name_ar":"Alqassim"},{"sid":"11","parent":"9","name_ar":"Um Kalthoum"},{"sid":"12","parent":"9","name_ar":"Zainab"},{"sid":"13","parent":"9","name_ar":"Ruqaya"},{"sid":"14","parent":"9","name_ar":"Fatima"},{"sid":"15","parent":"9","name_ar":"Ibrahim"},{"sid":"16","parent":"9","name_ar":"Abdulla"},{"sid":"17","parent":"9","name_ar":"Muhsen"},{"sid":"18","parent":"5","name_ar":"Ali"},{"sid":"19","parent":"18","name_ar":"Hassan"},{"sid":"20","parent":"18","name_ar":"Hussain"},{"sid":"21","parent":"20","name_ar":"Ali Zain Alabbideen"},{"sid":"22","parent":"21","name_ar":"Mohammed Baqer"},{"sid":"23","parent":"22","name_ar":"Jafar Sadeq"},{"sid":"24","parent":"23","name_ar":"Mousa Kadim"},{"sid":"25","parent":"24","name_ar":"Ali AlAreed"},{"sid":"26","parent":"24","name_ar":"Ibrahim Murtada"},{"sid":"27","parent":"26","name_ar":"Mousa (the second)"},{"sid":"28","parent":"27","name_ar":"Ahmed"},{"sid":"29","parent":"28","name_ar":"Hussain"},{"sid":"30","parent":"29","name_ar":"Abu Alqassim Mohammed"},{"sid":"31","parent":"30","name_ar":"Najm Aldeen Mahdi"}];

//need to find a way to dynamically set the "Width" as the tree is very deep
var margin = {top: 25, right: 120, bottom: 20, left: 120},
    width = 10000 - margin.right - margin.left,
    height = 5000 - margin.top - margin.bottom;
    
var i = 0,
    duration = 750,
    rectW = 100,
    rectH = 30,
    root;

// zoom functionality   
var zoom = d3.behavior.zoom()
    .scaleExtent([1, 10])
    .on("zoom", zoomed);

var tree = d3.layout.tree()
    .nodeSize([110, 50]); // increased to 110 to avoid node overlap
    
var diagonal = d3.svg.diagonal()
    .projection(function (d) { return [d.x + rectW / 2, d.y + rectH / 2]; });   

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.right + margin.left)
    .attr("height", height + margin.top + margin.bottom)
    .call(zoom) // added to call to zoom to enable zooming; it works :}
    .on("wheel.zoom", null) // disable zooming on mouse wheel scroll
   .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    //create a name-based map for the nodes
    //which starts with an empty object and iterates over the data array, adding an entry for each node
    //for the flat array
    var dataMap = data.reduce(function(map, node) {
    map[node.sid] = node;
    return map;
    }, {});

    //iteratively add each child to its parents, or to the root array if no parent is found
    //for the flat array
    var treeData = [];
    data.forEach(function(node) {
        // add to parent
        var parent = dataMap[node.parent];
        if (parent) {
            // create child array if it doesn't exist
            (parent.children || (parent.children = []))
                // add node to child array
                .push(node);
        } else {
            // parent is null or missing
            treeData.push(node);
        }
    });
    root = treeData[0];
    root.x0 = height / 2;  // should this be width/2 for the vertical?
    root.y0 = 0;

    //testing using depth to open at a specified level
    var nodes = tree.nodes(root);
    function collapseLevel(d) {
        console.log("sid "+d.sid+" depth "+d.depth);
        if (d.children && d.depth > 2) { // doesn't work as it exits at parent depth
            d._children = d.children;
            d._children.forEach(collapseLevel);
            d.children = null;
        }
    }
    root.children.forEach(collapseLevel);//iterate each node and collapse excluding node zero
    update(root);

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

//zoom (drag the tree around !) 
function zoomed() {
  svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}

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 * 180; });

  // 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.
  //vertical tree by swaping y0 and x0
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })
      .on("click", click)
    .on("mouseover", function(d) {
          var g = d3.select(this); // The node
          
          var info = g.append('text')
             .classed('info', true)
             .attr('x', 20)
             .attr('y', 10)
             .text(function(d) { return d.name_ar + " " + d.sid });
    })
    .on("mouseout", function() {
          // Remove the info text on mouse out.
          d3.select(this).select('text.info').remove()
    });
  ;

//rectagular nodes
nodeEnter.append("rect")
        .attr("width", rectW)
        .attr("height", rectH)
        .attr("stroke", "black")
        .attr("stroke-width", 1)
        .style("fill", function (d) {
        return d._children ? "lightsteelblue" : "#fff";
    });

 nodeEnter.append("text")
        .attr("x", rectW / 2)
        .attr("y", rectH / 2)
        .attr("dy", ".35em")
        .attr("text-anchor", "middle")
        .text(function(d) { return d.name_ar; })
        .style("fill-opacity", 1);

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

   nodeUpdate.select("rect")
        .attr("width", rectW)
        .attr("height", rectH)
        .attr("stroke", "black")
        .attr("stroke-width", 1)
        .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.
//vertical 
var nodeExit = node.exit().transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; })
      .remove();

    nodeExit.select("rect")
        .attr("width", rectW)
        .attr("height", rectH)
    .attr("stroke", "black")
        .attr("stroke-width", 1);

    nodeExit.select("text");

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

// 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);
}

</script>
Run Code Online (Sandbox Code Playgroud)
    <style>
    
    .node {
        cursor: pointer;
    }

    .node circle {
      fill: #fff;
      stroke: steelblue;
      stroke-width: 3px;
    }

    .node text {
      font: 18px sans-serif;
    }

    .link {
      fill: none;
      stroke: #ccc;
      stroke-width: 2px;
    }
    
    </style>
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<html lang="en">
  <head>
    <meta charset="utf-8">

    <title>Tree Example</title>

   </head>
Run Code Online (Sandbox Code Playgroud)

Ger*_*ado 3

你必须提供一个else if条件,以防if条件不成立

\n\n
function collapseLevel(d) {\n    if (d.children && d.depth > 1) {\n        d._children = d.children;\n        d._children.forEach(collapseLevel);\n        d.children = null;\n    } else if (d.children) {\n        d.children.forEach(collapseLevel);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这是经过更改的代码:

\n\n

\r\n
\r\n
function collapseLevel(d) {\n    if (d.children && d.depth > 1) {\n        d._children = d.children;\n        d._children.forEach(collapseLevel);\n        d.children = null;\n    } else if (d.children) {\n        d.children.forEach(collapseLevel);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\r\n
var data =[{"sid":"1","parent":"null","name_ar":"Hashim"},{"sid":"2","parent":"1","name_ar":"wahb"},{"sid":"3","parent":"1","name_ar":"Abdulmuttaleb"},{"sid":"4","parent":"2","name_ar":"Amina"},{"sid":"5","parent":"3","name_ar":"Abutaleb"},{"sid":"6","parent":"3","name_ar":"Abdulla"},{"sid":"7","parent":"3","name_ar":"Abbas"},{"sid":"8","parent":"3","name_ar":"Hamza"},{"sid":"9","parent":"6","name_ar":"Mohammed (Prophet)"},{"sid":"10","parent":"9","name_ar":"Alqassim"},{"sid":"11","parent":"9","name_ar":"Um Kalthoum"},{"sid":"12","parent":"9","name_ar":"Zainab"},{"sid":"13","parent":"9","name_ar":"Ruqaya"},{"sid":"14","parent":"9","name_ar":"Fatima"},{"sid":"15","parent":"9","name_ar":"Ibrahim"},{"sid":"16","parent":"9","name_ar":"Abdulla"},{"sid":"17","parent":"9","name_ar":"Muhsen"},{"sid":"18","parent":"5","name_ar":"Ali"},{"sid":"19","parent":"18","name_ar":"Hassan"},{"sid":"20","parent":"18","name_ar":"Hussain"},{"sid":"21","parent":"20","name_ar":"Ali Zain Alabbideen"},{"sid":"22","parent":"21","name_ar":"Mohammed Baqer"},{"sid":"23","parent":"22","name_ar":"Jafar Sadeq"},{"sid":"24","parent":"23","name_ar":"Mousa Kadim"},{"sid":"25","parent":"24","name_ar":"Ali AlAreed"},{"sid":"26","parent":"24","name_ar":"Ibrahim Murtada"},{"sid":"27","parent":"26","name_ar":"Mousa (the second)"},{"sid":"28","parent":"27","name_ar":"Ahmed"},{"sid":"29","parent":"28","name_ar":"Hussain"},{"sid":"30","parent":"29","name_ar":"Abu Alqassim Mohammed"},{"sid":"31","parent":"30","name_ar":"Najm Aldeen Mahdi"}];\r\n\r\n//need to find a way to dynamically set the "Width" as the tree is very deep\r\nvar margin = {top: 25, right: 120, bottom: 20, left: 120},\r\n\twidth = 10000 - margin.right - margin.left,\r\n\theight = 5000 - margin.top - margin.bottom;\r\n\t\r\nvar i = 0,\r\n\tduration = 750,\r\n\trectW = 100,\r\n    rectH = 30,\r\n\troot;\r\n\r\n// zoom functionality\t\r\nvar zoom = d3.behavior.zoom()\r\n    .scaleExtent([1, 10])\r\n    .on("zoom", zoomed);\r\n\r\nvar tree = d3.layout.tree()\r\n\t.nodeSize([110, 50]); // increased to 110 to avoid node overlap\r\n\t\r\nvar diagonal = d3.svg.diagonal()\r\n    .projection(function (d) { return [d.x + rectW / 2, d.y + rectH / 2]; });\t\r\n\r\nvar svg = d3.select("body").append("svg")\r\n\t.attr("width", width + margin.right + margin.left)\r\n\t.attr("height", height + margin.top + margin.bottom)\r\n\t.call(zoom) // added to call to zoom to enable zooming; it works :}\r\n\t.on("wheel.zoom", null) // disable zooming on mouse wheel scroll\r\n   .append("g")\r\n\t.attr("transform", "translate(" + margin.left + "," + margin.top + ")");\r\n\r\n\t//create a name-based map for the nodes\r\n\t//which starts with an empty object and iterates over the data array, adding an entry for each node\r\n\t//for the flat array\r\n\tvar dataMap = data.reduce(function(map, node) {\r\n\tmap[node.sid] = node;\r\n\treturn map;\r\n\t}, {});\r\n\r\n\t//iteratively add each child to its parents, or to the root array if no parent is found\r\n\t//for the flat array\r\n\tvar treeData = [];\r\n\tdata.forEach(function(node) {\r\n\t\t// add to parent\r\n\t\tvar parent = dataMap[node.parent];\r\n\t\tif (parent) {\r\n\t\t\t// create child array if it doesn\'t exist\r\n\t\t\t(parent.children || (parent.children = []))\r\n\t\t\t\t// add node to child array\r\n\t\t\t\t.push(node);\r\n\t\t} else {\r\n\t\t\t// parent is null or missing\r\n\t\t\ttreeData.push(node);\r\n\t\t}\r\n\t});\r\n\troot = treeData[0];\r\n\troot.x0 = height / 2;  // should this be width/2 for the vertical?\r\n\troot.y0 = 0;\r\n\r\n\t//testing using depth to open at a specified level\r\n\tvar nodes = tree.nodes(root);\r\n\tfunction collapseLevel(d) {\r\n\t\tif (d.children && d.depth > 1) {\r\n\t\t\td._children = d.children;\r\n\t\t\td._children.forEach(collapseLevel);\r\n\t\t\td.children = null;\r\n\t\t} else if (d.children){\r\n      d.children.forEach(collapseLevel);\r\n    }\r\n\t}\r\n    root.children.forEach(collapseLevel);//iterate each node and collapse excluding node zero\r\n\tupdate(root);\r\n\r\nd3.select(self.frameElement).style("height", "500px");\r\n\r\n//zoom (drag the tree around !) \r\nfunction zoomed() {\r\n  svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");\r\n}\r\n\r\nfunction update(source) {\r\n\r\n  // Compute the new tree layout.\r\n  var nodes = tree.nodes(root).reverse(),\r\n\t  links = tree.links(nodes);\r\n\r\n  // Normalize for fixed-depth.\r\n   nodes.forEach(function(d) { d.y = d.depth * 180; });\r\n\r\n  // Update the nodes\xe2\x80\xa6\r\n  var node = svg.selectAll("g.node")\r\n\t  .data(nodes, function(d) { return d.id || (d.id = ++i); });\r\n\r\n  // Enter any new nodes at the parent\'s previous position.\r\n  //vertical tree by swaping y0 and x0\r\n  var nodeEnter = node.enter().append("g")\r\n\t  .attr("class", "node")\r\n\t  .attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; })\r\n\t  .on("click", click)\r\n\t.on("mouseover", function(d) {\r\n          var g = d3.select(this); // The node\r\n          \r\n          var info = g.append(\'text\')\r\n             .classed(\'info\', true)\r\n             .attr(\'x\', 20)\r\n             .attr(\'y\', 10)\r\n             .text(function(d) { return d.name_ar + " " + d.sid });\r\n\t})\r\n\t.on("mouseout", function() {\r\n          // Remove the info text on mouse out.\r\n          d3.select(this).select(\'text.info\').remove()\r\n\t});\r\n  ;\r\n\r\n//rectagular nodes\r\nnodeEnter.append("rect")\r\n        .attr("width", rectW)\r\n        .attr("height", rectH)\r\n        .attr("stroke", "black")\r\n        .attr("stroke-width", 1)\r\n        .style("fill", function (d) {\r\n        return d._children ? "lightsteelblue" : "#fff";\r\n    });\r\n\r\n nodeEnter.append("text")\r\n        .attr("x", rectW / 2)\r\n        .attr("y", rectH / 2)\r\n        .attr("dy", ".35em")\r\n        .attr("text-anchor", "middle")\r\n        .text(function(d) { return d.name_ar; })\r\n        .style("fill-opacity", 1);\r\n\r\n// Transition nodes to their new position.\r\n//vertical\r\nvar nodeUpdate = node.transition()\r\n\t  .duration(duration)\r\n\t  .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });\r\n\r\n   nodeUpdate.select("rect")\r\n        .attr("width", rectW)\r\n        .attr("height", rectH)\r\n        .attr("stroke", "black")\r\n        .attr("stroke-width", 1)\r\n        .style("fill", function (d) {\r\n        return d._children ? "lightsteelblue" : "#fff";\r\n    });\r\n\r\n  nodeUpdate.select("text")\r\n\t  .style("fill-opacity", 1);\r\n\r\n// Transition exiting nodes to the parent\'s new position.\r\n//vertical \r\nvar nodeExit = node.exit().transition()\r\n\t  .duration(duration)\r\n\t  .attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; })\r\n\t  .remove();\r\n\r\n    nodeExit.select("rect")\r\n        .attr("width", rectW)\r\n        .attr("height", rectH)\r\n    .attr("stroke", "black")\r\n        .attr("stroke-width", 1);\r\n\r\n    nodeExit.select("text");\r\n\r\n  // Update the links\xe2\x80\xa6\r\n  var link = svg.selectAll("path.link")\r\n\t  .data(links, function(d) { return d.target.id; });\r\n\r\n  // Enter any new links at the parent\'s previous position.\r\n  link.enter().insert("path", "g")\r\n\t  .attr("class", "link")\r\n\t  .attr("d", function(d) {\r\n\t\tvar o = {x: source.x0, y: source.y0};\r\n\t\treturn diagonal({source: o, target: o});\r\n\t  });\r\n\r\n  // Transition links to their new position.\r\n  link.transition()\r\n\t  .duration(duration)\r\n\t  .attr("d", diagonal);\r\n\r\n  // Transition exiting nodes to the parent\'s new position.\r\n  link.exit().transition()\r\n\t  .duration(duration)\r\n\t  .attr("d", function(d) {\r\n\t\tvar o = {x: source.x, y: source.y};\r\n\t\treturn diagonal({source: o, target: o});\r\n\t  })\r\n\t  .remove();\r\n\r\n  // Stash the old positions for transition.\r\n  nodes.forEach(function(d) {\r\n\td.x0 = d.x;\r\n\td.y0 = d.y;\r\n  });\r\n}\r\n\r\n// Toggle children on click.\r\nfunction click(d) {\r\n  if (d.children) {\r\n\td._children = d.children;\r\n\td.children = null;\r\n  } else {\r\n\td.children = d._children;\r\n\td._children = null;\r\n  }\r\n  update(d);\r\n}\r\n\r\n</script>
Run Code Online (Sandbox Code Playgroud)\r\n
<style>\r\n\t\r\n\t.node {\r\n\t\tcursor: pointer;\r\n\t}\r\n\r\n\t.node circle {\r\n\t  fill: #fff;\r\n\t  stroke: steelblue;\r\n\t  stroke-width: 3px;\r\n\t}\r\n\r\n\t.node text {\r\n\t  font: 18px sans-serif;\r\n\t}\r\n\r\n\t.link {\r\n\t  fill: none;\r\n\t  stroke: #ccc;\r\n\t  stroke-width: 2px;\r\n\t}\r\n\t\r\n    </style>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n\n

PS:我改成d.depth > 1因为在我看来你想默认显示 2 个深度,而不是 3 个。如果我错了,只需相应地更改该数字即可。

\n