-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Improved animation merging for layout and traces #1041
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
Changes from 8 commits
0e86edd
a0da51d
b612ac3
8d35b71
c93c18f
33e23db
511de3d
adee9b2
e085a53
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 |
---|---|---|
|
@@ -1299,7 +1299,7 @@ plots.modifyFrames = function(gd, operations) { | |
*/ | ||
plots.computeFrame = function(gd, frameName) { | ||
var frameLookup = gd._transitionData._frameHash; | ||
var i, traceIndices, traceIndex, expandedObj, destIndex, copy; | ||
var i, traceIndices, traceIndex, destIndex; | ||
|
||
var framePtr = frameLookup[frameName]; | ||
|
||
|
@@ -1326,9 +1326,7 @@ plots.computeFrame = function(gd, frameName) { | |
// Merge, starting with the last and ending with the desired frame: | ||
while((framePtr = frameStack.pop())) { | ||
if(framePtr.layout) { | ||
copy = Lib.extendDeepNoArrays({}, framePtr.layout); | ||
expandedObj = Lib.expandObjectPaths(copy); | ||
result.layout = Lib.extendDeepNoArrays(result.layout || {}, expandedObj); | ||
result.layout = plots.extendLayout(result.layout, framePtr.layout); | ||
} | ||
|
||
if(framePtr.data) { | ||
|
@@ -1363,16 +1361,99 @@ plots.computeFrame = function(gd, frameName) { | |
result.traces[destIndex] = traceIndex; | ||
} | ||
|
||
copy = Lib.extendDeepNoArrays({}, framePtr.data[i]); | ||
expandedObj = Lib.expandObjectPaths(copy); | ||
result.data[destIndex] = Lib.extendDeepNoArrays(result.data[destIndex] || {}, expandedObj); | ||
result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]); | ||
} | ||
} | ||
} | ||
|
||
return result; | ||
}; | ||
|
||
/** | ||
* Extend an object, treating container arrays very differently by extracting | ||
* their contents and merging them separately. | ||
* | ||
* This exists so that we can extendDeepNoArrays and avoid stepping into data | ||
* arrays without knowledge of the plot schema, but so that we may also manually | ||
* recurse into known container arrays, such as transforms. | ||
* | ||
* See extendTrace and extendLayout below for usage. | ||
*/ | ||
plots.extendObjectWithContainers = function(dest, src, containerPaths) { | ||
var containerProp, containerVal, i, j, srcProp, destProp, srcContainer, destContainer; | ||
var copy = Lib.extendDeepNoArrays({}, src || {}); | ||
var expandedObj = Lib.expandObjectPaths(copy); | ||
var containerObj = {}; | ||
|
||
// Step through and extract any container properties. Otherwise extendDeepNoArrays | ||
// will clobber any existing properties with an empty array and then supplyDefaults | ||
// will reset everything to defaults. | ||
if(containerPaths && containerPaths.length) { | ||
for(i = 0; i < containerPaths.length; i++) { | ||
containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]); | ||
containerVal = containerProp.get(); | ||
containerProp.set(null); | ||
Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal); | ||
} | ||
} | ||
|
||
dest = Lib.extendDeepNoArrays(dest || {}, expandedObj); | ||
|
||
if(containerPaths && containerPaths.length) { | ||
for(i = 0; i < containerPaths.length; i++) { | ||
srcProp = Lib.nestedProperty(containerObj, containerPaths[i]); | ||
srcContainer = srcProp.get(); | ||
|
||
if(!srcContainer) continue; | ||
|
||
destProp = Lib.nestedProperty(dest, containerPaths[i]); | ||
|
||
destContainer = destProp.get(); | ||
if(!Array.isArray(destContainer)) { | ||
destContainer = []; | ||
destProp.set(destContainer); | ||
} | ||
|
||
for(j = 0; j < srcContainer.length; j++) { | ||
destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcContainer[j]); | ||
} | ||
} | ||
} | ||
|
||
return dest; | ||
}; | ||
|
||
/* | ||
* Extend a trace definition. This method: | ||
* | ||
* 1. directly transfers any array references | ||
* 2. manually recurses into container arrays like transforms | ||
* | ||
* The result is the original object reference with the new contents merged in. | ||
*/ | ||
plots.extendTrace = function(destTrace, srcTrace) { | ||
return plots.extendObjectWithContainers(destTrace, srcTrace, ['transforms']); | ||
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. For completeness, 👍 |
||
}; | ||
|
||
/* | ||
* Extend a layout definition. This method: | ||
* | ||
* 1. directly transfers any array references (not critically important for | ||
* layout since there aren't really data arrays) | ||
* 2. manually recurses into container arrays like annotations | ||
* | ||
* The result is the original object reference with the new contents merged in. | ||
*/ | ||
plots.extendLayout = function(destLayout, srcLayout) { | ||
return plots.extendObjectWithContainers(destLayout, srcLayout, [ | ||
'annotations', | ||
'shapes', | ||
'images', | ||
'sliders', | ||
'updatemenus' | ||
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. 👍 |
||
]); | ||
}; | ||
|
||
/** | ||
* Transition to a set of new data and layout properties | ||
* | ||
|
@@ -1411,16 +1492,7 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) | |
|
||
transitionedTraces.push(traceIdx); | ||
|
||
// This is a multi-step process. First clone w/o arrays so that | ||
// we're not modifying the original: | ||
var update = Lib.extendDeepNoArrays({}, data[i]); | ||
|
||
// Then expand object paths since we don't obey object-overwrite | ||
// semantics here: | ||
update = Lib.expandObjectPaths(update); | ||
|
||
// Finally apply the update (without copying arrays, of course): | ||
Lib.extendDeepNoArrays(gd.data[traceIndices[i]], update); | ||
gd.data[traceIndices[i]] = plots.extendTrace(gd.data[traceIndices[i]], data[i]); | ||
} | ||
|
||
// Follow the same procedure. Clone it so we don't mangle the input, then | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -537,6 +537,69 @@ describe('Test lib.js:', function() { | |
expect(input).toBe(expanded); | ||
expect(origArray).toBe(newArray); | ||
}); | ||
|
||
it('expands bracketed array notation', function() { | ||
var input = {'marker[1]': {color: 'red'}}; | ||
var expected = {marker: [undefined, {color: 'red'}]}; | ||
expect(Lib.expandObjectPaths(input)).toEqual(expected); | ||
}); | ||
|
||
it('expands nested arrays', function() { | ||
var input = {'marker[1].range[1]': 5}; | ||
var expected = {marker: [undefined, {range: [undefined, 5]}]}; | ||
var computed = Lib.expandObjectPaths(input); | ||
expect(computed).toEqual(expected); | ||
}); | ||
|
||
it('expands bracketed array with more nested attributes', function() { | ||
var input = {'marker[1]': {'color.alpha': 2}}; | ||
var expected = {marker: [undefined, {color: {alpha: 2}}]}; | ||
var computed = Lib.expandObjectPaths(input); | ||
expect(computed).toEqual(expected); | ||
}); | ||
|
||
it('expands bracketed array notation without further nesting', function() { | ||
var input = {'marker[1]': 8}; | ||
var expected = {marker: [undefined, 8]}; | ||
var computed = Lib.expandObjectPaths(input); | ||
expect(computed).toEqual(expected); | ||
}); | ||
|
||
it('expands bracketed array notation with further nesting', function() { | ||
var input = {'marker[1].size': 8}; | ||
var expected = {marker: [undefined, {size: 8}]}; | ||
var computed = Lib.expandObjectPaths(input); | ||
expect(computed).toEqual(expected); | ||
}); | ||
|
||
it('expands bracketed array notation with further nesting', function() { | ||
var input = {'marker[1].size.magnitude': 8}; | ||
var expected = {marker: [undefined, {size: {magnitude: 8}}]}; | ||
var computed = Lib.expandObjectPaths(input); | ||
expect(computed).toEqual(expected); | ||
}); | ||
|
||
it('combines changes with single array nesting', function() { | ||
var input = {'marker[1].foo': 5, 'marker[0].foo': 4}; | ||
var expected = {marker: [{foo: 4}, {foo: 5}]}; | ||
var computed = Lib.expandObjectPaths(input); | ||
expect(computed).toEqual(expected); | ||
}); | ||
|
||
// TODO: This test is unimplemented since it's a currently-unused corner case. | ||
// Getting the test to pass requires some extension (pun?) to extendDeepNoArrays | ||
// that's intelligent enough to only selectively merge *some* arrays, in particular | ||
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. those arrays are declared as 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. Does var input = {'marker[1].range': [4, 5] };
Lib.expandObjectPaths(input);
// => {marker: [undefined, {range: [4, 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. Yes, that one works. It's fairly robust when there's just one property. It's the merging of arrays that doesn't currently work. On that note, found a simpler case that might not be okay: Lib.expandObjectPaths({'marker.range[0]': 5, 'marker.range[1]': 2})
// => {marker: {range: [null, 2]}} This happens because there's not a way for it to tell how deeply it should merge, i.e. whether Note that this case does work because the array merge is not deep: Lib.expandObjectPaths({'range[0]': 5, 'range[1]': 2}
// => {range: [5, 2]} 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. My best guess at a workaround was to flag arrays that have been expanded by this routine with 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. @etpinard I added these examples to test comments so that when we regroup and come up with a more complete solution, the cases that work vs. do not work should be readily apparent. There's no regression in functionality here (the opposite, in fact), so it doesn't bother me to consider this mergable even though it's not as complete a solution as I'd like. Side note: I've still debated just making this a deep merge with arrays since the cost of iterating through ~500 array contents is still probably an order of magnitude or two less than the cost of SVG rendering ~500 elements, which kinda gets up to the limit of what you can animate nicely with SVG anyway. |
||
// not data arrays but yes on arrays that were previously expanded. This is a bit | ||
// tricky to get to work just right and currently doesn't have any known use since | ||
// container arrays are not multiply nested. | ||
/* | ||
it('combines changes', function() { | ||
var input = {'marker[1].range[1]': 5, 'marker[1].range[0]': 4}; | ||
var expected = {marker: [undefined, {range: [4, 5]}]}; | ||
var computed = Lib.expandObjectPaths(input); | ||
expect(computed).toEqual(expected); | ||
}); | ||
*/ | ||
}); | ||
|
||
describe('coerce', function() { | ||
|
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.
thanks for reusing this method 🎉