我一直在尝试通过阅读一些教程来学习d3,并决定构建一个气泡图,其中包括d3力以用于碰撞以及拖动和动态更新基础数据
基本上,我想要实现的是我通过增加a来每秒更改一次'a'的value属性,并期望'a'的气泡增加并推动周围的其他气泡
const circles = this.svg.selectAll('circle').data(this.state.data);
const node = circles
.enter()
.append('g')
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended))
.on('mouseover', d => {
tooltip.html(d.name + ' ' + d.value);
return tooltip.style('visibility', 'visible')
})
.on('mouseout', () => tooltip.style('visibility', 'hidden'));
node.append('circle')
.attr('r', d => d.value)
.style('fill', 'limegreen');
node.append('text')
.text(d => d.name);
我的完整尝试是在这里:https://codepen.io/luanped/pen/rojged?editors=0010
运行它时,您会看到前4秒钟几乎是正确的,“ a”会增加并推动其他气泡(但是将鼠标悬停在气泡上,左下方的工具提示将继续显示初始值,而不是新更新的值)
我猜测某种原因我没有正确地进行内部元素(g内的圆)的过渡-从某种意义上说,尽管我对绘制的半径进行了动画处理,但基础数据模型仍然认为它是原始值?因此,碰撞边界和错误的工具提示
任何帮助将不胜感激
谢谢
答案 0 :(得分:1)
从我所看到的有两个问题:
对于其中一个,最简单的解决方案是选择一个比默认值低的alpha衰减率,这与内存不足类似0.0228。您的模拟正在快速衰减:在您完成更新数值之前,它会先冷却下来,尝试使用较低的值:
.alphaDecay(0.001);
对于两个人来说,这个问题更加微妙。但是,如果您使用console.log(newData)
,则可以看到未为第一个节点分配x
,y
,vx
或vy
等属性数据数组中的其他项目:
这是因为新数据不是强制布局的一部分。并且,当您更新SVG圆圈和文本上的绑定数据时:
const circles = this.svg.selectAll('circle').data(this.state.data);
const labels = this.svg.selectAll('text').data(this.state.data);
...
circles.transition(t).attr('r', d => d.value);
labels.transition(t).text(d => d.name + ' ' + d.value);
您不会更新设置了鼠标悬停事件的g
上的数据,因此它永远不会改变。
我们可以一次性更新父g
,circle
,text
和力布局上的数据,我们可以通过更新第一个对象来实现数据数组而不是创建新对象:
Rx.Observable.interval(1000)
.take(10)
.subscribe(() => {
const newData = this.state.data;
newData[0].value += 10;
this.setState({ data: newData });
});
之所以可行,是因为在将数据绑定到选择或将节点分配给强制布局时,选择和强制布局都将使用对数据数组中对象的引用(而不是对数据数组本身的引用)。如果更新数据数组中的对象,则会更新力布局的节点和绑定到选区的数据(这在d3力示例中很常见,其中tick函数不会使用.data()
更新数据在重新放置元素之前)。但是,如果替换数据数组中的对象,则不会更新强制布局中的引用或对新数据项的选择。您可以使用simulation.nodes()
更新力的节点,但您希望保留vx
,vy
,x
和y
属性(否则,它们将默认为新值)。
这里是updated pen。
对于其他选项,您还可以在每次绘制时重新启动仿真-这将使Alpha衰减保持默认值。
当然,您也不想创建多余的强制布局/工具提示,如注释中所述