Skip to content

Commit 62b33d3

Browse files
authored
Merge pull request #1124 from plotly/cast-frame-names-to-string
Enforce casting requested frame names to strings
2 parents d3c59da + 84c1b0c commit 62b33d3

File tree

4 files changed

+68
-3
lines changed

4 files changed

+68
-3
lines changed

src/plot_api/plot_api.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -2258,7 +2258,6 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
22582258
frameOpts: frameOpts,
22592259
transitionOpts: transitionOpts,
22602260
};
2261-
22622261
if(i === frameList.length - 1) {
22632262
// The last frame in this .animate call stores the promise resolve
22642263
// and reject callbacks. This is how we ensure that the animation
@@ -2410,14 +2409,15 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
24102409
} else if(isFrameArray) {
24112410
for(i = 0; i < frameOrGroupNameOrFrameList.length; i++) {
24122411
var frameOrName = frameOrGroupNameOrFrameList[i];
2413-
if(typeof frameOrName === 'string') {
2412+
if(['number', 'string'].indexOf(typeof frameOrName) !== -1) {
2413+
frameOrName = String(frameOrName);
24142414
// In this case, there's an array and this frame is a string name:
24152415
frameList.push({
24162416
type: 'byname',
24172417
name: frameOrName,
24182418
data: setTransitionConfig({name: frameOrName})
24192419
});
2420-
} else {
2420+
} else if(Lib.isPlainObject(frameOrName)) {
24212421
frameList.push({
24222422
type: 'object',
24232423
data: setTransitionConfig(Lib.extendFlat({}, frameOrName))

src/plots/plots.js

+6
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,9 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts)
17981798
}
17991799

18001800
function completeTransition(callback) {
1801+
// Fail-safe against purged plot:
1802+
if(!gd._transitionData) return;
1803+
18011804
flushCallbacks(gd._transitionData._interruptCallbacks);
18021805

18031806
return Promise.resolve().then(function() {
@@ -1815,6 +1818,9 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts)
18151818
}
18161819

18171820
function interruptPreviousTransitions() {
1821+
// Fail-safe against purged plot:
1822+
if(!gd._transitionData) return;
1823+
18181824
// If a transition is interrupted, set this to false. At the moment, the only thing that would
18191825
// interrupt a transition is another transition, so that it will momentarily be set to true
18201826
// again, but this determines whether autorange or dragbox work, so it's for the sake of

test/jasmine/tests/animate_test.js

+53
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ describe('Test animate API', function() {
8888

8989
mockCopy = Lib.extendDeep({}, mock);
9090

91+
// ------------------------------------------------------------
92+
// NB: TRANSITION IS FAKED
93+
//
94+
// This means that you should not expect `.animate` to actually
95+
// modify the plot in any way in the tests below. For tests
96+
// involvingnon-faked transitions, see the bottom of this file.
97+
// ------------------------------------------------------------
98+
9199
spyOn(Plots, 'transition').and.callFake(function() {
92100
// Transition's fake behavior is just to delay by the duration
93101
// and resolve:
@@ -579,3 +587,48 @@ describe('Test animate API', function() {
579587
});
580588
});
581589
});
590+
591+
describe('Test animate API', function() {
592+
'use strict';
593+
594+
var gd, mockCopy;
595+
596+
beforeEach(function(done) {
597+
gd = createGraphDiv();
598+
mockCopy = Lib.extendDeep({}, mock);
599+
Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() {
600+
return Plotly.addFrames(gd, mockCopy.frames);
601+
}).then(done);
602+
});
603+
604+
afterEach(function() {
605+
Plotly.purge(gd);
606+
destroyGraphDiv();
607+
});
608+
609+
it('does not fail if strings are not used', function(done) {
610+
Plotly.addFrames(gd, [{name: 8, data: [{x: [8, 7, 6]}]}]).then(function() {
611+
// Verify it was added as a string name:
612+
expect(gd._transitionData._frameHash['8']).not.toBeUndefined();
613+
614+
// Transition using a number:
615+
return Plotly.animate(gd, [8], {transition: {duration: 0}, frame: {duration: 0}});
616+
}).then(function() {
617+
// Confirm the result:
618+
expect(gd.data[0].x).toEqual([8, 7, 6]);
619+
}).catch(fail).then(done);
620+
});
621+
622+
it('ignores null and undefined frames', function(done) {
623+
var cnt = 0;
624+
gd.on('plotly_animatingframe', function() {cnt++;});
625+
626+
Plotly.animate(gd, ['frame0', null, undefined], {transition: {duration: 0}, frame: {duration: 0}}).then(function() {
627+
// Check only one animating was fired:
628+
expect(cnt).toEqual(1);
629+
630+
// Check unused frames did not affect the current frame:
631+
expect(gd._fullLayout._currentFrame).toEqual('frame0');
632+
}).catch(fail).then(done);
633+
});
634+
});

test/jasmine/tests/frame_api_test.js

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ describe('Test frame api', function() {
6060
}).catch(fail).then(done);
6161
});
6262

63+
it('casts names to strings', function(done) {
64+
Plotly.addFrames(gd, [{name: 5}]).then(function() {
65+
expect(Object.keys(h)).toEqual(['5']);
66+
}).catch(fail).then(done);
67+
});
68+
6369
it('creates multiple unnamed frames at the same time', function(done) {
6470
Plotly.addFrames(gd, [{}, {}]).then(function() {
6571
expect(f).toEqual([{name: 'frame 0'}, {name: 'frame 1'}]);

0 commit comments

Comments
 (0)