在“径向”图表中编辑D3 SVG的链接位置

时间:2018-09-28 13:52:55

标签: javascript d3.js

我遇到的问题是我没有成功解决,在这种情况下,我想将链接从矩形的中间开始移动到末端,以便PUB-2和PUB-3不要互相交叉。如果可能的话,请展开第一级并默认关闭另一级。

有什么主意吗?这是源代码https://bl.ocks.org/mbostock/4339083(d3网站)和有效的代码笔(https://codepen.io/andrea06590/pen/mzJmpE),在其中我使用代码行+ 126行上的链接间距将原始圆更改为矩形:

  nodeEnter.append("rect")
      .attr("width", 75)
      .attr("height", 20)
      .attr("x", -10)
      .attr("y", -10)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "red"; });

D3 Tree Chart

2 个答案:

答案 0 :(得分:6)

以下是修复链接的方法:

首先,更改矩形的位置,以使其在标准链接节点图中的圆的中心居中。我没有在update函数中对值进行硬编码,而是在对象中配置了矩形尺寸:

var rect = {l: 60, w: 20};

编辑矩形属性以使用rect中的值,并通过将xy值偏移该矩形的一半长度和一半宽度,使矩形在节点上居中。矩形。

nodeEnter.append("rect")
  .attr("width", rect.l)  // long dimension
  .attr("height", rect.w) // short dimension
  .attr("x", -rect.l/2)   // offset by half the long dimension
  .attr("y", -rect.w/2)   // offset by half the short dimension
  .style("fill", function(d) {
    return d._children ? "lightsteelblue" : "red";
  });

要修复链接,我们需要将其开始和结束位置更改相同的偏移量。由于退出和进入链接都使用源节点的位置,因此我们唯一需要担心的链接就是更新选择:

link.transition()
  .duration(duration)
  .attr("d", diagonal);

diagonal采用以下形式的对象

{ source: { x: 123, y: 456 }, target: { x: 234, y: 567 } }

在选择更新的情况下,数据项已处于此配置中,因此调用diagonal(d)可以正常工作。由于我们要更改坐标以包括矩形的偏移量,因此必须中断diagonal调用。请注意,投影会切换xy坐标,因此我们必须更改y的值:

  link.transition()
    .duration(duration)
    .attr("d", function(d){
      return diagonal({
      source: {x: d.source.x, y: d.source.y + rect.l/2}, // add half the rectangle length
      target: {x: d.target.x, y: d.target.y - rect.l/2}  // minus half the rectangle length
    })
  });

我还通过删除添加的偏移量并将文本锚点设置为“中间”来使文本在矩形中居中。

结果如下:

