Skip to content
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

Transitions with Plotly.react #3217

Merged
merged 23 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0d34b05
add `anim:true` info to valObjects of animatable scatter attributes
etpinard Nov 5, 2018
1087b8e
add `anim:true` to cartesian axis range attribute declarations
etpinard Nov 5, 2018
78c3840
add layout.transition attribute
etpinard Nov 5, 2018
5010de0
track axes w/ autorange:true / altered ranges during react diffing
etpinard Nov 5, 2018
1cb0d5f
first cut Plotly.react transitions
etpinard Nov 5, 2018
209d7b4
fixup react 'anim' flag logic
etpinard Nov 7, 2018
472d0d1
no need to call _module.plot during layout-only transitions
etpinard Nov 7, 2018
badfeda
add react+transitions tests
etpinard Nov 7, 2018
8a37ff1
Merge branch 'master' into transitions-in-react
etpinard Dec 12, 2018
29ce6df
Merge branch 'master' into transitions-in-react
etpinard Jan 9, 2019
3c51bf3
adapt Axes.transition2 to new axes draw routines
etpinard Jan 9, 2019
19e2bb2
lint
etpinard Jan 9, 2019
c087b0a
improved transitionAxes yes/no logic
etpinard Jan 9, 2019
4ef048f
make trace `uid` accept numbers
etpinard Jan 10, 2019
61ee4e9
make trace uid an `anim:true` attr + improve description
etpinard Jan 10, 2019
73284f4
add test for trace object-constancy out-of-order case
etpinard Jan 10, 2019
33a9530
allow react-transition to diff when old/new fullData lengths mismatch
etpinard Jan 10, 2019
fd121f4
don't count editType:'none' edits as "changes",
etpinard Jan 14, 2019
eb7ba35
add Axes.redrawComponents
etpinard Jan 14, 2019
1ed4e42
merge Plotly.animate and Plotly.react transition pathways
etpinard Jan 14, 2019
8ab38be
add transition.ordering attr
etpinard Jan 14, 2019
057c811
Merge branch 'master' into transitions-in-react
etpinard Jan 16, 2019
ec4d8b9
some linting
etpinard Jan 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/colorscale/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ function code(s) {
* most of these attributes already require a recalc, but the ones that do not
* have editType *style* or *plot* unless you override (presumably with *calc*)
*
* - anim {boolean) (dflt: undefined): is 'color' animatable?
*
* @return {object}
*/
module.exports = function colorScaleAttrs(context, opts) {
Expand Down Expand Up @@ -109,6 +111,10 @@ module.exports = function colorScaleAttrs(context, opts) {
' ' + minmaxFull + ' if set.'
].join('')
};

if(opts.anim) {
attrs.color.anim = true;
}
}

attrs[auto] = {
Expand Down
5 changes: 5 additions & 0 deletions src/components/images/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ function imageDefaults(imageIn, imageOut, fullLayout) {
var axLetter = axLetters[i];
var axRef = Axes.coerceRef(imageIn, imageOut, gdMock, axLetter, 'paper');

if(axRef !== 'paper') {
var ax = Axes.getFromId(gdMock, axRef);
ax._imgIndices.push(imageOut._index);
}

Axes.coercePosition(imageOut, gdMock, coerce, axRef, axLetter, 0);
}

Expand Down
89 changes: 74 additions & 15 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2738,9 +2738,11 @@ exports.react = function(gd, data, layout, config) {
var newFullData = gd._fullData;
var newFullLayout = gd._fullLayout;
var immutable = newFullLayout.datarevision === undefined;
var transition = newFullLayout.transition;

var restyleFlags = diffData(gd, oldFullData, newFullData, immutable);
var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable);
var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition);
var newDataRevision = relayoutFlags.newDataRevision;
var restyleFlags = diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision);

// TODO: how to translate this part of relayout to Plotly.react?
// // Setting width or height to null must reset the graph's width / height
Expand Down Expand Up @@ -2770,7 +2772,19 @@ exports.react = function(gd, data, layout, config) {
seq.push(addFrames);
}

