I am trying to transition the nodes of a force simulation in D3v4. The original force is drawn correctly with the nodes aggregated by shark family, and I would like to click on the "Greenland shark" button so only the node with the name 'greenland shark' would be visible (this is working), and I would like that if the user clicks on the "sharks by family" button that the force would go back to it's original positions. However, if I click on "sharks by family" after clicking on "Greenland shark", only two nodes are visible since apart from the node that is updating directly from the greenland shark node, the rest don't have a cx and cy so they are all stacked on top of one another in the default position at 0,0. I am not sure why the cx and cy attributes are lost, could it be a bug in my enter, update, exit pattern?
Below is the relevant code and here is the Plunker link to the visualization + code https://plnkr.co/edit/rvbOJT2fIgxBlsqOtXR4 and visualization only https://run.plnkr.co/plunks/rvbOJT2fIgxBlsqOtXR4/
// Original force simulation by family
var simulation = d3.forceSimulation();
simulation.force('x', d3.forceX(function(d) {
var i = mapIndex(d.family);
return xposition(i)
}).strength(0.03))
.force('y', d3.forceY(function(d) {
var i = mapIndex(d.family);
return yposition(i)
}).strength((0.03)))
.force('collide', d3.forceCollide(function(d) {
return radiusScale(+d.size)
})).velocityDecay(0.1).alphaDecay(0.001);
var circles = g.selectAll(".sharks")
.data(nodes)
.enter().append("circle")
.attr("class", "sharks")
.attr("r", function(d) {
return radiusScale(+d.size)
})
.attr("fill", function(d) {
return colorScale(d.family)
})
.attr('stroke', '')
simulation.nodes(nodes)
.on('tick', ticked);
nodes.forEach(function(d) {
d.x = familyXScale(d.family)
d.y = yPositionScale(sharks.indexOf(d.name))
})
function ticked() {
circles
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
}
function charge(d) {
return -Math.pow(d.radius, 2.0) * forceStrength;
}
// function for only showing one node (greenland shark)
function greenlandShark() {
console.log('greenlandShark');
console.log(nodes);
var newNodes = filterNodes('common_name', 'Greenland shark');
circles = g.selectAll(".sharks").data(newNodes);
circles.exit()
.transition()
.duration(1000)
.attr("r", 0)
.remove();
circles
.attr('r', function(d) {
return radiusScale(+d.size)
})
.attr('fill', function(d) {
return colorScale(d.family)
});
simulation.nodes(newNodes)
.on('tick', ticked);
simulation.force('x', d3.forceX().strength(0.03).x(center.x))
.force('y', d3.forceY(function(d) {
return height / 2
}).strength((0.03)));
simulation.alpha(1).restart();
}
function filterNodes(key, group) {
var newnodes = nodes.filter(function(d) {
return d[key] == group;
});
return newnodes;;
}
// function for visualizing all nodes again organized by family
function sharksByFamily() {
circles = g.selectAll(".sharks").data(nodes);
circles.exit().transition().duration(750)
.attr("r", 0)
.remove();
circles.transition().duration(750)
.attr("fill", function(d) {
return colorScale(d.family)
}).attr("r", function(d) {
return radiusScale(+d.size);
})
circles.enter().append("circle").attr("class", "sharks")
.attr("fill", function(d) {
return colorScale(d.family)
}).attr("r", function(d) {
return radiusScale(+d.size);
})
.attr('stroke', '')
simulation.force('x', d3.forceX(function(d) {
var i = mapIndex(d.family);
return xposition(i)
}).strength(0.03))
.force('y', d3.forceY(function(d) {
var i = mapIndex(d.family);
return yposition(i)
}).strength((0.03)))
.force('collide', d3.forceCollide(function(d) {
return radiusScale(+d.size)
})).velocityDecay(0.1).alphaDecay(0.001);
// cx cy not showing up for nodes
simulation.nodes(nodes)
.on('tick', ticked);
simulation.alpha(1).restart();
}