var pubs =
{
    "name": "AUT-1",
    "children": [
        {
            "name": "PUB-1","children": [
                {"name": "AUT-11","children": [
                    {"name": "AFF-111"},
                    {"name": "AFF-112"}
                ]},
                {"name": "AUT-12","children": [
                    {"name": "AFF-121"}
                ]},
                {"name": "AUT-13","children": [
                    {"name": "AFF-131"},
                    {"name": "AFF-132"}
                ]},
                {"name": "AUT-14","children": [
                    {"name": "AFF-141"}
                ]}
            ]
        },
        {
            "name": "PUB-2","children": [
                {"name": "AUT-21"},
                {"name": "AUT-22"},
                {"name": "AUT-23"},
                {"name": "AUT-24"},
                {"name": "AUT-25"},
                {"name": "AUT-26"},
                {"name": "AUT-27"},
                {"name": "AUT-28","children":[
                    {"name": "AFF-281"},
                    {"name": "AFF-282"},
                    {"name": "AFF-283"},
                    {"name": "AFF-284"},
                    {"name": "AFF-285"},
                    {"name": "AFF-286"}
                ]}
            ]
        },
        {"name": "PUB-3"},
        {
            "name": "PUB-4","children": [
                {"name": "AUT-41"},
                {"name": "AUT-42"},
                {"name": "AUT-43","children": [
                    {"name": "AFF-431"},
                    {"name": "AFF-432"},
                    {"name": "AFF-433"},
                    {"name": "AFF-434","children":[
                        {"name": "ADD-4341"},
                        {"name": "ADD-4342"},
                    ]}
                ]},
                {"name": "AUT-44"}
            ]
        },
        {
            "name": "PUB-5","children": [
                {"name": "AUT-51","children":[
                    {"name": "AFF-511"},
                    {"name": "AFF-512"},
                    {"name": "AFF-513"},
                    {"name": "AFF-514"},
                    {"name": "AFF-515"},
                    {"name": "AFF-516"}
                ]},
                {"name": "AUT-52"},
                {"name": "AUT-53"},
                {"name": "AUT-54"},
                {"name": "AUT-55","children":[
                    {"name": "AFF-551"},
                    {"name": "AFF-552"},
                    {"name": "AFF-553"},
                    {"name": "AFF-554"}
                ]},
                {"name": "AUT-56"},
                {"name": "AUT-57"},
                {"name": "AUT-58"},
                {"name": "AUT-59"},
                {"name": "AUT-591"},
                {"name": "AUT-592"},
                {"name": "AUT-593"},
                {"name": "AUT-594"},
                {"name": "AUT-595"},
                {"name": "AUT-596"}
            ]
        },
        {
            "name": "PUB-6","children": [
              {"name": "AUT-61","children":[
                  {"name": "AFF-611"},
                  {"name": "AFF-612"},
                  {"name": "AFF-613"},
                  {"name": "AFF-614","children":[
                      {"name": "ADD-6141"},
                      {"name": "ADD-6142"},
                  ]}
              ]},
              {"name": "AUT-62"},
              {"name": "AUT-63"},
              {"name": "AUT-64"},
              {"name": "AUT-65"},
              {"name": "AUT-66"},
              {"name": "AUT-67"},
              {"name": "AUT-68"},
              {"name": "AUT-69"}
            ]
        }
    ]
};

var diameter = 800;

var margin = {top: 20, right: 120, bottom: 20, left: 120},
    width = diameter,
    height = diameter;
    
var i = 0,
    duration = 350,
    root;

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

var diagonal = 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(" + diameter / 2 + "," + diameter / 2 + ")");

var rect = {l: 60, w: 20}

root = pubs;
root.x0 = height / 2;
root.y0 = 0;

update(root);

d3.select(self.frameElement).style("height", "800px");

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root),
      links = tree.links(nodes),
      offset = nodes[0].x

  // Update the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .on("click", click);

  nodeEnter.append("rect")
      .attr("width", rect.l)
      .attr("height", rect.w)
      .attr("x", -rect.l/2)
      .attr("y", -rect.w/2)
      .style("fill", function(d) {
        return d._children ? "lightsteelblue" : "red";
      });

  nodeEnter.append("text")
      .attr("x", 0)
      .attr("dy", ".35em")
      .attr("text-anchor", "middle")
      //.attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)translate(-" + (d.name.length * 8.5)  + ")"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })

  // nodeUpdate.select("circle")
  //     .attr("r", 4.5)
  //     .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });

  nodeUpdate.select("text")
      .style("fill-opacity", 1)
      .attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)"; });

  // TODO: appropriate transform
  var nodeExit = node.exit().transition()
      .duration(duration)
      //.attr("transform", function(d) { return "diagonal(" + source.y + "," + source.x + ")"; })
      .remove();

  // nodeExit.select("circle")
  //     .attr("r", 1e-6);

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
      });

  // Transition links to their new position.
  link.transition()
      .duration(duration)
      .attr("d", function(d){
      return diagonal({
      source: {x: d.source.x, y: d.source.y+rect.l/2}, 
      target: {x: d.target.x, y: d.target.y-rect.l/2}
      })
  });

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
      .duration(duration)
      .attr("d", function(d) {
        var o = {x: source.x, y: source.y};
        return diagonal({source: o, target: o});
      })
      .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  
  update(d);
}

