Skip to content

Commit 57892ed

Browse files
committed
Animate API
1 parent cecf090 commit 57892ed

26 files changed

+2202
-215
lines changed

src/components/drawing/index.js

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

49-
drawing.translatePoints = function(s, xa, ya) {
50-
s.each(function(d) {
49+
drawing.translatePoints = function(s, xa, ya, trace, transitionConfig, joinDirection) {
50+
var size;
51+
52+
var hasTransition = transitionConfig && (transitionConfig || {}).duration > 0;
53+
54+
if(hasTransition) {
55+
size = s.size();
56+
}
57+
58+
s.each(function(d, i) {
5159
// put xp and yp into d if pixel scaling is already done
5260
var x = d.xp || xa.c2p(d.x),
5361
y = d.yp || ya.c2p(d.y),
5462
p = d3.select(this);
5563
if(isNumeric(x) && isNumeric(y)) {
5664
// for multiline text this works better
57-
if(this.nodeName === 'text') p.attr('x', x).attr('y', y);
58-
else p.attr('transform', 'translate(' + x + ',' + y + ')');
65+
if(this.nodeName === 'text') {
66+
p.attr('x', x).attr('y', y);
67+
} else {
68+
if(hasTransition) {
69+
var trans;
70+
if(!joinDirection) {
71+
trans = p.transition()
72+
.delay(transitionConfig.delay + transitionConfig.cascade / size * i)
73+
.duration(transitionConfig.duration)
74+
.ease(transitionConfig.ease)
75+
.attr('transform', 'translate(' + x + ',' + y + ')');
76+
77+
if(trace) {
78+
trans.call(drawing.pointStyle, trace);
79+
}
80+
} else if(joinDirection === -1) {
81+
trans = p.style('opacity', 1)
82+
.transition()
83+
.duration(transitionConfig.duration)
84+
.ease(transitionConfig.ease)
85+
.style('opacity', 0)
86+
.remove();
87+
} else if(joinDirection === 1) {
88+
trans = p.attr('transform', 'translate(' + x + ',' + y + ')');
89+
90+
if(trace) {
91+
trans.call(drawing.pointStyle, trace);
92+
}
93+
94+
trans.style('opacity', 0)
95+
.transition()
96+
.duration(transitionConfig.duration)
97+
.ease(transitionConfig.ease)
98+
.style('opacity', 1);
99+
}
100+
101+
} else {
102+
p.attr('transform', 'translate(' + x + ',' + y + ')');
103+
}
104+
}
59105
}
60106
else p.remove();
61107
});

src/components/errorbars/plot.js

+60-11
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@
1212
var d3 = require('d3');
1313
var isNumeric = require('fast-isnumeric');
1414

15-
var Lib = require('../../lib');
1615
var subTypes = require('../../traces/scatter/subtypes');
16+
var styleError = require('./style');
1717

1818

19-
module.exports = function plot(traces, plotinfo) {
19+
module.exports = function plot(traces, plotinfo, transitionConfig) {
20+
var isNew;
2021
var xa = plotinfo.x(),
2122
ya = plotinfo.y();
2223

24+
transitionConfig = transitionConfig || {};
25+
var hasAnimation = isNumeric(transitionConfig.duration) && transitionConfig.duration > 0;
26+
2327
traces.each(function(d) {
2428
var trace = d[0].trace,
2529
// || {} is in case the trace (specifically scatterternary)
@@ -29,6 +33,12 @@ module.exports = function plot(traces, plotinfo) {
2933
xObj = trace.error_x || {},
3034
yObj = trace.error_y || {};
3135

36+
var keyFunc;
37+
38+
if(trace.identifier) {
39+
keyFunc = function(d) {return d.identifier;};
40+
}
41+
3242
var sparse = (
3343
subTypes.hasMarkers(trace) &&
3444
trace.marker.maxdisplayed > 0
@@ -37,11 +47,21 @@ module.exports = function plot(traces, plotinfo) {
3747
if(!yObj.visible && !xObj.visible) return;
3848

3949
var errorbars = d3.select(this).selectAll('g.errorbar')
40-
.data(Lib.identity);
50+
.data(d, keyFunc);
4151

42-
errorbars.enter().append('g')
52+
errorbars.exit().remove();
53+
54+
errorbars.style('opacity', 1);
55+
56+
var enter = errorbars.enter().append('g')
4357
.classed('errorbar', true);
4458

59+
if(hasAnimation) {
60+
enter.style('opacity', 0).transition()
61+
.duration(transitionConfig.duration)
62+
.style('opacity', 1);
63+
}
64+
4565
errorbars.each(function(d) {
4666
var errorbar = d3.select(this);
4767
var coords = errorCoords(d, xa, ya);
@@ -59,14 +79,28 @@ module.exports = function plot(traces, plotinfo) {
5979
coords.yh + 'h' + (2 * yw) + // hat
6080
'm-' + yw + ',0V' + coords.ys; // bar
6181

82+
6283
if(!coords.noYS) path += 'm-' + yw + ',0h' + (2 * yw); // shoe
6384

64-
errorbar.append('path')
65-
.classed('yerror', true)
66-
.attr('d', path);
85+
var yerror = errorbar.select('path.yerror');
86+
87+
isNew = !yerror.size();
88+
89+
if(isNew) {
90+
yerror = errorbar.append('path')
91+
.classed('yerror', true);
92+
} else if(hasAnimation) {
93+
yerror = yerror
94+
.transition()
95+
.duration(transitionConfig.duration)
96+
.ease(transitionConfig.ease)
97+
.delay(transitionConfig.delay);
98+
}
99+
100+
yerror.attr('d', path);
67101
}
68102

69-
if(xObj.visible && isNumeric(coords.y) &&
103+
if(xObj.visible && isNumeric(coords.x) &&
70104
isNumeric(coords.xh) &&
71105
isNumeric(coords.xs)) {
72106
var xw = (xObj.copy_ystyle ? yObj : xObj).width;
@@ -77,11 +111,26 @@ module.exports = function plot(traces, plotinfo) {
77111

78112
if(!coords.noXS) path += 'm0,-' + xw + 'v' + (2 * xw); // shoe
79113

80-
errorbar.append('path')
81-
.classed('xerror', true)
82-
.attr('d', path);
114+
var xerror = errorbar.select('path.xerror');
115+
116+
isNew = !xerror.size();
117+
118+
if(isNew) {
119+
xerror = errorbar.append('path')
120+
.classed('xerror', true);
121+
} else if(hasAnimation) {
122+
xerror = xerror
123+
.transition()
124+
.duration(transitionConfig.duration)
125+
.ease(transitionConfig.ease)
126+
.delay(transitionConfig.delay);
127+
}
128+
129+
xerror.attr('d', path);
83130
}
84131
});
132+
133+
d3.select(this).call(styleError);
85134
});
86135
};
87136

src/core.js

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ exports.register = require('./plot_api/register');
4343
exports.toImage = require('./plot_api/to_image');
4444
exports.downloadImage = require('./snapshot/download');
4545
exports.validate = require('./plot_api/validate');
46+
exports.addFrames = Plotly.addFrames;
47+
exports.deleteFrames = Plotly.deleteFrames;
48+
exports.transition = Plotly.transition;
49+
exports.animate = Plotly.animate;
4650

4751
// scatter is the only trace included by default
4852
exports.register(require('./traces/scatter'));

src/lib/index.js

+120
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,40 @@ lib.objectFromPath = function(path, value) {
577577
return obj;
578578
};
579579

580+
/**
581+
* Iterate through an object in-place, converting dotted properties to objects.
582+
*
583+
* @example
584+
* lib.expandObjectPaths({'nested.test.path': 'value'});
585+
* // returns { nested: { test: {path: 'value'}}}
586+
*/
587+
588+
// Store this to avoid recompiling regex on every prop since this may happen many
589+
// many times for animations.
590+
// TODO: Premature optimization? Remove?
591+
var dottedPropertyRegex = /^([^\.]*)\../;
592+
593+
lib.expandObjectPaths = function(data) {
594+
var match, key, prop, datum;
595+
if(typeof data === 'object' && !Array.isArray(data)) {
596+
for(key in data) {
597+
if(data.hasOwnProperty(key)) {
598+
if((match = key.match(dottedPropertyRegex))) {
599+
datum = data[key];
600+
prop = match[1];
601+
602+
delete data[key];
603+
604+
data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]);
605+
} else {
606+
data[key] = lib.expandObjectPaths(data[key]);
607+
}
608+
}
609+
}
610+
}
611+
return data;
612+
};
613+
580614
/**
581615
* Converts value to string separated by the provided separators.
582616
*
@@ -626,3 +660,89 @@ lib.numSeparate = function(value, separators, separatethousands) {
626660

627661
return x1 + x2;
628662
};
663+
664+
/*
665+
* Compute a keyframe. Merge a keyframe into its base frame(s) and
666+
* expand properties.
667+
*
668+
* @param {object} frameLookup
669+
* An object containing frames keyed by name (i.e. gd._frameData._frameHash)
670+
* @param {string} frame
671+
* The name of the keyframe to be computed
672+
*
673+
* Returns: a new object with the merged content
674+
*/
675+
lib.computeFrame = function(frameLookup, frameName) {
676+
var i, traceIndices, traceIndex, expandedObj, destIndex, copy;
677+
678+
var framePtr = frameLookup[frameName];
679+
680+
// Return false if the name is invalid:
681+
if(!framePtr) {
682+
return false;
683+
}
684+
685+
var frameStack = [framePtr];
686+
var frameNameStack = [framePtr.name];
687+
688+
// Follow frame pointers:
689+
while((framePtr = frameLookup[framePtr.baseFrame])) {
690+
// Avoid infinite loops:
691+
if(frameNameStack.indexOf(framePtr.name) !== -1) break;
692+
693+
frameStack.push(framePtr);
694+
frameNameStack.push(framePtr.name);
695+
}
696+
697+
// A new object for the merged result:
698+
var result = {};
699+
700+
// Merge, starting with the last and ending with the desired frame:
701+
while((framePtr = frameStack.pop())) {
702+
if(framePtr.layout) {
703+
copy = lib.extendDeepNoArrays({}, framePtr.layout);
704+
expandedObj = lib.expandObjectPaths(copy);
705+
result.layout = lib.extendDeepNoArrays(result.layout || {}, expandedObj);
706+
}
707+
708+
if(framePtr.data) {
709+
if(!result.data) {
710+
result.data = [];
711+
}
712+
traceIndices = framePtr.traceIndices;
713+
714+
if(!traceIndices) {
715+
// If not defined, assume serial order starting at zero
716+
traceIndices = [];
717+
for(i = 0; i < framePtr.data.length; i++) {
718+
traceIndices[i] = i;
719+
}
720+
}
721+
722+
if(!result.traceIndices) {
723+
result.traceIndices = [];
724+
}
725+
726+
for(i = 0; i < framePtr.data.length; i++) {
727+
// Loop through this frames data, find out where it should go,
728+
// and merge it!
729+
traceIndex = traceIndices[i];
730+
if(traceIndex === undefined || traceIndex === null) {
731+
continue;
732+
}
733+
734+
destIndex = result.traceIndices.indexOf(traceIndex);
735+
if(destIndex === -1) {
736+
destIndex = result.data.length;
737+
result.traceIndices[destIndex] = traceIndex;
738+
}
739+
740+
copy = lib.extendDeepNoArrays({}, framePtr.data[i]);
741+
expandedObj = lib.expandObjectPaths(copy);
742+
result.data[destIndex] = lib.extendDeepNoArrays(result.data[destIndex] || {}, expandedObj);
743+
}
744+
}
745+
}
746+
747+
return result;
748+
};

0 commit comments

Comments
 (0)