Skip to content

Animate API #802

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

Merged
merged 45 commits into from
Sep 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
57892ed
Animate API
rreusser Aug 1, 2016
ae7c54c
Partial draw: Correct traces selected based on fill
rreusser Aug 4, 2016
4e23c21
Handle the case where no module found
rreusser Aug 4, 2016
b98be71
supplyDataDefaults instead of supplyDefaults for transitions
rreusser Aug 4, 2016
00bfd52
Dragbox compares axes by value instead of reference
rreusser Aug 4, 2016
f46e5fb
Attempt simultaneous axis and data transitions
rreusser Aug 9, 2016
981c1a8
Fall back to disallowing simultaneous data + axis transitions
rreusser Aug 11, 2016
80078e4
lib function to interleave trace updates into a restyle
rreusser Aug 12, 2016
a36fb6e
Add supplyTransitionDefaults to coerce input
rreusser Aug 12, 2016
b249f7b
Remove unexposed cascade option
rreusser Aug 19, 2016
d06699d
Group transitions to avoid race conditions
rreusser Aug 23, 2016
4ea79b2
Overhaul transition completion behavior
rreusser Aug 23, 2016
639faa2
Trigger transition completion even if nothing else happened
rreusser Aug 23, 2016
36207ee
Fixes from first review pass
rreusser Aug 24, 2016
4834703
Fix broken tests
rreusser Aug 24, 2016
6f0b42b
Mirror changes from #878 and small typo fix
rreusser Aug 25, 2016
a311922
Fix errorbar typo
rreusser Aug 25, 2016
d5c82c6
Add a tests for scatter simplify: false
rreusser Aug 25, 2016
0f848ab
Fix the scatter select id field
rreusser Aug 25, 2016
a6be166
Disable mouse interaction during finite-duration transition
rreusser Aug 25, 2016
dd0b1f8
Expand the animate API
rreusser Aug 25, 2016
bd6e8c0
Rework transition and animate to resolve at the *end*
rreusser Aug 28, 2016
1b497e3
Regroup animate tests to prevent race conditions
rreusser Aug 28, 2016
9079632
Add a couple more purge tests
rreusser Aug 28, 2016
14516cf
Tweak point enter/exit transitions
rreusser Aug 28, 2016
9c83914
Write Plotly.transition out of the picture yayyy
rreusser Aug 28, 2016
ee95be1
Fix frame attributes
rreusser Aug 29, 2016
dfd2037
Update attributes to improve interop with animations
rreusser Aug 29, 2016
82fefa9
Move point symbol style to single point style func
rreusser Aug 30, 2016
db6b942
Test moving transition and doCalcdata to plots.js
rreusser Sep 2, 2016
0d3da5e
Change namespace of transition
rreusser Sep 2, 2016
0221c79
Refactor animationOpts API
rreusser Sep 2, 2016
f3a292d
Apply non-range props from layout to transition
rreusser Sep 2, 2016
f709572
Use Lib.isPlainObject instead of typeof
rreusser Sep 2, 2016
68b9a0c
Stop testing the things that don't pass the tests 😑
rreusser Sep 6, 2016
29c14d7
Switch to window.[request|cancel]AnimationFrame for consistency
rreusser Sep 6, 2016
84c93fe
Remove .transition from core
rreusser Sep 6, 2016
75ec8a5
Add immediate interrupt
rreusser Sep 6, 2016
c8e46f2
Fix one of the animate tests
rreusser Sep 6, 2016
1d43355
Tweak tests
rreusser Sep 6, 2016
1fe652d
Fix race condition in .animate
rreusser Sep 6, 2016
2bc29b8
Limit transition duration to <= frame duration
rreusser Sep 6, 2016
0f23eef
jsDoc for animate, addFrames, and deleteFrames
rreusser Sep 6, 2016
c85c3a2
Verify gd is a plot div for animate, addFrames, deleteFrames
rreusser Sep 6, 2016
4e88184
Throw animate API errors whne gd is not a plot
rreusser Sep 6, 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
5 changes: 4 additions & 1 deletion src/components/color/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ color.stroke = function(s, c) {

color.fill = function(s, c) {
var tc = tinycolor(c);
s.style({'fill': color.tinyRGB(tc), 'fill-opacity': tc.getAlpha()});
s.style({
'fill': color.tinyRGB(tc),
'fill-opacity': tc.getAlpha()
});
};

// search container for colors with the deprecated rgb(fractions) format
Expand Down
152 changes: 93 additions & 59 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,26 @@ 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) {
// 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 + ')');
drawing.translatePoint = function(d, sel, xa, ya) {
// 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);

if(isNumeric(x) && isNumeric(y)) {
// for multiline text this works better
if(this.nodeName === 'text') {
sel.node().attr('x', x).attr('y', y);
} else {
sel.attr('transform', 'translate(' + x + ',' + y + ')');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have an in-house wrapper from translate transforms -> Lib.setTranslate

Copy link
Contributor Author

@rreusser rreusser Aug 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's usable here since this crux of this method is the ability to pass transitions instead of selections. Example:

> d3.select('g').attr('transform')
null
> d3.select('g').transition().attr('transform')
[Array[1]]

In other words, the getter does not work when a transition is supplied.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
else p.remove();
}
else sel.remove();
};

drawing.translatePoints = function(s, xa, ya, trace) {
s.each(function(d) {
var sel = d3.select(this);
drawing.translatePoint(d, sel, xa, ya, trace);
});
};

Expand All @@ -80,6 +88,16 @@ drawing.crispRound = function(td, lineWidth, dflt) {
return Math.round(lineWidth);
};

drawing.singleLineStyle = function(d, s, lw, lc, ld) {
s.style('fill', 'none');
var line = (((d || [])[0] || {}).trace || {}).line || {},
lw1 = lw || line.width||0,
dash = ld || line.dash || '';

Color.stroke(s, lc || line.color);
drawing.dashLine(s, dash, lw1);
};

drawing.lineGroupStyle = function(s, lw, lc, ld) {
s.style('fill', 'none')
.each(function(d) {
Expand Down Expand Up @@ -175,18 +193,13 @@ drawing.symbolNumber = function(v) {
return Math.floor(Math.max(v, 0));
};

drawing.pointStyle = function(s, trace) {
if(!s.size()) return;

var marker = trace.marker,
markerLine = marker.line;

function singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine) {
// only scatter & box plots get marker path and opacity
// bars, histograms don't
if(Registry.traceIs(trace, 'symbols')) {
var sizeFn = makeBubbleSizeFn(trace);

s.attr('d', function(d) {
sel.attr('d', function(d) {
var r;

// handle multi-trace graph edit case
Expand All @@ -212,54 +225,75 @@ drawing.pointStyle = function(s, trace) {
return (d.mo + 1 || marker.opacity + 1) - 1;
});
}

// 'so' is suspected outliers, for box plots
var fillColor,
lineColor,
lineWidth;
if(d.so) {
lineWidth = markerLine.outlierwidth;
lineColor = markerLine.outliercolor;
fillColor = marker.outliercolor;
}
else {
lineWidth = (d.mlw + 1 || markerLine.width + 1 ||
// TODO: we need the latter for legends... can we get rid of it?
(d.trace ? d.trace.marker.line.width : 0) + 1) - 1;

if('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc);
// weird case: array wasn't long enough to apply to every point
else if(Array.isArray(markerLine.color)) lineColor = Color.defaultLine;
else lineColor = markerLine.color;

if('mc' in d) fillColor = d.mcc = markerScale(d.mc);
else if(Array.isArray(marker.color)) fillColor = Color.defaultLine;
else fillColor = marker.color || 'rgba(0,0,0,0)';
}

if(d.om) {
// open markers can't have zero linewidth, default to 1px,
// and use fill color as stroke color
sel.call(Color.stroke, fillColor)
.style({
'stroke-width': (lineWidth || 1) + 'px',
fill: 'none'
});
}
else {
sel.style('stroke-width', lineWidth + 'px')
.call(Color.fill, fillColor);
if(lineWidth) {
sel.call(Color.stroke, lineColor);
}
}
}

drawing.singlePointStyle = function(d, sel, trace) {
var marker = trace.marker,
markerLine = marker.line;

// allow array marker and marker line colors to be
// scaled by given max and min to colorscales
var markerIn = (trace._input || {}).marker || {},
markerScale = drawing.tryColorscale(marker, markerIn, ''),
lineScale = drawing.tryColorscale(marker, markerIn, 'line.');

s.each(function(d) {
// 'so' is suspected outliers, for box plots
var fillColor,
lineColor,
lineWidth;
if(d.so) {
lineWidth = markerLine.outlierwidth;
lineColor = markerLine.outliercolor;
fillColor = marker.outliercolor;
}
else {
lineWidth = (d.mlw + 1 || markerLine.width + 1 ||
// TODO: we need the latter for legends... can we get rid of it?
(d.trace ? d.trace.marker.line.width : 0) + 1) - 1;

if('mlc' in d) lineColor = d.mlcc = lineScale(d.mlc);
// weird case: array wasn't long enough to apply to every point
else if(Array.isArray(markerLine.color)) lineColor = Color.defaultLine;
else lineColor = markerLine.color;

if('mc' in d) fillColor = d.mcc = markerScale(d.mc);
else if(Array.isArray(marker.color)) fillColor = Color.defaultLine;
else fillColor = marker.color || 'rgba(0,0,0,0)';
}
singlePointStyle(d, sel, trace, markerScale, lineScale, marker, markerLine);

var p = d3.select(this);
if(d.om) {
// open markers can't have zero linewidth, default to 1px,
// and use fill color as stroke color
p.call(Color.stroke, fillColor)
.style({
'stroke-width': (lineWidth || 1) + 'px',
fill: 'none'
});
}
else {
p.style('stroke-width', lineWidth + 'px')
.call(Color.fill, fillColor);
if(lineWidth) {
p.call(Color.stroke, lineColor);
}
}
};

drawing.pointStyle = function(s, trace) {
if(!s.size()) return;

// allow array marker and marker line colors to be
// scaled by given max and min to colorscales
var marker = trace.marker;
var markerIn = (trace._input || {}).marker || {},
markerScale = drawing.tryColorscale(marker, markerIn, ''),
lineScale = drawing.tryColorscale(marker, markerIn, 'line.');

s.each(function(d) {
drawing.singlePointStyle(d, d3.select(this), trace, markerScale, lineScale);
});
};

Expand Down
64 changes: 53 additions & 11 deletions src/components/errorbars/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
var d3 = require('d3');
var isNumeric = require('fast-isnumeric');

var Lib = require('../../lib');
var subTypes = require('../../traces/scatter/subtypes');


module.exports = function plot(traces, plotinfo) {
module.exports = function plot(traces, plotinfo, transitionOpts) {
var isNew;
var xa = plotinfo.x(),
ya = plotinfo.y();

var hasAnimation = transitionOpts && transitionOpts.duration > 0;

traces.each(function(d) {
var trace = d[0].trace,
// || {} is in case the trace (specifically scatterternary)
Expand All @@ -29,6 +30,12 @@ module.exports = function plot(traces, plotinfo) {
xObj = trace.error_x || {},
yObj = trace.error_y || {};

var keyFunc;

if(trace.ids) {
keyFunc = function(d) {return d.id;};
}

var sparse = (
subTypes.hasMarkers(trace) &&
trace.marker.maxdisplayed > 0
Expand All @@ -37,11 +44,21 @@ module.exports = function plot(traces, plotinfo) {
if(!yObj.visible && !xObj.visible) return;

var errorbars = d3.select(this).selectAll('g.errorbar')
.data(Lib.identity);
.data(d, keyFunc);

errorbars.exit().remove();

errorbars.enter().append('g')
errorbars.style('opacity', 1);

var enter = errorbars.enter().append('g')
.classed('errorbar', true);

if(hasAnimation) {
enter.style('opacity', 0).transition()
.duration(transitionOpts.duration)
.style('opacity', 1);
}

errorbars.each(function(d) {
var errorbar = d3.select(this);
var coords = errorCoords(d, xa, ya);
Expand All @@ -59,11 +76,24 @@ module.exports = function plot(traces, plotinfo) {
coords.yh + 'h' + (2 * yw) + // hat
'm-' + yw + ',0V' + coords.ys; // bar


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

errorbar.append('path')
.classed('yerror', true)
.attr('d', path);
var yerror = errorbar.select('path.yerror');

isNew = !yerror.size();

if(isNew) {
yerror = errorbar.append('path')
.classed('yerror', true);
} else if(hasAnimation) {
yerror = yerror
.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing);
}

yerror.attr('d', path);
}

if(xObj.visible && isNumeric(coords.y) &&
Expand All @@ -77,9 +107,21 @@ module.exports = function plot(traces, plotinfo) {

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

errorbar.append('path')
.classed('xerror', true)
.attr('d', path);
var xerror = errorbar.select('path.xerror');

isNew = !xerror.size();

if(isNew) {
xerror = errorbar.append('path')
.classed('xerror', true);
} else if(hasAnimation) {
xerror = xerror
.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing);
}

xerror.attr('d', path);
}
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/updatemenus/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var buttonsAttrs = {

method: {
valType: 'enumerated',
values: ['restyle', 'relayout'],
values: ['restyle', 'relayout', 'animate'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🍻

dflt: 'restyle',
role: 'info',
description: [
Expand Down
3 changes: 3 additions & 0 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ exports.register = require('./plot_api/register');
exports.toImage = require('./plot_api/to_image');
exports.downloadImage = require('./snapshot/download');
exports.validate = require('./plot_api/validate');
exports.addFrames = Plotly.addFrames;
exports.deleteFrames = Plotly.deleteFrames;
exports.animate = Plotly.animate;

// scatter is the only trace included by default
exports.register(require('./traces/scatter'));
Expand Down
34 changes: 34 additions & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,40 @@ lib.objectFromPath = function(path, value) {
return obj;
};

/**
* Iterate through an object in-place, converting dotted properties to objects.
*
* @example
* lib.expandObjectPaths({'nested.test.path': 'value'});
* // returns { nested: { test: {path: 'value'}}}
*/

// Store this to avoid recompiling regex on every prop since this may happen many
// many times for animations.
// TODO: Premature optimization? Remove?
var dottedPropertyRegex = /^([^\.]*)\../;

lib.expandObjectPaths = function(data) {
var match, key, prop, datum;
if(typeof data === 'object' && !Array.isArray(data)) {
for(key in data) {
if(data.hasOwnProperty(key)) {
if((match = key.match(dottedPropertyRegex))) {
datum = data[key];
prop = match[1];

delete data[key];

data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]);
} else {
data[key] = lib.expandObjectPaths(data[key]);
}
}
}
}
return data;
};

/**
* Converts value to string separated by the provided separators.
*
Expand Down
Loading