Tree(树状布局)

78 阅读3分钟

简介

显而易见,树状布局就是我们通常所理解的数据结构中的树,由一个根节点向外展开,与力导向图的数据结构模型其实是一致的,只是展示形式略有差别,用树状结构展示看起来更加简洁清晰一点。

数据

var dataset = {
    name: "中国",
    children: [
      {
        name: "浙江",
        children: [
          { name: "杭州", value: 100 },
          { name: "宁波", value: 100 },
          { name: "温州", value: 100 },
          { name: "绍兴", value: 100 }
        ]
      },
      {
        name: "广西",
        children: [
          {
            name: "桂林",
            children: [
              { name: "秀峰区", value: 100 },
              { name: "叠彩区", value: 100 },
              { name: "象山区", value: 100 },
              { name: "七星区", value: 100 }
            ]
          },
          { name: "南宁", value: 100 },
          { name: "柳州", value: 100 },
          { name: "防城港", value: 100 }
        ]
      },
      {
        name: "黑龙江",
        children: [
          { name: "哈尔滨", value: 100 },
          { name: "齐齐哈尔", value: 100 },
          { name: "牡丹江", value: 100 },
          { name: "大庆", value: 100 }
        ]
      },
      {
        name: "新疆",
        children:
          [
            { name: "乌鲁木齐" },
            { name: "克拉玛依" },
            { name: "吐鲁番" },
            { name: "哈密" }
          ]
      }
    ]
  };

主程序

  var width = 1000;
  var height = 700;
  var dome = document.getElementById('svgAll');
  var svg = d3.create("svg").attr("width", width).attr("height", height);

  var g = svg.append("g").attr("transform", "translate(10,10)");
  //指定缩放范围,缩放功能
  const zoom = d3
    .zoom()
    .scaleExtent([0.2, 4])
    .on("zoom", (e) => {
      g.attr("transform", `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`);
    });
  //动画持续时间
  var initialTransform = d3.zoomIdentity.translate(20, 20).scale(2);//初始缩放平移
  g.transition()
    .duration(1000)
    .call(zoom.transform, d3.zoomIdentity);
  svg.call(zoom);

  // 层级布局
  var hierarchyData = d3.hierarchy(dataset)
    .sum(function (d) {
      return d.value;
    });
  // 树状图
  var tree = d3.tree()
    .size([1000, 500])
    .separation(function (a, b) {
      return (a.parent == b.parent ? 1 : 2) / a.depth;
    })
  
  function drawData(g,hiData) {
    // 初始化绘制树基本数据
    var treeData = tree(hiData);
    // 边和节点
    var nodes = treeData.descendants();
    var links = treeData.links();

    // 绘制边
    var Bézier_curve_generator = d3.linkHorizontal()
      .x(function (d) { return d.y; })
      .y(function (d) { return d.x; });
    var drawLink = ({ source, target }) => {// 直角连接线
      const halfDistance = (target.y - source.y) / 2;
      const halfY = source.y + halfDistance;
      return `M${source.x},${source.y} L${source.x},${halfY} ${target.x},${halfY} ${target.x},${target.y}`;
    }
    g.append("g")
      .selectAll("path")
      .data(links)
      .enter()
      .append("path")
      .attr("d", function (d) {
        var start = { x: d.source.x, y: d.source.y };
        var end = { x: d.target.x, y: d.target.y };
        return drawLink({ source: start, target: end });
      })
      .attr("fill", "none")
      .attr("stroke", "yellow")
      .attr("stroke-width", 1);

    // 文字分组
    var gs = g.append("g")
      .selectAll("g")
      .data(nodes)
      .enter()
      .append("g")
      .attr("transform", function (d) {
        var cx = d.x;
        var cy = d.y;
        return "translate(" + cx + "," + cy + ")";
      });
    gs.append("circle")
      .attr("r", 6)
      .attr("fill", "white")
      .attr("stroke", "blue")
      .attr("stroke-width", 1)
      .on("click", function (e,d) {
        console.log(d);
      });
    gs.append("text")
      .attr("x", function (d) {
        return 8;
      })
      .attr("y", -5)
      .attr("dy", 10)
      .text(function (d) {
        return d.data.name;
      })
  }
  drawData(g,hierarchyData);

  dome.appendChild(svg.node());

