diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 0649a155296..646f5abedaf 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -63,24 +63,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) { - // XXX: Should trace carpet dependencies. Only replot all carpet plots if the carpet - // axis has actually changed: - // + var needed = false; + // If this trace is specifically requested, add it to the list: - if(traces.indexOf(trace.index) !== -1 || trace.carpet) { - // 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 && - pcd[0].trace.xaxis + pcd[0].trace.yaxis === subplot && + if(traces.indexOf(trace.index) !== -1) { + 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); - } + cdSubplot.indexOf(pcd) === -1) { + cdSubplot.push(pcd); + } + if(needed) { cdSubplot.push(cd); } @@ -105,7 +109,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 0e243c4ab69..d68d6ef300c 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1783,6 +1783,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/index.js b/src/traces/bar/index.js index f890fe8b673..350046e40f0 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 8f64743e833..7563d078f58 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -30,110 +30,51 @@ var attributes = require('./attributes'), // padding in pixels around text var TEXTPAD = 3; -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); - - bartraces.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); - - d3.select(this).selectAll('g.point') - .data(Lib.identity) - .enter().append('g').classed('point', true) - .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 + di.w, - 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); - } - - // append bar path and text - var bar = d3.select(this); - - bar.append('path').attr('d', - 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z'); - - appendBarText(gd, bar, d, i, x0, x1, y0, y1); - }); + .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 appendBarText(gd, bar, calcTrace, i, x0, x1, y0, y1) { @@ -516,3 +457,103 @@ function coerceColor(attributeDefinition, value, defaultValue) { defaultValue : attributeDefinition.dflt; } + +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); + + 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 + di.w, + 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); + } + + // update bar path and text + var bar = d3.select(this); + + transition(bar).attr('d', + 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z'); + + appendBarText(gd, bar, d, i, x0, x1, y0, y1); + }); +}