I'm making an interactive bubble chart and I'm working on functionality to split the data into two groups which move to opposite sides of the screen. I'm using a centering force for my simulation because I think it gives a much nicer and more consistent display of the data than using forceX and forceY does. However, I'm having trouble with the splitting of my data.
I had the idea that, since you can pass an anonymous function as a parameter to forceX to determine whether a node moves left or right, you could theoretically do the same thing for the x value in a centering force. My center force code looks like this:
var forceCenterSplit = d3.forceCenter(function(d) {
if (d[splitParameter] >= splitVal)
return 3*width/4;
else
return width/4;
}, height/2)
For comparison, here is the code for the forceX that accomplishes the same thing:
var forceXsplit = d3.forceX(function(d) {
if (d[splitParameter] >= splitVal)
return 3*width/4;
else
return width/4;
}).strength(.05);
Unfortunately, the console is saying "Unexpected value NaN parsing cx attribute." when I run the centering force and is pushing all of the data to cx = 0 (the default value).
Am I missing something basic here? Can you not pass an anonymous function as a parameter to the centering force? If not, is there a better way to do this?
Thanks!
// nicer looking splitting forces that use forceCenter
var forceCenterCombine = d3.forceCenter(width/2, height/2);
var forceCenterSplit = d3.forceCenter(function(d) {
if (d[splitParameter] >= splitVal)
return 3*width/4;
else
return width/4;
}, height/2);
// simple splitting forces that only use forceX
var forceXSplit = d3.forceX(function(d) {
if (d[splitParameter] >= splitVal)
return 3*width/4;
else
return width/4;
}).strength(.05);
var forceXCombine = d3.forceX(width/2).strength(.05);
// collision force to stop the bubbles from hitting each other
var forceCollide = d3.forceCollide(function(d){
console.log("forceCollide");
return radiusScale(d[radiusParam]) + 1;
}).strength(.75)
// This code is for the simulation that combines all the forces
var simulation = d3.forceSimulation()
.force("center", forceCenterCombine)
.force("collide", forceCollide)
.on('end', function(){console.log("Simulation ended!");});
function ticked() {
circles
.attr("cx", function(d){
return d.x;
})
.attr("cy", function(d){
return d.y;
})
}
var splitFlag = false;
// dynamically divide the bubbles into two (or probably more later on) groups
$scope.split = function() {
// split them apart
if (!splitFlag){
console.log("splitForce");
simulation.force("center", forceXSplit)
.force("y", d3.forceY(height/2).strength(.05))
.alphaTarget(.25)
.restart();
splitFlag = true;
}
// bring them back together
else {
console.log("combineForce");
simulation.force("center", forceCenterCombine)
.alphaTarget(.25)
.restart();
splitFlag = false;
}
};
Unfortunately, the answer seems to be no.
Due to the very nature of
d3.forceCenter
, that ("pass an anonymous function as a parameter") is not possible. The API says:Thus, there is no space for an accessor function here.
forceX
andforceY
, on the other hand...... and may suit you best.