Skip to content

Commit 09001f7

Browse files
committed
Animate API
1 parent 2d56127 commit 09001f7

26 files changed

+2202
-215
lines changed

src/components/drawing/index.js

Lines changed: 50 additions & 4 deletions
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

Lines changed: 60 additions & 11 deletions
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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ exports.register = Plotly.register;
3434
exports.toImage = require('./plot_api/to_image');
3535
exports.downloadImage = require('./snapshot/download');
3636
exports.validate = require('./plot_api/validate');
37+
exports.addFrames = Plotly.addFrames;
38+
exports.deleteFrames = Plotly.deleteFrames;
39+
exports.transition = Plotly.transition;
40+
exports.animate = Plotly.animate;
3741

3842
// plot icons
3943
exports.Icons = require('../build/ploticon');

src/lib/index.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,40 @@ lib.objectFromPath = function(path, value) {
573573
return obj;
574574
};
575575

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

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

0 commit comments

Comments
 (0)