-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Fix number named frames #1236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix number named frames #1236
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2327,7 +2327,11 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) { | |
var newFrame = trans._currentFrame = trans._frameQueue.shift(); | ||
|
||
if(newFrame) { | ||
gd._fullLayout._currentFrame = newFrame.name; | ||
// Since it's sometimes necessary to do deep digging into frame data, | ||
// we'll consider it not 100% impossible for nulls or numbers to sneak through, | ||
// so check when casting the name, just to be absolutely certain: | ||
var stringName = newFrame.name ? newFrame.name.toString() : null; | ||
gd._fullLayout._currentFrame = stringName; | ||
|
||
trans._lastFrameAt = Date.now(); | ||
trans._timeToNext = newFrame.frameOpts.duration; | ||
|
@@ -2349,7 +2353,7 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) { | |
}); | ||
|
||
gd.emit('plotly_animatingframe', { | ||
name: newFrame.name, | ||
name: stringName, | ||
frame: newFrame.frame, | ||
animation: { | ||
frame: newFrame.frameOpts, | ||
|
@@ -2416,18 +2420,18 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) { | |
type: 'object', | ||
data: setTransitionConfig(Lib.extendFlat({}, frameOrGroupNameOrFrameList)) | ||
}); | ||
} else if(allFrames || typeof frameOrGroupNameOrFrameList === 'string') { | ||
} else if(allFrames || ['string', 'number'].indexOf(typeof frameOrGroupNameOrFrameList) !== -1) { | ||
// In this case, null or undefined has been passed so that we want to | ||
// animate *all* currently defined frames | ||
for(i = 0; i < trans._frames.length; i++) { | ||
frame = trans._frames[i]; | ||
|
||
if(!frame) continue; | ||
|
||
if(allFrames || frame.group === frameOrGroupNameOrFrameList) { | ||
if(allFrames || String(frame.group) === String(frameOrGroupNameOrFrameList)) { | ||
frameList.push({ | ||
type: 'byname', | ||
name: frame.name, | ||
name: String(frame.name), | ||
data: setTransitionConfig({name: frame.name}) | ||
}); | ||
} | ||
|
@@ -2528,6 +2532,8 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) { | |
Plotly.addFrames = function(gd, frameList, indices) { | ||
gd = helpers.getGraphDiv(gd); | ||
|
||
var numericNameWarningCount = 0; | ||
|
||
if(frameList === null || frameList === undefined) { | ||
return Promise.resolve(); | ||
} | ||
|
@@ -2558,6 +2564,25 @@ Plotly.addFrames = function(gd, frameList, indices) { | |
|
||
var insertions = []; | ||
for(i = frameList.length - 1; i >= 0; i--) { | ||
var name = (_hash[frameList[i].name] || {}).name; | ||
var newName = frameList[i].name; | ||
|
||
if(name && newName && typeof newName === 'number' && _hash[name]) { | ||
numericNameWarningCount++; | ||
|
||
Lib.warn('addFrames: overwriting frame "' + _hash[name].name + | ||
'" with a frame whose name of type "number" also equates to "' + | ||
name + '". This is valid but may potentially lead to unexpected ' + | ||
'behavior since all plotly.js frame names are stored internally ' + | ||
'as strings.'); | ||
|
||
if(numericNameWarningCount > 5) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice touch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I published a module once where I said "meh, probably won't be too many warnings." Haha, WRONG. |
||
Lib.warn('addFrames: This API call has yielded too many warnings. ' + | ||
'For the rest of this call, further warnings about numeric frame ' + | ||
'names will be suppressed.'); | ||
} | ||
} | ||
|
||
insertions.push({ | ||
frame: Plots.supplyFrameDefaults(frameList[i]), | ||
index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i | ||
|
@@ -2578,6 +2603,12 @@ Plotly.addFrames = function(gd, frameList, indices) { | |
for(i = insertions.length - 1; i >= 0; i--) { | ||
frame = insertions[i].frame; | ||
|
||
if(typeof frame.name === 'number') { | ||
Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are' + | ||
'implicitly cast to strings'); | ||
|
||
} | ||
|
||
if(!frame.name) { | ||
// Repeatedly assign a default name, incrementing the counter each time until | ||
// we get a name that's not in the hashed lookup table: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -444,6 +444,12 @@ describe('Plots.computeAPICommandBindings', function() { | |
expect(result).toEqual([{type: 'layout', prop: '_currentFrame', value: 'framename'}]); | ||
}); | ||
|
||
it('treats numeric frame names as strings', function() { | ||
var result = Plots.computeAPICommandBindings(gd, 'animate', [[8]]); | ||
|
||
expect(result).toEqual([{type: 'layout', prop: '_currentFrame', value: '8'}]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nicely done |
||
}); | ||
|
||
it('binds to nothing for a multi-frame animate command', function() { | ||
var result = Plots.computeAPICommandBindings(gd, 'animate', [['frame1', 'frame2']]); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
var Plotly = require('@lib/index'); | ||
var Lib = require('@src/lib'); | ||
|
||
var createGraphDiv = require('../assets/create_graph_div'); | ||
var destroyGraphDiv = require('../assets/destroy_graph_div'); | ||
|
@@ -73,13 +74,41 @@ describe('Test frame api', function() { | |
}); | ||
|
||
it('creates multiple unnamed frames in series', function(done) { | ||
Plotly.addFrames(gd, [{}]).then( | ||
Plotly.addFrames(gd, [{}]) | ||
).then(function() { | ||
Plotly.addFrames(gd, [{}]).then(function() { | ||
return Plotly.addFrames(gd, [{}]); | ||
}).then(function() { | ||
expect(f).toEqual([{name: 'frame 0'}, {name: 'frame 1'}]); | ||
}).catch(fail).then(done); | ||
}); | ||
|
||
it('casts number names to strings on insertion', function(done) { | ||
Plotly.addFrames(gd, [{name: 2}]).then(function() { | ||
expect(f).toEqual([{name: '2'}]); | ||
}).catch(fail).then(done); | ||
}); | ||
|
||
it('updates frames referenced by number', function(done) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sweet 🎉 |
||
Plotly.addFrames(gd, [{name: 2}]).then(function() { | ||
return Plotly.addFrames(gd, [{name: 2, layout: {foo: 'bar'}}]); | ||
}).then(function() { | ||
expect(f).toEqual([{name: '2', layout: {foo: 'bar'}}]); | ||
}).catch(fail).then(done); | ||
}); | ||
|
||
it('issues a warning if a number-named frame would overwrite a frame', function(done) { | ||
var warnings = []; | ||
spyOn(Lib, 'warn').and.callFake(function(msg) { | ||
warnings.push(msg); | ||
}); | ||
|
||
Plotly.addFrames(gd, [{name: 2}]).then(function() { | ||
return Plotly.addFrames(gd, [{name: 2, layout: {foo: 'bar'}}]); | ||
}).then(function() { | ||
expect(warnings.length).toEqual(1); | ||
expect(warnings[0]).toMatch(/overwriting/); | ||
}).catch(fail).then(done); | ||
}); | ||
|
||
it('avoids name collisions', function(done) { | ||
Plotly.addFrames(gd, [{name: 'frame 0'}, {name: 'frame 2'}]).then(function() { | ||
expect(f).toEqual([{name: 'frame 0'}, {name: 'frame 2'}]); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha.
'string'.toString()
is a thing. How useful!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flushing all falsy names to
null
is a nice addition maintenance-wise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fun
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could maybe have
to make sure that we only cast integer to string.
This is probably an overkill though. @rreusser I'll let decide what's best.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have strong feelings. I feel like you kinda get what you deserve if you're going to be dealing in floating point lookups, so I think maybe it's best to let the chips fall where they may and a different fixed point cuttoff is somewhat arbitrary unless we get fancy with a sprintf library or something.
And yeah, one subtlety:
String(name)
is definitely not the same asname.toString()
:Which is why the extra gymnastics to ensure nothing is falsey.