D3js强制仿真具有特定目标位置

时间:2019-05-29 18:37:13

标签: javascript d3.js data-visualization force-layout

我正在尝试进行d3js强制仿真,但我想确保我的节点不会中继错误的信息。

使用以下代码显示节点,但由于力布局的动态特性,它偶尔会将某些节点推出其适当的x坐标位置。

 inOrder(){
  this.simulation
    .force("x", d3.forceX(d => this.xScale(d.value)))
    .force("y",  d3.forceY(this.height / 2))
    .alpha(1).restart();
},

这是发生这种情况的一个令人震惊的例子: 这些数字应按从左到右的顺序排列。 Out of order nodes

我尝试使用节点上的fx属性将位置锁定在适当的位置:

inOrder(){
  this.releases.forEach(x => {
    x.fx = this.xScale(x.value)
  })

  this.simulation
    .force("x", d3.forceX(d => this.xScale(d.value)))
    .force("y",  d3.forceY(this.height / 2))
    .alpha(1).restart();
},

Nodes in order

这可以保留x位置,但是当调用inOrder方法时,节点立即跳到其最终x位置。这破坏了力仿真的流动性和动态性。

有没有办法做到两全其美?也许通过使用.on("end", () => {}).on("tick", () => {})?事件处理程序?

Mike Bostock(https://stackoverflow.com/users/365814/mbostock)和Shan Carter创作了一些作品,这些作品启发了我在这里要做的事情:

在“更改”和“部门总计”选项卡之间单击 https://archive.nytimes.com/www.nytimes.com/interactive/2012/02/13/us/politics/2013-budget-proposal-graphic.html?hp

在“总体图片”和“按行业查看”选项卡之间单击 https://archive.nytimes.com/www.nytimes.com/interactive/2013/05/25/sunday-review/corporate-taxes.html

1 个答案:

答案 0 :(得分:2)

我在这里可能会丢失一些东西,但是对x定位力(和y)的强度进行修改可以帮助确保正确完成您的订购。默认的forceX或forceY强度为0.1,该强度实现如下:

  

值0.1表示每个应用程序节点应从其当前x位置向目标x位置移动十分之一的距离。较高的值通常会以其他力或约束为代价,将节点更快地移动到目标位置。不建议在[0,1]范围之外的值。 (docs

因此,我们可以增加forceX强度,并允许节点在x轴上自由移动,我们可以减小forceY-允许节点更轻松地相互跳动-减小碰撞强度也可能有所帮助。

我没有在下面标记圆圈(而是将它们依次涂上阴影),但是我确实进行了检查以查看它们是否井井有条(在模拟结束时记录到控制台),下面的代码片段仅修改了x和y力(不是碰撞力):

enter image description here

var height = 300;
var width = 500;

var data = d3.range(30).map(function(d,i) {
  return { size: Math.random()+1, index: i}
});

    
var svg = d3.select("body")
  .append("svg")
  .attr("width",width)
  .attr("height",height);
  
var x = d3.scaleLinear()
  .domain([1,30])
  .range([50,width-50]);
  
var color = d3.scaleLinear()
  .domain([0,29])
  .range(["#ccc","#000"])

var simulation = d3.forceSimulation()
    .force("x", d3.forceX(d => x(d.index)).strength(0.20))
    .force("y",  d3.forceY(height / 2).strength(0.05))
    .force("collide", d3.forceCollide().radius(d=> d.size*10))
    .alpha(1).restart();
    
var circles = svg.selectAll(null)
  .data(data)
  .enter()
  .append("circle")
  .attr("r", function(d) { return d.size * 10; })
  .attr("fill", function(d,i) { return color(i); })
    
simulation.nodes(data)
  .on("tick",tick)
  .on("end",verify);
  

function tick() {
  circles
  .attr("cx", function(d) { return d.x; })
  .attr("cy", function(d) { return d.y; })
}

function verify() {
  var n = 0;
  for(var i = 0; i < data.length - 1; i++) {
     if(data[i].x > data[i+1].x) n++;
  }
  console.log(n + " out of place");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

该代码段在500x300的区域中放置了30个圆圈,没有太大的问题:我已经测试了几次,其中0错位。在此处放置100个圆圈将引起问题:圆圈将无法在如此狭窄的区域中更改位置:可能需要对力进行进一步修改,但可能更希望使用较大尺寸的图(而不是小片段视图)


另一种选择是在整个模拟过程中修改力:从强大的x力强度和低的碰撞力强度开始,然后缓慢调高碰撞力,以最大程度地减少后续碰撞。这是example修改tick函数中的力的方法-尽管此示例是针对链接长度而不是x上的位置的-但是适应性不应该太难。

另一种可能性是保持alpha高,直到所有圆在x轴上正确排列,然后开始冷却为止,这又必须在tick函数中发生。