Reputation: 2974
I have two functions that perform animations on lines. The first function, one
is executed at the beginning, in order to perform operations on the enter selection, and only animates the lines' horizontal movement (x1
and x2
). The second function two
only animates the lines' height (only y2
,y1
stays fixed).
Through user events, function one
cannot be interrupted by function two
, but vice versa (also because the animation in two
is considerably longer). This means that, when the transition from two
is still running, the user can trigger one
.
This gave me serious headaches, because one
would somehow take the values of the last state of the running transition of two
instead of correctly assigning a data-driven value (i.e. .attr('y2', function(d){ ... });
).
http://jsfiddle.net/h39WN/6/ - Please do the following: Click on one
. You see that only the horizontal movement is animated as the data changes. You also see that at the end of its execution, the lines should always be ordered from lowest to highest.
Click on two
once and wait the full 2 seconds until its animation is completed. Then click on one
again. This is the desired behavior.
Now click on two
, wait a few ms and then click on one
- you see that the lines keep the height of the last state of two
's animation, even though they are correctly ordered. (I know the data are not realistic and maybe a bit confusing in this example, but they still allow to replicate the problem).
I then came up with the solution to schedule another, "empty", transition on the lines in one
- according to the docs this should cancel the one still running in two
when one
is invoked:
var one = function () {
var svg = d3.select('svg');
vis.mobileTeams = svg
.selectAll('.teamGroup')
.data(data.values, function (d) {
return d.ID;
});
// ENTER
var teamEnter = vis.mobileTeams
.enter()
.append('g')
.attr('class', 'teamGroup');
// enter line
teamEnter
.append('line')
.attr('class', 'teamWeightedLine');
// UPDATE THE LINE
// HEIGHT - SHOULD NOT BE ANIMATED
svg
.selectAll('.teamGroup line')
.attr('y1', paddingY)
// I inserted a transition here to cancel
// the one triggered by the other function
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.attr('y2', function(d){ ... });
// I need to use transition chaining so changing 'y2'
// with a transition does not get
// overwritten by the following transition
// HORIZONTAL POSITION - SHOULD BE ANIMATED
lineTransition
.transition()
.duration(500)
// 'x1' and 'x2' are the only values that
// need to be animated in this function
.attr('x1', function (d) {
return function(d){ ... });
})
.attr('x2', function (d) {
return function(d){ ... });
});
};
And here is the second function.
var two = function () {
var svg = d3.select('svg');
// this is where the only transition concerning HEIGHT is supposed to take place
svg
.selectAll('.teamGroup line')
.transition()
.ease('circle-out')
.duration(2000)
.attr('y2', vis.mobileCalculateScoreToHeight);
console.log('mobile vis updated');
};
Even though this fixes the "interference" problem as two
's transition is canceled because another one is scheduled, it brings another problem:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.attr('y2', function(d){ ... });
http://jsfiddle.net/96uN6/8/ This is the fiddle that incorporates this change. Even when two
is interrupted by one
, do the correct heights result in the end - but:
y2
is now being animated in one
too! I know that .transition()
brings with it a default duration of 250ms, so I did this:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(0)
.attr('y2', function(d){ ... });
This, in turn, brings another problem: y2
is not set at all now, i.e. the <line>
s don't even have it as an attribute:
Weirdly, it works when using a very short duration (so the animation is barely visible), but it only works sometimes and is probably browser-dependent:
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(10)
.attr('y2', function(d){ ... });
Setting y2
in the regular selection instead of the transition selection does not work either as it brings back the "interference" problem - as y2
is set when the animation from two
is still running.
svg
.selectAll('.teamGroup line')
.attr('y1', paddingY)
.attr('y2', function(d){ ... });
var lineTransition = svg
.selectAll('.teamGroup line')
.transition();
The approach without transition chaining does not work either, of course, because the first transition is immediately canceled by the second and y2
is never set.
svg
.selectAll('.teamGroup line')
.transition()
.duration(10)
.attr('y2', function(d){ ... });
svg
.selectAll('.teamGroup line')
.transition()
.duration(TRANSDURATION)
.attr('x1', function (d) {
return function(d){ ... };
})
.attr('x2', function (d) {
return function(d){ ... };
});
So the only possible solution working for me (the one with the short duration) seems very quirky, there must be a better solution, mustn't it? Feel free to ask if something is unclear.
Upvotes: 2
Views: 253
Reputation: 2974
Through Mike Bostock (https://github.com/mbostock/d3/issues/1877), I found out that I can use selection.interrupt()
to cancel any previous transitions, i.e., already running transitions.
So, the quirky
var lineTransition = svg
.selectAll('.teamGroup line')
.transition()
.duration(0)
.attr('y2', function(d){ ... });
becomes
var lineTransition = svg
.selectAll('.teamGroup line')
.interrupt()
.attr('y2', function(d){ ... });
It's as easy as that.
See: http://jsfiddle.net/96uN6/9/
Upvotes: 1