D3 具有非树数据的可折叠力有向图 - 链接对齐

Posted

技术标签:

【中文标题】D3 具有非树数据的可折叠力有向图 - 链接对齐【英文标题】:D3 Collapsible force directed graph with non-tree data - link alignment 【发布时间】:2020-10-15 21:52:39 【问题描述】:

如果您看到现有代码,https://jsfiddle.net/sheilak/9wvmL8q8 第一次加载图表时,连接父节点和子节点的链接来自父节点的边框,但一旦折叠和展开,您可以看到相同的链接来自中心的父节点。我不想链接到父节点的中心。

代码

var width = 960,
height = 500;

var svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height);

var force = d3.layout.force()
  .size([width, height])
  //gravity(0.2)
  .linkDistance(height / 6)
  .charge(function(node) 
    if (node.type !== 'ORG') return -2000;
    return -30;
  );

// build the arrow.
svg.append("svg:defs").selectAll("marker")
  .data(["end"]) // Different link/path types can be defined here
  .enter().append("svg:marker") // This section adds in the arrows
  .attr("id", function(d) 
    return d;
  )
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 12)
  .attr("refY", 0)
  .attr("markerWidth", 9)
  .attr("markerHeight", 5)
  .attr("orient", "auto")
  .attr("class", "arrow")
  .append("svg:path")
  .attr("d", "M0,-5L10,0L0,5");

  var json = dataset;

  var edges = [];
  json.edges.forEach(function(e) 
    var sourceNode = json.nodes.filter(function(n) 
        return n.id === e.from;
      )[0],
      targetNode = json.nodes.filter(function(n) 
        return n.id === e.to;
      )[0];

    edges.push(
      source: sourceNode,
      target: targetNode,
      value: e.Value
    );
  );

  for(var i = 0; i < json.nodes.length; i++) 
    json.nodes[i].collapsing = 0;
    json.nodes[i].collapsed = false;
  

  var link = svg.selectAll(".link");
  var node = svg.selectAll(".node");

  force.on("tick", function() 
    // make sure the nodes do not overlap the arrows
    link.attr("d", function(d) 
      // Total difference in x and y from source to target
      diffX = d.target.x - d.source.x;
      diffY = d.target.y - d.source.y;

      // Length of path from center of source node to center of target node
      pathLength = Math.sqrt((diffX * diffX) + (diffY * diffY));

      // x and y distances from center to outside edge of target node
      offsetX = (diffX * d.target.radius) / pathLength;
      offsetY = (diffY * d.target.radius) / pathLength;

      return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
    );

    node.attr("transform", function(d) 
      return "translate(" + d.x + "," + d.y + ")";
    );
  );

update();

function update()
  var nodes = json.nodes.filter(function(d) 
    return d.collapsing == 0;
  );

  var links = edges.filter(function(d) 
    return d.source.collapsing == 0 && d.target.collapsing == 0;
  );

  force
    .nodes(nodes)
    .links(links)
    .start();

  link = link.data(links)

  link.exit().remove();

  link.enter().append("path")
    .attr("class", "link")
    .attr("marker-end", "url(#end)");

  node = node.data(nodes);

  node.exit().remove();

  node.enter().append("g")
    .attr("class", function(d) 
      return "node " + d.type
    );

  node.append("circle")
    .attr("class", "circle")
    .attr("r", function(d) 
      d.radius = 30;
      return d.radius
    ); // return a radius for path to use 

  node.append("text")
    .attr("x", 0)
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .attr("class", "text")
    .text(function(d) 
      return d.type
    );

  // On node hover, examine the links to see if their
  // source or target properties match the hovered node.
  node.on('mouseover', function(d) 
    link.attr('class', function(l) 
      if (d === l.source || d === l.target)
        return "link active";
      else
        return "link inactive";
    );
  );

  // Set the stroke width back to normal when mouse leaves the node.
  node.on('mouseout', function() 
    link.attr('class', "link");
  )
  .on('click', click);

  function click(d) 
    if (!d3.event.defaultPrevented) 
      var inc = d.collapsed ? -1 : 1;
      recurse(d);

      function recurse(sourceNode)
        //check if link is from this node, and if so, collapse
        edges.forEach(function(l) 
          if (l.source.id === sourceNode.id)
            l.target.collapsing += inc;
            recurse(l.target);
          
        );
      
      d.collapsed = !d.collapsed;
          
    update();
  

【问题讨论】:

【参考方案1】:

有两种简单的方法可以解决这个问题,只需对现有代码进行少量修改。

当你定义你的路径数据时,因为每个链接的目标已经偏移了,所以前半部分已经完成了:

  return "M" + d.source.x + "," + d.source.y + "L" + (d.target.x - offsetX) + "," + (d.target.y - offsetY);
);

您可以很容易地将其扩展为从源节点偏移,只需将偏移量添加到 sourceX 和 sourceY 为here。这样,节点是在链接上方还是在链接下方都没有关系,因为它们不会重叠。 (可能会有轻微的重叠,因此您可以在偏移量中添加一两个像素以说明链接宽度)。

第二个选项在 d3v4+ 中可能更容易,因为它具有 selection.raise() (docs)。此方法将所选项目提升到 SVG 的顶部(作为父元素的最后一个子元素)。相当于:

 this.parentNode.appendChild(this);

在您的点击函数中,更新图表后,我们可以使用这条线来确保被点击的节点上升到顶部(在链接上方)。这是that 的示例。

【讨论】:

以上是关于D3 具有非树数据的可折叠力有向图 - 链接对齐的主要内容,如果未能解决你的问题,请参考以下文章

d3中力有向图的语义缩放

将由多条线组成的标签垂直居中于 D3 力有向图中的节点上

d3.js - 在力有向图组上添加背景矩形

D3 围绕一组圆圈绘制一个外壳

在 D3js 力图中添加和删除节点

D3.js 强制有向图,通过使边缘相互排斥来减少边缘交叉