0

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.

asked Nov 20, 2014 at 15:19

1 Answer 1

1

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.

answered Nov 20, 2014 at 19:22
Sign up to request clarification or add additional context in comments.

1 Comment

life saver. Appreciate the effort for the indepth answer. It works perfectly now. like you said all i was missing was the one line ''d3.select(this).classed("selectedNode", false); '' always one little thing. Thanks again :)

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.