Skip to content

Commit 4886d94

Browse files
authoredOct 26, 2016
Merge pull request #1087 from plotly/animation-fromcurrent
Animation direction and fromcurrent
2 parents fed224c + 84769d1 commit 4886d94

File tree

4 files changed

+157
-1
lines changed

4 files changed

+157
-1
lines changed
 

‎src/plot_api/plot_api.js

+27
Original file line numberDiff line numberDiff line change
@@ -2431,6 +2431,33 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
24312431
discardExistingFrames();
24322432
}
24332433

2434+
if(animationOpts.direction === 'reverse') {
2435+
frameList.reverse();
2436+
}
2437+
2438+
var currentFrame = gd._fullLayout._currentFrame;
2439+
if(currentFrame && animationOpts.fromcurrent) {
2440+
var idx = -1;
2441+
for(i = 0; i < frameList.length; i++) {
2442+
frame = frameList[i];
2443+
if(frame.type === 'byname' && frame.name === currentFrame) {
2444+
idx = i;
2445+
break;
2446+
}
2447+
}
2448+
2449+
if(idx > 0 && idx < frameList.length - 1) {
2450+
var filteredFrameList = [];
2451+
for(i = 0; i < frameList.length; i++) {
2452+
frame = frameList[i];
2453+
if(frameList[i].type !== 'byname' || i > idx) {
2454+
filteredFrameList.push(frame);
2455+
}
2456+
}
2457+
frameList = filteredFrameList;
2458+
}
2459+
}
2460+
24342461
if(frameList.length > 0) {
24352462
queueFrames(frameList);
24362463
} else {

‎src/plots/animation_attributes.js

+18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module.exports = {
1212
mode: {
1313
valType: 'enumerated',
1414
dflt: 'afterall',
15+
role: 'info',
1516
values: ['immediate', 'next', 'afterall'],
1617
description: [
1718
'Describes how a new animate call interacts with currently-running',
@@ -22,6 +23,23 @@ module.exports = {
2223
'is started.'
2324
].join(' ')
2425
},
26+
direction: {
27+
valType: 'enumerated',
28+
role: 'info',
29+
values: ['forward', 'reverse'],
30+
dflt: 'forward',
31+
description: [
32+
'The direction in which to play the frames triggered by the animation call'
33+
].join(' ')
34+
},
35+
fromcurrent: {
36+
valType: 'boolean',
37+
dflt: false,
38+
role: 'info',
39+
description: [
40+
'Play frames starting at the current frame instead of the beginning.'
41+
].join(' ')
42+
},
2543
frame: {
2644
duration: {
2745
valType: 'number',

‎src/plots/plots.js

+2
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,8 @@ plots.supplyAnimationDefaults = function(opts) {
668668
}
669669

670670
coerce('mode');
671+
coerce('direction');
672+
coerce('fromcurrent');
671673

672674
if(Array.isArray(opts.frame)) {
673675
optsOut.frame = [];

‎test/jasmine/tests/animate_test.js

+110-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ describe('Plots.supplyAnimationDefaults', function() {
1414

1515
it('supplies transition defaults', function() {
1616
expect(Plots.supplyAnimationDefaults({})).toEqual({
17+
fromcurrent: false,
1718
mode: 'afterall',
19+
direction: 'forward',
1820
transition: {
1921
duration: 500,
2022
easing: 'cubic-in-out'
@@ -29,6 +31,8 @@ describe('Plots.supplyAnimationDefaults', function() {
2931
it('uses provided values', function() {
3032
expect(Plots.supplyAnimationDefaults({
3133
mode: 'next',
34+
fromcurrent: true,
35+
direction: 'reverse',
3236
transition: {
3337
duration: 600,
3438
easing: 'elastic-in-out'
@@ -39,6 +43,8 @@ describe('Plots.supplyAnimationDefaults', function() {
3943
}
4044
})).toEqual({
4145
mode: 'next',
46+
fromcurrent: true,
47+
direction: 'reverse',
4248
transition: {
4349
duration: 600,
4450
easing: 'elastic-in-out'
@@ -63,7 +69,12 @@ describe('Test animate API', function() {
6369
function verifyFrameTransitionOrder(gd, expectedFrames) {
6470
var calls = Plots.transition.calls;
6571

66-
expect(calls.count()).toEqual(expectedFrames.length);
72+
var c1 = calls.count();
73+
var c2 = expectedFrames.length;
74+
expect(c1).toEqual(c2);
75+
76+
// Prevent lots of ugly logging when it's already failed:
77+
if(c1 !== c2) return;
6778

6879
for(var i = 0; i < calls.count(); i++) {
6980
expect(calls.argsFor(i)[1]).toEqual(
@@ -315,6 +326,104 @@ describe('Test animate API', function() {
315326
});
316327
}
317328

329+
describe('Animation direction', function() {
330+
var animOpts;
331+
332+
beforeEach(function() {
333+
animOpts = {
334+
frame: {duration: 0},
335+
transition: {duration: 0}
336+
};
337+
});
338+
339+
it('animates frames by name in reverse', function(done) {
340+
animOpts.direction = 'reverse';
341+
342+
Plotly.animate(gd, ['frame0', 'frame2', 'frame1', 'frame3'], animOpts).then(function() {
343+
verifyFrameTransitionOrder(gd, ['frame3', 'frame1', 'frame2', 'frame0']);
344+
verifyQueueEmpty(gd);
345+
}).catch(fail).then(done);
346+
});
347+
348+
it('animates a group in reverse', function(done) {
349+
animOpts.direction = 'reverse';
350+
Plotly.animate(gd, 'even-frames', animOpts).then(function() {
351+
verifyFrameTransitionOrder(gd, ['frame2', 'frame0']);
352+
verifyQueueEmpty(gd);
353+
}).catch(fail).then(done);
354+
});
355+
});
356+
357+
describe('Animation fromcurrent', function() {
358+
var animOpts;
359+
360+
beforeEach(function() {
361+
animOpts = {
362+
frame: {duration: 0},
363+
transition: {duration: 0},
364+
fromcurrent: true
365+
};
366+
});
367+
368+
it('animates starting at the current frame', function(done) {
369+
Plotly.animate(gd, ['frame1'], animOpts).then(function() {
370+
verifyFrameTransitionOrder(gd, ['frame1']);
371+
verifyQueueEmpty(gd);
372+
373+
return Plotly.animate(gd, null, animOpts);
374+
}).then(function() {
375+
verifyFrameTransitionOrder(gd, ['frame1', 'frame2', 'frame3']);
376+
verifyQueueEmpty(gd);
377+
}).catch(fail).then(done);
378+
});
379+
380+
it('plays from the start when current frame = last frame', function(done) {
381+
Plotly.animate(gd, null, animOpts).then(function() {
382+
verifyFrameTransitionOrder(gd, ['base', 'frame0', 'frame1', 'frame2', 'frame3']);
383+
verifyQueueEmpty(gd);
384+
385+
return Plotly.animate(gd, null, animOpts);
386+
}).then(function() {
387+
verifyFrameTransitionOrder(gd, [
388+
'base', 'frame0', 'frame1', 'frame2', 'frame3',
389+
'base', 'frame0', 'frame1', 'frame2', 'frame3'
390+
]);
391+
392+
verifyQueueEmpty(gd);
393+
}).catch(fail).then(done);
394+
});
395+
396+
it('animates in reverse starting at the current frame', function(done) {
397+
animOpts.direction = 'reverse';
398+
399+
Plotly.animate(gd, ['frame1'], animOpts).then(function() {
400+
verifyFrameTransitionOrder(gd, ['frame1']);
401+
verifyQueueEmpty(gd);
402+
return Plotly.animate(gd, null, animOpts);
403+
}).then(function() {
404+
verifyFrameTransitionOrder(gd, ['frame1', 'frame0', 'base']);
405+
verifyQueueEmpty(gd);
406+
}).catch(fail).then(done);
407+
});
408+
409+
it('plays in reverse from the end when current frame = first frame', function(done) {
410+
animOpts.direction = 'reverse';
411+
412+
Plotly.animate(gd, ['base'], animOpts).then(function() {
413+
verifyFrameTransitionOrder(gd, ['base']);
414+
verifyQueueEmpty(gd);
415+
416+
return Plotly.animate(gd, null, animOpts);
417+
}).then(function() {
418+
verifyFrameTransitionOrder(gd, [
419+
'base', 'frame3', 'frame2', 'frame1', 'frame0', 'base'
420+
]);
421+
422+
verifyQueueEmpty(gd);
423+
}).catch(fail).then(done);
424+
});
425+
});
426+
318427
// The tests above use promises to ensure ordering, but the tests below this call Plotly.animate
319428
// without chaining promises which would result in race conditions. This is not invalid behavior,
320429
// but it doesn't ensure proper ordering and completion, so these must be performed with finite

0 commit comments

Comments
 (0)
Please sign in to comment.