Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1cb0d5f

Browse filesBrowse files
committed
first cut Plotly.react transitions
- temporarily add Plots.transitions2 & Cartesian.transitionAxes2 - call Plots.transition2 from Plotly.react when at one animatable attribute has changed AND 'layout.transition` is set by user - 'redraw' after transition iff not all changed attributer are animatable - handle simultaneous trace + layout updates the same way as Plotly.animate - special handling for 'datarevision' diff'ing
1 parent 5010de0 commit 1cb0d5f
Copy full SHA for 1cb0d5f

File tree

4 files changed

+440
-10
lines changed
Filter options

4 files changed

+440
-10
lines changed

‎src/plot_api/plot_api.js

Copy file name to clipboardExpand all lines: src/plot_api/plot_api.js
+43-8Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,9 +2336,11 @@ exports.react = function(gd, data, layout, config) {
23362336
var newFullData = gd._fullData;
23372337
var newFullLayout = gd._fullLayout;
23382338
var immutable = newFullLayout.datarevision === undefined;
2339+
var transition = newFullLayout.transition;
23392340

2340-
var restyleFlags = diffData(gd, oldFullData, newFullData, immutable);
2341-
var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable);
2341+
var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition);
2342+
var newDataRevision = relayoutFlags.newDataRevision;
2343+
var restyleFlags = diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision);
23422344

23432345
// TODO: how to translate this part of relayout to Plotly.react?
23442346
// // Setting width or height to null must reset the graph's width / height
@@ -2368,7 +2370,19 @@ exports.react = function(gd, data, layout, config) {
23682370
seq.push(addFrames);
23692371
}
23702372

