Reputation: 823
here is my css
.node.selectedNode {
width:50px;
height:50px;
stroke-width: 3px;
stroke: #f00;
}
.node.unselectedNode {
width:35px;
height:35px;
stroke-width: 3px;
stroke: #000;
}
i want to click on a node making it selected (giving it the selected attributes), click again and make it unselected. Here is a piece of code where i check if the id of a node is in an array, if it isnt its adds it too it, giving me the ability to easily print out the selected nodes.
if(selectedNodesArray.indexOf(d.coreId)==-1){
selectedNodesArray.push(d.coreId);
d.selectedNode = true; //change style
d.unselectedNode = false;
d3.select(this).classed("selectedNode", true);
console.log("clicked");
}else{
selectedNodesArray.pop(d.coreId);
d.unselectedNode = true;
d.selectedNode = false;
d3.select(this).classed("unselectedNode", true);
console.log("pulled");
}
as you may notice i try change the style. Now this works twice; when i select it and when i deselect it. It doesnt work again after that. Any ideas ?
Also, i have created a button so that it clears all highlighted nodes. Like i have here, when i click the node it changes its property to fixed
function dragstart(d) {
d.fixed = true;
d3.select(this).classed("fixed", true);
I want to do that when i click a button, so it changes the css attributes of the node. I dont know how to pick out all of the nodes and give them all the same css attribute instead of doing it through D3 and changing the .style that way.
Sorry for the long winded essay i just want to give you as much detail as possible making it easier for everyone.
Upvotes: 0
Views: 411
Reputation: 25187
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.
Upvotes: 1