数据动态更新

树状布局数据更新,需要重新计算布局,重新绘制。

// 修改点击事件
  ...
  .on("click", function (e,d) {
    console.log(d);
    updateData(d);
  })

更新方法

  function updateData(d) {
    var addData = [
      { name: "测试1", value: 100 },
      { name: "测试2", value: 100 },
      { name: "测试3", value: 100 }
    ]
    var appendData = {
      children: addData
    }
    //重新设置depth
    var setDepth = function (node, num, appendLeaf) {
      node.depth = num;
      if (node.children && node.children.length) {
        node.children.forEach(function (item) {
          setDepth(item, num + 1, appendLeaf);
        });
      } else {
        appendLeaf.push(node);
      }
    };

    if (d.data.name === '柳州') {
      //设置数据
      var appendLeaf = []; //增加的叶子节点
      if (appendData.children.length) {
        d.children = [];
        appendData.children.forEach(function (item, index) {
          var newNode = d3.hierarchy(item);
          newNode.parent = d;
          setDepth(newNode, d.depth + 1, appendLeaf);
          d.children.push(newNode);
        });
      }
      d.data.children = addData;
      // 清空旧节点
      g.selectAll('g').remove();
      // 重绘
      drawData(g,hierarchyData);
    }
  }

tree update 优化

// 更新线条处修改
var mLinks = g.select('.link-class');
if(mLinks.empty()){
	mLinks = g.append("g").attr('class','link-class');
}
function linkD(d){
	var start = {
		x: d.source.x,
		y: d.source.y
	};
	var end = {
		x: d.target.x,
		y: d.target.y
	};
	return drawLink({
		source: start,
		target: end
	});
}
mLinks
	.selectAll("path")
	.data(links)
	.attr("d", linkD)
	.enter()
	.append("path")
	.attr("d", linkD)
	.attr("fill", "none")
	.attr("stroke", "yellow")
	.attr("stroke-width", 1);
	
// 更新文本处修改
var mNodes = g.select('.node-class');
if(mNodes.empty()){
	mNodes = g.append("g").attr('class','node-class');
}
function nodeTransform(d){
	var cx = d.x;
	var cy = d.y;
	return "translate(" + cx + "," + cy + ")";
}
var gs = mNodes
	.selectAll("g")
	.data(nodes)
	.attr("transform", nodeTransform)
	.enter()
	.append("g")
	.attr("transform", nodeTransform);
	
// 更函数 不在需要清空根部g组了
// g.selectAll('g').remove();


//局部更新节点坐标,只会更新d节点一下的坐标
// var mdata = tree(d);
// console.log(mdata)

tree数据层级分组

  • tree - 将指定的层次数据布局为整齐的树布局.
  • tree.size - 指定整个布局的宽和高.
  • tree.nodeSize - 给全部结点指定一个固定的大小(会导致tree.size失效).
  • tree.separation - 设置或获取相隔结点之间的间隔计算函数.
  • d3.group - 对数据集进行了分组
const data = [
	{"root":"project","project_nr":"project 1","department":"1","devision":"A"},
	{"root":"project","project_nr":"project 1","department":"1","devision":"B"},
	{"root":"project","project_nr":"project 1","department":"2","devision":"A"},
	{"root":"project","project_nr":"project 2","department":"3","devision":"A"}
]
var groupedData = d3.group(data,
                          d => d.root,
                          d => d.project_nr,
                          d => d.department,
                          d => d.devision);
var root = d3.hierarchy(groupedData);