Skip to content

[WIP] Frame + Animate API #717

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 94 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
f3fee88
Reuse SVG DOM elements for scatter traces
rreusser Jun 22, 2016
f50c295
Implement plotly .animate() and keyframe API
rreusser Jun 30, 2016
3054cb5
Reuse SVG DOM elements for scatter traces
rreusser Jun 22, 2016
fb04b65
Implement plotly .animate() and keyframe API
rreusser Jun 30, 2016
28be237
Merge branch 'merge-animate-api-take-3' into animate-api-take-3
rreusser Jul 5, 2016
3e18a98
Clean up frame API tests and corner cases
rreusser Jul 6, 2016
01b9287
Implement Lib.expandObjectPaths
rreusser Jul 6, 2016
42467ec
Frame merging logic
rreusser Jul 6, 2016
0542213
Clean up object identity sloppiness in frame computation
rreusser Jul 7, 2016
cdfec13
Remove dependence of frame logic on gd
rreusser Jul 7, 2016
c7be054
Really simple .animate function
rreusser Jul 7, 2016
e4363a2
Add .animate tests
rreusser Jul 7, 2016
2fd9c26
Start adding back transition behavior
rreusser Jul 7, 2016
b4a553b
Fix animation tests; lint
rreusser Jul 8, 2016
aec44c6
Animation cleanup
rreusser Jul 8, 2016
e031c27
Avoid transitioning axes if no change
rreusser Jul 8, 2016
bd949e6
Transfer animation code over to new PR
rreusser Jul 11, 2016
9132eb6
Fix bad json
rreusser Jul 11, 2016
a4e8282
Fix scatter line issue
rreusser Jul 12, 2016
86ca5d6
Expand the trace update to all interdependent traces #717
rreusser Jul 14, 2016
dd995a9
Fix lint issues
rreusser Jul 14, 2016
cef8bd5
Reorder path fill drawing
rreusser Jul 14, 2016
2fb95a7
Add config to disable line simplification
rreusser Jul 14, 2016
bf2aeec
Error bar styling tweaks
rreusser Jul 18, 2016
bf079e9
Cut losses; make error bars basically work
rreusser Jul 18, 2016
bb8893f
Merge remote-tracking branch 'upstream/master' into animate-api-take-3
rreusser Jul 21, 2016
8717877
Fix for issue 702.
nielsenb-jf Jul 12, 2016
d5a8834
Move injectStyles and helpers to lib/plotcss_utils
nielsenb-jf Jul 21, 2016
34b0ca3
Merge remote-tracking branch 'upstream/master' into animate-api-take-3
rreusser Jul 22, 2016
f218e09
Handle case where cssRules are undefined.
nielsenb-jf Jul 22, 2016
8f4e11e
clear promise queue on restyle and relayout (not after Plotly.plot)
etpinard Jul 25, 2016
30a3fbe
fix mapbox access token tests
etpinard Jul 25, 2016
c7a68e7
Merge remote-tracking branch 'upstream/master' into animate-api-take-3
rreusser Jul 25, 2016
e762bf4
Merge remote-tracking branch 'upstream/master' into animate-api-take-3
rreusser Jul 26, 2016
79e6e4b
Add 'in' to filter transform
rreusser Jul 26, 2016
feb7636
Clean up scatter trace lines
rreusser Jul 26, 2016
ff37557
Fix lint errors
rreusser Jul 26, 2016
1086023
Fix frame API tests and set queueLength as needed
rreusser Jul 26, 2016
977afdd
Add missing scattergeo attribute
rreusser Jul 27, 2016
246f815
Add missing scatterternay attribute
rreusser Jul 27, 2016
db8a1c5
Remove animation mock
rreusser Jul 27, 2016
bc7fe74
Catch degenerate trace-fill-linking case
rreusser Jul 27, 2016
48eec0c
Merge pull request #776 from plotly/mapbox-access-token-catches
etpinard Jul 28, 2016
85c2e20
Merge pull request #764 from nielsenb-jf/fix_child_window
etpinard Jul 28, 2016
6376166
lint: flatten range selector suite
etpinard Jul 28, 2016
10a655d
rangeselector: skip non-object buttons items
etpinard Jul 28, 2016
5904af2
Update README.md
etpinard Jul 28, 2016
c09cbb3
isPlainObject: by pass prototype check in nw.js environments
etpinard Jul 28, 2016
e1f6818
enfore isPlainObject in src files
etpinard Jul 28, 2016
9e2f251
HTML encode attributes in <tspan>s and <a>s
scjody Jul 29, 2016
4e2761c
Add tests of <tspan> generation
scjody Jul 29, 2016
763485c
Add test that relative links work
scjody Jul 29, 2016
8fc47ef
Merge pull request #791 from plotly/html-encode-attributes
scjody Jul 29, 2016
c9a6549
Merge pull request #792 from plotly/enforce-is-plain-object
etpinard Jul 29, 2016
2102d02
Merge branch 'master' into rangeselector-buttons
etpinard Jul 29, 2016
a992b59
Merge pull request #793 from plotly/rangeselector-buttons
etpinard Jul 29, 2016
0aab643
Reuse SVG DOM elements for scatter traces
rreusser Jul 29, 2016
d7e4e1a
Implement plotly .animate() and keyframe API
rreusser Jul 29, 2016
b39f221
Reuse SVG DOM elements for scatter traces
rreusser Jul 29, 2016
1767a73
Implement plotly .animate() and keyframe API
rreusser Jul 29, 2016
c2e1710
Clean up frame API tests and corner cases
rreusser Jul 6, 2016
9ea6850
Implement Lib.expandObjectPaths
rreusser Jul 6, 2016
bcb3ce2
Frame merging logic
rreusser Jul 6, 2016
78b37e2
Clean up object identity sloppiness in frame computation
rreusser Jul 7, 2016
57fe960
Remove dependence of frame logic on gd
rreusser Jul 7, 2016
f483788
Really simple .animate function
rreusser Jul 7, 2016
d2d7099
Add .animate tests
rreusser Jul 7, 2016
ea7c037
Start adding back transition behavior
rreusser Jul 7, 2016
9773720
Fix animation tests; lint
rreusser Jul 8, 2016
17529ab
Animation cleanup
rreusser Jul 8, 2016
9955625
Avoid transitioning axes if no change
rreusser Jul 8, 2016
93eeaa8
Transfer animation code over to new PR
rreusser Jul 11, 2016
9643811
Fix bad json
rreusser Jul 11, 2016
7e2e2d8
Fix scatter line issue
rreusser Jul 12, 2016
2cecc66
Expand the trace update to all interdependent traces #717
rreusser Jul 14, 2016
fd5a3bc
Fix lint issues
rreusser Jul 14, 2016
13053a3
Reorder path fill drawing
rreusser Jul 14, 2016
15b47f6
Add config to disable line simplification
rreusser Jul 14, 2016
e887faa
Error bar styling tweaks
rreusser Jul 18, 2016
f2ffa2a
Cut losses; make error bars basically work
rreusser Jul 18, 2016
2b0c537
Add 'in' to filter transform
rreusser Jul 26, 2016
fb87b42
Clean up scatter trace lines
rreusser Jul 29, 2016
8cf4395
Fix lint errors
rreusser Jul 26, 2016
99ee7d9
Fix frame API tests and set queueLength as needed
rreusser Jul 26, 2016
bcd2fbf
Add missing scattergeo attribute
rreusser Jul 27, 2016
7319c83
Add missing scatterternay attribute
rreusser Jul 27, 2016
248736e
Remove animation mock
rreusser Jul 27, 2016
6afc339
Catch degenerate trace-fill-linking case
rreusser Jul 27, 2016
6e426e7
Clean up scatter trace line enter/exit
rreusser Aug 1, 2016
0180730
Add dummy line attrs to scattergl/scatter3d
rreusser Aug 1, 2016
1166ac7
Merge branch 'animate-api-take-3' of github.com:plotly/plotly.js into…
rreusser Aug 1, 2016
3ee4bf5
Restore missing function
rreusser Aug 1, 2016
085203d
Fix lint issues
rreusser Aug 1, 2016
266f21a
Fix lint error for the last time
rreusser Aug 1, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions animate-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
## Top-level Plotly API methods