// Collapse nodes
function collapse(d) {
  if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
}
.node {
  cursor: pointer;
}

.node circle {
  fill: #fff;
  stroke: steelblue;
  stroke-width: 1.5px;
}

.node text {
  font: 10px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
}
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>

我认为直线在此可视化效果上看起来更好-例如:

var pubs =
{
    "name": "AUT-1",
    "children": [
        {
            "name": "PUB-1","children": [
                {"name": "AUT-11","children": [
                    {"name": "AFF-111"},
                    {"name": "AFF-112"}
                ]},
                {"name": "AUT-12","children": [
                    {"name": "AFF-121"}
                ]},
                {"name": "AUT-13","children": [
                    {"name": "AFF-131"},
                    {"name": "AFF-132"}
                ]},
                {"name": "AUT-14","children": [
                    {"name": "AFF-141"}
                ]}
            ]
        },
        {
            "name": "PUB-2","children": [
                {"name": "AUT-21"},
                {"name": "AUT-22"},
                {"name": "AUT-23"},
                {"name": "AUT-24"},
                {"name": "AUT-25"},
                {"name": "AUT-26"},
                {"name": "AUT-27"},
                {"name": "AUT-28","children":[
                    {"name": "AFF-281"},
                    {"name": "AFF-282"},
                    {"name": "AFF-283"},
                    {"name": "AFF-284"},
                    {"name": "AFF-285"},
                    {"name": "AFF-286"}
                ]}
            ]
        },
        {"name": "PUB-3"},
        {
            "name": "PUB-4","children": [
                {"name": "AUT-41"},
                {"name": "AUT-42"},
                {"name": "AUT-43","children": [
                    {"name": "AFF-431"},
                    {"name": "AFF-432"},
                    {"name": "AFF-433"},
                    {"name": "AFF-434","children":[
                        {"name": "ADD-4341"},
                        {"name": "ADD-4342"},
                    ]}
                ]},
                {"name": "AUT-44"}
            ]
        },
        {
            "name": "PUB-5","children": [
                {"name": "AUT-51","children":[
                    {"name": "AFF-511"},
                    {"name": "AFF-512"},
                    {"name": "AFF-513"},
                    {"name": "AFF-514"},
                    {"name": "AFF-515"},
                    {"name": "AFF-516"}
                ]},
                {"name": "AUT-52"},
                {"name": "AUT-53"},
                {"name": "AUT-54"},
                {"name": "AUT-55","children":[
                    {"name": "AFF-551"},
                    {"name": "AFF-552"},
                    {"name": "AFF-553"},
                    {"name": "AFF-554"}
                ]},
                {"name": "AUT-56"},
                {"name": "AUT-57"},
                {"name": "AUT-58"},
                {"name": "AUT-59"},
                {"name": "AUT-591"},
                {"name": "AUT-592"},
                {"name": "AUT-593"},
                {"name": "AUT-594"},
                {"name": "AUT-595"},
                {"name": "AUT-596"}
            ]
        },
        {
            "name": "PUB-6","children": [
              {"name": "AUT-61","children":[
                  {"name": "AFF-611"},
                  {"name": "AFF-612"},
                  {"name": "AFF-613"},
                  {"name": "AFF-614","children":[
                      {"name": "ADD-6141"},
                      {"name": "ADD-6142"},
                  ]}
              ]},
              {"name": "AUT-62"},
              {"name": "AUT-63"},
              {"name": "AUT-64"},
              {"name": "AUT-65"},
              {"name": "AUT-66"},
              {"name": "AUT-67"},
              {"name": "AUT-68"},
              {"name": "AUT-69"}
            ]
        }
    ]
};

var diameter = 800;

var margin = {top: 20, right: 120, bottom: 20, left: 120},
    width = diameter,
    height = diameter;
    
