Skip to content

Commit f50c295

Browse files
committed
Implement plotly .animate() and keyframe API
1 parent f3fee88 commit f50c295

File tree

8 files changed

+746
-3
lines changed

8 files changed

+746
-3
lines changed

Diff for: animate-api.md

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
## Top-level Plotly API methods
2+
3+
#### `Plotly.transition(gd, data, layout[, traceIndices[, config]])`
4+
Transition (eased or abruptly if desired) to a new set of data. Knows nothing about the larger state of transitions and frames; identically a 'transition the plot to look like X over Y ms' command.
5+
6+
**Parameters**:
7+
- `data`: an *array* of *objects* containing trace data, e.g. `[{x: [1, 2, 3], 'lines.color': 'red'}, {y: [7,8]}]`, mapped to traces.
8+
- `layout`: layout properties to which to transition, probably mostly just axis ranges
9+
- `traceIndices`: a mapping between the items of `data` and the trace indices, e.g. `[0, 2]`. If omitted, is inferred from semantics like for `restyle`—which means maybe affecting all traces?
10+
- `config`: object containing transition configuration, including:
11+
- `duration`: duration in ms of transition
12+
- `ease`: d3 easing function, e.g. `elastic-in-out`
13+
- `delay`: delay until animation; not so useful, just very very easy to pass to d3
14+
- `cascade`: transition points in sequence for a nice visual effect. Maybe just leave out. Kind of a common visual effect for eye candy purposes. Very easy. Can leave out if it leads to weird corner cases. See: http://rickyreusser.com/animation-experiments/#object-constancy
15+
16+
**Returns**: promise that resolves when animation begins or rejects if config is invalid.
17+
18+
**Events**:
19+
- `plotly_starttransition`
20+
- `plotly_endtransition`
21+
22+
<hr>
23+
24+
#### `Plotly.animate(gd, frame[, config])`
25+
Transition to a keyframe. Animation sequence is:
26+
27+
1. Compute the requested frame
28+
2. Separate animatable and non-animatable properties into separate objects
29+
3. Mark exactly what needs to happen. This includes transitions vs. non-animatable properties, whether the axis needs to be redrawn (`needsRelayout`?), and any other optimizations that seem relevant. Since for some cases very simple updates may be coming through at up to 60fps, cutting out work here could be fairly important.
30+
31+
**Parameters**:
32+
- `frame`: name of the frame to which to animate
33+
- `config`: see `.transition`.
34+
35+
**Returns**: promise that resolves when animation begins or rejects if config is invalid.
36+
37+
**Events**:
38+
- `plotly_startanimation`
39+
- `plotly_endanimation`
40+
41+
<hr>
42+
43+
#### `Plotly.addFrames(gd, frames[, frameIndices])`
44+
Add or overwrite frames. New frames are appended to current frame list.
45+
46+
**Parameters**
47+
- `frames`: an array of objects containing any of `name`, `data`, `layout` and `traceIndices` fields as specified above. If no name is provided, a unique name (e.g. `frame 7`) will be assigned. If the frame already exists, then its definition is overwritten.
48+
- `frameIndices`: optional array of indices at which to insert the given frames. If indices are omitted or a specific index is falsey, then frame is appended.
49+
50+
**Returns**: Promise that resolves on completion. (In this case, that's synchronously and mainly for the sake of API consistency.)
51+
52+
<hr>
53+
54+
#### `Plotly.deleteFrames(gd, frameIndices)`
55+
Remove frames by frame index.
56+
57+
**Parameters**:
58+
- `frameIndices`: an array of integer indices of the frames to be removed.
59+
60+
**Returns**: Promise that resolves on completion (which here means synchronously).
61+
62+
<hr>
63+
64+
## Frame definition
65+
66+
Frames are defined similarly to mirror the input format, *not* that of `Plotly.restyle`. The easiest way to explain seems to be via an example that touches all features:
67+
68+
```json
69+
{
70+
"data": [{
71+
"x": [1, 2, 3],
72+
"y": [4, 5, 6],
73+
"identifiers": ["China", "Pakistan", "Australia"],
74+
"lines": {
75+
"color": "red"
76+
}
77+
}, {
78+
"x": [1, 2, 3],
79+
"y": [3, 8, 9],
80+
"markers": {
81+
"color": "red"
82+
}
83+
}],
84+
"layout": {
85+
"slider": {
86+
"visible": true,
87+
"plotly_method": "animate",
88+
"args": ["$value", {"duration": 500}]
89+
},
90+
"slider2": {
91+
"visible": true,
92+
"plotly_method": "animate",
93+
"args": ["$value", {"duration": 500}]
94+
}
95+
},
96+
"frames": [
97+
{
98+
"name": "base",
99+
"y": [4, 5, 7],
100+
"identifiers": ["China", "Pakistan", "Australia"],
101+
}, {
102+
"name": "1960",
103+
"data": [{
104+
"y": [1, 2, 3],
105+
"identifiers": ["China", "Pakistan", "Australia"],
106+
}],
107+
"layout": {
108+
"xaxis": {"range": [7, 3]},
109+
"yaxis": {"range": [0, 5]}
110+
},
111+
"baseFrame": "base",
112+
"traceIndices": [0]
113+
}, {
114+
"name": "1965",
115+
"data": [{
116+
"y": [5, 3, 2],
117+
"identifiers": ["China", "Pakistan", "Australia"],
118+
}],
119+
"layout": {
120+
"xaxis": {"range": [7, 3]},
121+
"yaxis": {"range": [0, 5]}
122+
},
123+
"baseFrame": "base",
124+
"traceIndices": [0]
125+
}
126+
]
127+
}
128+
```
129+
130+
Notes on JSON:
131+
- `identifiers` is used as a d3 `key` argument.
132+
- `baseFrame` is merged… recursively? non-recursively? We'll see. Not a crucial implementation choice.
133+
- `frames` seems maybe best stored at top level. Or maybe best on the object. If on the object, `Plotly.plot` would have to be variadic (probably), accepting `Plotly.plot(gd, data, layout[, frames], config)`. That's backward-compatible but a bit ugly. If not on the object, then it would have to be shoved into `layout` (except how, because it's really awkward place in `layout`.

Diff for: src/core.js

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ exports.prependTraces = Plotly.prependTraces;
2828
exports.addTraces = Plotly.addTraces;
2929
exports.deleteTraces = Plotly.deleteTraces;
3030
exports.moveTraces = Plotly.moveTraces;
31+
exports.animate = Plotly.animate;
32+
exports.addFrames = Plotly.addFrames;
33+
exports.deleteFrames = Plotly.deleteFrames;
34+
exports.renameFrame = Plotly.renameFrame;
35+
exports.transition = Plotly.transition;
36+
exports.animate = Plotly.animate;
3137
exports.purge = Plotly.purge;
3238
exports.setPlotConfig = require('./plot_api/set_plot_config');
3339
exports.register = Plotly.register;

Diff for: src/lib/merge_keyframes.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var extend = require('./extend');
12+
13+
/*
14+
* Merge two keyframe specifications, returning in a third object that
15+
* can be used for plotting.
16+
*
17+
* @param {object} target
18+
* An object with data, layout, and trace data
19+
* @param {object} source
20+
* An object with data, layout, and trace data
21+
*
22+
* Returns: a third object with the merged content
23+
*/
24+
module.exports = function mergeKeyframes(target, source) {
25+
var result;
26+
27+
result = extend.extendDeep({}, target);
28+
result = extend.extendDeep(result, source);
29+
30+
return result;
31+
};

0 commit comments

Comments
 (0)