d3.js - 以树形布局绘制文件目录

d3.js - draw file directory with tree layout

文件目录结构看起来像一棵树,下面的代码尝试使用树形布局来绘制文件目录,就像linux平台中的树命令一样。 但看起来树布局在 BFS 模式而不是 DFS 模式下工作。

tree_as_directory()
function tree_as_directory() {
  var data = `
* AA
** BB
*** EE
*** FF
** CC
*** GG
*** HH
*** II
** DD
*** JJ
**** LL
**** MM
*** KK
**** NN
**** O
`

  var levels = []
  var lines = data.split("\n")
  lines = lines.filter(d => d.length > 0)
  for (var i=0;i<lines.length;i++) {
    var line = lines[i]
    var regex = '^' + '([\*]+) ' + '([\w\s]+)'
    var match = line.match(regex)
    if (match != null) {
      var num = match[1].length
      var name = match[2]
      levels.push({name:name,level:num})
    } 
  }

  var data = []
  for (var i=0;i<levels.length;i++) {
    var curr = data
    for (var j=1;j<levels[i].level;j++) {
      curr = curr[curr.length-1].children
    }

    var name = levels[i].name
    curr.push({name:name,children:[]})
  }

  var root = data[0]
  //console.log(data)
  show_tree(root)

  function show_tree(treeData) {
    var margin = {top: 20, right: 90, bottom: 30, left: 90},
        width = 660 - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;

    var treemap = d3.tree()
    .nodeSize([30,30])
    //.size([height, width]);

    var nodes = d3.hierarchy(treeData, function(d) {
      return d.children;
    });

    nodes = treemap(nodes);

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

    var curr = 0
    var node = g.selectAll(".node")
    .data(nodes)//nodes.descendants())
    .enter().append("g")
    .each(d => {
      d.x = curr
      curr += 15
    })
    .attr("class", function(d) { 
      return "node" + 
        (d.children ? " node--internal" : " node--leaf"); })
    .attr("transform", function(d) { 
      return "translate(" + d.y + "," + d.x + ")"; });

    node.append("text")
      .attr("x", 0)
      .style("text-anchor",'start')
      .attr("dominant-baseline", "central")
      .text(function(d) { return d.data.name; })
      .each((d,i,n) => {
      var bbox = d3.select(n[i]).node().getBBox()
      var margin = 0
      bbox.x -= margin
      bbox.y -= margin
      bbox.width += 2*margin
      bbox.height += 2*margin
      d.bbox = bbox
    })

    function diagonal2(d) {
      var ax = d.parent.x
      var ay = d.parent.y
      var bx = d.x
      var by = d.y
      var dir = 1

      ax += d.parent.bbox.height/2
      var path = ['M',ay,ax,'L',ay,bx,
                  ,by,bx]   

      return path.join(' ')
    }

    var link = g.selectAll(".link")
    .data(nodes.descendants().slice(1))
    .enter().append("path")
    .attr("class", "link")
    .attr('stroke','#A80036')
    .attr('stroke-width',1)
    .attr('fill','none')
    .attr("d", diagonal2);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

您可以通过 node.eachBefore() 将函数传递给前序遍历中的层次结构节点。在这种情况下,我们可以很容易地使用层次结构编写我们自己的位置数据:

  let y = 0;
  nodes.eachBefore(function(d) {
    d.x = d.depth * 20;
    d.y = y++ * 20;
  })

该片段使用您的约定(通常用于 D3 水平树):x 作为垂直值,y 作为水平值,对于上面的代码块,我没有把它发扬光大,因为它在孤立的情况下有点奇怪

当然,我们现在真的不需要树布局,因为我们使用层次结构设置自己的定位,所以我将从下面的代码片段中删除它:

tree_as_directory()
function tree_as_directory() {
  var data = `
* AA
** BB
*** EE
*** FF
** CC
*** GG
*** HH
*** II
** DD
*** JJ
**** LL
**** MM
*** KK
**** NN
**** O
`

  var levels = []
  var lines = data.split("\n")
  lines = lines.filter(d => d.length > 0)
  for (var i=0;i<lines.length;i++) {
    var line = lines[i]
    var regex = '^' + '([\*]+) ' + '([\w\s]+)'
    var match = line.match(regex)
    if (match != null) {
      var num = match[1].length
      var name = match[2]
      levels.push({name:name,level:num})
    } 
  }
 
 

  var data = []
  for (var i=0;i<levels.length;i++) {
    var curr = data
    for (var j=1;j<levels[i].level;j++) {
      curr = curr[curr.length-1].children
    }

    var name = levels[i].name
    curr.push({name:name,children:[]})
  }

  var root = data[0]
 
  show_tree(root)

  function show_tree(treeData) {
    var margin = {top: 20, right: 90, bottom: 30, left: 90},
        width = 660 - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;

    var nodes = d3.hierarchy(treeData, function(d) {
      return d.children;
    });

  // Start positioning:
  let h = 0;
  nodes.eachBefore(function(d) {
    d.y = d.depth * 20;
    d.x = h++ * 20;
  })
  // End positioning.
     
    var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom),
        g = svg.append("g")
    .attr("transform",
          "translate(" + margin.left + "," + margin.top + ")");

 
    var curr = 0
    var node = g.selectAll(".node")
   
    .data(nodes)
    .enter().append("g")
    .attr("class", function(d) { 
      return "node" + 
        (d.children ? " node--internal" : " node--leaf"); })
    .attr("transform", function(d) { 
      return "translate(" + d.y + "," + d.x + ")"; });
      
  

    node.append("text")
      .attr("x", 0)
      .style("text-anchor",'start')
      .attr("dominant-baseline", "central")
      .text(function(d) { return d.data.name; })
      .each((d,i,n) => {
      var bbox = d3.select(n[i]).node().getBBox()
      var margin = 0
      bbox.x -= margin
      bbox.y -= margin
      bbox.width += 2*margin
      bbox.height += 2*margin
      d.bbox = bbox
    })

    function diagonal2(d) {
      var ax = d.parent.x
      var ay = d.parent.y
      var bx = d.x
      var by = d.y
      var dir = 1

      ax += d.parent.bbox.height/2
      var path = ['M',ay,ax,'L',ay,bx,
                  ,by,bx]   

      return path.join(' ')
    }

    var link = g.selectAll(".link")
    .data(nodes.descendants().slice(1))
    .enter().append("path")
    .attr("class", "link")
    .attr('stroke','#A80036')
    .attr('stroke-width',1)
    .attr('fill','none')
    .attr("d", diagonal2);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>