Skip to content

Commit

Permalink
Merge pull request #802 from plotly/animate-api-take-4
Browse files Browse the repository at this point in the history
Animate API
  • Loading branch information
rreusser authored Sep 6, 2016
2 parents cecf090 + 4e88184 commit d736910
Show file tree
Hide file tree
Showing 37 changed files with 3,526 additions and 342 deletions.
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 + ')');
}
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'],
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

0 comments on commit d736910

Please sign in to comment.