I'm working with D3's enter/exit selections, and I want to add a transition on mouseover events too.
The problem is that if I mouseover the letters as they are moving, they freeze, because the position transition is interrupted.
Here is a JSFiddle demonstrating the problem: http://jsfiddle.net/uEuE4/1/ and this is the code I'm using to add mouseover events to the update and enter selections:
text
.on('mouseover', function(d) {
d3.select(this).transition().duration(100).style('fill', 'yellow');
});
How can I only add the mouseover event handlers once all other transitions are completed, in order to stop the letters freezing?
Any tips for making the code more DRY would be very welcome too.
You can allocate a name to transition, then this transition will only be interrupted by new transition with the same name.
text
.on('mouseover', function(d) {
d3.select(this).transition("fillColor").duration(100).style('fill', 'yellow');
});
I upvoted and agree with @Jason answer, this will try to complete the previous with some clarifications and a simple demo that can be used as playground for multiple transition behaviour.
Inspecting your code you have various animations going on but only two of them need to be named to get rid of all your transitions "colisions",
the two event listeners:
text.on('mouseover', function(d) {
d3.select(this).transition("texTr").duration(100).style('fill', 'yellow');
});
enter_text.on('mouseover', function(d) {
d3.select(this).transition("enterTexTr").duration(100).style('fill', 'yellow');
});
The long story is that without names D3 thinks that all the transitions in your code are the same thus it stops the ongoing transition (an example can be a letter transitioning) and replaces it with a new one (for example a fill transition called by the event listener), because the transition name are the same.
But sometimes the desired behaviour is to explicitly stop transition on some elements; this can be done using .interrupt("transitionName")
:
.on("mouseover", function() {
d3.select(this).interrupt("fadeOut")
.attr("fill", "orange")
})
.on("mouseout", function(d) {
d3.select(this).transition("fadeOut")
.duration(5000)
.attr("fill", "rgb(0, 0, " + (d * 10) + ")");
})
In this case without the interrupt command we can't trigger the fill orange
until the fadeOut
ends (5 seconds!).
Here the FIDDLE that you can play with :)
I also had a problem with mouseovers interrupting transitions, and came up with the following (admittedly hacky) solution: before the transition, add the css style pointer-events: none
to the svg
element; then remove it after the transition. Actually, I've found it works more reliably to apply the style to an element which encloses the svg
.
E.g.:
<div class="chart-container">
<svg></svg>
</div>
Then:
$('.chart-container').addClass('no-mouse');
d3.select("svg").on("mouseover", function(d) {...})
.on("mouseout", function(d) {...})
.transition()
.duration(animDuration)
.attr("...", ...);
setTimeout(function() {
$('.chart-container').removeClass('no-mouse');
}, animDuration+10);
with the css
:
.no-mouse {
pointer-events: none;
}
Works in Firefox, Safari, Chrome and even IE 8. But keen to hear a cleaner solution.