Skip to content

Commit 4ea79b2

Browse files
committed
Overhaul transition completion behavior
1 parent d06699d commit 4ea79b2

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
@@ -2620,57 +2620,56 @@ Plotly.transition = function(gd, data, layout, traceIndices, transitionConfig) {
26202620
var aborted = false;
26212621

26222622
function executeTransitions() {
2623-
gd._transitionData._interruptCallbacks.push(function () {
2623+
gd._transitionData._interruptCallbacks.push(function() {
26242624
aborted = true;
26252625
});
26262626

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

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

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

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

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

2685-
return Promise.resolve().then(function () {
2686-
if (transitionConfig.redraw) {
2684+
return Promise.resolve().then(function() {
2685+
if(transitionConfig.redraw) {
26872686
return Plotly.redraw(gd);
26882687
}
2689-
}).then(function () {
2688+
}).then(function() {
26902689
gd.emit('plotly_endtransition', []);
2691-
return executeCallbacks(gd._transitionData._cleanupCallbacks);
26922690
});
26932691
}
26942692

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

2698-
flushCallbacks(gd._transitionData._cleanupCallbacks);
26992696
return executeCallbacks(gd._transitionData._interruptCallbacks);
27002697
}
27012698

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)