diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 421fb0e0633..f8d1b4e8369 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1687,19 +1687,13 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { vals = vals.filter(ax._tickFilter); } - // remove zero lines, grid lines, and inside ticks if they're within - // 1 pixel of the end + // Remove zero lines, grid lines, and inside ticks if they're within + // 1 pixel of the end. // The key case here is removing zero lines when the axis bound is zero. - function clipEnds(d) { - var p = ax.l2p(d.x); - return (p > 1 && p < ax._length - 1); - } - var valsClipped = vals.filter(clipEnds); - - // don't clip angular values - if(isAngular(ax)) { - valsClipped = vals; - } + // Don't clip angular values. + var valsClipped = ax._valsClipped = isAngular(ax) ? + vals : + vals.filter(function(d) { return clipEnds(ax, d.x); }); function drawTicks(container, tickpath) { var ticks = container.selectAll('path.' + tcls) @@ -2142,69 +2136,17 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { }); } - function traceHasBarsOrFill(trace, subplot) { - if(trace.visible !== true || trace.xaxis + trace.yaxis !== subplot) return false; - if(Registry.traceIs(trace, 'bar') && trace.orientation === {x: 'h', y: 'v'}[axLetter]) return true; - return trace.fill && trace.fill.charAt(trace.fill.length - 1) === axLetter; - } - - function lineNearZero(ax2, position) { - if(!ax2.showline || !ax2.linewidth) return false; - var tolerance = Math.max((ax2.linewidth + ax.zerolinewidth) / 2, 1); - - function closeEnough(pos2) { - return typeof pos2 === 'number' && Math.abs(pos2 - position) < tolerance; - } - - if(closeEnough(ax2._mainLinePosition) || closeEnough(ax2._mainMirrorPosition)) { - return true; - } - var linePositions = ax2._linepositions || {}; - for(var k in linePositions) { - if(closeEnough(linePositions[k][0]) || closeEnough(linePositions[k][1])) { - return true; - } - } - } - - function anyCounterAxLineAtZero(counterAxis, rng) { - var mainCounterAxis = counterAxis._mainAxis; - if(!mainCounterAxis) return; - - var zeroPosition = ax._offset + ( - ((Math.abs(rng[0]) < Math.abs(rng[1])) === (axLetter === 'x')) ? - 0 : ax._length - ); - - var plotinfo = fullLayout._plots[counterAxis._mainSubplot]; - if(!(plotinfo.mainplotinfo || plotinfo).overlays.length) { - return lineNearZero(counterAxis, zeroPosition); - } - - var counterLetterAxes = axes.list(gd, counterLetter); - for(var i = 0; i < counterLetterAxes.length; i++) { - var counterAxis2 = counterLetterAxes[i]; - if( - counterAxis2._mainAxis === mainCounterAxis && - lineNearZero(counterAxis2, zeroPosition) - ) { - return true; - } - } - } - - function drawGrid(plotinfo, counteraxis, subplot) { + function drawGrid(plotinfo, counteraxis) { if(fullLayout._hasOnlyLargeSploms) return; var gridcontainer = plotinfo.gridlayer.selectAll('.' + axid); var zlcontainer = plotinfo.zerolinelayer; - var gridvals = plotinfo['hidegrid' + axLetter] ? [] : valsClipped; var gridpath = ax._gridpath || ((axLetter === 'x' ? ('M0,' + counteraxis._offset + 'v') : ('M' + counteraxis._offset + ',0h') ) + counteraxis._length); var grid = gridcontainer.selectAll('path.' + gcls) - .data((ax.showgrid === false) ? [] : gridvals, datafn); + .data((ax.showgrid === false) ? [] : valsClipped, datafn); grid.enter().append('path').classed(gcls, 1) .classed('crisp', 1) .attr('d', gridpath) @@ -2222,24 +2164,8 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { // zero line if(zlcontainer) { - var hasBarsOrFill = false; - for(var i = 0; i < gd._fullData.length; i++) { - if(traceHasBarsOrFill(gd._fullData[i], subplot)) { - hasBarsOrFill = true; - break; - } - } - var rng = Lib.simpleMap(ax.range, ax.r2l); var zlData = {x: 0, id: axid}; - - var showZl = (rng[0] * rng[1] <= 0) && ax.zeroline && - (ax.type === 'linear' || ax.type === '-') && gridvals.length && - ( - hasBarsOrFill || - clipEnds(zlData) || - !anyCounterAxLineAtZero(counteraxis, rng) - ); - + var showZl = axes.shouldShowZeroLine(gd, ax, counteraxis); var zl = zlcontainer.selectAll('path.' + zcls) .data(showZl ? [zlData] : []); zl.enter().append('path').classed(zcls, 1).classed('zl', 1) @@ -2327,6 +2253,96 @@ axes.doTicksSingle = function(gd, arg, skipTitle) { } }; +axes.shouldShowZeroLine = function(gd, ax, counterAxis) { + var rng = Lib.simpleMap(ax.range, ax.r2l); + return ( + (rng[0] * rng[1] <= 0) && + ax.zeroline && + (ax.type === 'linear' || ax.type === '-') && + ax._valsClipped.length && + ( + clipEnds(ax, 0) || + !anyCounterAxLineAtZero(gd, ax, counterAxis, rng) || + hasBarsOrFill(gd, ax) + ) + ); +}; + +function clipEnds(ax, l) { + var p = ax.l2p(l); + return (p > 1 && p < ax._length - 1); +} + +function anyCounterAxLineAtZero(gd, ax, counterAxis, rng) { + var mainCounterAxis = counterAxis._mainAxis; + if(!mainCounterAxis) return; + + var fullLayout = gd._fullLayout; + var axLetter = ax._id.charAt(0); + var counterLetter = axes.counterLetter(ax._id); + + var zeroPosition = ax._offset + ( + ((Math.abs(rng[0]) < Math.abs(rng[1])) === (axLetter === 'x')) ? + 0 : ax._length + ); + + function lineNearZero(ax2) { + if(!ax2.showline || !ax2.linewidth) return false; + var tolerance = Math.max((ax2.linewidth + ax.zerolinewidth) / 2, 1); + + function closeEnough(pos2) { + return typeof pos2 === 'number' && Math.abs(pos2 - zeroPosition) < tolerance; + } + + if(closeEnough(ax2._mainLinePosition) || closeEnough(ax2._mainMirrorPosition)) { + return true; + } + var linePositions = ax2._linepositions || {}; + for(var k in linePositions) { + if(closeEnough(linePositions[k][0]) || closeEnough(linePositions[k][1])) { + return true; + } + } + } + + var plotinfo = fullLayout._plots[counterAxis._mainSubplot]; + if(!(plotinfo.mainplotinfo || plotinfo).overlays.length) { + return lineNearZero(counterAxis, zeroPosition); + } + + var counterLetterAxes = axes.list(gd, counterLetter); + for(var i = 0; i < counterLetterAxes.length; i++) { + var counterAxis2 = counterLetterAxes[i]; + if( + counterAxis2._mainAxis === mainCounterAxis && + lineNearZero(counterAxis2, zeroPosition) + ) { + return true; + } + } +} + +function hasBarsOrFill(gd, ax) { + var fullData = gd._fullData; + var subplot = ax._mainSubplot; + var axLetter = ax._id.charAt(0); + + for(var i = 0; i < fullData.length; i++) { + var trace = fullData[i]; + + if(trace.visible === true && + (trace.xaxis + trace.yaxis) === subplot && + ( + Registry.traceIs(trace, 'bar') && trace.orientation === {x: 'h', y: 'v'}[axLetter] || + trace.fill && trace.fill.charAt(trace.fill.length - 1) === axLetter + ) + ) { + return true; + } + } + return false; +} + /** * Find all margin pushers for 2D axes and reserve them for later use * Both label and rangeslider automargin calculations happen later so diff --git a/src/traces/splom/base_plot.js b/src/traces/splom/base_plot.js index a91f83b7a67..c1489ef2fe2 100644 --- a/src/traces/splom/base_plot.js +++ b/src/traces/splom/base_plot.js @@ -11,11 +11,11 @@ var createLine = require('regl-line2d'); var Registry = require('../../registry'); -var Lib = require('../../lib'); var prepareRegl = require('../../lib/prepare_regl'); var getModuleCalcData = require('../../plots/get_data').getModuleCalcData; var Cartesian = require('../../plots/cartesian'); -var AxisIDs = require('../../plots/cartesian/axis_ids'); +var getFromId = require('../../plots/cartesian/axis_ids').getFromId; +var shouldShowZeroLine = require('../../plots/cartesian/axes').shouldShowZeroLine; var SPLOM = 'splom'; @@ -63,13 +63,13 @@ function dragOne(gd, trace, stash, scene) { var i = visibleDims[k]; var rng = ranges[k] = new Array(4); - var xa = AxisIDs.getFromId(gd, trace._diag[i][0]); + var xa = getFromId(gd, trace._diag[i][0]); if(xa) { rng[0] = xa.r2l(xa.range[0]); rng[2] = xa.r2l(xa.range[1]); } - var ya = AxisIDs.getFromId(gd, trace._diag[i][1]); + var ya = getFromId(gd, trace._diag[i][1]); if(ya) { rng[1] = ya.r2l(ya.range[0]); rng[3] = ya.r2l(ya.range[1]); @@ -144,17 +144,17 @@ function makeGridData(gd) { push('grid', xa, x, yOffset, x, yOffset + ya._length); } } - if(showZeroLine(xa)) { - 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(showZeroLine(ya)) { + if(shouldShowZeroLine(gd, xa, ya)) { + x = xa._offset + xa.l2p(0); + push('zeroline', xa, x, yOffset, x, yOffset + ya._length); + } + if(shouldShowZeroLine(gd, ya, xa)) { y = yOffset + yb + 0; push('zeroline', ya, xa._offset, y, xa._offset + xa._length, y); } @@ -168,20 +168,6 @@ function makeGridData(gd) { return gridBatches; } -// just like in Axes.doTicks but without the loop over traces -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, oldCalcdata) { var oldModules = oldFullLayout._modules || []; var newModules = newFullLayout._modules || []; diff --git a/test/image/baselines/splom_large.png b/test/image/baselines/splom_large.png index aeb4f38fc8c..f11ea1467eb 100644 Binary files a/test/image/baselines/splom_large.png and b/test/image/baselines/splom_large.png differ diff --git a/test/image/mocks/splom_large.json b/test/image/mocks/splom_large.json index 1c8ec021e7a..a9d20e7f48d 100644 --- a/test/image/mocks/splom_large.json +++ b/test/image/mocks/splom_large.json @@ -297,6 +297,14 @@ "yaxis": { "zerolinecolor": "red", "zerolinewidth": 2 + }, + "xaxis2": { + "zeroline": false + }, + "yaxis2": { + "rangemode": "tozero", + "zeroline": true, + "showline": true } } } diff --git a/test/jasmine/tests/splom_test.js b/test/jasmine/tests/splom_test.js index 473bd0cce8e..b44bf5d5917 100644 --- a/test/jasmine/tests/splom_test.js +++ b/test/jasmine/tests/splom_test.js @@ -478,26 +478,26 @@ describe('Test splom interactions:', function() { } Plotly.plot(gd, fig).then(function() { - _assert([1198, 3478, 16318, 118]); + _assert([1198, 16678, 3358, 118]); return Plotly.restyle(gd, 'showupperhalf', false); }) .then(function() { - _assert([1198, 1882, 8452, 4]); + _assert([1198, 8488, 1768, 4]); return Plotly.restyle(gd, 'diagonal.visible', false); }) .then(function() { - _assert([1138, 1702, 7636, 4]); + _assert([1138, 7636, 1594, 4]); return Plotly.restyle(gd, { showupperhalf: true, showlowerhalf: false }); }) .then(function() { - _assert([64, 1594, 7852, 112]); + _assert([64, 8176, 1582, 112]); return Plotly.restyle(gd, 'diagonal.visible', true); }) .then(function() { - _assert([58, 1768, 8680, 118]); + _assert([58, 9022, 1756, 118]); return Plotly.relayout(gd, { 'xaxis.gridcolor': null, 'xaxis.gridwidth': null, @@ -508,7 +508,7 @@ describe('Test splom interactions:', function() { .then(function() { // one batch for all 'grid' lines // and another for all 'zeroline' lines - _assert([8740, 1888]); + _assert([9082, 1876]); }) .catch(failTest) .then(done);