if(restyleFlags.fullReplot || relayoutFlags.layoutReplot || configChanged) {
// Transition pathway,
// only used when 'transition' is set by user and
// when at least one animatable attribute has changed,
// N.B. config changed aren't animatable
if(newFullLayout.transition && !configChanged && (restyleFlags.anim || relayoutFlags.anim)) {
Plots.doCalcdata(gd);
subroutines.doAutoRangeAndConstraints(gd);

seq.push(function() {
return Plots.transitionFromReact(gd, restyleFlags, relayoutFlags, oldFullLayout);
});
}
else if(restyleFlags.fullReplot || relayoutFlags.layoutReplot || configChanged) {
gd._fullLayout._skipDefaults = true;
seq.push(exports.plot);
}
Expand Down Expand Up @@ -2823,8 +2837,10 @@ exports.react = function(gd, data, layout, config) {

};

function diffData(gd, oldFullData, newFullData, immutable) {
if(oldFullData.length !== newFullData.length) {
function diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision) {
var sameTraceLength = oldFullData.length === newFullData.length;

if(!transition && !sameTraceLength) {
return {
fullReplot: true,
calc: true
Expand All @@ -2833,6 +2849,9 @@ function diffData(gd, oldFullData, newFullData, immutable) {

var flags = editTypes.traceFlags();
flags.arrays = {};
flags.nChanges = 0;
flags.nChangesAnim = 0;

var i, trace;

function getTraceValObject(parts) {
Expand All @@ -2843,31 +2862,41 @@ function diffData(gd, oldFullData, newFullData, immutable) {
getValObject: getTraceValObject,
flags: flags,
immutable: immutable,
transition: transition,
newDataRevision: newDataRevision,
gd: gd
};


var seenUIDs = {};

for(i = 0; i < oldFullData.length; i++) {
trace = newFullData[i]._fullInput;
if(Plots.hasMakesDataTransform(trace)) trace = newFullData[i];
if(seenUIDs[trace.uid]) continue;
seenUIDs[trace.uid] = 1;
if(newFullData[i]) {
trace = newFullData[i]._fullInput;
if(Plots.hasMakesDataTransform(trace)) trace = newFullData[i];
if(seenUIDs[trace.uid]) continue;
seenUIDs[trace.uid] = 1;

getDiffFlags(oldFullData[i]._fullInput, trace, [], diffOpts);
getDiffFlags(oldFullData[i]._fullInput, trace, [], diffOpts);
}
}

if(flags.calc || flags.plot) {
flags.fullReplot = true;
}

if(transition && flags.nChanges && flags.nChangesAnim) {
flags.anim = flags.nChanges === flags.nChangesAnim && sameTraceLength ? 'all' : 'some';
archmoj marked this conversation as resolved.
Show resolved Hide resolved
}

return flags;
}

function diffLayout(gd, oldFullLayout, newFullLayout, immutable) {
function diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition) {
var flags = editTypes.layoutFlags();
flags.arrays = {};
flags.rangesAltered = {};
flags.nChanges = 0;
flags.nChangesAnim = 0;

function getLayoutValObject(parts) {
return PlotSchema.getLayoutValObject(newFullLayout, parts);
Expand All @@ -2877,6 +2906,7 @@ function diffLayout(gd, oldFullLayout, newFullLayout, immutable) {
getValObject: getLayoutValObject,
flags: flags,
immutable: immutable,
transition: transition,
gd: gd
};

Expand All @@ -2886,11 +2916,15 @@ function diffLayout(gd, oldFullLayout, newFullLayout, immutable) {
flags.layoutReplot = true;
}

if(transition && flags.nChanges && flags.nChangesAnim) {
flags.anim = flags.nChanges === flags.nChangesAnim ? 'all' : 'some';
}

return flags;
}

function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
var valObject, key;
var valObject, key, astr;

var getValObject = opts.getValObject;
var flags = opts.flags;
Expand All @@ -2905,6 +2939,25 @@ function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
return;
}
editTypes.update(flags, valObject);

if(editType !== 'none') {
flags.nChanges++;
}

// track animatable changes
if(opts.transition && valObject.anim) {
flags.nChangesAnim++;
}

// track cartesian axes with altered ranges
if(AX_RANGE_RE.test(astr) || AX_AUTORANGE_RE.test(astr)) {
flags.rangesAltered[outerparts[0]] = 1;
}

// track datarevision changes
if(key === 'datarevision') {
flags.newDataRevision = 1;
}
}

function valObjectCanBeDataArray(valObject) {
Expand All @@ -2913,10 +2966,12 @@ function getDiffFlags(oldContainer, newContainer, outerparts, opts) {

for(key in oldContainer) {
// short-circuit based on previous calls or previous keys that already maximized the pathway
if(flags.calc) return;
if(flags.calc && !opts.transition) return;
archmoj marked this conversation as resolved.
Show resolved Hide resolved

var oldVal = oldContainer[key];
var newVal = newContainer[key];
var parts = outerparts.concat(key);
astr = parts.join('.');

if(key.charAt(0) === '_' || typeof oldVal === 'function' || oldVal === newVal) continue;
archmoj marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -2932,7 +2987,6 @@ function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
if(key === 'range' && newContainer.autorange) continue;
if((key === 'zmin' || key === 'zmax') && newContainer.type === 'contourcarpet') continue;

var parts = outerparts.concat(key);
valObject = getValObject(parts);

// in case type changed, we may not even *have* a valObject.
Expand Down Expand Up @@ -3003,6 +3057,11 @@ function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
if(immutable) {
flags.calc = true;
}

// look for animatable attributes when the data changed
if(immutable || opts.newDataRevision) {
changed();
}
}
else if(wasArray !== nowArray) {
flags.calc = true;
Expand Down
13 changes: 13 additions & 0 deletions src/plots/animation_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ module.exports = {
role: 'info',
min: 0,
dflt: 500,
editType: 'none',
description: [
'The duration of the transition, in milliseconds. If equal to zero,',
'updates are synchronous.'
Expand Down Expand Up @@ -116,7 +117,19 @@ module.exports = {
'bounce-in-out'
],
role: 'info',
editType: 'none',
description: 'The easing function used for the transition'
},
ordering: {
valType: 'enumerated',
values: ['layout first', 'traces first'],
dflt: 'layout first',
role: 'info',
editType: 'none',
description: [
'Determines whether the figure\'s layout or traces smoothly transitions',
'during updates that make both traces and layout change.'
].join(' ')
}
}
};
9 changes: 8 additions & 1 deletion src/plots/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,18 @@ module.exports = {
uid: {
valType: 'string',
role: 'info',
editType: 'plot'
editType: 'plot',
anim: true,
description: [
'Assign an id to this trace,',
'Use this to provide object constancy between traces during animations',
'and transitions.'
].join(' ')
},
ids: {
valType: 'data_array',
editType: 'calc',
anim: true,
description: [
'Assigns id labels to each datum.',
'These ids for object constancy of data points during animation.',
Expand Down
33 changes: 33 additions & 0 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,39 @@ axes.cleanPosition = function(pos, gd, axRef) {
return cleanPos(pos);
};

axes.redrawComponents = function(gd, axIds) {
axIds = axIds ? axIds : axes.listIds(gd);

var fullLayout = gd._fullLayout;

function _redrawOneComp(moduleName, methodName, stashName, shortCircuit) {
var method = Registry.getComponentMethod(moduleName, methodName);
var stash = {};

for(var i = 0; i < axIds.length; i++) {
var ax = fullLayout[axes.id2name(axIds[i])];
var indices = ax[stashName];

for(var j = 0; j < indices.length; j++) {
var ind = indices[j];

if(!stash[ind]) {
method(gd, ind);
stash[ind] = 1;
// once is enough for images (which doesn't use the `i` arg anyway)
if(shortCircuit) return;
}
}
}
}

// annotations and shapes 'draw' method is slow,
// use the finer-grained 'drawOne' method instead
_redrawOneComp('annotations', 'drawOne', '_annIndices');
_redrawOneComp('shapes', 'drawOne', '_shapeIndices');
_redrawOneComp('images', 'draw', '_imgIndices', true);
};

var getDataConversions = axes.getDataConversions = function(gd, trace, target, targetArray) {
var ax;

Expand Down
28 changes: 5 additions & 23 deletions src/plots/cartesian/dragbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {

// viewbox redraw at first
updateSubplots(scrollViewBox);
ticksAndAnnotations(ns, ew);
ticksAndAnnotations();

// then replot after a delay to make sure
// no more scrolling is coming
Expand Down Expand Up @@ -513,7 +513,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
if(xActive) dragAxList(xaxes, dx);
if(yActive) dragAxList(yaxes, dy);
updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]);
ticksAndAnnotations(yActive, xActive);
ticksAndAnnotations();
return;
}

Expand Down Expand Up @@ -585,12 +585,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
}

updateSubplots([x0, y0, pw - dx, ph - dy]);
ticksAndAnnotations(yActive, xActive);
ticksAndAnnotations();
}

// Draw ticks and annotations (and other components) when ranges change.
// Also records the ranges that have changed for use by update at the end.
function ticksAndAnnotations(ns, ew) {
function ticksAndAnnotations() {
var activeAxIds = [];
var i;

Expand Down Expand Up @@ -618,25 +618,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
updates[ax._name + '.range[1]'] = ax.range[1];
}

function redrawObjs(objArray, method, shortCircuit) {
for(i = 0; i < objArray.length; i++) {
var obji = objArray[i];

if((ew && activeAxIds.indexOf(obji.xref) !== -1) ||
(ns && activeAxIds.indexOf(obji.yref) !== -1)) {
method(gd, i);
// once is enough for images (which doesn't use the `i` arg anyway)
if(shortCircuit) return;
}
}
}

// annotations and shapes 'draw' method is slow,
// use the finer-grained 'drawOne' method instead

redrawObjs(gd._fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne'));
redrawObjs(gd._fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne'));
redrawObjs(gd._fullLayout.images || [], Registry.getComponentMethod('images', 'draw'), true);
Axes.redrawComponents(gd, activeAxIds);
}

function doubleClick() {
Expand Down
5 changes: 3 additions & 2 deletions src/plots/cartesian/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,12 @@ module.exports = {
valType: 'info_array',
role: 'info',
items: [
{valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}},
{valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}}
{valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}, anim: true},
{valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}, anim: true}
],
editType: 'axrange',
impliedEdits: {'autorange': false},
anim: true,
description: [
'Sets the range of this axis.',
'If the axis `type` is *log*, then you must take the log of your',
Expand Down
1 change: 1 addition & 0 deletions src/plots/cartesian/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; });
axLayoutOut._annIndices = [];
axLayoutOut._shapeIndices = [];
axLayoutOut._imgIndices = [];
axLayoutOut._subplotsWith = [];
axLayoutOut._counterAxes = [];

Expand Down
Loading