diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index b52fb695d7c..91ce130a4f3 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -229,6 +229,15 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) var hadGl, hasGl; var i, k, subplotInfo, moduleName; + // when going from a large splom graph to something else, + // we need to clear so that the new cartesian subplot + // can have the correct layer ordering + if(oldFullLayout._hasOnlyLargeSploms && !newFullLayout._hasOnlyLargeSploms) { + for(k in oldPlots) { + subplotInfo = oldPlots[k]; + if(subplotInfo.plotgroup) subplotInfo.plotgroup.remove(); + } + } for(i = 0; i < oldModules.length; i++) { moduleName = oldModules[i].name; @@ -330,7 +339,7 @@ exports.drawFramework = function(gd) { // initialize list of overlay subplots plotinfo.overlays = []; - makeSubplotLayer(plotinfo); + makeSubplotLayer(gd, plotinfo); // fill in list of overlay subplots if(plotinfo.mainplot) { @@ -346,7 +355,7 @@ exports.drawFramework = function(gd) { }; exports.rangePlot = function(gd, plotinfo, cdSubplot) { - makeSubplotLayer(plotinfo); + makeSubplotLayer(gd, plotinfo); plotOne(gd, plotinfo, cdSubplot); Plots.style(gd); }; @@ -377,45 +386,58 @@ function makeSubplotData(gd) { return subplotData; } -function makeSubplotLayer(plotinfo) { +function makeSubplotLayer(gd, plotinfo) { 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) { - var backLayer = ensureSingle(plotgroup, 'g', 'layer-subplot'); - plotinfo.shapelayer = ensureSingle(backLayer, 'g', 'shapelayer'); - plotinfo.imagelayer = ensureSingle(backLayer, 'g', 'imagelayer'); - - plotinfo.gridlayer = ensureSingle(plotgroup, 'g', 'gridlayer'); + if(hasOnlyLargeSploms) { + // TODO could do even better + // - we don't need plot (but we would have to mock it in lsInner + // and other places + // - we don't (x|y)lines and (x|y)axislayer for most subplots + // usually just the bottom x and left y axes. + plotinfo.plot = ensureSingle(plotgroup, 'g', 'plot'); + plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above'); + plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above'); + plotinfo.xaxislayer = ensureSingle(plotgroup, 'g', 'xaxislayer-above'); + plotinfo.yaxislayer = ensureSingle(plotgroup, 'g', 'yaxislayer-above'); + } + else { + var backLayer = ensureSingle(plotgroup, 'g', 'layer-subplot'); + plotinfo.shapelayer = ensureSingle(backLayer, 'g', 'shapelayer'); + plotinfo.imagelayer = ensureSingle(backLayer, 'g', 'imagelayer'); - plotinfo.zerolinelayer = ensureSingle(plotgroup, 'g', 'zerolinelayer'); + plotinfo.gridlayer = ensureSingle(plotgroup, 'g', 'gridlayer'); + plotinfo.zerolinelayer = ensureSingle(plotgroup, 'g', 'zerolinelayer'); - ensureSingle(plotgroup, 'path', 'xlines-below'); - ensureSingle(plotgroup, 'path', 'ylines-below'); - plotinfo.overlinesBelow = ensureSingle(plotgroup, 'g', 'overlines-below'); + ensureSingle(plotgroup, 'path', 'xlines-below'); + ensureSingle(plotgroup, 'path', 'ylines-below'); + plotinfo.overlinesBelow = ensureSingle(plotgroup, 'g', 'overlines-below'); - ensureSingle(plotgroup, 'g', 'xaxislayer-below'); - ensureSingle(plotgroup, 'g', 'yaxislayer-below'); - plotinfo.overaxesBelow = ensureSingle(plotgroup, 'g', 'overaxes-below'); + ensureSingle(plotgroup, 'g', 'xaxislayer-below'); + ensureSingle(plotgroup, 'g', 'yaxislayer-below'); + plotinfo.overaxesBelow = ensureSingle(plotgroup, 'g', 'overaxes-below'); - plotinfo.plot = ensureSingle(plotgroup, 'g', 'plot'); - plotinfo.overplot = ensureSingle(plotgroup, 'g', 'overplot'); + plotinfo.plot = ensureSingle(plotgroup, 'g', 'plot'); + plotinfo.overplot = ensureSingle(plotgroup, 'g', 'overplot'); - ensureSingle(plotgroup, 'path', 'xlines-above'); - ensureSingle(plotgroup, 'path', 'ylines-above'); - plotinfo.overlinesAbove = ensureSingle(plotgroup, 'g', 'overlines-above'); + plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above'); + plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above'); - ensureSingle(plotgroup, 'g', 'xaxislayer-above'); - ensureSingle(plotgroup, 'g', 'yaxislayer-above'); - plotinfo.overaxesAbove = ensureSingle(plotgroup, 'g', 'overaxes-above'); + ensureSingle(plotgroup, 'g', 'xaxislayer-above'); + ensureSingle(plotgroup, 'g', 'yaxislayer-above'); + plotinfo.overaxesAbove = ensureSingle(plotgroup, 'g', 'overaxes-above'); - // set refs to correct layers as determined by 'axis.layer' - plotinfo.xlines = plotgroup.select('.xlines-' + xLayer); - plotinfo.ylines = plotgroup.select('.ylines-' + yLayer); - plotinfo.xaxislayer = plotgroup.select('.xaxislayer-' + xLayer); - plotinfo.yaxislayer = plotgroup.select('.yaxislayer-' + yLayer); + // set refs to correct layers as determined by 'axis.layer' + plotinfo.xlines = plotgroup.select('.xlines-' + xLayer); + plotinfo.ylines = plotgroup.select('.ylines-' + yLayer); + plotinfo.xaxislayer = plotgroup.select('.xaxislayer-' + xLayer); + plotinfo.yaxislayer = plotgroup.select('.yaxislayer-' + yLayer); + } } else { var mainplotinfo = plotinfo.mainplotinfo; @@ -450,14 +472,16 @@ function makeSubplotLayer(plotinfo) { plotinfo.yaxislayer = mainplotgroup.select('.overaxes-' + yLayer).select('.' + yId); } - ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.xaxis._id); - ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.yaxis._id); - plotinfo.gridlayer.selectAll('g').sort(axisIds.idSort); - // common attributes for all subplots, overlays or not - for(var i = 0; i < constants.traceLayerClasses.length; i++) { - ensureSingle(plotinfo.plot, 'g', constants.traceLayerClasses[i]); + if(!hasOnlyLargeSploms) { + ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.xaxis._id); + ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.yaxis._id); + plotinfo.gridlayer.selectAll('g').sort(axisIds.idSort); + + for(var i = 0; i < constants.traceLayerClasses.length; i++) { + ensureSingle(plotinfo.plot, 'g', constants.traceLayerClasses[i]); + } } plotinfo.xlines diff --git a/src/plots/plots.js b/src/plots/plots.js index d0d3e32e5a1..69e6275e074 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -405,6 +405,19 @@ plots.supplyDefaults = function(gd) { // finally, fill in the pieces of layout that may need to look at data plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData, gd._transitionData); + // turn on flag to optimize large splom-only graphs + // mostly by omitting SVG layers during Cartesian.drawFramework + if( + newFullLayout._basePlotModules.length === 1 && + newFullLayout._basePlotModules[0].name === 'splom' && + splomXa.length > 15 && + splomYa.length > 15 && + newFullLayout.shapes.length === 0 && + newFullLayout.images.length === 0 + ) { + newFullLayout._hasOnlyLargeSploms = 1; + } + // TODO remove in v2.0.0 // add has-plot-type refs to fullLayout for backward compatibility newFullLayout._hasCartesian = newFullLayout._has('cartesian'); diff --git a/src/traces/splom/base_plot.js b/src/traces/splom/base_plot.js index aa4ea9124f4..e1831dd4db8 100644 --- a/src/traces/splom/base_plot.js +++ b/src/traces/splom/base_plot.js @@ -9,8 +9,10 @@ 'use strict'; var createRegl = require('regl'); +var createLine = require('regl-line2d'); var Registry = require('../../registry'); +var Lib = require('../../lib'); var getModuleCalcData = require('../../plots/get_data').getModuleCalcData; var Cartesian = require('../../plots/cartesian'); var AxisIDs = require('../../plots/cartesian/axis_ids'); @@ -43,11 +45,16 @@ function plot(gd) { }); }); + if(fullLayout._hasOnlyLargeSploms) { + drawGrid(gd); + } + _module.plot(gd, {}, splomCalcData); } function drag(gd) { var cd = gd.calcdata; + var fullLayout = gd._fullLayout; for(var i = 0; i < cd.length; i++) { var cd0 = cd[i][0]; @@ -68,6 +75,112 @@ function drag(gd) { scene.matrix.draw(); } } + + if(fullLayout._hasOnlyLargeSploms) { + fullLayout._modules[0].basePlotModule.drawGrid(gd); + } +} + +function drawGrid(gd) { + var fullLayout = gd._fullLayout; + var regl = fullLayout._glcanvas.data()[0].regl; + var splomGrid = fullLayout._splomGrid; + + if(!splomGrid) { + splomGrid = fullLayout._splomGrid = createLine(regl); + } + splomGrid.update(makeGridData(gd)); + splomGrid.draw(); +} + +function makeGridData(gd) { + var fullLayout = gd._fullLayout; + var gs = fullLayout._size; + var fullView = [0, 0, fullLayout.width, fullLayout.height]; + var splomXa = Object.keys(fullLayout._splomAxes.x); + var splomYa = Object.keys(fullLayout._splomAxes.y); + var lookup = {}; + var k; + + function push(prefix, ax, x0, x1, y0, y1) { + var lcolor = ax[prefix + 'color']; + var lwidth = ax[prefix + 'width']; + var key = String(lcolor + lwidth); + + if(key in lookup) { + lookup[key].data.push(NaN, NaN, x0, x1, y0, y1); + } else { + lookup[key] = { + data: [x0, x1, y0, y1], + join: 'rect', + thickness: lwidth, + color: lcolor, + viewport: fullView, + range: fullView + }; + } + } + + for(var i = 0; i < splomXa.length; i++) { + var xa = AxisIDs.getFromId(gd, splomXa[i]); + var xVals = xa._vals; + var xShowZl = showZeroLine(xa); + + for(var j = 0; j < splomYa.length; j++) { + var ya = AxisIDs.getFromId(gd, splomYa[j]); + var yVals = ya._vals; + var yShowZl = showZeroLine(ya); + + // ya.l2p assumes top-to-bottom coordinate system (a la SVG), + // we need to compute bottom-to-top offsets and slopes: + var yOffset = gs.b + ya.domain[0] * gs.h; + var ym = -ya._m; + var yb = -ym * ya.r2l(ya.range[0], ya.calendar); + + var x, y; + + if(xa.showgrid) { + for(k = 0; k < xVals.length; k++) { + x = xa._offset + xa.l2p(xVals[k].x); + push('grid', xa, x, yOffset, x, yOffset + ya._length); + } + } + if(xShowZl) { + x = xa._offset + xa.l2p(0); + push('zeroline', xa, x, yOffset, x, yOffset + ya._length); + } + if(ya.showgrid) { + for(k = 0; k < yVals.length; k++) { + y = yOffset + yb + ym * yVals[k].x; + push('grid', ya, xa._offset, y, xa._offset + xa._length, y); + } + } + if(yShowZl) { + y = yOffset + yb + 0; + push('zeroline', ya, xa._offset, y, xa._offset + xa._length, y); + } + } + } + + var gridBatches = []; + for(k in lookup) { + gridBatches.push(lookup[k]); + } + + return gridBatches; +} + +function showZeroLine(ax) { + var rng = Lib.simpleMap(ax.range, ax.r2l); + var p0 = ax.l2p(0); + + return ( + ax.zeroline && + ax._vals && ax._vals.length && + (rng[0] * rng[1] <= 0) && + (ax.type === 'linear' || ax.type === '-') && + ((p0 > 1 && p0 < ax._length - 1) || !ax.showline) + ); } function clean(newFullData, newFullLayout, oldFullData, oldFullLayout) { @@ -85,6 +198,7 @@ module.exports = { drawFramework: Cartesian.drawFramework, plot: plot, drag: drag, + drawGrid: drawGrid, clean: clean, toSVG: Cartesian.toSVG };