Skip to content

Commit 8ab38be

Browse files
committed
add transition.ordering attr
- with values 'layout first' and 'traces first' which determines whether the figure's layout OR traces are smoothly transitions during Plotly.react calls that generate a data AND layout diff.
1 parent 1ed4e42 commit 8ab38be

File tree

3 files changed

+71
-28
lines changed

3 files changed

+71
-28
lines changed

src/plots/animation_attributes.js

+11
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,16 @@ module.exports = {
120120
editType: 'none',
121121
description: 'The easing function used for the transition'
122122
},
123+
ordering: {
124+
valType: 'enumerated',
125+
values: ['layout first', 'traces first'],
126+
dflt: 'layout first',
127+
role: 'info',
128+
editType: 'none',
129+
description: [
130+
'Determines whether the figure\'s layout or traces smoothly transitions',
131+
'during updates that make both traces and layout change.'
132+
].join(' ')
133+
}
123134
}
124135
};

src/plots/plots.js

+35-23
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,7 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
14621462
if(Lib.isPlainObject(layoutIn.transition)) {
14631463
coerce('transition.duration');
14641464
coerce('transition.easing');
1465+
coerce('transition.ordering');
14651466
}
14661467

14671468
Registry.getComponentMethod(
@@ -2509,39 +2510,50 @@ plots.transitionFromReact = function(gd, restyleFlags, relayoutFlags, oldFullLay
25092510
var basePlotModules = fullLayout._basePlotModules;
25102511
var i;
25112512

2512-
// Here handle the exception that we refuse to animate traces and axes at the same
2513-
// time. In other words, if there's an axis transition, then set the data transition
2514-
// to instantaneous.
2513+
var axisTransitionOpts;
25152514
var traceTransitionOpts;
25162515
var transitionedTraces;
25172516

2518-
if(axEdits.length) {
2519-
for(i = 0; i < basePlotModules.length; i++) {
2520-
if(basePlotModules[i].transitionAxes2) {
2521-
basePlotModules[i].transitionAxes2(gd, axEdits, transitionOpts, makeCallback);
2517+
var allTraceIndices = [];
2518+
for(i = 0; i < fullData.length; i++) {
2519+
allTraceIndices.push(i);
2520+
}
2521+
2522+
function transitionAxes() {
2523+
for(var i = 0; i < basePlotModules.length; i++) {
2524+
if(basePlotModules[i].transitionAxes) {
2525+
basePlotModules[i].transitionAxes(gd, axEdits, axisTransitionOpts, makeCallback);
25222526
}
25232527
}
2528+
}
25242529

2525-
// This means do not transition traces,
2526-
// this happens on layout-only (e.g. axis range) animations
2527-
traceTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0});
2528-
transitionedTraces = null;
2529-
} else {
2530-
traceTransitionOpts = transitionOpts;
2531-
transitionedTraces = [];
2532-
for(i = 0; i < fullData.length; i++) {
2533-
transitionedTraces.push(i);
2530+
function transitionTraces() {
2531+
for(var i = 0; i < basePlotModules.length; i++) {
2532+
basePlotModules[i].plot(gd, transitionedTraces, traceTransitionOpts, makeCallback);
25342533
}
25352534
}
25362535

2537-
// Note that we pass a callback to *create* the callback that must be invoked on completion.
2538-
// This is since not all traces know about transitions, so it greatly simplifies matters if
2539-
// the trace is responsible for creating a callback, if needed, and then executing it when
2540-
// the time is right.
2541-
if(restyleFlags.anim) {
2542-
for(i = 0; i < basePlotModules.length; i++) {
2543-
basePlotModules[i].plot(gd, transitionedTraces, traceTransitionOpts, makeCallback);
2536+
if(axEdits.length && restyleFlags.anim) {
2537+
if(transitionOpts.ordering === 'traces first') {
2538+
axisTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0});
2539+
transitionedTraces = allTraceIndices;
2540+
traceTransitionOpts = transitionOpts;
2541+
transitionTraces();
2542+
setTimeout(transitionAxes, transitionOpts.duration);
2543+
} else {
2544+
axisTransitionOpts = transitionOpts;
2545+
transitionedTraces = null;
2546+
traceTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0});
2547+
transitionAxes();
2548+
transitionTraces();
25442549
}
2550+
} else if(axEdits.length) {
2551+
axisTransitionOpts = transitionOpts;
2552+
transitionAxes();
2553+
} else if(restyleFlags.anim) {
2554+
transitionedTraces = allTraceIndices;
2555+
traceTransitionOpts = transitionOpts;
2556+
transitionTraces();
25452557
}
25462558
};
25472559

test/jasmine/tests/transition_test.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ describe('Plotly.react transitions:', function() {
515515
.then(done);
516516
});
517517

518-
it('should only transition the layout when both traces and layout have animatable changes', function(done) {
518+
it('should only transition the layout when both traces and layout have animatable changes by default', function(done) {
519519
var data = [{y: [1, 2, 1]}];
520520
var layout = {
521521
transition: {duration: 10},
@@ -566,12 +566,32 @@ describe('Plotly.react transitions:', function() {
566566
[gd._fullLayout._basePlotModules[0], 'plot', [
567567
// one instantaneous transition options to halt
568568
// other trace transitions (if any)
569-
[gd, null, {duration: 0, easing: 'cubic-in-out'}, 'function'],
569+
[gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function'],
570570
// one _module.plot call from the relayout at end of axis transition
571571
[gd]
572572
]],
573573
]);
574574
})
575+
.then(function() {
576+
data[0].marker.color = 'red';
577+
layout.xaxis.range = [-2, 2];
578+
layout.transition.ordering = 'traces first';
579+
return Plotly.react(gd, data, layout);
580+
})
581+
.then(delay(20))
582+
.then(function() {
583+
assertSpies('both trace and layout transitions under *ordering:traces first*', [
584+
[Plots, 'transitionFromReact', 1],
585+
[gd._fullLayout._basePlotModules[0], 'plot', [
586+
// one smooth transition
587+
[gd, [0], {duration: 10, easing: 'cubic-in-out', ordering: 'traces first'}, 'function'],
588+
// one by relayout call at the end of instantaneous axis transition
589+
[gd]
590+
]],
591+
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 1],
592+
[Registry, 'call', [['relayout', gd, {'xaxis.range': [-2, 2]}]]]
593+
]);
594+
})
575595
.catch(failTest)
576596
.then(done);
577597
});
@@ -674,7 +694,7 @@ describe('Plotly.react transitions:', function() {
674694
[gd._fullLayout._basePlotModules[0], 'plot', [
675695
// one instantaneous transition options to halt
676696
// other trace transitions (if any)
677-
[gd, null, {duration: 0, easing: 'cubic-in-out'}, 'function'],
697+
[gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function'],
678698
// one _module.plot call from the relayout at end of axis transition
679699
[gd]
680700
]],
@@ -690,8 +710,8 @@ describe('Plotly.react transitions:', function() {
690710
[Plots, 'transitionFromReact', 1],
691711
[gd._fullLayout._basePlotModules[0], 'transitionAxes', 0],
692712
[gd._fullLayout._basePlotModules[0], 'plot', [
693-
[gd, [0], {duration: 10, easing: 'cubic-in-out'}, 'function'],
694713
// called from Plots.transitionFromReact
714+
[gd, [0], {duration: 10, easing: 'cubic-in-out', ordering: 'layout first'}, 'function'],
695715
]],
696716
]);
697717
assertAxAutorange('axes are still autorange:false', false);
@@ -762,7 +782,7 @@ describe('Plotly.react transitions:', function() {
762782
[gd._fullLayout._basePlotModules[0], 'plot', [
763783
// one instantaneous transition options to halt
764784
// other trace transitions (if any)
765-
[gd, null, {duration: 0, easing: 'cubic-in-out'}, 'function'],
785+
[gd, null, {duration: 0, easing: 'cubic-in-out', ordering: 'layout first'}, 'function'],
766786
// one _module.plot call from the relayout at end of axis transition
767787
[gd]
768788
]]

0 commit comments

Comments
 (0)