#### `Plotly.transition(gd, data, layout[, traceIndices[, config]])`
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.

**Parameters**:
- `data`: an *array* of *objects* containing trace data, e.g. `[{x: [1, 2, 3], 'lines.color': 'red'}, {y: [7,8]}]`, mapped to traces.
- `layout`: layout properties to which to transition, probably mostly just axis ranges
- `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?
- `config`: object containing transition configuration, including:
- `duration`: duration in ms of transition
- `ease`: d3 easing function, e.g. `elastic-in-out`
- `delay`: delay until animation; not so useful, just very very easy to pass to d3
- `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

**Returns**: promise that resolves when animation begins or rejects if config is invalid.

**Events**:
- `plotly_starttransition`
- `plotly_endtransition`

<hr>

#### `Plotly.animate(gd, frame[, config])`
Transition to a keyframe. Animation sequence is:

1. Compute the requested frame
2. Separate animatable and non-animatable properties into separate objects
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.

**Parameters**:
- `frame`: name of the frame to which to animate
- `config`: see `.transition`.

**Returns**: promise that resolves when animation begins or rejects if config is invalid.

**Events**:
- `plotly_startanimation`
- `plotly_endanimation`

<hr>

#### `Plotly.addFrames(gd, frames[, frameIndices])`
Add or overwrite frames. New frames are appended to current frame list.

