diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js index a7e08ea41dd..c2e633347c5 100644 --- a/src/plots/cartesian/graph_interact.js +++ b/src/plots/cartesian/graph_interact.js @@ -33,6 +33,7 @@ exports.initInteractions = function initInteractions(gd) { return fullLayout._plots[a].mainplot ? 1 : -1; }); + subplots = subplots.filter(function(subplot) { return !subplot.includes('-over-'); }); subplots.forEach(function(subplot) { var plotinfo = fullLayout._plots[subplot]; var xa = plotinfo.xaxis; @@ -40,7 +41,7 @@ exports.initInteractions = function initInteractions(gd) { // main and corner draggers need not be repeated for // overlaid subplots - these draggers drag them all - if(!plotinfo.mainplot) { + if(!plotinfo.mainplot || plotinfo.mainplot === plotinfo.id) { // main dragger goes over the grids and data, so we use its // mousemove events for all data hover effects var maindrag = makeDragBox(gd, plotinfo, xa._offset, ya._offset, diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 2a52d2628e0..79bdb0425e8 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -126,9 +126,8 @@ exports.finalizeSubplots = function(layoutIn, layoutOut) { */ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { var fullLayout = gd._fullLayout; - var subplots = fullLayout._subplots.cartesian; var calcdata = gd.calcdata; - var i; + var i, j; // Traces is a list of trace indices to (re)plot. If it's not provided, // then it's a complete replot so we create a new list and add all trace indices @@ -141,55 +140,98 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { for(i = 0; i < calcdata.length; i++) traces.push(i); } - // For each subplot - for(i = 0; i < subplots.length; i++) { - var subplot = subplots[i]; - var subplotInfo = fullLayout._plots[subplot]; - - // Get all calcdata (traces) for this subplot: - var cdSubplot = []; - var pcd; - - // For each trace - for(var j = 0; j < calcdata.length; j++) { - var cd = calcdata[j]; - var trace = cd[0].trace; - - // 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: - // - // 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 && - ['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1 && - cdSubplot.indexOf(pcd) === -1 - ) { - cdSubplot.push(pcd); + var trace; + var subplot; + var subplotZorderGroups = {}; + for(var t = 0; t < calcdata.length; t++) { + trace = calcdata[t][0].trace; + var zi = trace.zorder || 0; + subplot = trace.xaxis + trace.yaxis; + if(!subplotZorderGroups[zi]) subplotZorderGroups[zi] = {}; + if(!subplotZorderGroups[zi][subplot]) subplotZorderGroups[zi][subplot] = []; + subplotZorderGroups[zi][subplot].push(calcdata[t]); + } + var zindices = Object.keys(subplotZorderGroups) + .map(Number) + .sort(Lib.sorterAsc); + + var subplots; + var zindex; + var subplotId; + var newsubplotZorderGroups = {}; + var prevSubplots = []; + for(i = 0; i < zindices.length; i++) { + zindex = zindices[i]; + subplots = Object.keys(subplotZorderGroups[zindex]); + + // For each subplot + for(j = 0; j < subplots.length; j++) { + subplot = subplots[j]; + if(prevSubplots.indexOf(subplot) === -1) { + prevSubplots.push(subplot); + subplotId = subplot; + } else { + subplotId = subplot + '-over-' + i; + } + if(!newsubplotZorderGroups[zindex]) newsubplotZorderGroups[zindex] = {}; + if(!newsubplotZorderGroups[zindex][subplotId]) newsubplotZorderGroups[zindex][subplotId] = []; + newsubplotZorderGroups[zindex][subplotId].push(subplotZorderGroups[zindex][subplot]); + } + } + + var subplotLayerData = {}; + for(i = 0; i < zindices.length; i++) { + zindex = zindices[i]; + subplots = Object.keys(newsubplotZorderGroups[zindex]); + + // For each subplot + for(j = 0; j < subplots.length; j++) { + subplot = subplots[j]; + var subplotInfo = fullLayout._plots[subplot]; + + // Get all calcdata (traces) for this subplot: + var cdSubplot = []; + var pcd; + // For each trace + for(var k = 0; k < newsubplotZorderGroups[zindex][subplot].length; k++) { + var cd = newsubplotZorderGroups[zindex][subplot][k][0]; + trace = cd[0].trace; + + // 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 || (subplot.indexOf('-over-') !== -1 && subplot.indexOf(trace.xaxis + trace.yaxis) === 0)) { + // XXX: Should trace carpet dependencies. Only replot all carpet plots if the carpet + // axis has actually changed: + // + // 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 && + ['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1 && + cdSubplot.indexOf(pcd) === -1 + ) { + cdSubplot.push(pcd); + } + cdSubplot.push(cd); } - cdSubplot.push(cd); + // Track the previous trace on this subplot for the retroactive-add step + // above: + pcd = cd; } - - // Track the previous trace on this subplot for the retroactive-add step - // above: - pcd = cd; } + if(!subplotLayerData[subplot]) subplotLayerData[subplot] = []; + if(subplotInfo) subplotLayerData[subplot] = plotOne(gd, subplotInfo, cdSubplot, transitionOpts, makeOnCompleteCallback, subplotLayerData[subplot]); } - // Plot the traces for this subplot - plotOne(gd, subplotInfo, cdSubplot, transitionOpts, makeOnCompleteCallback); } }; -function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback) { +function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback, layerData) { var traceLayerClasses = constants.traceLayerClasses; var fullLayout = gd._fullLayout; var modules = fullLayout._modules; @@ -205,7 +247,6 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback traceZorderGroups[zi].push(cdSubplot[t]); } - var layerData = []; var zoomScaleQueryParts = []; // Plot each zorder group in ascending order @@ -263,7 +304,6 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback .attr('class', function(d) { return d.className; }) .classed('mlayer', true) .classed('rangeplot', plotinfo.isRangePlot); - layers.exit().remove(); layers.order(); @@ -307,6 +347,7 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback plotinfo.zoomScaleTxt = traces.selectAll('.textpoint'); } } + return layerData; } exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { @@ -392,17 +433,17 @@ exports.drawFramework = function(gd) { var plotinfo = fullLayout._plots[id]; plotinfo.plotgroup = d3.select(this); - makeSubplotLayer(gd, plotinfo); + makeSubplotLayer(gd, plotinfo, id); // make separate drag layers for each subplot, // but append them to paper rather than the plot groups, // so they end up on top of the rest - plotinfo.draglayer = ensureSingle(fullLayout._draggers, 'g', id); + if(id.indexOf('over') === -1) plotinfo.draglayer = ensureSingle(fullLayout._draggers, 'g', id); }); }; exports.rangePlot = function(gd, plotinfo, cdSubplot) { - makeSubplotLayer(gd, plotinfo); + makeSubplotLayer(gd, plotinfo, plotinfo.id); plotOne(gd, plotinfo, cdSubplot); Plots.style(gd); }; @@ -410,49 +451,79 @@ exports.rangePlot = function(gd, plotinfo, cdSubplot) { function makeSubplotData(gd) { var fullLayout = gd._fullLayout; var ids = fullLayout._subplots.cartesian; - var len = ids.length; + var i, j, id, plotinfo, xa, ya; // split 'regular' and 'overlaying' subplots var regulars = []; var overlays = []; - for(i = 0; i < len; i++) { - id = ids[i]; - plotinfo = fullLayout._plots[id]; - xa = plotinfo.xaxis; - ya = plotinfo.yaxis; + var calcdata = gd.calcdata; + + var trace; + var subplot; + var subplotZorderGroups = {}; + for(var t = 0; t < calcdata.length; t++) { + trace = calcdata[t][0].trace; + var zi = trace.zorder || 0; + subplot = trace.xaxis + trace.yaxis; + if(!subplotZorderGroups[zi]) subplotZorderGroups[zi] = {}; + if(!subplotZorderGroups[zi][subplot]) subplotZorderGroups[zi][subplot] = []; + subplotZorderGroups[zi][subplot].push(calcdata[t]); + } + var zindices = Object.keys(subplotZorderGroups) + .map(Number) + .sort(Lib.sorterAsc); + var len = zindices.length; - var xa2 = xa._mainAxis; - var ya2 = ya._mainAxis; - var mainplot = xa2._id + ya2._id; - var mainplotinfo = fullLayout._plots[mainplot]; - plotinfo.overlays = []; - - if(mainplot !== id && mainplotinfo) { - plotinfo.mainplot = mainplot; - plotinfo.mainplotinfo = mainplotinfo; - overlays.push(id); - } else { - plotinfo.mainplot = undefined; - plotinfo.mainplotinfo = undefined; - regulars.push(id); + var mainplot; + var mainplotinfo; + for(i = 0; i < len; i++) { + var zindex = subplotZorderGroups[zindices[i]]; + ids = Object.keys(zindex); + for(j = 0; j < ids.length; j++) { + id = ids[j]; + plotinfo = fullLayout._plots[id]; + + mainplot = mainplot ? mainplot : id; + mainplotinfo = fullLayout._plots[mainplot]; + plotinfo.overlays = []; + + if(regulars.indexOf(id) !== -1 || overlays.indexOf(id) !== -1) { + plotinfo.mainplot = mainplot; + plotinfo.mainplotinfo = mainplotinfo; + overlays.push(id + '-over-' + i); + } else if(mainplot !== id) { + plotinfo.mainplot = mainplot; + plotinfo.mainplotinfo = mainplotinfo; + overlays.push(id); + } else { + plotinfo.mainplot = undefined; + plotinfo.mainplotinfo = undefined; + regulars.push(id); + } } } // fill in list of overlaying subplots in 'main plot' for(i = 0; i < overlays.length; i++) { id = overlays[i]; - plotinfo = fullLayout._plots[id]; - plotinfo.mainplotinfo.overlays.push(plotinfo); + if(id.indexOf('-over-') !== -1) { + id = id.split('-over-')[0]; + } + var plotinfoCopy = Object.assign({}, fullLayout._plots[id]); + plotinfoCopy.id = overlays[i]; + fullLayout._plots[id].mainplotinfo.overlays.push(plotinfoCopy); } // put 'regular' subplot data before 'overlaying' var subplotIds = regulars.concat(overlays); - var subplotData = new Array(len); - - for(i = 0; i < len; i++) { + var subplotData = new Array(subplotIds.length); + for(i = 0; i < subplotIds.length; i++) { id = subplotIds[i]; + if(id.indexOf('-over-') !== -1) { + fullLayout._plots[id] = Object.assign({}, fullLayout._plots[id.split('-over-')[0]]); + } plotinfo = fullLayout._plots[id]; xa = plotinfo.xaxis; ya = plotinfo.yaxis; @@ -460,22 +531,23 @@ function makeSubplotData(gd) { // use info about axis layer and overlaying pattern // to clean what need to be cleaned up in exit selection var d = [id, xa.layer, ya.layer, xa.overlaying || '', ya.overlaying || '']; - for(j = 0; j < plotinfo.overlays.length; j++) { - d.push(plotinfo.overlays[j].id); + if(id.indexOf('-over-') !== -1) { + for(j = 0; j < plotinfo.overlays.length; j++) { + d.push(plotinfo.overlays[j].id); + } } subplotData[i] = d; } return subplotData; } -function makeSubplotLayer(gd, plotinfo) { +function makeSubplotLayer(gd, plotinfo, id) { var plotgroup = plotinfo.plotgroup; - var id = plotinfo.id; var xLayer = constants.layerValue2layerClass[plotinfo.xaxis.layer]; var yLayer = constants.layerValue2layerClass[plotinfo.yaxis.layer]; var hasOnlyLargeSploms = gd._fullLayout._hasOnlyLargeSploms; - if(!plotinfo.mainplot) { + if(!plotinfo.mainplot || plotinfo.mainplot === id) { if(hasOnlyLargeSploms) { // TODO could do even better // - we don't need plot (but we would have to mock it in lsInner @@ -486,7 +558,7 @@ function makeSubplotLayer(gd, plotinfo) { plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above'); plotinfo.xaxislayer = ensureSingle(plotgroup, 'g', 'xaxislayer-above'); plotinfo.yaxislayer = ensureSingle(plotgroup, 'g', 'yaxislayer-above'); - } else { + } else if(id.indexOf('-over-') === -1) { var backLayer = ensureSingle(plotgroup, 'g', 'layer-subplot'); plotinfo.shapelayer = ensureSingle(backLayer, 'g', 'shapelayer'); plotinfo.imagelayer = ensureSingle(backLayer, 'g', 'imagelayer'); diff --git a/test/image/mocks/zorder_mult_axes.json b/test/image/mocks/zorder_mult_axes.json new file mode 100644 index 00000000000..ac83fe7858e --- /dev/null +++ b/test/image/mocks/zorder_mult_axes.json @@ -0,0 +1,57 @@ +{ + "data": [ + { + "x": [2, 3, 4, 5], + "y": [4, 3, 9, 5], + "name": "xy - zorder 10", + "type": "scatter", + "marker": {"size": 20}, + "line": {"width": 6}, + "yaxis": "y", + "zorder": 10 + }, + { + "x": [2, 3, 4, 5, 6], + "y": [2, 5, 4, 6, 4], + "name": "xy - zorder 0", + "type": "scatter", + "marker": {"size": 20}, + "line": {"width": 6}, + "zorder": 0 + }, + { + "x": [3, 4, 5, 6, 7], + "y": [1, 8, 6, 8, 5], + "name": "xy2 - zorder 5", + "type": "scatter", + "marker": {"size": 20}, + "line": {"width": 6}, + "yaxis": "y2", + "zorder": 5 + }, + + { + "x": [2, 3, 4, 5, 6, 7], + "y": [4, 2, 6, 1, 6, 9], + "name": "xy2 - zorder 6", + "type": "bar", + "yaxis": "y2", + "zorder": 6 + } + ], + "layout": { + "xaxis": { + "side": "top", + "title": { + "text": "zorder stacking" + } + }, + "yaxis": { + "side": "right", + "title":{"text":"y"}, "overlaying": "y2" + }, + "y2axis": { + "title":{"text":"y2"} + } + } +} \ No newline at end of file