diff --git a/src/components/colorbar/draw.js b/src/components/colorbar/draw.js index f879e3661ad..83ac58960f6 100644 --- a/src/components/colorbar/draw.js +++ b/src/components/colorbar/draw.js @@ -418,7 +418,7 @@ module.exports = function draw(gd, id) { // this title call only handles side=right return Lib.syncOrAsync([ function() { - return Axes.doTicks(gd, cbAxisOut, true); + return Axes.doTicksSingle(gd, cbAxisOut, true); }, function() { if(['top', 'bottom'].indexOf(opts.titleside) === -1) { diff --git a/src/components/grid/index.js b/src/components/grid/index.js index f03653522a2..d5986fffc76 100644 --- a/src/components/grid/index.js +++ b/src/components/grid/index.js @@ -225,9 +225,9 @@ function sizeDefaults(layoutIn, layoutOut) { var dfltGapY = hasSubplotGrid ? 0.3 : 0.1; var dfltSideX, dfltSideY; - if(isSplomGenerated) { - dfltSideX = 'bottom'; - dfltSideY = 'left'; + if(isSplomGenerated && layoutOut._splomGridDflt) { + dfltSideX = layoutOut._splomGridDflt.xside; + dfltSideY = layoutOut._splomGridDflt.yside; } gridOut._domains = { diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 939238fb304..6d23574423f 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1689,19 +1689,9 @@ exports.relayout = function relayout(gd, astr, val) { // subroutine of its own so that finalDraw always gets // executed after drawData seq.push( - // TODO - // no test fail when commenting out doAutoRangeAndConstraints, - // but I think we do need this (maybe just the enforce part?) - // Am I right? - // More info in: - // https://github.com/plotly/plotly.js/issues/2540 - subroutines.doAutoRangeAndConstraints, - // TODO - // can target specific axes, - // do not have to redraw all axes here - // See: - // https://github.com/plotly/plotly.js/issues/2547 - subroutines.doTicksRelayout, + function(gd) { + return subroutines.doTicksRelayout(gd, specs.rangesAltered); + }, subroutines.drawData, subroutines.finalDraw ); @@ -1728,6 +1718,10 @@ exports.relayout = function relayout(gd, astr, val) { }); }; +var AX_RANGE_RE = /^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/; +var AX_AUTORANGE_RE = /^[xyz]axis[0-9]*\.autorange$/; +var AX_DOMAIN_RE = /^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/; + function _relayout(gd, aobj) { var layout = gd.layout, fullLayout = gd._fullLayout, @@ -1806,7 +1800,7 @@ function _relayout(gd, aobj) { var plen = p.parts.length; // p.parts may end with an index integer if the property is an array var pend = plen - 1; - while(pend > 0 && typeof p.parts[plen - 1] !== 'string') { pend--; } + while(pend > 0 && typeof p.parts[pend] !== 'string') pend--; // last property in chain (leaf node) var pleaf = p.parts[pend]; // leaf plus immediate parent @@ -1841,11 +1835,11 @@ function _relayout(gd, aobj) { fullLayout[ai] = gd._initialAutoSize[ai]; } // check autorange vs range - else if(pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) { + else if(pleafPlus.match(AX_RANGE_RE)) { recordAlteredAxis(pleafPlus); Lib.nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); } - else if(pleafPlus.match(/^[xyz]axis[0-9]*\.autorange$/)) { + else if(pleafPlus.match(AX_AUTORANGE_RE)) { recordAlteredAxis(pleafPlus); Lib.nestedProperty(fullLayout, ptrunk + '._inputRange').set(null); var axFull = Lib.nestedProperty(fullLayout, ptrunk).get(); @@ -1855,7 +1849,7 @@ function _relayout(gd, aobj) { axFull._input.domain = axFull._inputDomain.slice(); } } - else if(pleafPlus.match(/^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/)) { + else if(pleafPlus.match(AX_DOMAIN_RE)) { Lib.nestedProperty(fullLayout, ptrunk + '._inputDomain').set(null); } @@ -2061,6 +2055,7 @@ function _relayout(gd, aobj) { return { flags: flags, + rangesAltered: rangesAltered, undoit: undoit, redoit: redoit, eventData: Lib.extendDeep({}, redoit) @@ -2174,8 +2169,9 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) { if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles); if(relayoutFlags.axrange) { seq.push( - subroutines.doAutoRangeAndConstraints, - subroutines.doTicksRelayout, + function(gd) { + return subroutines.doTicksRelayout(gd, relayoutSpecs.rangesAltered); + }, subroutines.drawData, subroutines.finalDraw ); @@ -2340,7 +2336,6 @@ exports.react = function(gd, data, layout, config) { if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles); if(relayoutFlags.axrange) { seq.push( - subroutines.doAutoRangeAndConstraints, subroutines.doTicksRelayout, subroutines.drawData, subroutines.finalDraw diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 019072e20cf..c27788542af 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -50,7 +50,7 @@ exports.lsInner = function(gd) { var fullLayout = gd._fullLayout; var gs = fullLayout._size; var pad = gs.p; - var axList = Axes.list(gd); + var axList = Axes.list(gd, '', true); // _has('cartesian') means SVG specifically, not GL2D - but GL2D // can still get here because it makes some of the SVG structure @@ -95,6 +95,11 @@ exports.lsInner = function(gd) { ax._mainMirrorPosition = (ax.mirror && counterAx) ? getLinePosition(ax, counterAx, alignmentConstants.OPPOSITE_SIDE[ax.side]) : null; + + // Figure out which subplot to draw ticks, labels, & axis lines on + // do this as a separate loop so we already have all the + // _mainAxis and _anchorAxis links set + ax._mainSubplot = findMainSubplot(ax, fullLayout); } fullLayout._paperdiv @@ -324,6 +329,48 @@ exports.lsInner = function(gd) { return gd._promises.length && Promise.all(gd._promises); }; +function findMainSubplot(ax, fullLayout) { + var subplotList = fullLayout._subplots; + var ids = subplotList.cartesian.concat(subplotList.gl2d || []); + var mockGd = {_fullLayout: fullLayout}; + + var isX = ax._id.charAt(0) === 'x'; + var anchorAx = ax._mainAxis._anchorAxis; + var mainSubplotID = ''; + var nextBestMainSubplotID = ''; + var anchorID = ''; + + // First try the main ID with the anchor + if(anchorAx) { + anchorID = anchorAx._mainAxis._id; + mainSubplotID = isX ? (ax._id + anchorID) : (anchorID + ax._id); + } + + // Then look for a subplot with the counteraxis overlaying the anchor + // If that fails just use the first subplot including this axis + if(!mainSubplotID || !fullLayout._plots[mainSubplotID]) { + mainSubplotID = ''; + + for(var j = 0; j < ids.length; j++) { + var id = ids[j]; + var yIndex = id.indexOf('y'); + var idPart = isX ? id.substr(0, yIndex) : id.substr(yIndex); + var counterPart = isX ? id.substr(yIndex) : id.substr(0, yIndex); + + if(idPart === ax._id) { + if(!nextBestMainSubplotID) nextBestMainSubplotID = id; + var counterAx = Axes.getFromId(mockGd, counterPart); + if(anchorID && counterAx.overlaying === anchorID) { + mainSubplotID = id; + break; + } + } + } + } + + return mainSubplotID || nextBestMainSubplotID; +} + function shouldShowLinesOrTicks(ax, subplot) { return (ax.ticks || ax.showline) && (subplot === ax._mainSubplot || ax.mirror === 'all' || ax.mirror === 'allticks'); @@ -448,8 +495,12 @@ exports.doLegend = function(gd) { return Plots.previousPromises(gd); }; -exports.doTicksRelayout = function(gd) { - Axes.doTicks(gd, 'redraw'); +exports.doTicksRelayout = function(gd, rangesAltered) { + if(rangesAltered) { + Axes.doTicks(gd, Object.keys(rangesAltered), true); + } else { + Axes.doTicks(gd, 'redraw'); + } if(gd._fullLayout._hasOnlyLargeSploms) { clearGlCanvases(gd); diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index c44dd3db8f9..091b2b5df03 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1456,6 +1456,10 @@ axes.findSubplotsWithAxis = function(subplots, ax) { // makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings axes.makeClipPaths = function(gd) { var fullLayout = gd._fullLayout; + + // for more info: https://github.com/plotly/plotly.js/issues/2595 + if(fullLayout._hasOnlyLargeSploms) return; + var fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''}; var fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''}; var xaList = axes.list(gd, 'x', true); @@ -1494,58 +1498,95 @@ axes.makeClipPaths = function(gd) { }); }; -// doTicks: draw ticks, grids, and tick labels -// axid: 'x', 'y', 'x2' etc, -// blank to do all, -// 'redraw' to force full redraw, and reset: -// ax._r (stored range for use by zoom/pan) -// ax._rl (stored linearized range for use by zoom/pan) -// or can pass in an axis object directly -axes.doTicks = function(gd, axid, skipTitle) { +/** Main multi-axis drawing routine! + * + * @param {DOM element} gd : graph div + * @param {string or array of strings} arg : polymorphic argument + * @param {boolean} skipTitle : optional flag to skip axis title draw/update + * + * Signature 1: Axes.doTicks(gd, 'redraw') + * use this to clear and redraw all axes on graph + * + * Signature 2: Axes.doTicks(gd, '') + * use this to draw all axes on graph w/o the selectAll().remove() + * of the 'redraw' signature + * + * Signature 3: Axes.doTicks(gd, [axId, axId2, ...]) + * where the items are axis id string, + * use this to update multiple axes in one call + * + * N.B doTicks updates: + * - ax._r (stored range for use by zoom/pan) + * - ax._rl (stored linearized range for use by zoom/pan) + */ +axes.doTicks = function(gd, arg, skipTitle) { var fullLayout = gd._fullLayout; - var ax; - var independent = false; - // allow passing an independent axis object instead of id - if(typeof axid === 'object') { - ax = axid; - axid = ax._id; - independent = true; + if(arg === 'redraw') { + fullLayout._paper.selectAll('g.subplot').each(function(subplot) { + var plotinfo = fullLayout._plots[subplot]; + var xa = plotinfo.xaxis; + var ya = plotinfo.yaxis; + + plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove(); + plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove(); + if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove(); + if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove(); + fullLayout._infolayer.select('.g-' + xa._id + 'title').remove(); + fullLayout._infolayer.select('.g-' + ya._id + 'title').remove(); + }); } - else { - ax = axes.getFromId(gd, axid); - - if(axid === 'redraw') { - fullLayout._paper.selectAll('g.subplot').each(function(subplot) { - var plotinfo = fullLayout._plots[subplot]; - var xa = plotinfo.xaxis; - var ya = plotinfo.yaxis; - - plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove(); - plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove(); - if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove(); - if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove(); - fullLayout._infolayer.select('.g-' + xa._id + 'title').remove(); - fullLayout._infolayer.select('.g-' + ya._id + 'title').remove(); - }); - } - if(!axid || axid === 'redraw') { - return Lib.syncOrAsync(axes.list(gd, '', true).map(function(ax) { - return function() { - if(!ax._id) return; - var axDone = axes.doTicks(gd, ax._id); - ax._r = ax.range.slice(); - ax._rl = Lib.simpleMap(ax._r, ax.r2l); - return axDone; - }; - })); - } + var axList = (!arg || arg === 'redraw') ? axes.listIds(gd) : arg; + + Lib.syncOrAsync(axList.map(function(axid) { + return function() { + if(!axid) return; + + var axDone = axes.doTicksSingle(gd, axid, skipTitle); + + var ax = axes.getFromId(gd, axid); + ax._r = ax.range.slice(); + ax._rl = Lib.simpleMap(ax._r, ax.r2l); + + return axDone; + }; + })); +}; + +/** Per axis drawing routine! + * + * This routine draws axis ticks and much more (... grids, labels, title etc.) + * Supports multiple argument signatures. + * N.B. this thing is async in general (because of MathJax rendering) + * + * @param {DOM element} gd : graph div + * @param {string or array of strings} arg : polymorphic argument + * @param {boolean} skipTitle : optional flag to skip axis title draw/update + * @return {promise} + * + * Signature 1: Axes.doTicks(gd, ax) + * where ax is an axis object as in fullLayout + * + * Signature 2: Axes.doTicks(gd, axId) + * where axId is a axis id string + */ +axes.doTicksSingle = function(gd, arg, skipTitle) { + var fullLayout = gd._fullLayout; + var independent = false; + var ax; + + if(Lib.isPlainObject(arg)) { + ax = arg; + independent = true; + } else { + ax = axes.getFromId(gd, arg); } // set scaling to pixels ax.setScale(); + var axid = ax._id; var axLetter = axid.charAt(0); var counterLetter = axes.counterLetter(axid); var vals = ax._vals = axes.calcTicks(ax); diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 099a0cf09e3..b5c2cd0445e 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -26,7 +26,7 @@ var FROM_TL = require('../../constants/alignment').FROM_TL; var Plots = require('../plots'); -var doTicks = require('./axes').doTicks; +var doTicksSingle = require('./axes').doTicksSingle; var getFromId = require('./axis_ids').getFromId; var prepSelect = require('./select').prepSelect; var clearSelect = require('./select').clearSelect; @@ -585,7 +585,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { updates = {}; for(i = 0; i < activeAxIds.length; i++) { var axId = activeAxIds[i]; - doTicks(gd, axId, true); + doTicksSingle(gd, axId, true); var ax = getFromId(gd, axId); updates[ax._name + '.range[0]'] = ax.range[0]; updates[ax._name + '.range[1]'] = ax.range[1]; diff --git a/src/plots/cartesian/transition_axes.js b/src/plots/cartesian/transition_axes.js index ab88e2f905c..4a21331ba97 100644 --- a/src/plots/cartesian/transition_axes.js +++ b/src/plots/cartesian/transition_axes.js @@ -125,7 +125,7 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo activeAxIds = [xa._id, ya._id]; for(i = 0; i < activeAxIds.length; i++) { - Axes.doTicks(gd, activeAxIds[i], true); + Axes.doTicksSingle(gd, activeAxIds[i], true); } function redrawObjs(objArray, method, shortCircuit) { diff --git a/src/plots/plots.js b/src/plots/plots.js index 020a6d346ac..5be17ecdfbc 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -382,6 +382,8 @@ plots.supplyDefaults = function(gd, opts) { // initialize axis and subplot hash objects for splom-generated grids var splomAxes = newFullLayout._splomAxes = {x: {}, y: {}}; var splomSubplots = newFullLayout._splomSubplots = {}; + // initialize splom grid defaults + newFullLayout._splomGridDflt = {}; // for traces to request a default rangeslider on their x axes // eg set `_requestRangeslider.x2 = true` for xaxis2 @@ -801,43 +803,6 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa null : axisIDs.getFromId(mockGd, ax.anchor); } - - for(i = 0; i < axList.length; i++) { - // Figure out which subplot to draw ticks, labels, & axis lines on - // do this as a separate loop so we already have all the - // _mainAxis and _anchorAxis links set - ax = axList[i]; - var isX = ax._id.charAt(0) === 'x'; - var anchorAx = ax._mainAxis._anchorAxis; - var mainSubplotID = ''; - var nextBestMainSubplotID = ''; - var anchorID = ''; - // First try the main ID with the anchor - if(anchorAx) { - anchorID = anchorAx._mainAxis._id; - mainSubplotID = isX ? (ax._id + anchorID) : (anchorID + ax._id); - } - // Then look for a subplot with the counteraxis overlaying the anchor - // If that fails just use the first subplot including this axis - if(!mainSubplotID || !newSubplots[mainSubplotID]) { - mainSubplotID = ''; - for(j = 0; j < ids.length; j++) { - id = ids[j]; - var yIndex = id.indexOf('y'); - var idPart = isX ? id.substr(0, yIndex) : id.substr(yIndex); - var counterPart = isX ? id.substr(yIndex) : id.substr(0, yIndex); - if(idPart === ax._id) { - if(!nextBestMainSubplotID) nextBestMainSubplotID = id; - var counterAx = axisIDs.getFromId(mockGd, counterPart); - if(anchorID && counterAx.overlaying === anchorID) { - mainSubplotID = id; - break; - } - } - } - } - ax._mainSubplot = mainSubplotID || nextBestMainSubplotID; - } }; // This function clears any trace attributes with valType: color and diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 555a16252b7..5b8264194a3 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -60,7 +60,7 @@ function Polar(gd, id) { .attr('class', id); // unfortunately, we have to keep track of some axis tick settings - // so that we don't have to call Axes.doTicks with its special redraw flag + // so that we don't have to call Axes.doTicksSingle with its special redraw flag this.radialTickLayout = null; this.angularTickLayout = null; } @@ -286,7 +286,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { anchor: 'free', position: 0, - // dummy truthy value to make Axes.doTicks draw the grid + // dummy truthy value to make Axes.doTicksSingle draw the grid _counteraxis: true, // don't use automargins routine for labels @@ -302,7 +302,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { // rotate auto tick labels by 180 if in quadrant II and III to make them // readable from left-to-right // - // TODO try moving deeper in doTicks for better results? + // TODO try moving deeper in doTicksSingle for better results? if(ax.tickangle === 'auto' && (a0 > 90 && a0 <= 270)) { ax.tickangle = 180; } @@ -324,7 +324,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { _this.radialTickLayout = newTickLayout; } - Axes.doTicks(gd, ax, true); + Axes.doTicksSingle(gd, ax, true); updateElement(layers['radial-axis'], radialLayout.showticklabels || radialLayout.ticks, { transform: strTranslate(cx, cy) + strRotate(-radialLayout.angle) @@ -413,7 +413,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { anchor: 'free', position: 0, - // dummy truthy value to make Axes.doTicks draw the grid + // dummy truthy value to make Axes.doTicksSingle draw the grid _counteraxis: true, // don't use automargins routine for labels @@ -460,7 +460,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { setScale(ax, angularLayout, fullLayout); // wrapper around c2rad from setConvertAngular - // note that linear ranges are always set in degrees for Axes.doTicks + // note that linear ranges are always set in degrees for Axes.doTicksSingle function c2rad(d) { return ax.c2rad(d.x, 'degrees'); } @@ -530,7 +530,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { _this.angularTickLayout = newTickLayout; } - Axes.doTicks(gd, ax, true); + Axes.doTicksSingle(gd, ax, true); updateElement(layers['angular-line'].select('path'), angularLayout.showline, { d: pathSectorClosed(radius, sector), @@ -833,7 +833,7 @@ proto.updateRadialDrag = function(fullLayout, polarLayout) { if((drange > 0) !== (rprime > range0[0])) return; rng1 = radialAxis.range[1] = rprime; - Axes.doTicks(gd, _this.radialAxis, true); + Axes.doTicksSingle(gd, _this.radialAxis, true); layers['radial-grid'] .attr('transform', strTranslate(cx, cy)) .selectAll('path').attr('transform', null); @@ -961,7 +961,7 @@ proto.updateAngularDrag = function(fullLayout, polarLayout) { } setConvertAngular(angularAxis); - Axes.doTicks(gd, angularAxis, true); + Axes.doTicksSingle(gd, angularAxis, true); if(_this._hasClipOnAxisFalse && !isFullCircle(sector)) { // mutate sector to trick isPtWithinSector diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 122430b660e..436397bc5aa 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -376,9 +376,9 @@ proto.drawAxes = function(doTitles) { caxis = _this.caxis; // 3rd arg true below skips titles, so we can configure them // correctly later on. - Axes.doTicks(gd, aaxis, true); - Axes.doTicks(gd, baxis, true); - Axes.doTicks(gd, caxis, true); + Axes.doTicksSingle(gd, aaxis, true); + Axes.doTicksSingle(gd, baxis, true); + Axes.doTicksSingle(gd, caxis, true); if(doTitles) { var apad = Math.max(aaxis.showticklabels ? aaxis.tickfont.size / 2 : 0, diff --git a/src/traces/splom/defaults.js b/src/traces/splom/defaults.js index dafe263d645..8e5f930d085 100644 --- a/src/traces/splom/defaults.js +++ b/src/traces/splom/defaults.js @@ -150,6 +150,13 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { // note that some the entries here may be undefined diag[i] = [xa, ya]; } + + // when lower half is omitted, override grid default + // to make sure axes remain on the left/bottom of the plot area + if(!showLower) { + layout._splomGridDflt.xside = 'bottom'; + layout._splomGridDflt.yside = 'left'; + } } function fillAxisIdArray(axLetter, len) { diff --git a/test/image/baselines/splom_0.png b/test/image/baselines/splom_0.png index e4729fe2691..9e2afb4d22b 100644 Binary files a/test/image/baselines/splom_0.png and b/test/image/baselines/splom_0.png differ diff --git a/test/image/baselines/splom_array-styles.png b/test/image/baselines/splom_array-styles.png index d608b2f9d40..673770efc3a 100644 Binary files a/test/image/baselines/splom_array-styles.png and b/test/image/baselines/splom_array-styles.png differ diff --git a/test/image/baselines/splom_dates.png b/test/image/baselines/splom_dates.png index fa6cfc8aa57..cec3ad9a099 100644 Binary files a/test/image/baselines/splom_dates.png and b/test/image/baselines/splom_dates.png differ diff --git a/test/image/baselines/splom_iris.png b/test/image/baselines/splom_iris.png index a97f4c5b85a..7380d3e80ee 100644 Binary files a/test/image/baselines/splom_iris.png and b/test/image/baselines/splom_iris.png differ diff --git a/test/image/baselines/splom_large.png b/test/image/baselines/splom_large.png index 1fcf729c532..aeb4f38fc8c 100644 Binary files a/test/image/baselines/splom_large.png and b/test/image/baselines/splom_large.png differ diff --git a/test/image/baselines/splom_log.png b/test/image/baselines/splom_log.png index e32063f9f3e..c5a1b8b3bb4 100644 Binary files a/test/image/baselines/splom_log.png and b/test/image/baselines/splom_log.png differ diff --git a/test/image/baselines/splom_lower-nodiag.png b/test/image/baselines/splom_lower-nodiag.png index 1a4f8f587ac..20bec8e458b 100644 Binary files a/test/image/baselines/splom_lower-nodiag.png and b/test/image/baselines/splom_lower-nodiag.png differ diff --git a/test/image/baselines/splom_lower.png b/test/image/baselines/splom_lower.png index d3a4518a7bf..34ee5498d49 100644 Binary files a/test/image/baselines/splom_lower.png and b/test/image/baselines/splom_lower.png differ diff --git a/test/image/baselines/splom_ragged-via-axes.png b/test/image/baselines/splom_ragged-via-axes.png index b3a54479fa2..4acf13430c7 100644 Binary files a/test/image/baselines/splom_ragged-via-axes.png and b/test/image/baselines/splom_ragged-via-axes.png differ diff --git a/test/image/baselines/splom_ragged-via-visible-false.png b/test/image/baselines/splom_ragged-via-visible-false.png index 8b95fff9ffb..7137bb838b8 100644 Binary files a/test/image/baselines/splom_ragged-via-visible-false.png and b/test/image/baselines/splom_ragged-via-visible-false.png differ diff --git a/test/image/baselines/splom_with-cartesian.png b/test/image/baselines/splom_with-cartesian.png index db20b630af7..973d540e3b7 100644 Binary files a/test/image/baselines/splom_with-cartesian.png and b/test/image/baselines/splom_with-cartesian.png differ diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index d105bb5b4a3..49857bcc3c0 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -554,6 +554,39 @@ describe('axis zoom/pan and main plot zoom', function() { .catch(failTest) .then(done); }); + + it('updates axis layout when the constraints require it', function(done) { + function _assert(xGridCnt) { + var xGrid = d3.select(gd).selectAll('.gridlayer > .x > path.xgrid'); + expect(xGrid.size()).toEqual(xGridCnt); + } + + Plotly.plot(gd, [{ + x: [1, 1.5, 0, -1.5, -1, -1.5, 0, 1.5, 1], + y: [0, 1.5, 1, 1.5, 0, -1.5, -1, -1.5, 0], + line: {shape: 'spline'} + }], { + xaxis: {constrain: 'domain'}, + yaxis: {scaleanchor: 'x'}, + width: 700, + height: 500 + }) + .then(function() { + _assert(2); + + return Plotly.relayout(gd, { + 'xaxis.range[0]': 0, + 'xaxis.range[1]': 1, + 'yaxis.range[0]': 0, + 'yaxis.range[1]': 1 + }); + }) + .then(function() { + _assert(1); + }) + .catch(failTest) + .then(done); + }); }); describe('Event data:', function() { diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index 8d03a850f01..c5e09531c55 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -631,7 +631,7 @@ describe('Test plot api', function() { }); it('should trigger minimal sequence for cartesian axis range updates', function() { - var seq = ['doAutoRangeAndConstraints', 'doTicksRelayout', 'drawData', 'finalDraw']; + var seq = ['doTicksRelayout', 'drawData', 'finalDraw']; function _assert(msg) { expect(gd.calcdata).toBeDefined(); @@ -643,21 +643,20 @@ describe('Test plot api', function() { }); } - var trace = {y: [1, 2, 1]}; - var specs = [ ['relayout', ['xaxis.range[0]', 0]], ['relayout', ['xaxis.range[1]', 3]], ['relayout', ['xaxis.range', [-1, 5]]], - ['update', [{}, {'xaxis.range': [-1, 10]}]], - ['react', [[trace], {xaxis: {range: [0, 1]}}]] + ['update', [{}, {'xaxis.range': [-1, 10]}]] ]; specs.forEach(function(s) { - // create 'real' div for Plotly.react to work - gd = createGraphDiv(); - Plotly.plot(gd, [trace], {xaxis: {range: [1, 2]}}); - mock(gd); + gd = mock({ + data: [{y: [1, 2, 1]}], + layout: { + xaxis: {range: [1, 2]} + } + }); Plotly[s[0]](gd, s[1][0], s[1][1]); @@ -665,9 +664,22 @@ describe('Test plot api', function() { 'Plotly.', s[0], '(gd, ', JSON.stringify(s[1][0]), ', ', JSON.stringify(s[1][1]), ')' ].join('')); + }); + }); - destroyGraphDiv(); + it('should trigger calc on axis range updates when constraints are present', function() { + gd = mock({ + data: [{ + y: [1, 2, 1] + }], + layout: { + xaxis: {constrain: 'domain'}, + yaxis: {scaleanchor: 'x'} + } }); + + Plotly.relayout(gd, 'xaxis.range[0]', 0); + expect(gd.calcdata).toBeUndefined(); }); }); @@ -2758,6 +2770,24 @@ describe('Test plot api', function() { .then(done); }); + it('picks up minimal sequence for cartesian axis range updates', function(done) { + var data = [{y: [1, 2, 1]}]; + var layout = {xaxis: {range: [1, 2]}}; + var layout2 = {xaxis: {range: [0, 1]}}; + + Plotly.newPlot(gd, data, layout) + .then(countPlots) + .then(function() { + return Plotly.react(gd, data, layout2); + }) + .then(function() { + expect(subroutines.doTicksRelayout).toHaveBeenCalledTimes(1); + expect(subroutines.layoutStyles).not.toHaveBeenCalled(); + }) + .catch(failTest) + .then(done); + }); + it('redraws annotations one at a time', function(done) { var data = [{y: [1, 2, 3], mode: 'markers'}]; var layout = {}; diff --git a/test/jasmine/tests/splom_test.js b/test/jasmine/tests/splom_test.js index f44b6368cff..e894ab7fe88 100644 --- a/test/jasmine/tests/splom_test.js +++ b/test/jasmine/tests/splom_test.js @@ -100,9 +100,10 @@ describe('Test splom trace defaults:', function() { expect(subplots.cartesian).toEqual(['xy', 'xy2', 'x2y', 'x2y2']); }); - it('should use special `grid.xside` and `grid.yside` defaults on splom generated grids', function() { + it('should use special `grid.xside` and `grid.yside` defaults on splom w/o lower half generated grids', function() { var gridOut; + // base case _supply({ dimensions: [ {values: [1, 2, 3]}, @@ -110,10 +111,24 @@ describe('Test splom trace defaults:', function() { ] }); + gridOut = gd._fullLayout.grid; + expect(gridOut.xside).toBe('bottom plot'); + expect(gridOut.yside).toBe('left plot'); + + // w/o lower half case + _supply({ + dimensions: [ + {values: [1, 2, 3]}, + {values: [2, 1, 2]} + ], + showlowerhalf: false + }); + gridOut = gd._fullLayout.grid; expect(gridOut.xside).toBe('bottom'); expect(gridOut.yside).toBe('left'); + // non-splom generated grid _supply({ dimensions: [ {values: [1, 2, 3]},