I am trying to implement a simple force layout in which nodes (without links) can be dynamically added and removed. I was successful in implementing the concept in D3 version 3,
Please see plunkr example
I'm using canvas, but the theory is the same:
You have to give your new array of nodes and links to D3 core functions first, before adding them to the original array.
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();
}
Afterwards, tell D3 to restart with the new data.
When D3 calls your tick()
function, it already knows what coordinates you need to apply to your SVG elements.
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