grssnbchr
grssnbchr

Reputation: 2974

Transition chaining and cancellation

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

Answers (1)

grssnbchr
grssnbchr

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

Related Questions