Looks like you're doing it almost right. The issue is that your code assumes that NOT calling .classed("selectedNode", true) on a node causes it to NOT have the .selectedNode class applied to it. But in reality (and you'll be able to see this in the Elements panel of your browser's developer tools), if you deselect a node, it'll then have .unselectedNode but also .selectedNode, beause nothing removes the .selectedNode class. And, any subsequent select/deselect don't modify stuff any more, because the node already has both classes.
So you need to remove the un-applicable class each time interaction happens, by calling
d3.select(this).classed("selectedNode", false);
and
d3.select(this).classed("unselectedNode", false);
at the appropriate places. And that'll work.
But now you have an opportunity to refactor some things.
First, my recommendation is to forget about the unselectedNode class altogether and just use the class .node to set the style of the deselected state. That way you'll only have a selectedNode class that is a modifier of the default style, and your code will be simpler that way.
Finally, I'm guessing you're working with a force layout and so you already have a tick() function or something like it that updates all the nodes a bunch of times per second. So, putting it all together, here's how you can do everything from within that method:
var selectedNodesArray = [];
function tick() {
var nodes = d3.selectAll('.node').data(force.nodes);
// ENTER
nodes.enter()
.append('circle')// or 'rect' or whatever
.attr('class', 'node')
.on('click', function(d) {
// here you set the selected-ness, but not the visual representation
d.selectedNode = !d.selectedNode; // flip the selected-ness from true->false or vice versa
console.log(d.selectedNode ? "clicked" : "pulled");
// here you manage the array
if(!d.selectedNode) {
// note that pop() is actually unsafe here, bc it
// removes the last-selected node, but what if you
// deselect something from 2 clicks ago?
selectedNodesArray.pop();
}
else {
// Here I recommend pushing d, instead of its d.coreId, because
// then you can use this array to get the actual datums of the
// selectedNodes, rather than just their id
selectedNodesArray.push(d);
}
})
...// do whatever else you need to do to the entering nodes
// UPDATE
nodes
// Here you take care of the representation of selected-ness
.classed('selectedNode', function(d) {
return d.selectedNode
// or: return selectedNodesArray.indexOf(d)==-1
})
// do whatever else you need to do to the updating nodes (position, etc)
}
Regarding your second question: I'm not sure what exactly you're asking there, but I think that if you think of your tick() method as the thing that updates the representation based on a data-only model or state (eg selectedNodesArray or d.selectedNode), then any interaction can just modify that state and let tick() bring the representation up to speed. For example, here's a bulk way to deselect everything:
// loop through the array and set selected to `false`
selectedNodesArray.forEach(function(d) {
d.selectedNode = false;
})
selectedNodesArray = [];// clear the array
tick();// To update the visuals
Last thing: it's kinda odd that you maintain info about selected-ness in 2 places (selectedNodesArray and d.selectedNode). If you can just pick and use one of those two ways to represent selected-ness, you'll have an easier time moving forward.