首页 > 解决方案 > 在 D3 树布局中使用多种链接类型

问题描述

我正在使用 Tree Layout 在 D3 v3 中构建一个复杂的图表。我的图表通常具有树结构,因此我使用对角线链接如下:

var diagonal = d3.svg.diagonal()
  .projection(function (d) {
    return [d.x, d.y];
  });

但是,对于图表中的某些节点,我有不同类型的节点来显示其父节点的属性。因此,我希望它们与父节点非常接近,而不是与其他子节点处于同一级别。我认为一个可能的解决方案可能是使用如下径向链接:

var radialDiagonal = d3.svg.diagonal.radial()
  .projection(function (d) {
    return [d.y, d.x / 180 * Math.PI];
  });

但是,我找不到任何关于如何在图表中使用多种链接类型的示例。是否有可能做到这一点?或者,是否有任何解决方案可以使某些节点与其父节点如此接近?

提前致谢。

标签: javascriptd3.js

解决方案


我正在为树使用 d3v4+ 语法,下面的方法相对容易地适用于 v3

听起来您正在寻找使用不同类型链接的能力。

链接类型,d3.svg.diaganol这里与节点放置无关。该链接仅使用某种预定义的曲线连接两个点。因此,如果您想允许节点“非常接近其父节点,而不是与其他子节点处于同一级别”,您需要操纵节点的位置,而不是实际的链接。一种选择是手动移动应该靠近其父节点的节点。在下面的示例中,我使用节点属性 ( near) 过滤节点(在运行布局生成器之后)并将那些应该更靠近其父节点的节点移动:

 root.descendants().reverse().forEach(function(d) {
  if(d.data.near) d.y = (d.parent.y+d.y)/2;
 })

var data = { "name": "Parent", "children": [ 
    { "name": "Child A", near:true, "children": [
    { "name": "Grandkid A", near: true },
    { "name": "Grandkid B" },
    { "name": "Grandkid C" }
    
    ] }, 
    { "name": "Child B", },
  { "name": "Child C", children: [
     { "name": "Grandkid D", near:true },
     { "name": "Grandkid E" }   
  ]}
    ] };

var width = 400;
var height = 300;

margin = {left: 50, top: 10, right:50, bottom: 10}

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

var root = d3.hierarchy(data);
      
var tree = d3.tree()
    .size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
    
    tree(root)
    
 root.descendants().reverse().forEach(function(d) {
  if(d.data.near) d.y = (d.parent.y+d.y)/2;
 })

 var link = g.selectAll(".link")
    .data(root.links())
    .enter().append("path")
      .attr("class", "link")
      .attr("d", d3.linkHorizontal()
          .x(function(d) { return d.y; })
          .y(function(d) { return d.x; }));

  var node = g.selectAll(".node")
    .data(root.descendants())
    .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("circle")
      .attr("r", 2.5);
      
  node.append("text")
     .text(function(d) { return d.data.name; })
     .attr('y',-10)
     .attr('x',-10)
     .attr('text-anchor','middle');
path{     fill: #fff;
          stroke: steelblue;
          stroke-width: 3px;
        }

        .link {
          fill: none;
          stroke: #ccc;
          stroke-width: 2px;
        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

上述解决方案非常基本,在具有很多深度的复杂树中,它可能会变得更加困难。您还可以手动放置每个节点的 x,这提供了更多控制,但代价是编写一个新的算法来替换或纠正至少一半的树布局生成器输出。最终,“正确”的答案将取决于偏好和数据结构。

如果您想使用不同的链接生成器,这也很容易。我将使用节点属性(near再次)来指示是否应使用替代链接:

var data = { "name": "Parent", "children": [ 
    { "name": "Child A", near:true, "children": [
    { "name": "Grandkid A", near: true },
    { "name": "Grandkid B" },
    { "name": "Grandkid C" }
    
    ] }, 
    { "name": "Child B", },
  { "name": "Child C", children: [
     { "name": "Grandkid D", near: true},
     { "name": "Grandkid E" }   
  ]}
    ] };

var width = 400;
var height = 300;

margin = {left: 50, top: 10, right:50, bottom: 10}

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

var root = d3.hierarchy(data);
      
var tree = d3.tree()
    .size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
    
    tree(root)
    
 root.descendants().forEach(function(d) {
  if(d.data.near) d.y = (d.parent.y+d.y)/2;
 })


 var horizontal = d3.linkHorizontal()
          .x(function(d) { return d.y; })
          .y(function(d) { return d.x; })
          
 // some different path drawing function:         
 var straight = function(d) {
   return "M"+d.source.y+" "+d.source.x+"L"+(d.target.y-20)+" "+d.source.x+"L"+d.target.y+" "+d.target.x;
 }

 var link = g.selectAll(".link")
    .data(root.links())
    .enter().append("path")
      .attr("class", "link")
      .attr("d", function(d) {
        if(!d.target.data.near) return straight(d);
        else return horizontal(d);
      });

  var node = g.selectAll(".node")
    .data(root.descendants())
    .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("circle")
      .attr("r", 2.5);
      
  node.append("text")
     .text(function(d) { return d.data.name; })
     .attr('y',-10)
     .attr('x',-10)
     .attr('text-anchor','middle');
path{     fill: #fff;
          stroke: steelblue;
          stroke-width: 3px;
        }

        .link {
          fill: none;
          stroke: #ccc;
          stroke-width: 2px;
        }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>


推荐阅读