首页 > 解决方案 > d3 直链树图

问题描述

我正在创建一个具有树形和径向可视化的树形图。

我在用着

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

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

渲染节点之间的链接,但我希望它是直的而不是圆形的。

我错过了什么吗?

这是我的代码的一个jsfiddle。

https://jsfiddle.net/burro92/j4q5yf7m/

标签: javascriptd3.jssvgtreeviewdata-visualization

解决方案


要使用直线渲染链接,您只需设置 的d属性<path>以在节点之间形成直线。 d然而,巧合的是,d3.js 约定命名回调函数的参数,这些回调函数传递给函数 likeselection.attr()selection.style()函数。后一个d参数是指附加到选择的数据。对于树形布局,您可以分别用d.source.x和访问一个节点的位置,用 访问另一个节点d.source.y的位置d.target.x and d.target.y。请参阅https://github.com/d3/d3-hierarchy/blob/master/README.md#node_links

节点链接()

返回此节点的链接数组,其中每个链接都是定义源和目标属性的对象。每个链接的源是父节点,目标是子节点。

在您的情况下,您只需定义一个函数来为每个链接创建正确的 d 属性:

var straight = d => "M" + d.source.x + "," + d.source.y
      + "H" + d.target.x + "V" + d.target.y;

然后在您绘制链接的路径时调用该函数:

var link = svg.selectAll('.link')
      .data(links)
      .enter()
      .append('path')
      .attr('class', 'link')
      .style('stroke', '#8da0cb')
      .attr('d', straight);

这是您使用直线树的示例:

   function findColor(info) {
      if (info.data.color) return info.data.color;
      if (info.parent) return findColor(info.parent)
      return '#ccc';
    }

    function transitionToRadialTree() {

      var nodes = radialTree.nodes(root), // recalculate layout
        links = radialTree.links(nodes);

      svg.transition().duration(duration)
        .attr('transform', 'translate(' + (width / 2) + ',' +
          (height / 2) + ')');
      // set appropriate translation (origin in middle of svg)

      link.data(links)
        .transition()
        .duration(duration)
        .style('stroke', '#fc8d62')
        .attr('d', radialDiagonal); //get the new radial path

      node.data(nodes)
        .transition()
        .duration(duration)
        .attr('transform', function (d) {
          return 'rotate(' + (d.x - 90) + ')translate(' + d.y + ')';
        });

      node.select('circle')
        .transition()
        .duration(duration)
        .style('stroke', '#984ea3');

    };

    function transitionToTree(isStraight) {

      var nodes = tree.nodes(root), //recalculate layout
        links = tree.links(nodes);

      svg.transition().duration(duration)
        .attr("transform", 'translate(' + margin.left + ',' + margin.top + ')');

      link.data(links)
        .transition()
        .duration(duration)
        .style('stroke', '#e78ac3')
        .attr('d', isStraight ? straight : diagonal); // get the new tree path

      node.data(nodes)
        .transition()
        .duration(duration)
        .attr('transform', function (d) {
          return 'translate(' + d.x + ',' + d.y + ')';
        });

      node.select('circle')
        .transition()
        .duration(duration)
        .style('stroke', '#377eb8');

    };

    const root =
    {
      name: 'Main',
      color: '#00FF00',
      children: [
        {
          name: 'First level A',
          color: '#F5FF8A',
          children: [
            {
              name: 'Second level A',
              children: [
                {
                  name: 'Third level A',
                  children: [
                    {
                      name: 'Fourth level A',
                    },
                    {
                      name: 'Fourth level B',
                    }
                  ]
                },
              ]
            }
          ]
        },
        {
          color: '#DA59FF',
          name: 'First level B',
          children: [
            {
              name: 'Second level B',
              children: [
                {
                  name: 'Third level B',
                  children: [
                    {
                      name: 'Fourth level A',
                    }
                  ]
                },
              ]
            }
          ]
        },
        {
          name: 'First level C',
          color: '#FA935A',
          children: [
            {
              name: 'Second level C',
              children: [
                {
                  name: 'Third level C',
                  children: [
                    {
                      name: 'Fourth level A',
                    },
                    {
                      name: 'Fourth level B',
                    }
                  ]
                },
              ]
            }
          ]
        },
      ]
    };

    // set the dimensions and margins of the diagram
    var margin = { top: 40, right: 0, bottom: 50, left: 0 };
    var width = 500;
    var height = 500;
    var diameter = 500 - margin.top - margin.bottom;
    var duration = 300;


    var tree = d3.layout.tree()
      .size([height, width - 160]);

    var cluster = d3.layout.cluster()
      .size([height, width - 160]);

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

    // var straight = d => "M" + d.target.x + "," + d.target.y
    //  + "V" + d.source.y + "H" + d.source.x;

    var straight = d => "M" + d.source.x + "," + d.source.y
      + "H" + d.target.x + "V" + d.target.y;
		
    var radialTree = d3.layout.tree()
      .size([360, diameter / 2])
      .separation(function (a, b) {
        return (a.parent == b.parent ? 1 : 2) / a.depth;
      });
      

    var radialCluster = d3.layout.cluster()
      .size([360, diameter / 2])
      .separation(function (a, b) {
        return (a.parent == b.parent ? 1 : 2) / a.depth;
      });

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


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


    // var root = getData(),
    var nodes = cluster.nodes(root);
    var links = cluster.links(nodes);

    var link = svg.selectAll('.link')
      .data(links)
      .enter()
      .append('path')
      .attr('class', 'link')
      .style('stroke', '#8da0cb')
      .attr('d', diagonal);

    var node = svg.selectAll('.node')
      .data(nodes)
      .enter()
      .append('g')
      
      .attr('class', 'node')
      
      .attr('transform', function (d) {
        return 'translate(' + d.x + ',' + d.y + ')';
      })

    node.append('circle')
      .attr('r', 4.5)
      .style('stroke', '#e41a1c');
/*
            node.append('text')
                .attr('dx', function (d) { return d.children ? -8 : 8; })
                .attr('dy', 3)
                .style('text-anchor', function (d) { return d.children ? 'end' : 'start'; })
                .text(function (d) { return d.name; });
        */
    // renderTree();
  /* set the CSS */
  body {
    background-color: #181833;
  }

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

  .node text {
    font: 12px sans-serif;
    fill: #ccc;
  }

  /* .node--internal text {
    text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
  }*/

  .link {
    fill: none;
    stroke: #ccc;
    stroke-width: 2px;
    opacity: 0.75;
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<meta charset='utf-8'>

  <button onclick='transitionToRadialTree()'>Radial</button>
  <button onclick='transitionToTree(false)'>Tree</button>
  <button onclick='transitionToTree(true)'>straight Tree</button>

  <svg id='hostElement'></svg>

但效果不佳的是另一个问题,即如何在不同表示之间平滑过渡。原因是,如果标准转换可以“轻松”找出如何将初始路径的“结构”与目标路径匹配,则两条任意路径之间的转换就是平滑的。您的两个示例都满足该标准,因为它们都使用基于三次贝塞尔曲线的类似路径。为此,您可能正在研究类似https://github.com/pbeshai/d3-interpolate-path的东西(但我认为这个库适用于 d3.v4)


推荐阅读