D3力导向布局-链接距离优先级

8
使用d3中的力导向布局,如何将链接距离设为优先级,并仍保持良好的图形布局?
如果我指定动态链接距离,但保持默认电荷,则我的图形距离会被电荷函数改变一些,不再是准确的距离。

enter image description here

然而,如果我去掉这个费用,图表会变成这样:

enter image description here

任何建议都将不胜感激!


2
请点击此处查看likDistance和linkStrength的示例代码:http://bl.ocks.org/sathomas/774d02a21dc1c714def8 这对你应该会有帮助。 - Cyril Cherian
1
感谢 Cyril 的推荐。您也可以在我的网站上阅读我的书的全部内容,包括关于 D3 的章节:http://jsDataV.is。 - Stephen Thomas
希望你可能已经看到了这个链接 https://github.com/d3/d3-force/blob/master/README.md#link_distance 对于v4,你可以给出一个链接距离 http://plnkr.co/edit/12D55owSNuDnSH0hNfWu?p=info 但我不确定你是否在寻找上述内容。 - Cyril Cherian
1个回答

3
如果我理解正确,我相信有一个潜在的解决方案。
为了使链接距离准确,需要将电荷和碰撞力设置为零,但正如您的图像所示,节点的间距不考虑其他节点,只考虑它们共享链接的节点。由于d3.force对没有x、y值的节点进行蕨类排列初始化,因此链接和节点会以意外的方式聚集。但是,如果在仿真过程中应用排斥力,则间距得到改善,但距离会失真。
可能的解决方案是最初使用排斥力,因为需要基于链接将节点分成可识别的群集。然后,在将它们分开后,将排斥力降至零,以便唯一应用的力与所需的链接距离有关。
这要求您在图形演变时修改tick函数中的力。这也要求所有链接距离彼此兼容(三角形节点不能有两个角之间隔着100像素,剩下的角与另外两个角之间连接10像素)。
在简单情况下,可以像这样在tick函数中使用这个东西:
var alpha = this.alpha();   // starts at 1 by default, simulation ends at zero

var chargeStrength; // a multiplier for charge strength

if ( alpha > 0.2 ) {
    chargeStrength = (alpha - 0.2 / 0.8); // decrease for the first portion of the simulation
}
else {
    chargeStrength = 0; // leave at zero and give the link distance force time to work without competing forces
}

对于更复杂的可视化,您可以通过减少alphaDecay来增加冷却时间,或者增加它以获得更简单的效果。

我在这里做了一个简单的例子,在可视化结束时记录了距离(在下面的代码片段中增加了alphaDecay以加快速度但牺牲精度,但仍然相当不错),并和期望距离进行比较。

var graph = {
  nodes: d3.range(15).map(Object),
  links: [
    {source:  0, target:  1, distance: 20 },
    {source:  0, target:  2, distance: 40},
    {source:  0, target:  3, distance: 80},
    {source:  1, target:  4, distance: 20},
    {source:  1, target:  5, distance: 40},
    {source:  1, target:  6, distance: 80},
    {source:  2, target:  7, distance: 12},
    {source:  2, target:  8, distance: 8},
    {source:  2, target:  9, distance: 6},
    {source:  3, target:  10, distance: 10},
    {source:  3, target:  11, distance: 10},
    {source:  3, target:  12, distance: 2},
 {source:  3, target:  13, distance: 2},
 {source:  3, target:  14, distance: 2}
  ]
};

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

var color = d3.scaleOrdinal(d3.schemeCategory20);

var simulation = d3.forceSimulation()
    .force("charge", d3.forceManyBody().strength(-30 ))
 .force("link", d3.forceLink().distance(function(d) { return d.distance } ).strength(2) )
    .force("center", d3.forceCenter(width / 2, height / 2))
 .force("collide",d3.forceCollide().strength(0).radius(0))
 .alphaDecay(0.03)
    .velocityDecay(0.4);
 
 
 
  var link = svg.append("g")
      .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")
      .attr("stroke-width", 1);

  var node = svg.append("g")
     .attr("class", "nodes")
    .selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
     .attr("r", 3)
   
 simulation
      .nodes(graph.nodes)
      .on("tick", ticked);

  simulation.force("link")
      .links(graph.links);

  
  
   
  function ticked() {
 
 var alpha = this.alpha();
 var chargeStrength;

    if ( alpha > 0.2 ) {
  chargeStrength = (alpha - 0.2 / 0.8);
 }
 else {
  chargeStrength = 0;
 }

 this.force("charge", d3.forceManyBody().strength( -30 * chargeStrength ))
 
 
    link
        .attr("x1", function(d) { return d.source.x; })
        .attr("y1", function(d) { return d.source.y; })
        .attr("x2", function(d) { return d.target.x; })
        .attr("y2", function(d) { return d.target.y; });

    node
        .attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; });
  
 // validate:
 if (alpha < 0.001) {
  link.each(function(d,i) {
  
   var a = d.source.x - d.target.x;
   var b = d.source.y - d.target.y;
      var c = Math.pow(a*a + b*b, 0.5);
   
   console.log("specified length: " + graph.links[i].distance + ", realized distance: " + c );
  })
 }
  }
.links line {
  stroke: #999;
  stroke-opacity: 0.6;
}

.nodes circle {
  stroke: #fff;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>

根据图形的复杂程度,您可能需要调整冷却时间、斥力强度以及在alpha冷却时如何改变它、velocityDecay(可能在tick函数中修改它)和/或距离力本身。


这是一个很好的解决方案!我从未想过修改运行中的力。我一定会记住这个方法,因为它在其他应用中也可能非常有用。赏金非常值得获得! - altocumulus
然而,我无法理解的一件事是,在模拟的初始设置中,您正在覆盖“link”力。由于只有名称的最后一个力将被使用,这似乎有些多余。您能否为此提供一些解释? - altocumulus
这是我犯的一个相当严重的疏忽,不确定为什么会这样。感谢您指出。 - Andrew Reid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接