我正在尝试实现一个简单的力布局,其中可以动态添加和删除节点(没有链接)。我成功地在 D3 版本 3 中实现了这个概念,但我无法将其转换为版本 4。添加和更新节点后,模拟冻结,传入的圆圈绘制在 svg 的左上角。有人知道为什么会这样吗?谢谢你的帮助 :)
我的概念基于这个解决方案:将新节点添加到力导向布局 https://stackoverflow.com/questions/9539294/adding-new-nodes-to-force-directed-layout/9544595#9544595
JSFiddle:d3 v3 中的工作代码 https://jsfiddle.net/Lvkg1avo/
/* 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();
}
}
}