Skip to content

Commit 0616495

Browse files
committed
Add slight modification to animation resolution
1 parent 5acfb06 commit 0616495

File tree

3 files changed

+112
-6
lines changed

3 files changed

+112
-6
lines changed

src/plot_api/plot_api.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,6 +2213,20 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
22132213
}
22142214
}
22152215

2216+
// Execute a callback after the wrapper function has been called n times.
2217+
// This is used to defer the resolution until a transition has resovled *and*
2218+
// the frame has completed. If it's not done this way, then we get a race
2219+
// condition in which the animation might resolve before a transition is complete
2220+
// or vice versa.
2221+
function callbackOnNthTime(cb, n) {
2222+
var cnt = 0;
2223+
return function() {
2224+
if(cb && ++cnt === n) {
2225+
return cb();
2226+
}
2227+
};
2228+
}
2229+
22162230
return new Promise(function(resolve, reject) {
22172231
function discardExistingFrames() {
22182232
if(trans._frameQueue.length === 0) {
@@ -2264,7 +2278,7 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
22642278
// loop (which may exist as a result of a *different* .animate call)
22652279
// still resolves or rejecdts this .animate call's promise. once it's
22662280
// complete.
2267-
nextFrame.onComplete = resolve;
2281+
nextFrame.onComplete = callbackOnNthTime(resolve, 2);
22682282
nextFrame.onInterrupt = reject;
22692283
}
22702284

@@ -2302,7 +2316,6 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
23022316
// Execute the callback and unset it to ensure it doesn't
23032317
// accidentally get called twice
23042318
trans._currentFrame.onComplete();
2305-
trans._currentFrame.onComplete = null;
23062319
}
23072320

23082321
var newFrame = trans._currentFrame = trans._frameQueue.shift();
@@ -2322,7 +2335,12 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
23222335
helpers.coerceTraceIndices(gd, newFrame.frame.traces),
23232336
newFrame.frameOpts,
23242337
newFrame.transitionOpts
2325-
);
2338+
).then(function() {
2339+
if(newFrame.onComplete) {
2340+
newFrame.onComplete();
2341+
}
2342+
2343+
});
23262344

23272345
gd.emit('plotly_animatingframe', {
23282346
name: newFrame.name,

src/plots/plots.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,6 +1700,9 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts)
17001700
var aborted = false;
17011701

17021702
function executeTransitions() {
1703+
1704+
gd.emit('plotly_transitioning', []);
1705+
17031706
return new Promise(function(resolve) {
17041707
// This flag is used to disabled things like autorange:
17051708
gd._transitioning = true;
@@ -1828,13 +1831,13 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts)
18281831

18291832
var seq = [plots.previousPromises, interruptPreviousTransitions, prepareTransitions, executeTransitions];
18301833

1831-
18321834
var transitionStarting = Lib.syncOrAsync(seq, gd);
18331835

1834-
if(!transitionStarting || !transitionStarting.then) transitionStarting = Promise.resolve();
1836+
if(!transitionStarting || !transitionStarting.then) {
1837+
transitionStarting = Promise.resolve();
1838+
}
18351839

18361840
return transitionStarting.then(function() {
1837-
gd.emit('plotly_transitioning', []);
18381841
return gd;
18391842
});
18401843
};

test/jasmine/tests/animate_test.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,3 +579,88 @@ describe('Test animate API', function() {
579579
});
580580
});
581581
});
582+
583+
describe('layout animation', function() {
584+
'use strict';
585+
586+
var gd;
587+
var dur = 30;
588+
589+
beforeEach(function(done) {
590+
gd = createGraphDiv();
591+
var mockCopy = Lib.extendDeep({}, mock);
592+
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(done);
593+
});
594+
595+
afterEach(function() {
596+
Plotly.purge(gd);
597+
destroyGraphDiv();
598+
});
599+
600+
it('redraws after a layout animation', function(done) {
601+
var redraws = 0;
602+
gd.on('plotly_redraw', function() {redraws++;});
603+
604+
Plotly.animate(gd,
605+
{layout: {'xaxis.range': [0, 1]}},
606+
{frame: {redraw: true, duration: dur}, transition: {duration: dur}}
607+
).then(function() {
608+
// Something is delayed a single tick, it seems, so the redraw
609+
// isn't triggered until next tick:
610+
expect(redraws).toBe(1);
611+
}).catch(fail).then(done);
612+
});
613+
614+
it('forces a relayout after layout animations', function(done) {
615+
var relayouts = 0;
616+
var restyles = 0;
617+
var redraws = 0;
618+
gd.on('plotly_relayout', function() {relayouts++;});
619+
gd.on('plotly_restyle', function() {restyles++;});
620+
gd.on('plotly_redraw', function() {redraws++;});
621+
622+
Plotly.animate(gd,
623+
{layout: {'xaxis.range': [0, 1]}},
624+
{frame: {redraw: false, duration: dur}, transition: {duration: dur}}
625+
).then(function() {
626+
// Something is delayed a single tick, it seems, so the redraw
627+
// isn't triggered until next tick:
628+
expect(relayouts).toBe(1);
629+
expect(restyles).toBe(0);
630+
expect(redraws).toBe(0);
631+
}).catch(fail).then(done);
632+
});
633+
634+
it('triggers plotly_animated after a single layout animation', function(done) {
635+
var animateds = 0;
636+
gd.on('plotly_animated', function() {animateds++;});
637+
638+
Plotly.animate(gd, [
639+
{layout: {'xaxis.range': [0, 1]}},
640+
], {frame: {redraw: false, duration: dur}, transition: {duration: dur}}
641+
).then(function() {
642+
// Wait a bit just to be sure:
643+
setTimeout(function() {
644+
expect(animateds).toBe(1);
645+
done();
646+
}, dur);
647+
});
648+
});
649+
650+
it('triggers plotly_animated after a multi-step layout animation', function(done) {
651+
var animateds = 0;
652+
gd.on('plotly_animated', function() {animateds++;});
653+
654+
Plotly.animate(gd, [
655+
{layout: {'xaxis.range': [0, 1]}},
656+
{layout: {'xaxis.range': [2, 4]}},
657+
], {frame: {redraw: false, duration: dur}, transition: {duration: dur}}
658+
).then(function() {
659+
// Wait a bit just to be sure:
660+
setTimeout(function() {
661+
expect(animateds).toBe(1);
662+
done();
663+
}, dur);
664+
});
665+
});
666+
});

0 commit comments

Comments
 (0)