Skip to content

Commit c0087c2

Browse files
committed
Overhaul transition completion behavior
1 parent b47b7f6 commit c0087c2

File tree

6 files changed

+68
-65
lines changed

6 files changed

+68
-65
lines changed

src/components/drawing/index.js

+10-11
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ drawing.setRect = function(s, x, y, w, h) {
4646
s.call(drawing.setPosition, x, y).call(drawing.setSize, w, h);
4747
};
4848

49-
drawing.translatePoint = function(d, sel, xa, ya, trace, transitionConfig, joinDirection) {
49+
drawing.translatePoint = function(d, sel, xa, ya) {
5050
// put xp and yp into d if pixel scaling is already done
5151
var x = d.xp || xa.c2p(d.x),
5252
y = d.yp || ya.c2p(d.y);
@@ -62,10 +62,10 @@ drawing.translatePoint = function(d, sel, xa, ya, trace, transitionConfig, joinD
6262
else sel.remove();
6363
};
6464

65-
drawing.translatePoints = function(s, xa, ya, trace, transitionConfig, joinDirection) {
66-
s.each(function(d, i) {
65+
drawing.translatePoints = function(s, xa, ya, trace, transitionConfig) {
66+
s.each(function(d) {
6767
var sel = d3.select(this);
68-
drawing.translatePoint(d, sel, xa, ya, trace, transitionConfig, joinDirection);
68+
drawing.translatePoint(d, sel, xa, ya, trace, transitionConfig);
6969
});
7070
};
7171

@@ -89,7 +89,7 @@ drawing.crispRound = function(td, lineWidth, dflt) {
8989
};
9090

9191
drawing.singleLineStyle = function(d, s, lw, lc, ld) {
92-
s.style('fill', 'none')
92+
s.style('fill', 'none');
9393
var line = (((d || [])[0] || {}).trace || {}).line || {},
9494
lw1 = lw || line.width||0,
9595
dash = ld || line.dash || '';
@@ -193,7 +193,7 @@ drawing.symbolNumber = function(v) {
193193
return Math.floor(Math.max(v, 0));
194194
};
195195

196-
function singlePointStyle (d, sel, trace, markerScale, lineScale, marker, markerLine) {
196+
function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine) {
197197

198198
// 'so' is suspected outliers, for box plots
199199
var fillColor,
@@ -235,7 +235,7 @@ function singlePointStyle (d, sel, trace, markerScale, lineScale, marker, marker
235235
sel.call(Color.stroke, lineColor);
236236
}
237237
}
238-
};
238+
}
239239

240240
drawing.singlePointStyle = function(d, sel, trace) {
241241
var marker = trace.marker,
@@ -249,13 +249,12 @@ drawing.singlePointStyle = function(d, sel, trace) {
249249

250250
singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine);
251251

252-
}
252+
};
253253

254254
drawing.pointStyle = function(s, trace) {
255255
if(!s.size()) return;
256256

257-
var marker = trace.marker,
258-
markerLine = marker.line;
257+
var marker = trace.marker;
259258

260259
// only scatter & box plots get marker path and opacity
261260
// bars, histograms don't
@@ -295,7 +294,7 @@ drawing.pointStyle = function(s, trace) {
295294
lineScale = drawing.tryColorscale(marker, markerIn, 'line.');
296295

297296
s.each(function(d) {
298-
drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale)
297+
drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale);
299298
});
300299
};
301300

src/plot_api/plot_api.js

+28-31
Original file line numberDiff line numberDiff line change
@@ -2621,57 +2621,56 @@ Plotly.transition = function(gd, data, layout, traceIndices, transitionConfig) {
26212621
var aborted = false;
26222622

26232623
function executeTransitions() {
2624-
gd._transitionData._interruptCallbacks.push(function () {
2624+
gd._transitionData._interruptCallbacks.push(function() {
26252625
aborted = true;
26262626
});
26272627

2628+
// Construct callbacks that are executed on transition end. This ensures the d3 transitions
2629+
// are *complete* before anything else is done.
2630+
var numCallbacks = 0;
2631+
var numCompleted = 0;
2632+
function makeCallback() {
2633+
numCallbacks++;
2634+
return function() {
2635+
numCompleted++;
2636+
// When all are complete, perform a redraw:
2637+
if(!aborted && numCompleted === numCallbacks) {
2638+
completeTransition();
2639+
}
2640+
};
2641+
}
2642+
26282643
var traceTransitionConfig;
26292644
var hasTraceTransition = false;
26302645
var j;
26312646
var basePlotModules = fullLayout._basePlotModules;
2632-
for(j = 0; j < basePlotModules.length; j++) {
2633-
if(basePlotModules[j].animatable) {
2634-
hasTraceTransition = true;
2635-
}
2636-
basePlotModules[j].plot(gd, transitionedTraces, transitionConfig);
2637-
}
2638-
26392647
var hasAxisTransition = false;
26402648

26412649
if(layout) {
26422650
for(j = 0; j < basePlotModules.length; j++) {
26432651
if(basePlotModules[j].transitionAxes) {
26442652
var newLayout = Lib.expandObjectPaths(layout);
2645-
hasAxisTransition = hasAxisTransition || basePlotModules[j].transitionAxes(gd, newLayout, transitionConfig);
2653+
hasAxisTransition = basePlotModules[j].transitionAxes(gd, newLayout, transitionConfig, makeCallback) || hasAxisTransition;
26462654
}
26472655
}
26482656
}
26492657

2658+
// Here handle the exception that we refuse to animate scales and axes at the same
2659+
// time. In other words, if there's an axis transition, then set the data transition
2660+
// to instantaneous.
26502661
if(hasAxisTransition) {
26512662
traceTransitionConfig = Lib.extendFlat({}, transitionConfig);
26522663
traceTransitionConfig.duration = 0;
26532664
} else {
26542665
traceTransitionConfig = transitionConfig;
26552666
}
26562667

2657-
// Construct callbacks that are executed on transition end. This ensures the d3 transitions
2658-
// are *complete* before anything else is done.
2659-
var numCallbacks = 0;
2660-
var numCompleted = 0;
2661-
function makeCallback () {
2662-
numCallbacks++;
2663-
return function () {
2664-
numCompleted++;
2665-
// When all are complete, perform a redraw:
2666-
if (!aborted && numCompleted === numCallbacks) {
2667-
completeTransition();
2668-
}
2669-
}
2670-
}
2671-
26722668
for(j = 0; j < basePlotModules.length; j++) {
2673-
var _config = Lib.extendFlat({onComplete: makeCallback()}, traceTransitionConfig);
2674-
basePlotModules[j].plot(gd, transitionedTraces, _config);
2669+
// Note that we pass a callback to *create* the callback that must be invoked on completion.
2670+
// This is since not all traces know about transitions, so it greatly simplifies matters if
2671+
// the trace is responsible for creating a callback, if needed, and then executing it when
2672+
// the time is right.
2673+
basePlotModules[j].plot(gd, transitionedTraces, traceTransitionConfig, makeCallback);
26752674
}
26762675

26772676
if(!hasAxisTransition && !hasTraceTransition) {
@@ -2683,20 +2682,18 @@ Plotly.transition = function(gd, data, layout, traceIndices, transitionConfig) {
26832682
function completeTransition() {
26842683
flushCallbacks(gd._transitionData._interruptCallbacks);
26852684

2686-
return Promise.resolve().then(function () {
2687-
if (transitionConfig.redraw) {
2685+
return Promise.resolve().then(function() {
2686+
if(transitionConfig.redraw) {
26882687
return Plotly.redraw(gd);
26892688
}
2690-
}).then(function () {
2689+
}).then(function() {
26912690
gd.emit('plotly_endtransition', []);
2692-
return executeCallbacks(gd._transitionData._cleanupCallbacks);
26932691
});
26942692
}
26952693

26962694
function interruptPreviousTransitions() {
26972695
gd.emit('plotly_interrupttransition', []);
26982696

2699-
flushCallbacks(gd._transitionData._cleanupCallbacks);
27002697
return executeCallbacks(gd._transitionData._interruptCallbacks);
27012698
}
27022699

src/plots/cartesian/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ exports.attributes = require('./attributes');
2727

2828
exports.transitionAxes = require('./transition_axes');
2929

30-
exports.plot = function(gd, traces, transitionOpts) {
30+
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
3131
var cdSubplot, cd, trace, i, j, k;
3232

3333
var fullLayout = gd._fullLayout,
@@ -106,7 +106,7 @@ exports.plot = function(gd, traces, transitionOpts) {
106106
}
107107
}
108108

109-
_module.plot(gd, subplotInfo, cdModule, transitionOpts);
109+
_module.plot(gd, subplotInfo, cdModule, transitionOpts, makeOnCompleteCallback);
110110
}
111111
}
112112
};

src/plots/cartesian/transition_axes.js

+13-10
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var Lib = require('../../lib');
1616
var Axes = require('./axes');
1717
var axisRegex = /((x|y)([2-9]|[1-9][0-9]+)?)axis$/;
1818

19-
module.exports = function transitionAxes(gd, newLayout, transitionConfig) {
19+
module.exports = function transitionAxes(gd, newLayout, transitionConfig, makeOnCompleteCallback) {
2020
var fullLayout = gd._fullLayout;
2121
var axes = [];
2222

@@ -227,6 +227,12 @@ module.exports = function transitionAxes(gd, newLayout, transitionConfig) {
227227

228228
}
229229

230+
var onComplete;
231+
if(makeOnCompleteCallback) {
232+
// This module makes the choice whether or not it notifies Plotly.transition
233+
// about completion:
234+
onComplete = makeOnCompleteCallback();
235+
}
230236

231237
function transitionComplete() {
232238
var attrs = {};
@@ -239,15 +245,17 @@ module.exports = function transitionAxes(gd, newLayout, transitionConfig) {
239245
axi.range = to.slice();
240246
}
241247

248+
// Signal that this transition has completed:
249+
onComplete && onComplete();
250+
242251
return Plotly.relayout(gd, attrs).then(function() {
243252
for(var i = 0; i < affectedSubplots.length; i++) {
244253
unsetSubplotTransform(affectedSubplots[i]);
245254
}
246-
247255
});
248256
}
249257

250-
function transitionTail() {
258+
function transitionInterrupt() {
251259
var attrs = {};
252260
for(var i = 0; i < updatedAxisIds.length; i++) {
253261
var axi = gd._fullLayout[updatedAxisIds[i] + 'axis'];
@@ -270,13 +278,7 @@ module.exports = function transitionAxes(gd, newLayout, transitionConfig) {
270278
gd._transitionData._interruptCallbacks.push(function() {
271279
cancelAnimationFrame(raf);
272280
raf = null;
273-
return transitionTail();
274-
});
275-
276-
gd._transitionData._cleanupCallbacks.push(function() {
277-
cancelAnimationFrame(raf);
278-
raf = null;
279-
return transitionComplete();
281+
return transitionInterrupt();
280282
});
281283

282284
function doFrame() {
@@ -290,6 +292,7 @@ module.exports = function transitionAxes(gd, newLayout, transitionConfig) {
290292
}
291293

292294
if(t2 - t1 > transitionConfig.duration) {
295+
transitionComplete();
293296
raf = cancelAnimationFrame(doFrame);
294297
} else {
295298
raf = requestAnimationFrame(doFrame);

src/plots/plots.js

-4
Original file line numberDiff line numberDiff line change
@@ -447,10 +447,6 @@ plots.createTransitionData = function(gd) {
447447
gd._transitionData._counter = 0;
448448
}
449449

450-
if(!gd._transitionData._cleanupCallbacks) {
451-
gd._transitionData._cleanupCallbacks = [];
452-
}
453-
454450
if(!gd._transitionData._interruptCallbacks) {
455451
gd._transitionData._interruptCallbacks = [];
456452
}

src/traces/scatter/plot.js

+15-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var linePoints = require('./line_points');
2121
var linkTraces = require('./link_traces');
2222
var polygonTester = require('../../lib/polygon').tester;
2323

24-
module.exports = function plot(gd, plotinfo, cdscatter, transitionConfig) {
24+
module.exports = function plot(gd, plotinfo, cdscatter, transitionConfig, makeOnCompleteCallback) {
2525
var i, uids, selection, join;
2626

2727
var scatterlayer = plotinfo.plot.select('g.scatterlayer');
@@ -31,6 +31,14 @@ module.exports = function plot(gd, plotinfo, cdscatter, transitionConfig) {
3131
var isFullReplot = !transitionConfig;
3232
var hasTransition = !!transitionConfig && transitionConfig.duration > 0;
3333

34+
var onComplete;
35+
if(makeOnCompleteCallback && hasTransition) {
36+
// If it was passed a callback to register completion, make a callback. If
37+
// this is created, then it must be executed on completion, otherwise the
38+
// pos-transition redraw will not execute:
39+
onComplete = makeOnCompleteCallback();
40+
}
41+
3442
selection = scatterlayer.selectAll('g.trace');
3543

3644
join = selection.data(cdscatter, function(d) {return d[0].trace.uid;});
@@ -62,16 +70,16 @@ module.exports = function plot(gd, plotinfo, cdscatter, transitionConfig) {
6270
return idx1 > idx2 ? 1 : -1;
6371
});
6472

65-
if (hasTransition) {
73+
if(hasTransition) {
6674
var transition = d3.transition()
6775
.duration(transitionConfig.duration)
6876
.ease(transitionConfig.ease)
6977
.delay(transitionConfig.delay)
70-
.each('end', function () {
71-
transitionConfig.onComplete && transitionConfig.onComplete();
78+
.each('end', function() {
79+
onComplete && onComplete();
7280
});
7381

74-
transition.each(function () {
82+
transition.each(function() {
7583
// Must run the selection again since otherwise enters/updates get grouped together
7684
// and these get executed out of order. Except we need them in order!
7785
scatterlayer.selectAll('g.trace').each(function(d, i) {
@@ -393,9 +401,9 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
393401
enter.call(Drawing.pointStyle, trace)
394402
.call(Drawing.translatePoints, xa, ya, trace, transitionConfig, 1);
395403

396-
join.transition().each(function (d) {
404+
join.transition().each(function(d) {
397405
var sel = transition(d3.select(this));
398-
Drawing.translatePoint(d, sel, xa, ya, trace, transitionConfig, 0)
406+
Drawing.translatePoint(d, sel, xa, ya, trace, transitionConfig, 0);
399407
Drawing.singlePointStyle(d, sel, trace);
400408
});
401409

0 commit comments

Comments
 (0)