Reputation: 539
What I mean by a click equivalent of mouseout is that I'd like a way to click on an element to change some attribute then have it change back when I click on anything but that element. Toggling this attribute change with hovering is easy because you change things based on mouseover and mouseout, but I'm unsure of how to do the same based on the click event.
So what I have is an svg element with circles on it, which display a red outline when they're clicked on. I know enough to be able to make only one circle appear selected at a time, but I don't know how to deselect all nodes when I click on a part of the svg that isn't a circle. If this isn't clear enough, I can create a jsfiddle to demonstrate what I have so far.
I have a working knowledge of selections from reading many examples, but can't seem to figure out what approach I should take to achieve this.
Upvotes: 3
Views: 2300
Reputation: 3791
Jshanley's answer is great and is probably better in most cases, but I ended up modifying it a bit:
svg.selectAll("dot").data(datasource).enter() //you probably have your own thing here
.on("mousedown", function(d) {
d3.selectAll("circle") //this selects all of the elements you want to deselect by html tag (here its "circle")
.style("fill", "black"); //default color of unselected elements, here its black
d3.select(this) //select the element that's just been clicked
.style("fill", "orange"); //orange is the color of currently selected element
});
This works as long as the default style on all elements is applied before the style of the selected element.
Upvotes: 0
Reputation: 9128
You could use d3.dispatch
to set up some custom event handling. Sometimes separating out distinct behaviors from the rest of your layout code helps to keep things organized.
You might want one function to unhighlight all of the clickable circles, and another to toggle a single circle. Then when the svg is clicked, you can decide whether to unhighlight all based on whether a circle was clicked or not.
In other words...
When a circle is clicked, toggle it.
When the svg document is clicked, and the click is not on a circle, unhighlight all circles.
Then you can set up separate dispatch events for the two processes. This is nice because then these become reusable behaviors. If for example, you later want to add a button to unhighlight all circles, or want to highlight a circle when it's moused over, you can call the same dispatch functions.
var dispatch = d3.dispatch('unhighlightAll','toggleSingle')
// remove the `highlighted` class on all circles
.on('unhighlightAll', function() {
d3.selectAll('.clickable-circle').classed('highlighted', false);
})
// toggle the `highlighted` class on element `el`
.on('toggleSingle', function(el) {
d3.select(el).classed('highlighted', function() {
return !d3.select(el).classed('highlighted');
});
});
Finally, you call the dispatch functions from your click handlers:
svg.on('click', function() {
// do nothing if a clickable circle is clicked
if (d3.select(d3.event.target).classed('clickable-circle')) {
return;
} else {
// otherwise unhighlight all circles
dispatch.unhighlightAll();
}
});
circles.on('click', function() {
dispatch.toggleSingle(this);
});
Then all that's left is to decide how to display the highlighted
class, and handle that in your css.
Here's a demo JSBin
--EDIT--
I just realized that since you're trying to mimic mouseout, you probably don't want multi-select. You'd just need to change the toggleSingle
function a bit:
dispatch.on('toggleSingle', function(el) {
// store state of current element
var highlighted = d3.select(el).classed('highlighted');
// unhighlight all
dispatch.unhighlightAll();
// set opposite of stored state
d3.select(el).classed('highlighted', !highlighted);
});
And here's the updated JSBin.
Upvotes: 5
Reputation: 2883
Add a click handler on the SVG. In that click handler, first deselect all circles. Then, check the event target via d3.event
; if it is a circle, select it. Pseudocode-ish description:
svg.on('click', function() {
circles.classed('selected', false);
var target = /* get event target */;
if (/* target is circle */) {
target.classed('selected', true);
}
});
Upvotes: 0