d3.js: click equivalent of mouseout

d3.js, javascript

Solution

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.

Problem

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.

Original source