提示:本地新建 html 文件,拷贝代码填入文件后,浏览器查看即可查看效果。
html<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Collapsible Tree with D3.js</title>
  <script src="https://d3js.org/d3.v7.min.js"></script>
  <style>
    /* 定义节点样式 */
    .node {
      cursor: pointer;
    }
    .node circle {
      fill: #fff;
      stroke: steelblue;
      stroke-width: 3px;
    }
    .node text {
      font: 12px sans-serif;
    }
    .link {
      fill: none;
      stroke: #ccc;
      stroke-width: 2px;
    }
  </style>
</head>
<body>
  <script>
    // 定义树数据结构
    var treeData = {
      name: "Root",
      children: [
        {
          name: "Child 1",
          children: [
            { name: "Grandchild 1.1" },
            { name: "Grandchild 1.2" }
          ]
        },
        { name: "Child 2" }
      ]
    };
    // 定义树图的边距和尺寸
    var margin = { top: 20, right: 120, bottom: 20, left: 120 },
        width = 960 - margin.right - margin.left,
        height = 500 - margin.top - margin.bottom;
    var i = 0,
        duration = 750, // 动画持续时间
        root;
    // 创建树布局
    var tree = d3.tree().size([height, width]);
    // 创建对角线生成器
    var diagonal = d3.linkHorizontal().x(d => d.y).y(d => d.x);
    // 创建SVG容器
    var svg = d3.select("body").append("svg")
        .attr("width", width + margin.right + margin.left)
        .attr("height", height + margin.top + margin.bottom)
      .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
    // 将数据转换为树节点
    root = d3.hierarchy(treeData, d => d.children);
    root.x0 = height / 2;
    root.y0 = 0;
    // 初始折叠函数,递归折叠所有子节点
    function collapse(d) {
      if (d.children) {
        d._children = d.children; // 将子节点保存到隐藏节点中
        d._children.forEach(collapse);
        d.children = null; // 清空子节点,实现折叠
      }
    }
    // 初始化时折叠所有子节点
    root.children.forEach(collapse);
    update(root); // 更新树图
    function update(source) {
      // 将数据映射为树布局
      var treeData = tree(root);
      // 获取所有节点和链接
      var nodes = treeData.descendants(),
          links = treeData.links();
      // 标准化节点深度
      nodes.forEach(d => { d.y = d.depth * 180 });
      // ****************** 节点部分 ******************
      // 使用唯一ID绑定数据
      var node = svg.selectAll('g.node')
          .data(nodes, d => d.id || (d.id = ++i));
      // 为新节点添加g元素
      var nodeEnter = node.enter().append('g')
          .attr('class', 'node')
          .attr('transform', d => 'translate(' + source.y0 + ',' + source.x0 + ')')
          .on('click', click); // 绑定点击事件
      // 添加圆形元素表示节点
      nodeEnter.append('circle')
          .attr('r', 1e-6)
          .style('fill', d => d._children ? "lightsteelblue" : "#fff");
      // 添加文本标签
      nodeEnter.append('text')
          .attr('dy', '.35em')
          .attr('x', d => d.children || d._children ? -13 : 13)
          .attr('text-anchor', d => d.children || d._children ? "end" : "start")
          .text(d => d.data.name);
      // 过渡更新节点
      var nodeUpdate = nodeEnter.merge(node);
      // 将节点过渡到新位置
      nodeUpdate.transition()
        .duration(duration)
        .attr('transform', d => 'translate(' + d.y + ',' + d.x + ')');
      // 更新节点样式
      nodeUpdate.select('circle')
        .attr('r', 10)
        .style('fill', d => d._children ? "lightsteelblue" : "#fff");
      // 过渡移除旧节点
      var nodeExit = node.exit().transition()
          .duration(duration)
          .attr('transform', d => 'translate(' + source.y + ',' + source.x + ')')
          .remove();
      // 在退出时缩小节点
      nodeExit.select('circle')
        .attr('r', 1e-6);
      nodeExit.select('text')
        .style('fill-opacity', 1e-6);
      // ****************** 链接部分 ******************
      // 使用唯一ID绑定数据
      var link = svg.selectAll('path.link')
          .data(links, d => d.target.id);
      // 为新链接添加path元素
      var linkEnter = link.enter().insert('path', 'g')
          .attr('class', 'link')
          .attr('d', d => {
            var o = { x: source.x0, y: source.y0 };
            return diagonal({ source: o, target: o });
          });
      // 过渡更新链接
      var linkUpdate = linkEnter.merge(link);
      // 将链接过渡到新位置
      linkUpdate.transition()
          .duration(duration)
          .attr('d', diagonal);
      // 过渡移除旧链接
      var linkExit = link.exit().transition()
          .duration(duration)
          .attr('d', d => {
            var o = { x: source.x, y: source.y };
            return diagonal({ source: o, target: o });
          })
          .remove();
      // 将旧位置存储以便过渡
      nodes.forEach(d => {
        d.x0 = d.x;
        d.y0 = d.y;
      });
      // 点击事件处理函数
      function click(event, d) {
        if (d.children) {
          d._children = d.children;
          d.children = null; // 折叠子节点
        } else {
          d.children = d._children;
          d._children = null; // 展开子节点
        }
        update(d); // 更新树图
      }
    }
  </script>
</body>
</html>
本文作者:DingDangDog
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!