diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index e643a1fd3c5..f3095be6ca8 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -60,18 +60,28 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { // Skip trace if whitelist provided and it's not whitelisted: // if (Array.isArray(traces) && traces.indexOf(i) === -1) continue; if(trace.xaxis + trace.yaxis === subplot) { + var needed = false; + // If this trace is specifically requested, add it to the list: if(traces.indexOf(trace.index) !== -1) { - // Okay, so example: traces 0, 1, and 2 have fill = tonext. You animate - // traces 0 and 2. Trace 1 also needs to be updated, otherwise its fill - // is outdated. So this retroactively adds the previous trace if the - // traces are interdependent. - if(pcd && - ['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1 && - cdSubplot.indexOf(pcd) === -1) { - cdSubplot.push(pcd); - } + needed = true; + } + + if(gd._fullLayout.barmode === 'stack') { + needed = true; + } + + // Okay, so example: traces 0, 1, and 2 have fill = tonext. You animate + // traces 0 and 2. Trace 1 also needs to be updated, otherwise its fill + // is outdated. So this retroactively adds the previous trace if the + // traces are interdependent. + if(needed && pcd && + ['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1 && + cdSubplot.indexOf(pcd) === -1) { + cdSubplot.push(pcd); + } + if(needed) { cdSubplot.push(cd); } @@ -96,7 +106,7 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback // remaining plot traces should also be able to do this. Once implemented, // we won't need this - which should sometimes be a big speedup. if(plotinfo.plot) { - plotinfo.plot.selectAll('g:not(.scatterlayer)').selectAll('g.trace').remove(); + plotinfo.plot.selectAll('g:not(.scatterlayer):not(.barlayer)').selectAll('g.trace').remove(); } // plot all traces for each module at once diff --git a/src/plots/plots.js b/src/plots/plots.js index fca05f9a6a8..e1aa201a5ef 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1711,6 +1711,19 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) plots.doCalcdata(gd); + var subplotInfo, _module; + var subplots = plots.getSubplotIds(gd._fullLayout, 'cartesian'), + modules = gd._fullLayout._modules; + + for(i = 0; i < subplots.length; i++) { + subplotInfo = gd._fullLayout._plots[subplots[i]]; + + for(var j = 0; j < modules.length; j++) { + _module = modules[j]; + if(_module.setPositions) _module.setPositions(gd, subplotInfo); + } + } + ErrorBars.calc(gd); return Promise.resolve(); diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 22c2a1a9871..1241b4729bb 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -31,11 +31,13 @@ module.exports = function calc(gd, trace) { sa = xa; size = xa.makeCalcdata(trace, 'x'); pos = ya.makeCalcdata(trace, 'y'); + Axes.setConvert(sa); } else { sa = ya; size = ya.makeCalcdata(trace, 'y'); pos = xa.makeCalcdata(trace, 'x'); + Axes.setConvert(sa); } // create the "calculated data" to plot diff --git a/src/traces/bar/index.js b/src/traces/bar/index.js index 5561f9c6b3c..93b6b467ac1 100644 --- a/src/traces/bar/index.js +++ b/src/traces/bar/index.js @@ -22,6 +22,7 @@ Bar.arraysToCalcdata = require('./arrays_to_calcdata'); Bar.plot = require('./plot'); Bar.style = require('./style'); Bar.hoverPoints = require('./hover'); +Bar.animatable = true; Bar.moduleType = 'trace'; Bar.name = 'bar'; diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 4e0cb53ac29..a136c93257a 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -19,101 +19,144 @@ var ErrorBars = require('../../components/errorbars'); var arraysToCalcdata = require('./arrays_to_calcdata'); -module.exports = function plot(gd, plotinfo, cdbar) { - var xa = plotinfo.xaxis, - ya = plotinfo.yaxis, - fullLayout = gd._fullLayout; +module.exports = function plot(gd, plotinfo, cdbar, transitionOpts, makeOnCompleteCallback) { + var onComplete; + var hasTransition = !!transitionOpts && transitionOpts.duration > 0; - var bartraces = plotinfo.plot.select('.barlayer') + var barJoin = plotinfo.plot.select('.barlayer') .selectAll('g.trace.bars') - .data(cdbar) - .enter().append('g') - .attr('class', 'trace bars'); - - bartraces.append('g') - .attr('class', 'points') - .each(function(d) { - var t = d[0].t, - trace = d[0].trace, - poffset = t.poffset, - poffsetIsArray = Array.isArray(poffset), - barwidth = t.barwidth, - barwidthIsArray = Array.isArray(barwidth); - - arraysToCalcdata(d); - - d3.select(this).selectAll('path') - .data(Lib.identity) - .enter().append('path') - .each(function(di, i) { - // now display the bar - // clipped xf/yf (2nd arg true): non-positive - // log values go off-screen by plotwidth - // so you see them continue if you drag the plot - var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset), - p1 = p0 + ((barwidthIsArray) ? barwidth[i] : barwidth), - s0 = di.b, - s1 = s0 + di.s; - - var x0, x1, y0, y1; - if(trace.orientation === 'h') { - y0 = ya.c2p(p0, true); - y1 = ya.c2p(p1, true); - x0 = xa.c2p(s0, true); - x1 = xa.c2p(s1, true); - } - else { - x0 = xa.c2p(p0, true); - x1 = xa.c2p(p1, true); - y0 = ya.c2p(s0, true); - y1 = ya.c2p(s1, true); - } - - if(!isNumeric(x0) || !isNumeric(x1) || - !isNumeric(y0) || !isNumeric(y1) || - x0 === x1 || y0 === y1) { - d3.select(this).remove(); - return; - } - var lw = (di.mlw + 1 || trace.marker.line.width + 1 || - (di.trace ? di.trace.marker.line.width : 0) + 1) - 1, - offset = d3.round((lw / 2) % 1, 2); - function roundWithLine(v) { - // if there are explicit gaps, don't round, - // it can make the gaps look crappy - return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ? - d3.round(Math.round(v) - offset, 2) : v; - } - function expandToVisible(v, vc) { - // if it's not in danger of disappearing entirely, - // round more precisely - return Math.abs(v - vc) >= 2 ? roundWithLine(v) : - // but if it's very thin, expand it so it's - // necessarily visible, even if it might overlap - // its neighbor - (v > vc ? Math.ceil(v) : Math.floor(v)); - } - if(!gd._context.staticPlot) { - // if bars are not fully opaque or they have a line - // around them, round to integer pixels, mainly for - // safari so we prevent overlaps from its expansive - // pixelation. if the bars ARE fully opaque and have - // no line, expand to a full pixel to make sure we - // can see them - var op = Color.opacity(di.mc || trace.marker.color), - fixpx = (op < 1 || lw > 0.01) ? - roundWithLine : expandToVisible; - x0 = fixpx(x0, x1); - x1 = fixpx(x1, x0); - y0 = fixpx(y0, y1); - y1 = fixpx(y1, y0); - } - d3.select(this).attr('d', - 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z'); - }); + .data(cdbar); + + barJoin.enter().append('g') + .attr('class', 'trace bars') + .append('g') + .attr('class', 'points'); + + if(hasTransition) { + if(makeOnCompleteCallback) { + onComplete = makeOnCompleteCallback(); + } + + var transition = d3.transition() + .duration(transitionOpts.duration) + .ease(transitionOpts.easing) + .each('end', function() { + onComplete && onComplete(); + }) + .each('interrupt', function() { + onComplete && onComplete(); + }); + + transition.each(function() { + barJoin.each(function(d) { + plotOne(gd, d, this, plotinfo, transitionOpts); + }); + }); + } else { + barJoin.each(function(d) { + plotOne(gd, d, this, plotinfo, transitionOpts); }); + } - // error bars are on the top - bartraces.call(ErrorBars.plot, plotinfo); + if(!hasTransition) { + barJoin.exit().remove(); + } + // error bars are on the top + barJoin.call(ErrorBars.plot, plotinfo); }; + +function plotOne(gd, d, el, plotinfo, transitionOpts) { + var fullLayout = gd._fullLayout, + xa = plotinfo.xaxis, + ya = plotinfo.yaxis; + + var hasTransition = !!transitionOpts && transitionOpts.duration > 0; + + function transition(selection) { + return hasTransition ? selection.transition() : selection; + } + + var t = d[0].t, + trace = d[0].trace, + poffset = t.poffset, + poffsetIsArray = Array.isArray(poffset), + barwidth = t.barwidth, + barwidthIsArray = Array.isArray(barwidth); + + arraysToCalcdata(d); + + var points = d3.select(el).select('.points'); + + var pathJoin = points.selectAll('path').data(Lib.identity); + pathJoin.enter().append('path'); + pathJoin.exit().remove(); + + pathJoin.each(function(di, i) { + // now display the bar + // clipped xf/yf (2nd arg true): non-positive + // log values go off-screen by plotwidth + // so you see them continue if you drag the plot + var p0 = di.p + ((poffsetIsArray) ? poffset[i] : poffset), + p1 = p0 + ((barwidthIsArray) ? barwidth[i] : barwidth), + s0 = di.b, + s1 = s0 + di.s; + + var x0, x1, y0, y1; + if(trace.orientation === 'h') { + y0 = ya.c2p(p0, true); + y1 = ya.c2p(p1, true); + x0 = xa.c2p(s0, true); + x1 = xa.c2p(s1, true); + } + else { + x0 = xa.c2p(p0, true); + x1 = xa.c2p(p1, true); + y0 = ya.c2p(s0, true); + y1 = ya.c2p(s1, true); + } + + if(!isNumeric(x0) || !isNumeric(x1) || + !isNumeric(y0) || !isNumeric(y1) || + x0 === x1 || y0 === y1) { + + d3.select(this).remove(); + return; + } + var lw = (di.mlw + 1 || trace.marker.line.width + 1 || + (di.trace ? di.trace.marker.line.width : 0) + 1) - 1, + offset = d3.round((lw / 2) % 1, 2); + function roundWithLine(v) { + // if there are explicit gaps, don't round, + // it can make the gaps look crappy + return (fullLayout.bargap === 0 && fullLayout.bargroupgap === 0) ? + d3.round(Math.round(v) - offset, 2) : v; + } + function expandToVisible(v, vc) { + // if it's not in danger of disappearing entirely, + // round more precisely + return Math.abs(v - vc) >= 2 ? roundWithLine(v) : + // but if it's very thin, expand it so it's + // necessarily visible, even if it might overlap + // its neighbor + (v > vc ? Math.ceil(v) : Math.floor(v)); + } + if(!gd._context.staticPlot) { + // if bars are not fully opaque or they have a line + // around them, round to integer pixels, mainly for + // safari so we prevent overlaps from its expansive + // pixelation. if the bars ARE fully opaque and have + // no line, expand to a full pixel to make sure we + // can see them + var op = Color.opacity(di.mc || trace.marker.color), + fixpx = (op < 1 || lw > 0.01) ? + roundWithLine : expandToVisible; + x0 = fixpx(x0, x1); + x1 = fixpx(x1, x0); + y0 = fixpx(y0, y1); + y1 = fixpx(y1, y0); + } + transition(d3.select(this)).attr('d', + 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z'); + }); +}