var i = 0,
    duration = 350,
    root;

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


var diagonal_extras = {
  path: {
      // diagonal line
      direct: function(p){
        return [ p.source, p.target ];
      }

      // this is also the default path in radial trees
      , l_shape: function(p){
        return [ p.source, { x: p.target.x, y: p.source.y }, p.target ];
      }

      , l_shape_2: function(p){
        return [ p.source, { x: p.source.x, y: p.target.y }, p.target ];
      }

      , dogleg: function(p){
        return [ p.source,
        {   x: p.source.x,
            y: (p.source.y + p.target.y) / 2
        },
        {   x: (p.source.x + p.target.x) / 2,
            y: (p.source.y + p.target.y) / 2
        },
        {   x: p.target.x,
            y: (p.source.y + p.target.y) / 2
        }, p.target];
      }

      , dogleg_2: function(p){
        return [ p.source,
        {   x: (p.source.x + p.target.x) / 2,
            y: p.source.y
        },
        {   x: (p.source.x + p.target.x) / 2,
            y: (p.source.y + p.target.y) / 2
        },
        {   x: (p.source.x + p.target.x) / 2,
            y: p.target.y
        }, p.target];
      }

    }

    , polar_obj_to_cart: function(pt){
        var angle = pt.x / 180 * Math.PI;
        return [pt.y * Math.cos(angle), pt.y * Math.sin(angle)];
    }

    , polar_coords_to_cart: function(xy){
        var angle = xy[0] / 180 * Math.PI;
        return [ xy[1] * Math.cos(angle), xy[1] * Math.sin(angle)];
    }

}

diagonal_extras.right_angle = function() {

      var projection = d3.svg.diagonal().projection()
      , path_type = 'dogleg'
      ;

      function diagonal(d) {
        return diagonal.path_maker( diagonal_extras.path[ diagonal.path_type() ](d) );
      }

      diagonal.path_maker = function( pathData ) {
        return "M" + pathData.map( projection ).join(' ');
      };

      diagonal.valid_path_types = function() {
        return Object.keys( diagonal_extras.path );
      };

      diagonal.path_type = function(x) {
        if (! arguments.length) { return path_type; }
        if ( diagonal_extras.path[ x ] ) {
          path_type = x;
          return diagonal;
        }
        throw new Error( x + ' is not a valid path type' );
      };

      diagonal.projection = function(x) {
        if (!arguments.length) { return projection; }
        projection = x;
        return diagonal;
      };

      diagonal.path = function(x) {
        if (!arguments.length) { return path; }
        path = x;
        return diagonal;
      };

      diagonal.draw = function(d) {
        return diagonal(d);
      };

      return diagonal;
}
diagonal_extras.radial = function() {
      var diagonal = diagonal_extras.right_angle()
      , projection = function(pt){
        return [ pt.x, pt.y ];
      };

      diagonal.path_type('direct');

      diagonal.projection = function(x) {
        if (!arguments.length) { return projection; }
        projection = x;
        return diagonal;
      };

      diagonal.path_maker = function( pathData ) {

        var projected = pathData.map( function(x){ return projection(x); })
        , pl = projected.length
        , points
        , prev_angle
        ;

        // direct link:
        if ( 2 === pl ) {
          return 'M' + projected.map( function(x){ return diagonal_extras.polar_coords_to_cart(x); }).join(' ');
        }

        points = projected.map( function(obj){
          return { angle: obj[0] / 180 * Math.PI, radius: obj[1] };
        });

        return "M" + points.map( function(pt){
          var str = '';
          if ( prev_angle ) {
            if ( prev_angle === pt.angle ) {
              // draw a straight line
              str = 'L';
            }
            else {
              // draw an arc to the new radius and angle
              str = 'A' + pt.radius + ',' + pt.radius
              // x axis rotation
              + " 0 "
              // large arc flag
              + " 0,"
              // sweep
              + ( pt.angle > prev_angle ? 1 : 0) + " ";

            }
          }
          prev_angle = pt.angle;
          return str + pt.radius * Math.cos(pt.angle) + "," + pt.radius * Math.sin(pt.angle);

        }).join(' ');

      };
      return diagonal;
}