2371-
if(restyleFlags.fullReplot || relayoutFlags.layoutReplot || configChanged) {
2373+
// Transition pathway,
2374+
// only used when 'transition' is set by user and
2375+
// when at least one animatable attribute has changed,
2376+
// N.B. config changed aren't animatable
2377+
if(newFullLayout.transition && !configChanged && (restyleFlags.anim || relayoutFlags.anim)) {
2378+
Plots.doCalcdata(gd);
2379+
subroutines.doAutoRangeAndConstraints(gd);
2380+
2381+
seq.push(function() {
2382+
return Plots.transition2(gd, restyleFlags, relayoutFlags, oldFullLayout);
2383+
});
2384+
}
2385+
else if(restyleFlags.fullReplot || relayoutFlags.layoutReplot || configChanged) {
23722386
gd._fullLayout._skipDefaults = true;
23732387
seq.push(exports.plot);
23742388
}
@@ -2421,7 +2435,7 @@ exports.react = function(gd, data, layout, config) {
24212435

24222436
};
24232437

2424-
function diffData(gd, oldFullData, newFullData, immutable) {
2438+
function diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision) {
24252439
if(oldFullData.length !== newFullData.length) {
24262440
return {
24272441
fullReplot: true,
@@ -2441,10 +2455,11 @@ function diffData(gd, oldFullData, newFullData, immutable) {
24412455
getValObject: getTraceValObject,
24422456
flags: flags,
24432457
immutable: immutable,
2458+
transition: transition,
2459+
newDataRevision: newDataRevision,
24442460
gd: gd
24452461
};
24462462

2447-
24482463
var seenUIDs = {};
24492464

24502465
for(i = 0; i < oldFullData.length; i++) {
@@ -2463,7 +2478,7 @@ function diffData(gd, oldFullData, newFullData, immutable) {
24632478
return flags;
24642479
}
24652480

2466-
function diffLayout(gd, oldFullLayout, newFullLayout, immutable) {
2481+
function diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition) {
24672482
var flags = editTypes.layoutFlags();
24682483
flags.arrays = {};
24692484
flags.rangesAltered = {};
@@ -2477,6 +2492,7 @@ function diffLayout(gd, oldFullLayout, newFullLayout, immutable) {
24772492
getValObject: getLayoutValObject,
24782493
flags: flags,
24792494
immutable: immutable,
2495+
transition: transition,
24802496
gd: gd
24812497
};
24822498

@@ -2490,7 +2506,7 @@ function diffLayout(gd, oldFullLayout, newFullLayout, immutable) {
24902506
}
24912507

24922508
function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
2493-
var valObject, key;
2509+
var valObject, key, astr;
24942510

24952511
var getValObject = opts.getValObject;
24962512
var flags = opts.flags;
@@ -2506,10 +2522,24 @@ function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
25062522
}
25072523
editTypes.update(flags, valObject);
25082524

2525+
// track animatable changes
2526+
if(opts.transition) {
2527+
if(flags.anim === 'all' && !valObject.anim) {
2528+
flags.anim = 'some';
2529+
} else if(!flags.anim && valObject.anim) {
2530+
flags.anim = 'all';
2531+
}
2532+
}
2533+
25092534
// track cartesian axes with altered ranges
25102535
if(AX_RANGE_RE.test(astr) || AX_AUTORANGE_RE.test(astr)) {
25112536
flags.rangesAltered[outerparts[0]] = 1;
25122537
}
2538+
2539+
// track datarevision changes
2540+
if(key === 'datarevision') {
2541+
flags.newDataRevision = 1;
2542+
}
25132543
}
25142544

25152545
function valObjectCanBeDataArray(valObject) {
@@ -2518,7 +2548,7 @@ function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
25182548

25192549
for(key in oldContainer) {
25202550
// short-circuit based on previous calls or previous keys that already maximized the pathway
2521-
if(flags.calc) return;
2551+
if(flags.calc && !opts.transition) return;
25222552

25232553
var oldVal = oldContainer[key];
25242554
var newVal = newContainer[key];
@@ -2614,6 +2644,11 @@ function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
26142644
if(immutable) {
26152645
flags.calc = true;
26162646
}
2647+
2648+
// look for animatable attributes when the data changed
2649+
if(immutable || opts.newDataRevision) {
2650+
changed();
2651+
}
26172652
}
26182653
else if(wasArray !== nowArray) {
26192654
flags.calc = true;

‎src/plots/cartesian/index.js

Copy file name to clipboardExpand all lines: src/plots/cartesian/index.js
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ exports.layoutAttributes = require('./layout_attributes');
4545

4646
exports.supplyLayoutDefaults = require('./layout_defaults');
4747

48-
exports.transitionAxes = require('./transition_axes');
48+
exports.transitionAxes = require('./transition_axes').transitionAxes;
49+
exports.transitionAxes2 = require('./transition_axes').transitionAxes2;
4950

5051
exports.finalizeSubplots = function(layoutIn, layoutOut) {
5152
var subplots = layoutOut._subplots;

‎src/plots/cartesian/transition_axes.js

Copy file name to clipboardExpand all lines: src/plots/cartesian/transition_axes.js
+186-1Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ var Drawing = require('../../components/drawing');
1515
var Axes = require('./axes');
1616
var axisRegex = require('./constants').attrRegex;
1717

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

@@ -323,4 +323,189 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo
323323
raf = window.requestAnimationFrame(doFrame);
324324

325325
return Promise.resolve();
326+
}
327+
328+
function transitionAxes2(gd, edits, transitionOpts, makeOnCompleteCallback) {
329+
var fullLayout = gd._fullLayout;
330+
331+
function ticksAndAnnotations(xa, ya) {
332+
var activeAxIds = [xa._id, ya._id];
333+
var i;
334+
335+
for(i = 0; i < activeAxIds.length; i++) {
336+
Axes.doTicksSingle(gd, activeAxIds[i], true);
337+
}
338+
339+
function redrawObjs(objArray, method, shortCircuit) {
340+
for(i = 0; i < objArray.length; i++) {
341+
var obji = objArray[i];
342+
343+
if((activeAxIds.indexOf(obji.xref) !== -1) ||
344+
(activeAxIds.indexOf(obji.yref) !== -1)) {
345+
method(gd, i);
346+
}
347+
348+
// once is enough for images (which doesn't use the `i` arg anyway)
349+
if(shortCircuit) return;
350+
}
351+
}
352+
353+
redrawObjs(fullLayout.annotations || [], Registry.getComponentMethod('annotations', 'drawOne'));
354+
redrawObjs(fullLayout.shapes || [], Registry.getComponentMethod('shapes', 'drawOne'));
355+
redrawObjs(fullLayout.images || [], Registry.getComponentMethod('images', 'draw'), true);
356+
}
357+
358+
function unsetSubplotTransform(plotinfo) {
359+
var xa = plotinfo.xaxis;
360+
var ya = plotinfo.yaxis;
361+
362+
fullLayout._defs.select('#' + plotinfo.clipId + '> rect')
363+
.call(Drawing.setTranslate, 0, 0)
364+
.call(Drawing.setScale, 1, 1);
365+
366+
plotinfo.plot
367+
.call(Drawing.setTranslate, xa._offset, ya._offset)
368+
.call(Drawing.setScale, 1, 1);
369+
370+
var traceGroups = plotinfo.plot.selectAll('.scatterlayer .trace');
371+
372+
// This is specifically directed at scatter traces, applying an inverse
373+
// scale to individual points to counteract the scale of the trace
374+
// as a whole:
375+
traceGroups.selectAll('.point')
376+
.call(Drawing.setPointGroupScale, 1, 1);
377+
traceGroups.selectAll('.textpoint')
378+
.call(Drawing.setTextPointsScale, 1, 1);
379+
traceGroups
380+
.call(Drawing.hideOutsideRangePoints, plotinfo);
381+
}
382+
383+
function updateSubplot(edit, progress) {
384+
var plotinfo = edit.plotinfo;
385+
var xa1 = plotinfo.xaxis;
386+
var ya1 = plotinfo.yaxis;
387+
388+
var xr0 = edit.xr0;
389+
var xr1 = edit.xr1;
390+
var xlen = xa1._length;
391+
var yr0 = edit.yr0;
392+
var yr1 = edit.yr1;
393+
var ylen = ya1._length;
394+
395+
var editX = xr0[0] !== xr1[0] || xr0[1] !== xr1[1];
396+
var editY = yr0[0] !== yr1[0] || yr0[1] !== yr1[1];
397+
var viewBox = [];
398+
399+
if(editX) {
400+
var dx0 = xr0[1] - xr0[0];
401+
var dx1 = xr1[1] - xr1[0];
402+
viewBox[0] = (xr0[0] * (1 - progress) + progress * xr1[0] - xr0[0]) / (xr0[1] - xr0[0]) * xlen;
403+
viewBox[2] = xlen * ((1 - progress) + progress * dx1 / dx0);
404+
xa1.range[0] = xr0[0] * (1 - progress) + progress * xr1[0];
405+
xa1.range[1] = xr0[1] * (1 - progress) + progress * xr1[1];
406+
} else {
407+
viewBox[0] = 0;
408+
viewBox[2] = xlen;
409+
}
410+
411+
if(editY) {
412+
var dy0 = yr0[1] - yr0[0];
413+
var dy1 = yr1[1] - yr1[0];
414+
viewBox[1] = (yr0[1] * (1 - progress) + progress * yr1[1] - yr0[1]) / (yr0[0] - yr0[1]) * ylen;
415+
viewBox[3] = ylen * ((1 - progress) + progress * dy1 / dy0);
416+
ya1.range[0] = yr0[0] * (1 - progress) + progress * yr1[0];
417+
ya1.range[1] = yr0[1] * (1 - progress) + progress * yr1[1];
418+
} else {
419+
viewBox[1] = 0;
420+
viewBox[3] = ylen;
421+
}
422+
423+
ticksAndAnnotations(plotinfo.xaxis, plotinfo.yaxis);
424+
425+
var xScaleFactor = editX ? xlen / viewBox[2] : 1;
426+
var yScaleFactor = editY ? ylen / viewBox[3] : 1;
427+
var clipDx = editX ? viewBox[0] : 0;
428+
var clipDy = editY ? viewBox[1] : 0;
429+
var fracDx = editX ? (viewBox[0] / viewBox[2] * xlen) : 0;
430+
var fracDy = editY ? (viewBox[1] / viewBox[3] * ylen) : 0;
431+
var plotDx = xa1._offset - fracDx;
432+
var plotDy = ya1._offset - fracDy;
433+
434+
plotinfo.clipRect
435+
.call(Drawing.setTranslate, clipDx, clipDy)
436+
.call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor);
437+
438+
plotinfo.plot
439+
.call(Drawing.setTranslate, plotDx, plotDy)
440+
.call(Drawing.setScale, xScaleFactor, yScaleFactor);
441+
442+
// apply an inverse scale to individual points to counteract
443+
// the scale of the trace group.
444+
Drawing.setPointGroupScale(plotinfo.zoomScalePts, 1 / xScaleFactor, 1 / yScaleFactor);
445+
Drawing.setTextPointsScale(plotinfo.zoomScaleTxt, 1 / xScaleFactor, 1 / yScaleFactor);
446+
}
447+
448+
var onComplete;
449+
if(makeOnCompleteCallback) {
450+
// This module makes the choice whether or not it notifies Plotly.transition
451+
// about completion:
452+
onComplete = makeOnCompleteCallback();
453+
}
454+
455+
function transitionComplete() {
456+
var aobj = {};
457+
var k;
458+
459+
for(k in edits) {
460+
var edit = edits[k];
461+
aobj[edit.plotinfo.xaxis._name + '.range'] = edit.xr1.slice();
462+
aobj[edit.plotinfo.yaxis._name + '.range'] = edit.yr1.slice();
463+
}
464+
465+
// Signal that this transition has completed:
466+
onComplete && onComplete();
467+
468+
return Registry.call('relayout', gd, aobj).then(function() {
469+
for(k in edits) {
470+
unsetSubplotTransform(edits[k].plotinfo);
471+
}
472+
});
473+
}
474+
475+
var t1, t2, raf;
476+
var easeFn = d3.ease(transitionOpts.easing);
477+
478+
gd._transitionData._interruptCallbacks.push(function() {
479+
window.cancelAnimationFrame(raf);
480+
raf = null;
481+
return transitionComplete();
482+
});
483+
484+
function doFrame() {
485+
t2 = Date.now();
486+
487+
var tInterp = Math.min(1, (t2 - t1) / transitionOpts.duration);
488+
var progress = easeFn(tInterp);
489+
490+
for(var k in edits) {
491+
updateSubplot(edits[k], progress);
492+
}
493+
494+
if(t2 - t1 > transitionOpts.duration) {
495+
transitionComplete();
496+
raf = window.cancelAnimationFrame(doFrame);
497+
} else {
498+
raf = window.requestAnimationFrame(doFrame);
499+
}
500+
}
501+
502+
t1 = Date.now();
503+
raf = window.requestAnimationFrame(doFrame);
504+
505+
return Promise.resolve();
506+
}
507+
508+
module.exports = {
509+
transitionAxes: transitionAxes,
510+
transitionAxes2: transitionAxes2
326511
};

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.