javascript - 具有特定目标目的地的 D3js 强制模拟

标签 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

最佳答案

我可能在这里遗漏了一些东西,但是修改 x 定位力(和 y)的强度可以帮助确保您的排序正确完成。 forceX或forceY的默认强度为0.1,强度实现如下:

a value of 0.1 indicates that the node should move a tenth of the way from its current x-position to the target x-position with each application. Higher values moves nodes more quickly to the target position, often at the expense of other forces or constraints. A value outside the range [0,1] is not recommended. (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 个圆圈会导致问题:圆圈将无法在如此狭窄的区域中改变位置:可能需要进一步修改力,但更大尺寸的图可能更受欢迎(与小片段 View 相反)也是。


另一种选择是在整个模拟成熟过程中修改力:从强大的 x 力强度和低碰撞力强度开始,然后缓慢增加碰撞,以便将随后的推挤降至最低。这是一个 example修改刻度函数中的力——虽然,这个例子是为了链接长度而不是在 x 上的位置——但适应应该不会太难。

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

关于javascript - 具有特定目标目的地的 D3js 强制模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56366666/

相关文章:

javascript - 如何在Crossfilter中正确创建维度和组?

jquery - 具有多个数据和各个数据点作为图例的堆叠条形图

javascript - 格式化 Highcharts - 工具提示中的时间戳、天 x 轴

javascript - d3.js 在鼠标悬停时更改折线图点的颜色和大小

python - 绘制时间序列数据

javascript - 类型错误 : Cannot read property 'then' of undefined Dialogflow Promise

javascript - 使用 JavaScript 将类添加到特定的 href 元素

javascript - RecaptchaState 未定义

javascript - 将数字格式化为带有 SI 前缀的整数

html - html5 canvas 散点图上的 D3.js 鼠标悬停