var diagonal = diagonal_extras.radial()
  .path_type('dogleg')
  .projection(function(d){ return [ d.x - 90, d.y ]; });

var svg = d3.select("body").append("svg")
    .attr("width", width )
    .attr("height", height )
  .append("g")
    .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")rotate(90)"); // note the altered rotation

var rect = {l: 60, w: 20}

root = pubs;
root.x0 = height / 2;
root.y0 = 0;

//root.children.forEach(collapse); // start with all children collapsed
update(root);

d3.select(self.frameElement).style("height", "800px");

function update(source) {

  // Compute the new tree layout.
  var nodes = tree.nodes(root),
      links = tree.links(nodes),
      offset = nodes[0].x

  // Normalise angles so that the root is horizontal
  nodes.forEach(function(d) {
    d.x -= offset;
    if (d.x < 180) { d.x += 360 }
  });
  
  // Update the nodes…
  var node = svg.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = ++i); });

  // Enter any new nodes at the parent's previous position.
  var nodeEnter = node.enter().append("g")
      .attr("class", "node")
      .on("click", click);

  nodeEnter.append("rect")
      .attr("width", rect.l)
      .attr("height", rect.w)
      .attr("x", -rect.l/2)
      .attr("y", -rect.w/2)
      .style("fill", function(d) {
        return d._children ? "lightsteelblue" : "red";
      });

  nodeEnter.append("text")
      .attr("x", 0)
      .attr("dy", ".35em")
      .attr("text-anchor", "middle")
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })

  nodeUpdate.select("text")
      .style("fill-opacity", 1)
      .attr("transform", function(d) {
        return ( d.x < 270 || d.x > 450 )
        ? 'rotate(180)'
        : ''
       });

  // TODO: appropriate transform
  var nodeExit = node.exit().transition()
      .duration(duration)
      //.attr("transform", function(d) { return "diagonal(" + source.y + "," + source.x + ")"; })
      .remove();

  nodeExit.select("text")
      .style("fill-opacity", 1e-6);

  // Update the links…
  var link = svg.selectAll("path.link")
      .data(links, function(d) { return d.target.id; });

  // Enter any new links at the parent's previous position.
  link.enter().insert("path", "g")
      .attr("class", "link")
      .attr("d", function(d) {
        var o = {x: source.x0, y: source.y0};
        return diagonal({source: o, target: o});
      });

  // Transition links to their new position.
  link.transition()
      .duration(duration)
      .attr("d", function(d){
      return diagonal({
      source: {x: d.source.x, y: d.source.y+rect.l/2}, 
      target: {x: d.target.x, y: d.target.y-rect.l/2}
      })
  });

  // Transition exiting nodes to the parent's new position.
  link.exit().transition()
      .duration(duration)
      .attr("d", function(d) {
        var o = {x: source.x, y: source.y};
        return diagonal({source: o, target: o});
      })
      .remove();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}

// Toggle children on click.
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
  
  update(d);
}

// Collapse nodes
function collapse(d) {
  if (d.children) {
      d._children = d.children;
      d._children.forEach(collapse);
      d.children = null;
    }
}
.node {
  cursor: pointer;
}

.node text {
  font: 10px sans-serif;
}

.link {
  fill: none;
  stroke: #ccc;
  stroke-width: 1.5px;
}
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>

答案 1 :(得分:-1)

我设法使用偏移量(nodes [0] .x)和此代码使其“起作用”:

if (nodes[0].x > 180) {
  nodes[0].x = nodes[0].x - 90
} 
else {
  nodes[0].x = nodes[0].x + 90
}

但是在这种情况下不是100%水平的