Reputation: 23
I am trying to update the position of a node, based on a click event.
I can see that the data is updated in console, however the x,y coordinates aren't updating. I was expecting the forceSimulation to update the x,y coordinates as soon as the data changed.
Obviously, I have misunderstood how this works. But if you could state where exactly I was wrong in my intuition, that would really help me out. My thought process was that the force simulation would update the the forceX and forceY coordinates based on the foci_dict.
In console : {name: "b", age: 27, index: 0, x: 45.46420808466252, y: 54.94672336907456, …}
The object was updated, however the coordinates didn't update.
<html>
<body>
<canvas id="bubbles" width="1500" height="500"></canvas>
<button onClick="changeMe()"> clicck me </button>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var height = 500;
var width = 1500;
var canvas = d3.select("#bubbles");
var ctx = canvas.node().getContext("2d");
var r = 5;
var data = [
{ name: "a", age: 27 },
{ name: "a", age: 21 },
{ name: "a", age: 37 },
{ name: "b", age: 23 },
{ name: "b", age: 33 },
{ name: "c", age: 23 },
{ name: "d", age: 33 }
];
var foci_points = {
'a': { x: 50, y: 50 },
'b': { x: 150, y: 150 },
'c': { x: 250, y: 250 },
'd': { x: 350, y: 350 }
};
var simulation = d3.forceSimulation()
.force("x", d3.forceX(function(d){
return foci_points[d.name].x;
}))
.force("y", d3.forceY(function(d){
return foci_points[d.name].y;
})
)
.force("collide", d3.forceCollide(r+1))
.force("charge", d3.forceManyBody().strength(10))
.on("tick", update);
simulation.nodes(data);
function update() {
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
data.forEach(drawNode);
ctx.fill();
}
function changeMe(){
data[0].name="b";
console.log(data);
}
function drawNode(d) {
ctx.moveTo(d.x, d.y);
ctx.arc(d.x, d.y, r, 0, 2 * Math.PI);
}
update();
</script>
</html>
Upvotes: 1
Views: 724
Reputation: 21578
There are two issues you have to address:
The forces will not update automatically just because the underlying data has changed. You have to programmatically re-initialize the forces by calling force.initialize(nodes)
. Given your code this could be somewhat along the following lines:
simulation.force("x").initialize(data); // Get the x force and re-initialize.
simulation.force("y").initialize(data); // Get the y force and re-initialize.
By the time you click the button the simulation will have cooled down or even stopped. To get it going again you have to reheat it by setting alpha to some value greater than alphaMin
and by restarting the simulation run.
simulation.alpha(1).restart(); // Reheat and restart the simulation.
These actions can best be done in your changeMe()
function.
Have a look at the following snippet for a running demo:
var height = 500;
var width = 1500;
var canvas = d3.select("#bubbles");
var ctx = canvas.node().getContext("2d");
var r = 5;
var data = [{
name: "a",
age: 27
},
{
name: "a",
age: 21
},
{
name: "a",
age: 37
},
{
name: "b",
age: 23
},
{
name: "b",
age: 33
},
{
name: "c",
age: 23
},
{
name: "d",
age: 33
}
];
var foci_points = {
'a': {
x: 50,
y: 50
},
'b': {
x: 150,
y: 150
},
'c': {
x: 250,
y: 250
},
'd': {
x: 350,
y: 350
}
};
var simulation = d3.forceSimulation()
.force("x", d3.forceX(function(d) {
return foci_points[d.name].x;
}))
.force("y", d3.forceY(function(d) {
return foci_points[d.name].y;
}))
.force("collide", d3.forceCollide(r + 1))
.force("charge", d3.forceManyBody().strength(10))
.on("tick", update);
simulation.nodes(data);
function update() {
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
data.forEach(drawNode);
ctx.fill();
}
function changeMe() {
data[0].name = "b";
simulation.force("x").initialize(data);
simulation.force("y").initialize(data);
simulation.alpha(1).restart();
}
function drawNode(d) {
ctx.moveTo(d.x, d.y);
ctx.arc(d.x, d.y, r, 0, 2 * Math.PI);
}
update();
<script src="https://d3js.org/d3.v4.min.js"></script>
<body>
<button onClick="changeMe()"> clicck me </button>
<canvas id="bubbles" width="1500" height="500"></canvas>
</body>
Upvotes: 1