在版本 4 中将节点动态添加到 D3 Force Layout
Posted
技术标签:
【中文标题】在版本 4 中将节点动态添加到 D3 Force Layout【英文标题】:Adding nodes dynamically to D3 Force Layout in version 4 【发布时间】:2017-01-04 14:59:51 【问题描述】:我正在尝试实现一个简单的强制布局,其中可以动态添加和删除节点(没有链接)。我在 D3 版本 3 中成功实现了这个概念,但我无法将其转换为版本 4。添加和更新节点后,模拟冻结,传入的圆圈绘制在 svg 的左上角。有人知道为什么会这样吗?感谢您的帮助:)
我的概念是基于这个解决方案: Adding new nodes to Force-directed layout
JSFiddle: working code in d3 v3
/* Define class */
class Planet
constructor(selector)
this.w = $(selector).innerWidth();
this.h = $(selector).innerHeight();
this.svg = d3.select(selector)
.append('svg')
.attr('width', this.w)
.attr('height', this.h);
this.force = d3.layout.force()
.gravity(0.05)
.charge(-100)
.size([this.w, this.h]);
this.nodes = this.force.nodes();
/* Methods (are called on object) */
update()
/* Join selection to data array -> results in three new selections enter, update and exit */
const circles = this.svg.selectAll('circle')
.data(this.nodes, d => d.id); // arrow function, function(d) return d.y;
/* Add missing elements by calling append on enter selection */
circles.enter()
.append('circle')
.attr('r', 10)
.style('fill', 'steelblue')
.call(this.force.drag);
/* Remove surplus elements from exit selection */
circles.exit()
.remove();
this.force.on('tick', () =>
circles.attr('cx', d => d.x)
.attr('cy', d => d.y);
);
/* Restart the force layout */
this.force.start();
addThought(content)
this.nodes.push( id: content );
this.update();
findThoughtIndex(content)
return this.nodes.findIndex(node => node.id === content);
removeThought(content)
const index = this.findThoughtIndex(content);
if (index !== -1)
this.nodes.splice(index, 1);
this.update();
/* Instantiate class planet with selector and initial data*/
const planet = new Planet('.planet');
planet.addThought('Hallo');
planet.addThought('Ballo');
planet.addThought('Yallo');
这是我将代码翻译成 v4 的意图:
/* Define class */
class Planet
constructor(selector)
this.w = $(selector).innerWidth();
this.h = $(selector).innerHeight();
this.svg = d3.select(selector)
.append('svg')
.attr('width', this.w)
.attr('height', this.h);
this.simulation = d3.forceSimulation()
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(this.w / 2, this.h / 2));
this.nodes = this.simulation.nodes();
/* Methods (are called on object) */
update()
/* Join selection to data array -> results in three new selections enter, update and exit */
let circles = this.svg.selectAll('circle')
.data(this.nodes, d => d.id); // arrow function, function(d) return d.y;
/* Add missing elements by calling append on enter selection */
const circlesEnter = circles.enter()
.append('circle')
.attr('r', 10)
.style('fill', 'steelblue');
circles = circlesEnter.merge(circles);
/* Remove surplus elements from exit selection */
circles.exit()
.remove();
this.simulation.on('tick', () =>
circles.attr('cx', d => d.x)
.attr('cy', d => d.y);
);
/* Assign nodes to simulation */
this.simulation.nodes(this.nodes);
/* Restart the force layout */
this.simulation.restart();
addThought(content)
this.nodes.push( id: content );
this.update();
findThoughtIndex(content)
return this.nodes.findIndex(node => node.id === content);
removeThought(content)
const index = this.findThoughtIndex(content);
if (index !== -1)
this.nodes.splice(index, 1);
this.update();
【问题讨论】:
我目前遇到了类似的问题,但在画布上。我还没有解决方案,但我认为您分享的帖子中突出显示了您的问题:“第二个关键部分是节点和链接数组必须基于 force() -- 否则图表当节点被删除和添加时,模型将不同步。” 您添加了节点,但您没有定义坐标。 【参考方案1】:请see plunkr example
我用的是canvas,但是原理是一样的:
在将它们添加到原始数组之前,您必须先提供 new 节点数组和指向 D3 核心函数的链接。
drawData: function(graph)
var countExtent = d3.extent(graph.nodes,function(d)return d.connections),
radiusScale = d3.scalePow().exponent(2).domain(countExtent).range(this.nodes.sizeRange);
// Let D3 figure out the forces
for(var i=0,ii=graph.nodes.length;i<ii;i++)
var node = graph.nodes[i];
node.r = radiusScale(node.connections);
node.force = this.forceScale(node);
;
// Concat new and old data
this.graph.nodes = this.graph.nodes.concat(graph.nodes);
this.graph.links = this.graph.links.concat(graph.links);
// Feed to simulation
this.simulation
.nodes(this.graph.nodes);
this.simulation.force("link")
.links(this.graph.links);
this.simulation.alpha(0.3).restart();
然后,告诉 D3 使用新数据重新启动。
当 D3 调用您的 tick()
函数时,它已经知道您需要将什么坐标应用于 SVG 元素。
ticked: function()
if(!this.graph)
return false;
this.context.clearRect(0,0,this.width,this.height);
this.context.save();
this.context.translate(this.width / 2, this.height / 2);
this.context.beginPath();
this.graph.links.forEach((d)=>
this.context.moveTo(d.source.x, d.source.y);
this.context.lineTo(d.target.x, d.target.y);
);
this.context.strokeStyle = this.lines.stroke.color;
this.context.lineWidth = this.lines.stroke.thickness;
this.context.stroke();
this.graph.nodes.forEach((d)=>
this.context.beginPath();
this.context.moveTo(d.x + d.r, d.y);
this.context.arc(d.x, d.y, d.r, 0, 2 * Math.PI);
this.context.fillStyle = d.colour;
this.context.strokeStyle =this.nodes.stroke.color;
this.context.lineWidth = this.nodes.stroke.thickness;
this.context.fill();
this.context.stroke();
);
this.context.restore();
Plunkr example
【讨论】:
以上是关于在版本 4 中将节点动态添加到 D3 Force Layout的主要内容,如果未能解决你的问题,请参考以下文章