Reputation: 37
I have a problem with my bubble chart.
I used forceSimulation() previously with an array of objects and it worked. Now I changed the data source and it doesn't, even if the console displays no errors.
My data is an object called "lightWeight", with the following structure:
I use it to append circles like so:
// draw circles
var node = bubbleSvg.selectAll("circle")
.data(d3.entries(lightWeight))
.enter()
.append("circle")
.attr('r', function(d) { return scaleRadius(d.value.length)})
.attr("fill", function(d) { return colorCircles(d.key)})
.attr('transform', 'translate(' + [w/2, 150] + ')');
Then I create the simulation:
// simulate physics
var simulation = d3.forceSimulation()
.nodes(lightWeight)
.force("charge", d3.forceCollide(function(d) { return d.r + 10; }))
.force("x", d3.forceX())
.force("y", d3.forceY())
.on("tick", ticked); // updates the position of each circle (from function to DOM)
// call to check the position of each circle
function ticked(e) {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
But the circles remain on top of each other and do not become a bubble chart like they did before.
I apologise if this is probably a dumb question, I am new to d3 and understood very little of how forceSimulation() actually works.
For example, if I call it multiple times with different data, will the resulting simulation affect only the specified data?
Thanks in advance!
Upvotes: 1
Views: 442
Reputation: 769
There are a couple problems here:
.data(d3.entries(lightWeight))
creates a new array of objects that you use to bind to the DOM, whereas .nodes(lightWeight)
attempts to run the force simulation on the original lightWeight
object (it expects an array, so this isn't going to work).Try doing something like var lightWeightList = d3.entries(lightWeight);
up before any of this code starts, and use that array for both binding to the DOM and as the parameter to the force simulation. Of course, this should make it clear that you might run into other challenges when it comes to updating which nodes you're looking at—overwriting lightWeightList
will nuke any of the previous node positions (as we can't see more of your code, especially how you'd call this a second time, I don't have any helpful ideas).
.enter()
call means that node
will only refer to the enter selection—meaning that, if you call this code again, the force simulation is only going to update the new nodes inside ticked
.With D3, I've found that a good habit is to keep your selections in separate variables, e.g.:
var lightWeightList = d3.entries(lightWeight);
// ...
var nodes = bubbleSvg.selectAll('circle')
.data(lightWeightList);
var nodesEnter = nodes.enter()
.append('circle');
// If you're using D3 v4 and above, you'll need to merge the selections:
nodes = nodes.merge(nodesEnter);
nodes.select('circle')
.attr('r', function(d) { return scaleRadius(d.value.length)})
.attr('fill', function(d) { return colorCircles(d.key)})
.attr('transform', 'translate(' + [w/2, 150] + ')');
// ...
var simulation = d3.forceSimulation()
.nodes(lightWeightList)
.force("charge", d3.forceCollide(function(d) { return d.r + 10; }))
.force("x", d3.forceX())
.force("y", d3.forceY())
.on("tick", ticked);
function ticked(e) {
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
Upvotes: 1