I am trying to create a swarm graph that transitions between a few states, but have hit a few roadblocks. The best way I've found to set this up is to cluster my nodes in the center and then isolate forceX and forceY based on my data. However, I am finding that once I have done that, it is impossible to 'reset' the whole swarm and just bring every node back to the center. It seems almost as if every node starts moving relative to the last isolated force even if I add forceCenters.
I am admittedly new to d3-force so this may be a dumb question, but I have done a lot of searching with no answers.
var width = 400;
var height = 150;
var radius = 3;
var data = [
{"id":1, "a":1, "b":1, "color":"#ff0000"},
{"id":2, "a":1, "b":2, "color":"#ff0000"},
{"id":3, "a":2, "b":1, "color":"#00ff00"},
{"id":4, "a":2, "b":2, "color":"#00ff00"},
{"id":5, "a":3, "b":1, "color":"#0000ff"},
{"id":6, "a":3, "b":2, "color":"#0000ff"},
];
$(document).ready(function(){
createGraph();
makeForce();
});
var svg;
function createGraph(){
svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("background-color", "#dddddd");
}
var simulation;
function makeForce(){
var nodes=data;
node = svg.append("g").attr("stroke", "#bbb").attr("stroke-width", .5).selectAll(".node");
var attractForce = d3.forceManyBody().strength(20).distanceMax(40).distanceMin(60);
var repelForce = d3.forceManyBody().strength(-10).distanceMax(50).distanceMin(10);
simulation = d3.forceSimulation(nodes)
.alphaDecay(0.03)
// .force("attractForce",attractForce)
.force("repelForce",repelForce)
.force("x", d3.forceX(width/2))
.force("y", d3.forceY(height/2))
.force('collision', d3.forceCollide().radius(function(d) {
return (radius+2);
}))
// .alphaTarget(.1)
.on("tick", ticked);
restart(0);
function restart(split){
if(split==0){
node = node.data(nodes, function(d) { return d.id;});
node.exit().remove();
node = node.enter().append("circle").attr("fill", function(d) { return d.color; }).attr("r", radius).merge(node);
simulation.nodes(nodes);
simulation.alpha(1).restart();
}else if(split==1){
d3.select("#comments").html("Dots split");
node = node.data(nodes, function(d) { return d.id;});
node.exit().remove();
node = node.enter().append("circle").attr("fill", function(d) { return d.color; }).attr("r", radius).merge(node);
// Update and restart the simulation.
simulation.nodes(nodes);
simulation.force("y", d3.forceY(height/2))
.force("A", isolate(d3.forceX(width), function(d) {
return (d.b == 2);
}))
.force("B", isolate(d3.forceX(0), function(d) {
return (d.b == 1);
}))
.on("tick", ticked);
// simulation.alpha(1).restart();
}else if(split==2){
d3.select("svg").style("background-color", "#ffffdd");
d3.select("#comments").html("Nothing happens here, but I'd like to clear out all the forces on the dots and have them return to the center");
node = node.data(nodes, function(d) { return d.id;});
node.exit().remove();
node = node.enter().append("circle").attr("fill", function(d) { return d.color; }).attr("r", radius).merge(node);
// Update and restart the simulation.
simulation.nodes(nodes);
simulation.force("x", d3.forceX(width/2))
.force("y", d3.forceY(width/2))
.on("tick", ticked);
// simulation.alpha(1).restart();
}
function isolate(force, filter) {
var initialize = force.initialize;
force.initialize = function() { initialize.call(force, nodes.filter(filter)); };
return force;
}
}
setTimeout(function(){
restart(1);
}, 1000);
setTimeout(function(){
restart(2);
}, 4000);
function ticked() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
}
}
<head>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
</head>
<body>
<p id="comments">Dots load</p>
</body>
As a secondary question, if anyone can explain to me why forceX()
ing to 0 and the width does not bring the dots to the edges, that would also be useful. I imagine that is rooted in my misunderstanding of the above.