**Parameters**
- `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.
- `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.

**Returns**: Promise that resolves on completion. (In this case, that's synchronously and mainly for the sake of API consistency.)

<hr>

#### `Plotly.deleteFrames(gd, frameIndices)`
Remove frames by frame index.

**Parameters**:
- `frameIndices`: an array of integer indices of the frames to be removed.

**Returns**: Promise that resolves on completion (which here means synchronously).

<hr>

## Frame definition

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:

```json
{
"data": [{
"x": [1, 2, 3],
"y": [4, 5, 6],
"identifiers": ["China", "Pakistan", "Australia"],
"lines": {
"color": "red"
}
}, {
"x": [1, 2, 3],
"y": [3, 8, 9],
"markers": {
"color": "red"
}
}],
"layout": {
"slider": {
"visible": true,
"plotly_method": "animate",
"args": ["$value", {"duration": 500}]
},
"slider2": {
"visible": true,
"plotly_method": "animate",
"args": ["$value", {"duration": 500}]
}
},
"frames": [
{
"name": "base",
"y": [4, 5, 7],
"identifiers": ["China", "Pakistan", "Australia"],
}, {
"name": "1960",
"data": [{
"y": [1, 2, 3],
"identifiers": ["China", "Pakistan", "Australia"],
}],
"layout": {
"xaxis": {"range": [7, 3]},
"yaxis": {"range": [0, 5]}
},
"baseFrame": "base",
"traceIndices": [0]
}, {
"name": "1965",
"data": [{
"y": [5, 3, 2],
"identifiers": ["China", "Pakistan", "Australia"],
}],
"layout": {
"xaxis": {"range": [7, 3]},
"yaxis": {"range": [0, 5]}
},
"baseFrame": "base",
"traceIndices": [0]
}
]
}
```

Notes on JSON:
- `identifiers` is used as a d3 `key` argument.
- `baseFrame` is merged… recursively? non-recursively? We'll see. Not a crucial implementation choice.
- `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`.
8 changes: 1 addition & 7 deletions build/plotcss.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';

var Plotly = require('../src/plotly');
var rules = {
"X,X div": "font-family:'Open Sans', verdana, arial, sans-serif;margin:0;padding:0;",
"X input,X button": "font-family:'Open Sans', verdana, arial, sans-serif;",
Expand Down Expand Up @@ -54,9 +53,4 @@ var rules = {
"Y .notifier-close:hover": "color:#444;text-decoration:none;cursor:pointer;"
};

for(var selector in rules) {
var fullSelector = selector.replace(/^,/,' ,')
.replace(/X/g, '.js-plotly-plot .plotly')
.replace(/Y/g, '.plotly-notifier');
Plotly.Lib.addStyleRule(fullSelector, rules[selector]);
}
module.exports = rules;
7 changes: 3 additions & 4 deletions src/components/colorbar/has_colorbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@

'use strict';

var Lib = require('../../lib');


module.exports = function hasColorbar(container) {
return (
typeof container.colorbar === 'object' &&
container.colorbar !== null
);
return Lib.isPlainObject(container.colorbar);
};
4 changes: 2 additions & 2 deletions src/components/colorscale/has_colorscale.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ module.exports = function hasColorscale(trace, containerStr) {
}

return (
(typeof container === 'object' && container !== null) && (
Lib.isPlainObject(container) && (
isArrayWithOneNumber ||
container.showscale === true ||
(isNumeric(container.cmin) && isNumeric(container.cmax)) ||
isValidScale(container.colorscale) ||
(typeof container.colorbar === 'object' && container.colorbar !== null)
Lib.isPlainObject(container.colorbar)
)
);
};
10 changes: 5 additions & 5 deletions src/components/dragelement/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ dragElement.init = function init(options) {

if(options.prepFn) options.prepFn(e, startX, startY);

dragCover = coverSlip();
dragCover = coverSlip(gd);

dragCover.onmousemove = onMove;
dragCover.onmouseup = onDone;
Expand Down Expand Up @@ -139,7 +139,7 @@ dragElement.init = function init(options) {
if(options.doneFn) options.doneFn(gd._dragged, numClicks);

if(!gd._dragged) {
var e2 = document.createEvent('MouseEvents');
var e2 = gd._document.createEvent('MouseEvents');
e2.initEvent('click', true, true);
initialTarget.dispatchEvent(e2);
}
Expand All @@ -159,8 +159,8 @@ dragElement.init = function init(options) {
options.element.style.pointerEvents = 'all';
};

function coverSlip() {
var cover = document.createElement('div');
function coverSlip(gd) {
var cover = gd._document.createElement('div');

cover.className = 'dragcover';
var cStyle = cover.style;
Expand All @@ -172,7 +172,7 @@ function coverSlip() {
cStyle.zIndex = 999999999;
cStyle.background = 'none';

document.body.appendChild(cover);
gd._document.body.appendChild(cover);

return cover;
}
Expand Down
54 changes: 50 additions & 4 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,62 @@ drawing.setRect = function(s, x, y, w, h) {
s.call(drawing.setPosition, x, y).call(drawing.setSize, w, h);
};

drawing.translatePoints = function(s, xa, ya) {
s.each(function(d) {
drawing.translatePoints = function(s, xa, ya, trace, transitionConfig, joinDirection) {
var size;

var hasTransition = transitionConfig && (transitionConfig || {}).duration > 0;

if(hasTransition) {
size = s.size();
}

s.each(function(d, i) {
// put xp and yp into d if pixel scaling is already done
var x = d.xp || xa.c2p(d.x),
y = d.yp || ya.c2p(d.y),
p = d3.select(this);
if(isNumeric(x) && isNumeric(y)) {
// for multiline text this works better
if(this.nodeName === 'text') p.attr('x', x).attr('y', y);
else p.attr('transform', 'translate(' + x + ',' + y + ')');
if(this.nodeName === 'text') {
p.attr('x', x).attr('y', y);
} else {
if(hasTransition) {
var trans;
if(!joinDirection) {
trans = p.transition()
.delay(transitionConfig.delay + transitionConfig.cascade / size * i)
.duration(transitionConfig.duration)
.ease(transitionConfig.ease)
.attr('transform', 'translate(' + x + ',' + y + ')');

if(trace) {
trans.call(drawing.pointStyle, trace);
}
} else if(joinDirection === -1) {
trans = p.style('opacity', 1)
.transition()
.duration(transitionConfig.duration)
.ease(transitionConfig.ease)
.style('opacity', 0)
.remove();
} else if(joinDirection === 1) {
trans = p.attr('transform', 'translate(' + x + ',' + y + ')');

if(trace) {
trans.call(drawing.pointStyle, trace);
}

trans.style('opacity', 0)
.transition()
.duration(transitionConfig.duration)
.ease(transitionConfig.ease)
.style('opacity', 1);
}

} else {
p.attr('transform', 'translate(' + x + ',' + y + ')');
}
}
}
else p.remove();
});
Expand Down
Loading