diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js index e87752bb1e7..65b753e7077 100644 --- a/src/components/annotations/calc_autorange.js +++ b/src/components/annotations/calc_autorange.js @@ -34,63 +34,54 @@ function annAutorange(gd) { Lib.filterVisible(fullLayout.annotations).forEach(function(ann) { var xa = Axes.getFromId(gd, ann.xref); var ya = Axes.getFromId(gd, ann.yref); - var headSize = 3 * ann.arrowsize * ann.arrowwidth || 0; - var startHeadSize = 3 * ann.startarrowsize * ann.arrowwidth || 0; - var headPlus, headMinus, startHeadPlus, startHeadMinus; - - if(xa) { - headPlus = headSize + ann.xshift; - headMinus = headSize - ann.xshift; - startHeadPlus = startHeadSize + ann.xshift; - startHeadMinus = startHeadSize - ann.xshift; + ann._extremes = {}; + if(xa) calcAxisExpansion(ann, xa); + if(ya) calcAxisExpansion(ann, ya); + }); +} - if(ann.axref === ann.xref) { - // expand for the arrowhead (padded by arrowhead) - Axes.expand(xa, [xa.r2c(ann.x)], { - ppadplus: headPlus, - ppadminus: headMinus - }); - // again for the textbox (padded by textbox) - Axes.expand(xa, [xa.r2c(ann.ax)], { - ppadplus: Math.max(ann._xpadplus, startHeadPlus), - ppadminus: Math.max(ann._xpadminus, startHeadMinus) - }); - } - else { - startHeadPlus = ann.ax ? startHeadPlus + ann.ax : startHeadPlus; - startHeadMinus = ann.ax ? startHeadMinus - ann.ax : startHeadMinus; - Axes.expand(xa, [xa.r2c(ann.x)], { - ppadplus: Math.max(ann._xpadplus, headPlus, startHeadPlus), - ppadminus: Math.max(ann._xpadminus, headMinus, startHeadMinus) - }); - } - } +function calcAxisExpansion(ann, ax) { + var axId = ax._id; + var letter = axId.charAt(0); + var pos = ann[letter]; + var apos = ann['a' + letter]; + var ref = ann[letter + 'ref']; + var aref = ann['a' + letter + 'ref']; + var padplus = ann['_' + letter + 'padplus']; + var padminus = ann['_' + letter + 'padminus']; + var shift = {x: 1, y: -1}[letter] * ann[letter + 'shift']; + var headSize = 3 * ann.arrowsize * ann.arrowwidth || 0; + var headPlus = headSize + shift; + var headMinus = headSize - shift; + var startHeadSize = 3 * ann.startarrowsize * ann.arrowwidth || 0; + var startHeadPlus = startHeadSize + shift; + var startHeadMinus = startHeadSize - shift; + var extremes; - if(ya) { - headPlus = headSize - ann.yshift; - headMinus = headSize + ann.yshift; - startHeadPlus = startHeadSize - ann.yshift; - startHeadMinus = startHeadSize + ann.yshift; + if(aref === ref) { + // expand for the arrowhead (padded by arrowhead) + var extremeArrowHead = Axes.findExtremes(ax, [ax.r2c(pos)], { + ppadplus: headPlus, + ppadminus: headMinus + }); + // again for the textbox (padded by textbox) + var extremeText = Axes.findExtremes(ax, [ax.r2c(apos)], { + ppadplus: Math.max(padplus, startHeadPlus), + ppadminus: Math.max(padminus, startHeadMinus) + }); + extremes = { + min: [extremeArrowHead.min[0], extremeText.min[0]], + max: [extremeArrowHead.max[0], extremeText.max[0]] + }; + } else { + startHeadPlus = apos ? startHeadPlus + apos : startHeadPlus; + startHeadMinus = apos ? startHeadMinus - apos : startHeadMinus; + extremes = Axes.findExtremes(ax, [ax.r2c(pos)], { + ppadplus: Math.max(padplus, headPlus, startHeadPlus), + ppadminus: Math.max(padminus, headMinus, startHeadMinus) + }); + } - if(ann.ayref === ann.yref) { - Axes.expand(ya, [ya.r2c(ann.y)], { - ppadplus: headPlus, - ppadminus: headMinus - }); - Axes.expand(ya, [ya.r2c(ann.ay)], { - ppadplus: Math.max(ann._ypadplus, startHeadPlus), - ppadminus: Math.max(ann._ypadminus, startHeadMinus) - }); - } - else { - startHeadPlus = ann.ay ? startHeadPlus + ann.ay : startHeadPlus; - startHeadMinus = ann.ay ? startHeadMinus - ann.ay : startHeadMinus; - Axes.expand(ya, [ya.r2c(ann.y)], { - ppadplus: Math.max(ann._ypadplus, headPlus, startHeadPlus), - ppadminus: Math.max(ann._ypadminus, headMinus, startHeadMinus) - }); - } - } - }); + ann._extremes[axId] = extremes; } diff --git a/src/components/annotations/defaults.js b/src/components/annotations/defaults.js index 2ca55c44bae..b04657c2995 100644 --- a/src/components/annotations/defaults.js +++ b/src/components/annotations/defaults.js @@ -48,6 +48,11 @@ function handleAnnotationDefaults(annIn, annOut, fullLayout) { // xref, yref var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper'); + if(axRef !== 'paper') { + var ax = Axes.getFromId(gdMock, axRef); + ax._annIndices.push(annOut._index); + } + // x, y Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5); diff --git a/src/components/errorbars/calc.js b/src/components/errorbars/calc.js index 124211f913b..92e61bd021b 100644 --- a/src/components/errorbars/calc.js +++ b/src/components/errorbars/calc.js @@ -57,5 +57,8 @@ function calcOneAxis(calcTrace, trace, axis, coord) { } } - Axes.expand(axis, vals, {padded: true}); + var extremes = Axes.findExtremes(axis, vals, {padded: true}); + var axId = axis._id; + trace._extremes[axId].min = trace._extremes[axId].min.concat(extremes.min); + trace._extremes[axId].max = trace._extremes[axId].max.concat(extremes.max); } diff --git a/src/components/rangeslider/calc_autorange.js b/src/components/rangeslider/calc_autorange.js index c722836a453..8681877621b 100644 --- a/src/components/rangeslider/calc_autorange.js +++ b/src/components/rangeslider/calc_autorange.js @@ -21,15 +21,12 @@ module.exports = function calcAutorange(gd) { // this step in subsequent draw calls. for(var i = 0; i < axes.length; i++) { - var ax = axes[i], - opts = ax[constants.name]; + var ax = axes[i]; + var opts = ax[constants.name]; - // Don't try calling getAutoRange if _min and _max are filled in. - // This happens on updates where the calc step is skipped. - - if(opts && opts.visible && opts.autorange && ax._min.length && ax._max.length) { + if(opts && opts.visible && opts.autorange) { opts._input.autorange = true; - opts._input.range = opts.range = getAutoRange(ax); + opts._input.range = opts.range = getAutoRange(gd, ax); } } }; diff --git a/src/components/shapes/calc_autorange.js b/src/components/shapes/calc_autorange.js index 967cbcc4992..a05a6bc11c2 100644 --- a/src/components/shapes/calc_autorange.js +++ b/src/components/shapes/calc_autorange.js @@ -24,6 +24,7 @@ module.exports = function calcAutorange(gd) { for(var i = 0; i < shapeList.length; i++) { var shape = shapeList[i]; + shape._extremes = {}; var ax, bounds; @@ -33,8 +34,9 @@ module.exports = function calcAutorange(gd) { ax = Axes.getFromId(gd, shape.xref); bounds = shapeBounds(ax, vx0, vx1, shape.path, constants.paramIsX); - - if(bounds) Axes.expand(ax, bounds, calcXPaddingOptions(shape)); + if(bounds) { + shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcXPaddingOptions(shape)); + } } if(shape.yref !== 'paper') { @@ -43,7 +45,9 @@ module.exports = function calcAutorange(gd) { ax = Axes.getFromId(gd, shape.yref); bounds = shapeBounds(ax, vy0, vy1, shape.path, constants.paramIsY); - if(bounds) Axes.expand(ax, bounds, calcYPaddingOptions(shape)); + if(bounds) { + shape._extremes[ax._id] = Axes.findExtremes(ax, bounds, calcYPaddingOptions(shape)); + } } } }; diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index bc2934e0fee..fff7f9d612b 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -61,6 +61,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { if(axRef !== 'paper') { ax = Axes.getFromId(gdMock, axRef); + ax._shapeIndices.push(shapeOut._index); r2pos = helpers.rangeToShapePosition(ax); pos2r = helpers.shapePositionToRange(ax); } diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 1ee438d5f0b..aece3e99257 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -288,6 +288,7 @@ exports.plot = function(gd, data, layout, config) { function positionAndAutorange() { if(!recalc) { + Plots.doSetPositions(gd); doAutoRangeAndConstraints(); return; } diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index d45aa6683d9..dcbcec47112 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -576,7 +576,7 @@ exports.doAutoRangeAndConstraints = function(gd) { for(var i = 0; i < axList.length; i++) { var ax = axList[i]; cleanAxisConstraints(gd, ax); - doAutoRange(ax); + doAutoRange(gd, ax); } enforceAxisConstraints(gd); diff --git a/src/plots/cartesian/autorange.js b/src/plots/cartesian/autorange.js index 63017327ee0..cc33cd09767 100644 --- a/src/plots/cartesian/autorange.js +++ b/src/plots/cartesian/autorange.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var isNumeric = require('fast-isnumeric'); @@ -18,51 +17,69 @@ module.exports = { getAutoRange: getAutoRange, makePadFn: makePadFn, doAutoRange: doAutoRange, - expand: expand + findExtremes: findExtremes, + concatExtremes: concatExtremes }; -// Find the autorange for this axis -// -// assumes ax._min and ax._max have already been set by calling axes.expand -// using calcdata from all traces. These are arrays of objects: -// { -// val: calcdata value, -// pad: extra pixels beyond this value, -// extrapad: bool, does this point want 5% extra padding -// } -// -// Returns an array of [min, max]. These are calcdata for log and category axes -// and data for linear and date axes. -// -// TODO: we want to change log to data as well, but it's hard to do this -// maintaining backward compatibility. category will always have to use calcdata -// though, because otherwise values between categories (or outside all categories) -// would be impossible. -function getAutoRange(ax) { +/** + * getAutoRange + * + * Collects all _extremes values corresponding to a given axis + * and computes its auto range. + * + * getAutoRange uses return values from findExtremes where: + * + * @param {object} gd: + * graph div object with filled-in fullData and fullLayout, in particular + * with filled-in '_extremes' containers: + * { + * val: calcdata value, + * pad: extra pixels beyond this value, + * extrapad: bool, does this point want 5% extra padding + * } + * @param {object} ax: + * full axis object, in particular with filled-in '_traceIndices' + * and '_annIndices' / '_shapeIndices' if applicable + * @return {array} + * an array of [min, max]. These are calcdata for log and category axes + * and data for linear and date axes. + * + * TODO: we want to change log to data as well, but it's hard to do this + * maintaining backward compatibility. category will always have to use calcdata + * though, because otherwise values between categories (or outside all categories) + * would be impossible. + */ +function getAutoRange(gd, ax) { + var i, j; var newRange = []; - var minmin = ax._min[0].val; - var maxmax = ax._max[0].val; - var mbest = 0; - var axReverse = false; var getPad = makePadFn(ax); + var extremes = concatExtremes(gd, ax); + var minArray = extremes.min; + var maxArray = extremes.max; - var i, j, minpt, maxpt, minbest, maxbest, dp, dv; + if(minArray.length === 0 || maxArray.length === 0) { + return Lib.simpleMap(ax.range, ax.r2l); + } + + var minmin = minArray[0].val; + var maxmax = maxArray[0].val; - for(i = 1; i < ax._min.length; i++) { + for(i = 1; i < minArray.length; i++) { if(minmin !== maxmax) break; - minmin = Math.min(minmin, ax._min[i].val); + minmin = Math.min(minmin, minArray[i].val); } - for(i = 1; i < ax._max.length; i++) { + for(i = 1; i < maxArray.length; i++) { if(minmin !== maxmax) break; - maxmax = Math.max(maxmax, ax._max[i].val); + maxmax = Math.max(maxmax, maxArray[i].val); } + var axReverse = false; + if(ax.range) { var rng = Lib.simpleMap(ax.range, ax.r2l); axReverse = rng[1] < rng[0]; } - // one-time setting to easily reverse the axis // when plotting from code if(ax.autorange === 'reversed') { @@ -70,10 +87,13 @@ function getAutoRange(ax) { ax.autorange = true; } - for(i = 0; i < ax._min.length; i++) { - minpt = ax._min[i]; - for(j = 0; j < ax._max.length; j++) { - maxpt = ax._max[j]; + var mbest = 0; + var minpt, maxpt, minbest, maxbest, dp, dv; + + for(i = 0; i < minArray.length; i++) { + minpt = minArray[i]; + for(j = 0; j < maxArray.length; j++) { + maxpt = maxArray[j]; dv = maxpt.val - minpt.val; dp = ax._length - getPad(minpt) - getPad(maxpt); if(dv > 0 && dp > 0 && dv / dp > mbest) { @@ -89,11 +109,9 @@ function getAutoRange(ax) { var upper = minmin + 1; if(ax.rangemode === 'tozero') { newRange = minmin < 0 ? [lower, 0] : [0, upper]; - } - else if(ax.rangemode === 'nonnegative') { + } else if(ax.rangemode === 'nonnegative') { newRange = [Math.max(0, lower), Math.max(0, upper)]; - } - else { + } else { newRange = [lower, upper]; } } @@ -133,11 +151,9 @@ function getAutoRange(ax) { if(ax.rangemode === 'tozero') { if(newRange[0] < 0) { newRange = [newRange[0], 0]; - } - else if(newRange[0] > 0) { + } else if(newRange[0] > 0) { newRange = [0, newRange[0]]; - } - else { + } else { newRange = [0, 1]; } } @@ -173,15 +189,45 @@ function makePadFn(ax) { return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); }; } -function doAutoRange(ax) { +function concatExtremes(gd, ax) { + var axId = ax._id; + var fullData = gd._fullData; + var fullLayout = gd._fullLayout; + var minArray = []; + var maxArray = []; + var i, j, d; + + function _concat(cont, indices) { + for(i = 0; i < indices.length; i++) { + var item = cont[indices[i]]; + var extremes = (item._extremes || {})[axId]; + if(item.visible === true && extremes) { + for(j = 0; j < extremes.min.length; j++) { + d = extremes.min[j]; + collapseMinArray(minArray, d.val, d.pad, {extrapad: d.extrapad}); + } + for(j = 0; j < extremes.max.length; j++) { + d = extremes.max[j]; + collapseMaxArray(maxArray, d.val, d.pad, {extrapad: d.extrapad}); + } + } + } + } + + _concat(fullData, ax._traceIndices); + _concat(fullLayout.annotations || [], ax._annIndices || []); + _concat(fullLayout.shapes || [], ax._shapeIndices || []); + + return {min: minArray, max: maxArray}; +} + +function doAutoRange(gd, ax) { if(!ax._length) ax.setScale(); - // TODO do we really need this? - var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length); var axIn; - if(ax.autorange && hasDeps) { - ax.range = getAutoRange(ax); + if(ax.autorange) { + ax.range = getAutoRange(gd, ax); ax._r = ax.range.slice(); ax._rl = Lib.simpleMap(ax._r, ax.r2l); @@ -198,11 +244,7 @@ function doAutoRange(ax) { var axeRangeOpts = ax._anchorAxis.rangeslider[ax._name]; if(axeRangeOpts) { if(axeRangeOpts.rangemode === 'auto') { - if(hasDeps) { - axeRangeOpts.range = getAutoRange(ax); - } else { - axeRangeOpts.range = ax._rangeInitial ? ax._rangeInitial.slice() : ax.range.slice(); - } + axeRangeOpts.range = getAutoRange(gd, ax); } } axIn = ax._anchorAxis._input; @@ -210,18 +252,27 @@ function doAutoRange(ax) { } } -/* - * expand: if autoranging, include new data in the outer limits for this axis. - * Note that `expand` is called during `calc`, when we don't yet know the axis +/** + * findExtremes + * + * Find min/max extremes of an array of coordinates on a given axis. + * + * Note that findExtremes is called during `calc`, when we don't yet know the axis * length; all the inputs should be based solely on the trace data, nothing * about the axis layout. + * * Note that `ppad` and `vpad` as well as their asymmetric variants refer to * the before and after padding of the passed `data` array, not to the whole axis. * - * @param {object} ax: the axis being expanded. The result will be more entries - * in ax._min and ax._max if necessary to include the new data - * @param {array} data: an array of numbers (ie already run through ax.d2c) - * @param {object} options: available keys are: + * @param {object} ax: full axis object + * relies on + * - ax.type + * - ax._m (just its sign) + * - ax.d2l + * @param {array} data: + * array of numbers (i.e. already run though ax.d2c) + * @param {object} options: + * available keys are: * vpad: (number or number array) pad values (data value +-vpad) * ppad: (number or number array) pad pixels (pixel location +-ppad) * ppadplus, ppadminus, vpadplus, vpadminus: @@ -230,21 +281,28 @@ function doAutoRange(ax) { * (unless one end is overridden by tozero) * tozero: (boolean) make sure to include zero if axis is linear, * and make it a tight bound if possible + * + * @return {object} + * - min {array of objects} + * - max {array of objects} + * each object item has fields: + * - val {number} + * - pad {number} + * - extrappad {number} */ -function expand(ax, data, options) { - if(!ax._min) ax._min = []; - if(!ax._max) ax._max = []; +function findExtremes(ax, data, options) { if(!options) options = {}; if(!ax._m) ax.setScale(); + var minArray = []; + var maxArray = []; + var len = data.length; var extrapad = options.padded || false; var tozero = options.tozero && (ax.type === 'linear' || ax.type === '-'); - var isLog = (ax.type === 'log'); - - var i, j, k, v, di, dmin, dmax, ppadiplus, ppadiminus, includeThis, vmin, vmax; - + var isLog = ax.type === 'log'; var hasArrayOption = false; + var i, v, di, dmin, dmax, ppadiplus, ppadiminus, vmin, vmax; function makePadAccessor(item) { if(Array.isArray(item)) { @@ -277,8 +335,7 @@ function expand(ax, data, options) { if(v < vmin && v > 0) vmin = v; if(v > vmax && v < FP_SAFE) vmax = v; } - } - else { + } else { for(i = 0; i < len; i++) { v = data[i]; if(v < vmin && v > -FP_SAFE) vmin = v; @@ -290,6 +347,8 @@ function expand(ax, data, options) { len = 2; } + var collapseOpts = {tozero: tozero, extrapad: extrapad}; + function addItem(i) { di = data[i]; if(!isNumeric(di)) return; @@ -310,49 +369,11 @@ function expand(ax, data, options) { dmin = Math.min(0, dmin); dmax = Math.max(0, dmax); } - - for(k = 0; k < 2; k++) { - var newVal = k ? dmax : dmin; - if(goodNumber(newVal)) { - var extremes = k ? ax._max : ax._min; - var newPad = k ? ppadiplus : ppadiminus; - var atLeastAsExtreme = k ? greaterOrEqual : lessOrEqual; - - includeThis = true; - /* - * Take items v from ax._min/_max and compare them to the presently active point: - * - Since we don't yet know the relationship between pixels and values - * (that's what we're trying to figure out!) AND we don't yet know how - * many pixels `extrapad` represents (it's going to be 5% of the length, - * but we don't want to have to redo _min and _max just because length changed) - * two point must satisfy three criteria simultaneously for one to supersede the other: - * - at least as extreme a `val` - * - at least as big a `pad` - * - an unpadded point cannot supersede a padded point, but any other combination can - * - * - If the item supersedes the new point, set includethis false - * - If the new pt supersedes the item, delete it from ax._min/_max - */ - for(j = 0; j < extremes.length && includeThis; j++) { - v = extremes[j]; - if(atLeastAsExtreme(v.val, newVal) && v.pad >= newPad && (v.extrapad || !extrapad)) { - includeThis = false; - break; - } - else if(atLeastAsExtreme(newVal, v.val) && v.pad <= newPad && (extrapad || !v.extrapad)) { - extremes.splice(j, 1); - j--; - } - } - if(includeThis) { - var clipAtZero = (tozero && newVal === 0); - extremes.push({ - val: newVal, - pad: clipAtZero ? 0 : newPad, - extrapad: clipAtZero ? false : extrapad - }); - } - } + if(goodNumber(dmin)) { + collapseMinArray(minArray, dmin, ppadiminus, collapseOpts); + } + if(goodNumber(dmax)) { + collapseMaxArray(maxArray, dmax, ppadiplus, collapseOpts); } } @@ -362,6 +383,78 @@ function expand(ax, data, options) { var iMax = Math.min(6, len); for(i = 0; i < iMax; i++) addItem(i); for(i = len - 1; i >= iMax; i--) addItem(i); + + return {min: minArray, max: maxArray}; +} + +function collapseMinArray(array, newVal, newPad, opts) { + collapseArray(array, newVal, newPad, opts, lessOrEqual); +} + +function collapseMaxArray(array, newVal, newPad, opts) { + collapseArray(array, newVal, newPad, opts, greaterOrEqual); +} + +/** + * collapseArray + * + * Take items v from 'array' compare them to 'newVal', 'newPad' + * + * @param {array} array: + * current set of min or max extremes + * @param {number} newVal: + * new value to compare against + * @param {number} newPad: + * pad value associated with 'newVal' + * @param {object} opts: + * - tozero {boolean} + * - extrapad {number} + * @param {function} atLeastAsExtreme: + * comparison function, use + * - lessOrEqual for min 'array' and + * - greaterOrEqual for max 'array' + * + * In practice, 'array' is either + * - 'extremes[ax._id].min' or + * - 'extremes[ax._id].max + * found in traces and layout items that affect autorange. + * + * Since we don't yet know the relationship between pixels and values + * (that's what we're trying to figure out!) AND we don't yet know how + * many pixels `extrapad` represents (it's going to be 5% of the length, + * but we don't want to have to redo calc just because length changed) + * two point must satisfy three criteria simultaneously for one to supersede the other: + * - at least as extreme a `val` + * - at least as big a `pad` + * - an unpadded point cannot supersede a padded point, but any other combination can + * + * Then: + * - If the item supersedes the new point, set includeThis false + * - If the new pt supersedes the item, delete it from 'array' + */ +function collapseArray(array, newVal, newPad, opts, atLeastAsExtreme) { + var tozero = opts.tozero; + var extrapad = opts.extrapad; + var includeThis = true; + + for(var j = 0; j < array.length && includeThis; j++) { + var v = array[j]; + if(atLeastAsExtreme(v.val, newVal) && v.pad >= newPad && (v.extrapad || !extrapad)) { + includeThis = false; + break; + } else if(atLeastAsExtreme(newVal, v.val) && v.pad <= newPad && (extrapad || !v.extrapad)) { + array.splice(j, 1); + j--; + } + } + if(includeThis) { + var clipAtZero = (tozero && newVal === 0); + array.push({ + val: newVal, + pad: clipAtZero ? 0 : newPad, + extrapad: clipAtZero ? false : extrapad + }); + } } // In order to stop overflow errors, don't consider points diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 38d56e912ca..97b06b9c64a 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -48,8 +48,8 @@ axes.getFromId = axisIds.getFromId; axes.getFromTrace = axisIds.getFromTrace; var autorange = require('./autorange'); -axes.expand = autorange.expand; axes.getAutoRange = autorange.getAutoRange; +axes.findExtremes = autorange.findExtremes; /* * find the list of possible axes to reference with an xref or yref attribute diff --git a/src/plots/cartesian/constraints.js b/src/plots/cartesian/constraints.js index f88bb397c10..70859a2ad0b 100644 --- a/src/plots/cartesian/constraints.js +++ b/src/plots/cartesian/constraints.js @@ -12,6 +12,7 @@ var id2name = require('./axis_ids').id2name; var scaleZoom = require('./scale_zoom'); var makePadFn = require('./autorange').makePadFn; +var concatExtremes = require('./autorange').concatExtremes; var ALMOST_EQUAL = require('../../constants/numerical').ALMOST_EQUAL; @@ -112,7 +113,7 @@ exports.enforce = function enforceAxisConstraints(gd) { factor *= rangeShrunk; } - if(ax.autorange && ax._min.length && ax._max.length) { + if(ax.autorange) { /* * range & factor may need to change because range was * calculated for the larger scaling, so some pixel @@ -140,18 +141,21 @@ exports.enforce = function enforceAxisConstraints(gd) { updateDomain(ax, factor); ax.setScale(); var m = Math.abs(ax._m); + var extremes = concatExtremes(gd, ax); + var minArray = extremes.min; + var maxArray = extremes.max; var newVal; var k; - for(k = 0; k < ax._min.length; k++) { - newVal = ax._min[k].val - getPad(ax._min[k]) / m; + for(k = 0; k < minArray.length; k++) { + newVal = minArray[k].val - getPad(minArray[k]) / m; if(newVal > outerMin && newVal < rangeMin) { rangeMin = newVal; } } - for(k = 0; k < ax._max.length; k++) { - newVal = ax._max[k].val + getPad(ax._max[k]) / m; + for(k = 0; k < maxArray.length; k++) { + newVal = maxArray[k].val + getPad(maxArray[k]) / m; if(newVal < outerMax && newVal > rangeMax) { rangeMax = newVal; } diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index f17c71f7f19..6c2f824781e 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -9,7 +9,6 @@ 'use strict'; -var Registry = require('../../registry'); var Lib = require('../../lib'); var Color = require('../../components/color'); var Template = require('../../plot_api/plot_template'); @@ -20,31 +19,57 @@ var handleTypeDefaults = require('./type_defaults'); var handleAxisDefaults = require('./axis_defaults'); var handleConstraintDefaults = require('./constraint_defaults'); var handlePositionDefaults = require('./position_defaults'); + var axisIds = require('./axis_ids'); +var id2name = axisIds.id2name; +var name2id = axisIds.name2id; +var Registry = require('../../registry'); +var traceIs = Registry.traceIs; +var getComponentMethod = Registry.getComponentMethod; + +function appendList(cont, k, item) { + if(Array.isArray(cont[k])) cont[k].push(item); + else cont[k] = [item]; +} module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { + var ax2traces = {}; var xaCheater = {}; var xaNonCheater = {}; var outerTicks = {}; var noGrids = {}; - var i; + var i, j; // look for axes in the data for(i = 0; i < fullData.length; i++) { var trace = fullData[i]; - - if(!Registry.traceIs(trace, 'cartesian') && !Registry.traceIs(trace, 'gl2d')) { - continue; + if(!traceIs(trace, 'cartesian') && !traceIs(trace, 'gl2d')) continue; + + var xaName; + if(trace.xaxis) { + xaName = id2name(trace.xaxis); + appendList(ax2traces, xaName, trace); + } else if(trace.xaxes) { + for(j = 0; j < trace.xaxes.length; j++) { + appendList(ax2traces, id2name(trace.xaxes[j]), trace); + } } - var xaName = axisIds.id2name(trace.xaxis); - var yaName = axisIds.id2name(trace.yaxis); + var yaName; + if(trace.yaxis) { + yaName = id2name(trace.yaxis); + appendList(ax2traces, yaName, trace); + } else if(trace.yaxes) { + for(j = 0; j < trace.yaxes.length; j++) { + appendList(ax2traces, id2name(trace.yaxes[j]), trace); + } + } // Two things trigger axis visibility: // 1. is not carpet // 2. carpet that's not cheater - if(!Registry.traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) { + if(!traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) { if(xaName) xaNonCheater[xaName] = 1; } @@ -57,22 +82,22 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { } // check for default formatting tweaks - if(Registry.traceIs(trace, '2dMap')) { - outerTicks[xaName] = true; - outerTicks[yaName] = true; + if(traceIs(trace, '2dMap')) { + outerTicks[xaName] = 1; + outerTicks[yaName] = 1; } - if(Registry.traceIs(trace, 'oriented')) { + if(traceIs(trace, 'oriented')) { var positionAxis = trace.orientation === 'h' ? yaName : xaName; - noGrids[positionAxis] = true; + noGrids[positionAxis] = 1; } } var subplots = layoutOut._subplots; var xIds = subplots.xaxis; var yIds = subplots.yaxis; - var xNames = Lib.simpleMap(xIds, axisIds.id2name); - var yNames = Lib.simpleMap(yIds, axisIds.id2name); + var xNames = Lib.simpleMap(xIds, id2name); + var yNames = Lib.simpleMap(yIds, id2name); var axNames = xNames.concat(yNames); // plot_bgcolor only makes sense if there's a (2D) plot! @@ -108,7 +133,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var axName2 = list[j]; if(axName2 !== axName && !(layoutIn[axName2] || {}).overlaying) { - out.push(axisIds.name2id(axName2)); + out.push(name2id(axName2)); } } @@ -127,7 +152,12 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { axLayoutIn = layoutIn[axName]; axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis'); - handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, fullData, axName); + var traces = ax2traces[axName] || []; + axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; }); + axLayoutOut._annIndices = []; + axLayoutOut._shapeIndices = []; + + handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, traces, axName); var overlayableAxes = getOverlayableAxes(axLetter, axName); @@ -136,7 +166,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { font: layoutOut.font, outerTicks: outerTicks[axName], showGrid: !noGrids[axName], - data: fullData, + data: traces, bgColor: bgColor, calendar: layoutOut.calendar, automargin: true, @@ -173,8 +203,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { } // quick second pass for range slider and selector defaults - var rangeSliderDefaults = Registry.getComponentMethod('rangeslider', 'handleDefaults'); - var rangeSelectorDefaults = Registry.getComponentMethod('rangeselector', 'handleDefaults'); + var rangeSliderDefaults = getComponentMethod('rangeslider', 'handleDefaults'); + var rangeSelectorDefaults = getComponentMethod('rangeselector', 'handleDefaults'); for(i = 0; i < xNames.length; i++) { axName = xNames[i]; @@ -201,7 +231,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { axLayoutIn = layoutIn[axName]; axLayoutOut = layoutOut[axName]; - var anchoredAxis = layoutOut[axisIds.id2name(axLayoutOut.anchor)]; + var anchoredAxis = layoutOut[id2name(axLayoutOut.anchor)]; var fixedRangeDflt = ( anchoredAxis && diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index d3471547328..5c3a6295dd1 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -52,8 +52,7 @@ function fromLog(v) { * Creates/updates these conversion functions, and a few more utilities * like cleanRange, and makeCalcdata * - * also clears the autorange bounds ._min and ._max - * and the autotick constraints ._minDtick, ._forceTick0 + * also clears the autotick constraints ._minDtick, ._forceTick0 */ module.exports = function setConvert(ax, fullLayout) { fullLayout = fullLayout || {}; @@ -460,15 +459,6 @@ module.exports = function setConvert(ax, fullLayout) { }; ax.clearCalc = function() { - // for autoranging: arrays of objects: - // { - // val: axis value, - // pad: pixel padding, - // extrapad: boolean, should this val get 5% additional padding - // } - ax._min = []; - ax._max = []; - // initialize the category list, if there is one, so we start over // to be filled in later by ax.d2c ax._categories = (ax._initialCategories || []).slice(); diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index 9bc5db60cf2..56e12929508 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -441,7 +441,7 @@ proto.plot = function(fullData, calcData, fullLayout) { ax = this[AXES[i]]; ax._length = options.viewBox[i + 2] - options.viewBox[i]; - doAutoRange(ax); + doAutoRange(this.graphDiv, ax); ax.setScale(); } diff --git a/src/plots/plots.js b/src/plots/plots.js index fae7126684e..90dcfaeea44 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2455,10 +2455,13 @@ plots.doCalcdata = function(gd, traces) { } } - // find array attributes in trace for(i = 0; i < fullData.length; i++) { trace = fullData[i]; + trace._arrayAttrs = PlotSchema.findArrayAttributes(trace); + + // keep track of trace extremes (for autorange) in here + trace._extremes = {}; } // add polar axes to axis list diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index 5c103448f72..b83f486573a 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -55,6 +55,7 @@ function handleDefaults(contIn, contOut, coerce, opts) { // propagate the template. var axOut = contOut[axName] = {}; axOut._id = axOut._name = axName; + axOut._traceIndices = subplotData.map(function(t) { return t.index; }); var dataAttr = constants.axisName2dataArray[axName]; var axType = handleAxisTypeDefaults(axIn, axOut, coerceAxis, subplotData, dataAttr); @@ -80,11 +81,11 @@ function handleDefaults(contIn, contOut, coerce, opts) { // Furthermore, angular axes don't have a set range. // // Mocked domains and ranges are set by the polar subplot instances, - // but Axes.expand uses the sign of _m to determine which padding value + // but Axes.findExtremes uses the sign of _m to determine which padding value // to use. // - // By setting, _m to 1 here, we make Axes.expand think that range[1] > range[0], - // and vice-versa for `autorange: 'reversed'` below. + // By setting, _m to 1 here, we make Axes.findExtremes think that + // range[1] > range[0], and vice-versa for `autorange: 'reversed'` below. axOut._m = 1; switch(axName) { diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 3a91414d45b..e300807ed1c 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -302,7 +302,7 @@ proto.doAutoRange = function(fullLayout, polarLayout) { var ax = this.radialAxis; setScale(ax, radialLayout, fullLayout); - doAutoRange(ax); + doAutoRange(this.gd, ax); radialLayout.range = ax.range.slice(); radialLayout._input.range = ax.range.slice(); @@ -1207,12 +1207,6 @@ proto.fillViewInitialKey = function(key, val) { function setScale(ax, axLayout, fullLayout) { Axes.setConvert(ax, fullLayout); - - // _min and _max are filled in during Axes.expand - // and cleared during Axes.setConvert - ax._min = axLayout._min; - ax._max = axLayout._max; - ax.setScale(); } diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js index 423682eff04..10fc320af06 100644 --- a/src/traces/bar/calc.js +++ b/src/traces/bar/calc.js @@ -6,12 +6,8 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; -var isNumeric = require('fast-isnumeric'); -var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray; - var Axes = require('../../plots/cartesian/axes'); var hasColorscale = require('../../components/colorscale/has_colorscale'); var colorscaleCalc = require('../../components/colorscale/calc'); @@ -27,24 +23,14 @@ module.exports = function calc(gd, trace) { var xa = Axes.getFromId(gd, trace.xaxis || 'x'), ya = Axes.getFromId(gd, trace.yaxis || 'y'), orientation = trace.orientation || ((trace.x && !trace.y) ? 'h' : 'v'), - sa, pos, size, i, scalendar; + pos, size, i; if(orientation === 'h') { - sa = xa; size = xa.makeCalcdata(trace, 'x'); pos = ya.makeCalcdata(trace, 'y'); - - // not sure if it really makes sense to have dates for bar size data... - // ideally if we want to make gantt charts or something we'd treat - // the actual size (trace.x or y) as time delta but base as absolute - // time. But included here for completeness. - scalendar = trace.xcalendar; - } - else { - sa = ya; + } else { size = ya.makeCalcdata(trace, 'y'); pos = xa.makeCalcdata(trace, 'x'); - scalendar = trace.ycalendar; } // create the "calculated data" to plot @@ -60,33 +46,6 @@ module.exports = function calc(gd, trace) { } } - // set base - var base = trace.base, - b; - - if(isArrayOrTypedArray(base)) { - for(i = 0; i < Math.min(base.length, cd.length); i++) { - b = sa.d2c(base[i], 0, scalendar); - if(isNumeric(b)) { - cd[i].b = +b; - cd[i].hasB = 1; - } - else cd[i].b = 0; - } - for(; i < cd.length; i++) { - cd[i].b = 0; - } - } - else { - b = sa.d2c(base, 0, scalendar); - var hasBase = isNumeric(b); - b = hasBase ? b : 0; - for(i = 0; i < cd.length; i++) { - cd[i].b = b; - if(hasBase) cd[i].hasB = 1; - } - } - // auto-z and autocolorscale if applicable if(hasColorscale(trace, 'marker')) { colorscaleCalc(trace, trace.marker.color, 'marker', 'c'); diff --git a/src/traces/bar/set_positions.js b/src/traces/bar/set_positions.js index b9a2fdf0746..1021a400a16 100644 --- a/src/traces/bar/set_positions.js +++ b/src/traces/bar/set_positions.js @@ -65,6 +65,8 @@ function setGroupPositions(gd, pa, sa, calcTraces) { included, i, calcTrace, fullTrace; + initBase(gd, pa, sa, calcTraces); + if(overlay) { setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces); } @@ -110,6 +112,45 @@ function setGroupPositions(gd, pa, sa, calcTraces) { collectExtents(calcTraces, pa); } +function initBase(gd, pa, sa, calcTraces) { + var i, j; + + for(i = 0; i < calcTraces.length; i++) { + var cd = calcTraces[i]; + var trace = cd[0].trace; + var base = trace.base; + var b; + + // not sure if it really makes sense to have dates for bar size data... + // ideally if we want to make gantt charts or something we'd treat + // the actual size (trace.x or y) as time delta but base as absolute + // time. But included here for completeness. + var scalendar = trace.orientation === 'h' ? trace.xcalendar : trace.ycalendar; + + if(isArrayOrTypedArray(base)) { + for(j = 0; j < Math.min(base.length, cd.length); j++) { + b = sa.d2c(base[j], 0, scalendar); + if(isNumeric(b)) { + cd[j].b = +b; + cd[j].hasB = 1; + } + else cd[j].b = 0; + } + for(; j < cd.length; j++) { + cd[j].b = 0; + } + } else { + b = sa.d2c(base, 0, scalendar); + var hasBase = isNumeric(b); + b = hasBase ? b : 0; + for(j = 0; j < cd.length; j++) { + cd[j].b = b; + if(hasBase) cd[j].hasB = 1; + } + } + } +} + function setGroupPositionsInOverlayMode(gd, pa, sa, calcTraces) { var barnorm = gd._fullLayout.barnorm, @@ -455,7 +496,8 @@ function updatePositionAxis(gd, pa, sieve, allowMinDtick) { } } - Axes.expand(pa, [pMin, pMax], {padded: false}); + var extremes = Axes.findExtremes(pa, [pMin, pMax], {padded: false}); + putExtremes(calcTraces, pa, extremes); } function expandRange(range, newValue) { @@ -489,7 +531,8 @@ function setBaseAndTop(gd, sa, sieve) { } } - Axes.expand(sa, sRange, {tozero: true, padded: true}); + var extremes = Axes.findExtremes(sa, sRange, {tozero: true, padded: true}); + putExtremes(traces, sa, extremes); } @@ -527,7 +570,10 @@ function stackBars(gd, sa, sieve) { } // if barnorm is set, let normalizeBars update the axis range - if(!barnorm) Axes.expand(sa, sRange, {tozero: true, padded: true}); + if(!barnorm) { + var extremes = Axes.findExtremes(sa, sRange, {tozero: true, padded: true}); + putExtremes(traces, sa, extremes); + } } @@ -592,7 +638,8 @@ function normalizeBars(gd, sa, sieve) { } // update range of size axis - Axes.expand(sa, sRange, {tozero: true, padded: padded}); + var extremes = Axes.findExtremes(sa, sRange, {tozero: true, padded: padded}); + putExtremes(traces, sa, extremes); } @@ -600,6 +647,12 @@ function getAxisLetter(ax) { return ax._id.charAt(0); } +function putExtremes(cd, ax, extremes) { + for(var i = 0; i < cd.length; i++) { + cd[i][0].trace._extremes[ax._id] = extremes; + } +} + // find the full position span of bars at each position // for use by hover, to ensure labels move in if bars are // narrower than the space they're in. diff --git a/src/traces/box/calc.js b/src/traces/box/calc.js index d9f6aeb7e5b..095048f19a7 100644 --- a/src/traces/box/calc.js +++ b/src/traces/box/calc.js @@ -122,7 +122,8 @@ module.exports = function calc(gd, trace) { } calcSelection(cd, trace); - Axes.expand(valAxis, val, {padded: true}); + var extremes = Axes.findExtremes(valAxis, val, {padded: true}); + trace._extremes[valAxis._id] = extremes; if(cd.length > 0) { cd[0].t = { diff --git a/src/traces/box/set_positions.js b/src/traces/box/set_positions.js index ca460fd8297..c10f511ce83 100644 --- a/src/traces/box/set_positions.js +++ b/src/traces/box/set_positions.js @@ -86,12 +86,6 @@ function setPositionOffset(traceType, gd, boxList, posAxis, pad) { // check for forced minimum dtick Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true); - // set the width of all boxes - for(i = 0; i < boxList.length; i++) { - calcTrace = calcdata[boxList[i]]; - calcTrace[0].t.dPos = dPos; - } - var gap = fullLayout[traceType + 'gap']; var groupgap = fullLayout[traceType + 'groupgap']; var padfactor = (1 - gap) * (1 - groupgap) * dPos / fullLayout[numKey]; @@ -99,10 +93,19 @@ function setPositionOffset(traceType, gd, boxList, posAxis, pad) { // autoscale the x axis - including space for points if they're off the side // TODO: this will overdo it if the outermost boxes don't have // their points as far out as the other boxes - Axes.expand(posAxis, boxdv.vals, { + var extremes = Axes.findExtremes(posAxis, boxdv.vals, { vpadminus: dPos + pad[0] * padfactor, vpadplus: dPos + pad[1] * padfactor }); + + for(i = 0; i < boxList.length; i++) { + calcTrace = calcdata[boxList[i]]; + // set the width of all boxes + calcTrace[0].t.dPos = dPos; + // link extremes to all boxes + calcTrace[0].trace._extremes[posAxis._id] = extremes; + } + } module.exports = { diff --git a/src/traces/carpet/calc.js b/src/traces/carpet/calc.js index f9b0e65b648..3a69c021e40 100644 --- a/src/traces/carpet/calc.js +++ b/src/traces/carpet/calc.js @@ -82,8 +82,8 @@ module.exports = function calc(gd, trace) { xrange = [xc - dx * grow, xc + dx * grow]; yrange = [yc - dy * grow, yc + dy * grow]; - Axes.expand(xa, xrange, {padded: true}); - Axes.expand(ya, yrange, {padded: true}); + trace._extremes[xa._id] = Axes.findExtremes(xa, xrange, {padded: true}); + trace._extremes[ya._id] = Axes.findExtremes(ya, yrange, {padded: true}); // Enumerate the gridlines, both major and minor, and store them on the trace // object: diff --git a/src/traces/contourgl/convert.js b/src/traces/contourgl/convert.js index 417b6c096d1..32632c5ee11 100644 --- a/src/traces/contourgl/convert.js +++ b/src/traces/contourgl/convert.js @@ -125,9 +125,10 @@ proto.update = function(fullTrace, calcTrace) { this.contour.update(this.contourOptions); this.heatmap.update(this.heatmapOptions); - // expand axes - Axes.expand(this.scene.xaxis, calcPt.x); - Axes.expand(this.scene.yaxis, calcPt.y); + var xa = this.scene.xaxis; + var ya = this.scene.yaxis; + fullTrace._extremes[xa._id] = Axes.findExtremes(xa, calcPt.x); + fullTrace._extremes[ya._id] = Axes.findExtremes(ya, calcPt.y); }; proto.dispose = function() { diff --git a/src/traces/heatmap/calc.js b/src/traces/heatmap/calc.js index a1f0e7bb6c1..37477db0315 100644 --- a/src/traces/heatmap/calc.js +++ b/src/traces/heatmap/calc.js @@ -124,8 +124,8 @@ module.exports = function calc(gd, trace) { // handled in gl2d convert step if(!isGL2D) { - Axes.expand(xa, xArray); - Axes.expand(ya, yArray); + trace._extremes[xa._id] = Axes.findExtremes(xa, xArray); + trace._extremes[ya._id] = Axes.findExtremes(ya, yArray); } var cd0 = { diff --git a/src/traces/heatmapgl/convert.js b/src/traces/heatmapgl/convert.js index bb66bb1410f..8fc9980fcfb 100644 --- a/src/traces/heatmapgl/convert.js +++ b/src/traces/heatmapgl/convert.js @@ -95,8 +95,10 @@ proto.update = function(fullTrace, calcTrace) { this.heatmap.update(this.options); - Axes.expand(this.scene.xaxis, calcPt.x); - Axes.expand(this.scene.yaxis, calcPt.y); + var xa = this.scene.xaxis; + var ya = this.scene.yaxis; + fullTrace._extremes[xa._id] = Axes.findExtremes(xa, calcPt.x); + fullTrace._extremes[ya._id] = Axes.findExtremes(ya, calcPt.y); }; proto.dispose = function() { diff --git a/src/traces/ohlc/calc.js b/src/traces/ohlc/calc.js index 42de0e1a086..f7553444bfa 100644 --- a/src/traces/ohlc/calc.js +++ b/src/traces/ohlc/calc.js @@ -25,8 +25,7 @@ function calc(gd, trace) { var cd = calcCommon(gd, trace, x, ya, ptFunc); - Axes.expand(xa, x, {vpad: minDiff / 2}); - + trace._extremes[xa._id] = Axes.findExtremes(xa, x, {vpad: minDiff / 2}); if(cd.length) { Lib.extendFlat(cd[0].t, { wHover: minDiff / 2, @@ -93,7 +92,7 @@ function calcCommon(gd, trace, x, ya, ptFunc) { } } - Axes.expand(ya, l.concat(h), {padded: true}); + trace._extremes[ya._id] = Axes.findExtremes(ya, l.concat(h), {padded: true}); if(cd.length) { cd[0].t = { diff --git a/src/traces/pointcloud/convert.js b/src/traces/pointcloud/convert.js index 721b964f5a5..9d7af610ab6 100644 --- a/src/traces/pointcloud/convert.js +++ b/src/traces/pointcloud/convert.js @@ -11,7 +11,7 @@ var createPointCloudRenderer = require('gl-pointcloud2d'); var str2RGBArray = require('../../lib/str2rgbarray'); -var expandAxis = require('../../plots/cartesian/autorange').expand; +var findExtremes = require('../../plots/cartesian/autorange').findExtremes; var getTraceColor = require('../scatter/get_trace_color'); function Pointcloud(scene, uid) { @@ -195,14 +195,11 @@ proto.updateFast = function(options) { this.pointcloud.update(this.pointcloudOptions); // add item for autorange routine - this.expandAxesFast(bounds, markerSizeMax / 2); // avoid axis reexpand just because of the adaptive point size -}; - -proto.expandAxesFast = function(bounds, markerSize) { - var pad = markerSize || 0.5; - - expandAxis(this.scene.xaxis, [bounds[0], bounds[2]], {ppad: pad}); - expandAxis(this.scene.yaxis, [bounds[1], bounds[3]], {ppad: pad}); + var xa = this.scene.xaxis; + var ya = this.scene.yaxis; + var pad = markerSizeMax / 2 || 0.5; + options._extremes[xa._id] = findExtremes(xa, [bounds[0], bounds[2]], {ppad: pad}); + options._extremes[ya._id] = findExtremes(ya, [bounds[1], bounds[3]], {ppad: pad}); }; proto.dispose = function() { diff --git a/src/traces/scatter/calc.js b/src/traces/scatter/calc.js index 97c3f187bdb..37a9068c8e9 100644 --- a/src/traces/scatter/calc.js +++ b/src/traces/scatter/calc.js @@ -97,8 +97,8 @@ function calcAxisExpansion(gd, trace, xa, ya, x, y, ppad) { } // N.B. asymmetric splom traces call this with blank {} xa or ya - if(xa._id) Axes.expand(xa, x, xOptions); - if(ya._id) Axes.expand(ya, y, yOptions); + if(xa._id) trace._extremes[xa._id] = Axes.findExtremes(xa, x, xOptions); + if(ya._id) trace._extremes[ya._id] = Axes.findExtremes(ya, y, yOptions); } function calcMarkerSize(trace, serieslen) { diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index dd0b3366c14..f0ffbf8220f 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -15,7 +15,6 @@ var rgba = require('color-normalize'); var Registry = require('../../registry'); var Lib = require('../../lib'); var Drawing = require('../../components/drawing'); -var Axes = require('../../plots/cartesian/axes'); var AxisIDs = require('../../plots/cartesian/axis_ids'); var formatColor = require('../../lib/gl_format_color').formatColor; @@ -511,11 +510,10 @@ function convertErrorBarPositions(gd, trace, positions, x, y) { } } - Axes.expand(ax, [minShoe, maxHat], {padded: true}); - out[axLetter] = { positions: positions, - errors: errors + errors: errors, + _bnds: [minShoe, maxHat] }; } } diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index d643e8a5520..3efb2dae4b3 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -19,6 +19,7 @@ var Registry = require('../../registry'); var Lib = require('../../lib'); var prepareRegl = require('../../lib/prepare_regl'); var AxisIDs = require('../../plots/cartesian/axis_ids'); +var findExtremes = require('../../plots/cartesian/autorange').findExtremes; var Color = require('../../components/color'); var subTypes = require('../scatter/subtypes'); @@ -85,11 +86,9 @@ function calc(gd, trace) { var opts = sceneOptions(gd, subplot, trace, positions, x, y); var scene = sceneUpdate(gd, subplot); - // Re-use SVG scatter axis expansion routine except - // for graph with very large number of points where it - // performs poorly. - // In big data case, fake Axes.expand outputs with data bounds, - // and an average size for array marker.size inputs. + // Reuse SVG scatter axis expansion routine. + // For graphs with very large number of points and array marker.size, + // use average marker size instead to speed things up. var ppad; if(count < TOO_MANY_POINTS) { ppad = calcMarkerSize(trace, count); @@ -97,6 +96,8 @@ function calc(gd, trace) { ppad = 2 * (opts.marker.sizeAvg || Math.max(opts.marker.size, 3)); } calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); + if(opts.errorX) expandForErrorBars(trace, xa, opts.errorX); + if(opts.errorY) expandForErrorBars(trace, ya, opts.errorY); // set flags to create scene renderers if(opts.fill && !scene.fill2d) scene.fill2d = true; @@ -138,6 +139,12 @@ function calc(gd, trace) { return [{x: false, y: false, t: stash, trace: trace}]; } +function expandForErrorBars(trace, ax, opts) { + var extremes = trace._extremes[ax._id]; + var errExt = findExtremes(ax, opts._bnds, {padded: true}); + extremes.min = extremes.min.concat(errExt.min); + extremes.max = extremes.max.concat(errExt.max); +} // create scene options function sceneOptions(gd, subplot, trace, positions, x, y) { diff --git a/src/traces/scatterpolar/calc.js b/src/traces/scatterpolar/calc.js index 7d9662324d0..f2f10dd7f2d 100644 --- a/src/traces/scatterpolar/calc.js +++ b/src/traces/scatterpolar/calc.js @@ -48,7 +48,7 @@ module.exports = function calc(gd, trace) { } var ppad = calcMarkerSize(trace, len); - Axes.expand(radialAxis, rArray, {ppad: ppad}); + trace._extremes.x = Axes.findExtremes(radialAxis, rArray, {ppad: ppad}); calcColorscale(trace); arraysToCalcdata(cd, trace); diff --git a/src/traces/scatterpolargl/index.js b/src/traces/scatterpolargl/index.js index e82b8db30ea..141a09fcd57 100644 --- a/src/traces/scatterpolargl/index.js +++ b/src/traces/scatterpolargl/index.js @@ -36,13 +36,7 @@ function calc(container, trace) { stash.r = rArray; stash.theta = thetaArray; - Axes.expand(radialAxis, rArray, {tozero: true}); - - if(angularAxis.type !== 'linear') { - angularAxis.autorange = true; - Axes.expand(angularAxis, thetaArray); - delete angularAxis.autorange; - } + trace._extremes.x = Axes.findExtremes(radialAxis, rArray, {tozero: true}); return [{x: false, y: false, t: stash, trace: trace}]; } diff --git a/src/traces/splom/index.js b/src/traces/splom/index.js index 6905ef6b247..c5613e5844f 100644 --- a/src/traces/splom/index.js +++ b/src/traces/splom/index.js @@ -66,11 +66,9 @@ function calc(gd, trace) { var xa = AxisIDs.getFromId(gd, trace._diag[i][0]) || {}; var ya = AxisIDs.getFromId(gd, trace._diag[i][1]) || {}; - // Re-use SVG scatter axis expansion routine except - // for graph with very large number of points where it - // performs poorly. - // In big data case, fake Axes.expand outputs with data bounds, - // and an average size for array marker.size inputs. + // Reuse SVG scatter axis expansion routine. + // For graphs with very large number of points and array marker.size, + // use average marker size instead to speed things up. var ppad; if(hasTooManyPoints) { ppad = 2 * (opts.sizeAvg || Math.max(opts.size, 3)); diff --git a/src/traces/violin/calc.js b/src/traces/violin/calc.js index e4852c02b8d..1994f4235c3 100644 --- a/src/traces/violin/calc.js +++ b/src/traces/violin/calc.js @@ -35,6 +35,9 @@ module.exports = function calc(gd, trace) { }; } + var spanMin = Infinity; + var spanMax = -Infinity; + for(var i = 0; i < cd.length; i++) { var cdi = cd[i]; var vals = cdi.pts.map(helpers.extractVal); @@ -62,10 +65,15 @@ module.exports = function calc(gd, trace) { cdi.density[k] = {v: v, t: t}; } - Axes.expand(valAxis, span, {padded: true}); groupStats.maxCount = Math.max(groupStats.maxCount, vals.length); + + spanMin = Math.min(spanMin, span[0]); + spanMax = Math.max(spanMax, span[1]); } + var extremes = Axes.findExtremes(valAxis, [spanMin, spanMax], {padded: true}); + trace._extremes[valAxis._id] = extremes; + cd[0].t.labels.kde = Lib._(gd, 'kde:'); return cd; diff --git a/test/jasmine/tests/annotations_test.js b/test/jasmine/tests/annotations_test.js index 1039facbd04..a4331307cfe 100644 --- a/test/jasmine/tests/annotations_test.js +++ b/test/jasmine/tests/annotations_test.js @@ -27,7 +27,13 @@ describe('Test annotations', function() { layoutOut._has = Plots._hasPlotType.bind(layoutOut); layoutOut._subplots = {xaxis: ['x', 'x2'], yaxis: ['y', 'y2']}; ['xaxis', 'yaxis', 'xaxis2', 'yaxis2'].forEach(function(axName) { - if(!layoutOut[axName]) layoutOut[axName] = {type: 'linear', range: [0, 1]}; + if(!layoutOut[axName]) { + layoutOut[axName] = { + type: 'linear', + range: [0, 1], + _annIndices: [] + }; + } Axes.setConvert(layoutOut[axName]); }); @@ -90,7 +96,11 @@ describe('Test annotations', function() { }; var layoutOut = { - xaxis: { type: 'date', range: ['2000-01-01', '2016-01-01'] } + xaxis: { + type: 'date', + range: ['2000-01-01', '2016-01-01'], + _annIndices: [] + } }; _supply(layoutIn, layoutOut); @@ -128,10 +138,10 @@ describe('Test annotations', function() { }; var layoutOut = { - xaxis: {type: 'linear', range: [0, 1]}, - yaxis: {type: 'date', range: ['2000-01-01', '2018-01-01']}, - xaxis2: {type: 'log', range: [1, 2]}, - yaxis2: {type: 'category', range: [0, 1]} + xaxis: {type: 'linear', range: [0, 1], _annIndices: []}, + yaxis: {type: 'date', range: ['2000-01-01', '2018-01-01'], _annIndices: []}, + xaxis2: {type: 'log', range: [1, 2], _annIndices: []}, + yaxis2: {type: 'category', range: [0, 1], _annIndices: []} }; _supply(layoutIn, layoutOut); diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index fcb899b839c..2538771fc4e 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -1421,246 +1421,218 @@ describe('Test axes', function() { describe('getAutoRange', function() { var getAutoRange = Axes.getAutoRange; - var ax; + var gd, ax; - it('returns reasonable range without explicit rangemode or autorange', function() { - ax = { - _min: [ - // add in an extrapad to verify that it gets used on _min - // with a _length of 100, extrapad increases pad by 5 - {val: 1, pad: 15, extrapad: true}, - {val: 3, pad: 0}, - {val: 2, pad: 10} - ], - _max: [ - {val: 6, pad: 10}, - {val: 7, pad: 0}, - {val: 5, pad: 20}, - ], + function mockGd(min, max) { + return { + _fullData: [{ + type: 'scatter', + visible: true, + xaxis: 'x', + _extremes: { + x: {min: min, max: max} + } + }], + _fullLayout: {} + }; + } + + function mockAx() { + return { + _id: 'x', type: 'linear', - _length: 100 + _length: 100, + _traceIndices: [0] }; + } - expect(getAutoRange(ax)).toEqual([-0.5, 7]); + it('returns reasonable range without explicit rangemode or autorange', function() { + gd = mockGd([ + // add in an extrapad to verify that it gets used on _min + // with a _length of 100, extrapad increases pad by 5 + {val: 1, pad: 15, extrapad: true}, + {val: 3, pad: 0}, + {val: 2, pad: 10} + ], [ + {val: 6, pad: 10}, + {val: 7, pad: 0}, + {val: 5, pad: 20} + ]); + ax = mockAx(); + + expect(getAutoRange(gd, ax)).toEqual([-0.5, 7]); }); it('reverses axes', function() { - ax = { - _min: [ - {val: 1, pad: 20}, - {val: 3, pad: 0}, - {val: 2, pad: 10} - ], - _max: [ - {val: 6, pad: 10}, - {val: 7, pad: 0}, - {val: 5, pad: 20}, - ], - type: 'linear', - autorange: 'reversed', - rangemode: 'normal', - _length: 100 - }; + gd = mockGd([ + {val: 1, pad: 20}, + {val: 3, pad: 0}, + {val: 2, pad: 10} + ], [ + {val: 6, pad: 10}, + {val: 7, pad: 0}, + {val: 5, pad: 20} + ]); + ax = mockAx(); + ax.autorange = 'reversed'; + ax.rangemode = 'normarl'; - expect(getAutoRange(ax)).toEqual([7, -0.5]); + expect(getAutoRange(gd, ax)).toEqual([7, -0.5]); }); it('expands empty range', function() { - ax = { - _min: [ - {val: 2, pad: 0} - ], - _max: [ - {val: 2, pad: 0} - ], - type: 'linear', - rangemode: 'normal', - _length: 100 - }; + gd = mockGd([ + {val: 2, pad: 0} + ], [ + {val: 2, pad: 0} + ]); + ax = mockAx(); + ax.rangemode = 'normal'; - expect(getAutoRange(ax)).toEqual([1, 3]); + expect(getAutoRange(gd, ax)).toEqual([1, 3]); }); it('returns a lower bound of 0 on rangemode tozero with positive points', function() { - ax = { - _min: [ - {val: 1, pad: 20}, - {val: 3, pad: 0}, - {val: 2, pad: 10} - ], - _max: [ - {val: 6, pad: 10}, - {val: 7, pad: 0}, - {val: 5, pad: 20}, - ], - type: 'linear', - rangemode: 'tozero', - _length: 100 - }; + gd = mockGd([ + {val: 1, pad: 20}, + {val: 3, pad: 0}, + {val: 2, pad: 10} + ], [ + {val: 6, pad: 10}, + {val: 7, pad: 0}, + {val: 5, pad: 20} + ]); + ax = mockAx(); + ax.rangemode = 'tozero'; - expect(getAutoRange(ax)).toEqual([0, 7]); + expect(getAutoRange(gd, ax)).toEqual([0, 7]); }); it('returns an upper bound of 0 on rangemode tozero with negative points', function() { - ax = { - _min: [ - {val: -10, pad: 20}, - {val: -8, pad: 0}, - {val: -9, pad: 10} - ], - _max: [ - {val: -5, pad: 20}, - {val: -4, pad: 0}, - {val: -6, pad: 10}, - ], - type: 'linear', - rangemode: 'tozero', - _length: 100 - }; + gd = mockGd([ + {val: -10, pad: 20}, + {val: -8, pad: 0}, + {val: -9, pad: 10} + ], [ + {val: -5, pad: 20}, + {val: -4, pad: 0}, + {val: -6, pad: 10}, + ]); + ax = mockAx(); + ax.rangemode = 'tozero'; - expect(getAutoRange(ax)).toEqual([-12.5, 0]); + expect(getAutoRange(gd, ax)).toEqual([-12.5, 0]); }); it('returns a positive and negative range on rangemode tozero with positive and negative points', function() { - ax = { - _min: [ - {val: -10, pad: 20}, - {val: -8, pad: 0}, - {val: -9, pad: 10} - ], - _max: [ - {val: 6, pad: 10}, - {val: 7, pad: 0}, - {val: 5, pad: 20}, - ], - type: 'linear', - rangemode: 'tozero', - _length: 100 - }; + gd = mockGd([ + {val: -10, pad: 20}, + {val: -8, pad: 0}, + {val: -9, pad: 10} + ], [ + {val: 6, pad: 10}, + {val: 7, pad: 0}, + {val: 5, pad: 20} + ]); + ax = mockAx(); + ax.rangemode = 'tozero'; - expect(getAutoRange(ax)).toEqual([-15, 10]); + expect(getAutoRange(gd, ax)).toEqual([-15, 10]); }); it('reverses range after applying rangemode tozero', function() { - ax = { - _min: [ - {val: 1, pad: 20}, - {val: 3, pad: 0}, - {val: 2, pad: 10} - ], - _max: [ - // add in an extrapad to verify that it gets used on _max - {val: 6, pad: 15, extrapad: true}, - {val: 7, pad: 0}, - {val: 5, pad: 10}, - ], - type: 'linear', - autorange: 'reversed', - rangemode: 'tozero', - _length: 100 - }; + gd = mockGd([ + {val: 1, pad: 20}, + {val: 3, pad: 0}, + {val: 2, pad: 10} + ], [ + // add in an extrapad to verify that it gets used on _max + {val: 6, pad: 15, extrapad: true}, + {val: 7, pad: 0}, + {val: 5, pad: 10} + ]); + ax = mockAx(); + ax.autorange = 'reversed'; + ax.rangemode = 'tozero'; - expect(getAutoRange(ax)).toEqual([7.5, 0]); + expect(getAutoRange(gd, ax)).toEqual([7.5, 0]); }); it('expands empty positive range to something including 0 with rangemode tozero', function() { - ax = { - _min: [ - {val: 5, pad: 0} - ], - _max: [ - {val: 5, pad: 0} - ], - type: 'linear', - rangemode: 'tozero', - _length: 100 - }; + gd = mockGd([ + {val: 5, pad: 0} + ], [ + {val: 5, pad: 0} + ]); + ax = mockAx(); + ax.rangemode = 'tozero'; - expect(getAutoRange(ax)).toEqual([0, 6]); + expect(getAutoRange(gd, ax)).toEqual([0, 6]); }); it('expands empty negative range to something including 0 with rangemode tozero', function() { - ax = { - _min: [ - {val: -5, pad: 0} - ], - _max: [ - {val: -5, pad: 0} - ], - type: 'linear', - rangemode: 'tozero', - _length: 100 - }; + gd = mockGd([ + {val: -5, pad: 0} + ], [ + {val: -5, pad: 0} + ]); + ax = mockAx(); + ax.rangemode = 'tozero'; - expect(getAutoRange(ax)).toEqual([-6, 0]); + expect(getAutoRange(gd, ax)).toEqual([-6, 0]); }); it('never returns a negative range when rangemode nonnegative is set with positive and negative points', function() { - ax = { - _min: [ - {val: -10, pad: 20}, - {val: -8, pad: 0}, - {val: -9, pad: 10} - ], - _max: [ - {val: 6, pad: 20}, - {val: 7, pad: 0}, - {val: 5, pad: 10}, - ], - type: 'linear', - rangemode: 'nonnegative', - _length: 100 - }; + gd = mockGd([ + {val: -10, pad: 20}, + {val: -8, pad: 0}, + {val: -9, pad: 10} + ], [ + {val: 6, pad: 20}, + {val: 7, pad: 0}, + {val: 5, pad: 10} + ]); + ax = mockAx(); + ax.rangemode = 'nonnegative'; - expect(getAutoRange(ax)).toEqual([0, 7.5]); + expect(getAutoRange(gd, ax)).toEqual([0, 7.5]); }); it('never returns a negative range when rangemode nonnegative is set with only negative points', function() { - ax = { - _min: [ - {val: -10, pad: 20}, - {val: -8, pad: 0}, - {val: -9, pad: 10} - ], - _max: [ - {val: -5, pad: 20}, - {val: -4, pad: 0}, - {val: -6, pad: 10}, - ], - type: 'linear', - rangemode: 'nonnegative', - _length: 100 - }; + gd = mockGd([ + {val: -10, pad: 20}, + {val: -8, pad: 0}, + {val: -9, pad: 10} + ], [ + {val: -5, pad: 20}, + {val: -4, pad: 0}, + {val: -6, pad: 10} + ]); + ax = mockAx(); + ax.rangemode = 'nonnegative'; - expect(getAutoRange(ax)).toEqual([0, 1]); + expect(getAutoRange(gd, ax)).toEqual([0, 1]); }); it('expands empty range to something nonnegative with rangemode nonnegative', function() { - ax = { - _min: [ - {val: -5, pad: 0} - ], - _max: [ - {val: -5, pad: 0} - ], - type: 'linear', - rangemode: 'nonnegative', - _length: 100 - }; + gd = mockGd([ + {val: -5, pad: 0} + ], [ + {val: -5, pad: 0} + ]); + ax = mockAx(); + ax.rangemode = 'nonnegative'; - expect(getAutoRange(ax)).toEqual([0, 1]); + expect(getAutoRange(gd, ax)).toEqual([0, 1]); }); }); - describe('expand', function() { - var expand = Axes.expand; - var ax, data, options; + describe('findExtremes', function() { + var findExtremes = Axes.findExtremes; + var ax, data, options, out; - // Axes.expand modifies ax, so this provides a simple - // way of getting a new clean copy each time. function getDefaultAx() { return { - autorange: true, c2l: Number, type: 'linear', _m: 1 @@ -1671,55 +1643,42 @@ describe('Test axes', function() { ax = getDefaultAx(); data = [1, 4, 7, 2]; - expand(ax, data); - - expect(ax._min).toEqual([{val: 1, pad: 0, extrapad: false}]); - expect(ax._max).toEqual([{val: 7, pad: 0, extrapad: false}]); + out = findExtremes(ax, data); + expect(out.min).toEqual([{val: 1, pad: 0, extrapad: false}]); + expect(out.max).toEqual([{val: 7, pad: 0, extrapad: false}]); }); it('calls ax.setScale if necessary', function() { - ax = { - autorange: true, - c2l: Number, - type: 'linear', - setScale: function() {} - }; + ax = getDefaultAx(); + delete ax._m; + ax.setScale = function() {}; spyOn(ax, 'setScale'); - expand(ax, [1]); - + findExtremes(ax, [1]); expect(ax.setScale).toHaveBeenCalled(); }); it('handles symmetric pads as numbers', function() { ax = getDefaultAx(); data = [1, 4, 2, 7]; - options = { - vpad: 2, - ppad: 10 - }; - - expand(ax, data, options); + options = {vpad: 2, ppad: 10}; - expect(ax._min).toEqual([{val: -1, pad: 10, extrapad: false}]); - expect(ax._max).toEqual([{val: 9, pad: 10, extrapad: false}]); + out = findExtremes(ax, data, options); + expect(out.min).toEqual([{val: -1, pad: 10, extrapad: false}]); + expect(out.max).toEqual([{val: 9, pad: 10, extrapad: false}]); }); it('handles symmetric pads as number arrays', function() { ax = getDefaultAx(); data = [1, 4, 2, 7]; - options = { - vpad: [1, 10, 6, 3], - ppad: [0, 15, 20, 10] - }; - - expand(ax, data, options); + options = {vpad: [1, 10, 6, 3], ppad: [0, 15, 20, 10]}; - expect(ax._min).toEqual([ + out = findExtremes(ax, data, options); + expect(out.min).toEqual([ {val: -6, pad: 15, extrapad: false}, {val: -4, pad: 20, extrapad: false} ]); - expect(ax._max).toEqual([ + expect(out.max).toEqual([ {val: 14, pad: 15, extrapad: false}, {val: 8, pad: 20, extrapad: false} ]); @@ -1735,10 +1694,9 @@ describe('Test axes', function() { ppadplus: 20 }; - expand(ax, data, options); - - expect(ax._min).toEqual([{val: -4, pad: 10, extrapad: false}]); - expect(ax._max).toEqual([{val: 11, pad: 20, extrapad: false}]); + out = findExtremes(ax, data, options); + expect(out.min).toEqual([{val: -4, pad: 10, extrapad: false}]); + expect(out.max).toEqual([{val: 11, pad: 20, extrapad: false}]); }); it('handles separate pads as number arrays', function() { @@ -1751,13 +1709,12 @@ describe('Test axes', function() { ppadplus: [0, 0, 40, 20] }; - expand(ax, data, options); - - expect(ax._min).toEqual([ + out = findExtremes(ax, data, options); + expect(out.min).toEqual([ {val: 1, pad: 30, extrapad: false}, {val: -3, pad: 10, extrapad: false} ]); - expect(ax._max).toEqual([ + expect(out.max).toEqual([ {val: 9, pad: 0, extrapad: false}, {val: 3, pad: 40, extrapad: false}, {val: 8, pad: 20, extrapad: false} @@ -1776,70 +1733,49 @@ describe('Test axes', function() { ppadplus: 40 }; - expand(ax, data, options); - - expect(ax._min).toEqual([{val: -1, pad: 20, extrapad: false}]); - expect(ax._max).toEqual([{val: 9, pad: 40, extrapad: false}]); + out = findExtremes(ax, data, options); + expect(out.min).toEqual([{val: -1, pad: 20, extrapad: false}]); + expect(out.max).toEqual([{val: 9, pad: 40, extrapad: false}]); }); it('adds 5% padding if specified by flag', function() { ax = getDefaultAx(); data = [1, 5]; - options = { - vpad: 1, - ppad: 10, - padded: true - }; + options = {vpad: 1, ppad: 10, padded: true}; - expand(ax, data, options); - - expect(ax._min).toEqual([{val: 0, pad: 10, extrapad: true}]); - expect(ax._max).toEqual([{val: 6, pad: 10, extrapad: true}]); + out = findExtremes(ax, data, options); + expect(out.min).toEqual([{val: 0, pad: 10, extrapad: true}]); + expect(out.max).toEqual([{val: 6, pad: 10, extrapad: true}]); }); it('has lower bound zero with all positive data if tozero is sset', function() { ax = getDefaultAx(); data = [2, 5]; - options = { - vpad: 1, - ppad: 10, - tozero: true - }; - - expand(ax, data, options); + options = {vpad: 1, ppad: 10, tozero: true}; - expect(ax._min).toEqual([{val: 0, pad: 0, extrapad: false}]); - expect(ax._max).toEqual([{val: 6, pad: 10, extrapad: false}]); + out = findExtremes(ax, data, options); + expect(out.min).toEqual([{val: 0, pad: 0, extrapad: false}]); + expect(out.max).toEqual([{val: 6, pad: 10, extrapad: false}]); }); it('has upper bound zero with all negative data if tozero is set', function() { ax = getDefaultAx(); data = [-7, -4]; - options = { - vpad: 1, - ppad: 10, - tozero: true - }; + options = {vpad: 1, ppad: 10, tozero: true}; - expand(ax, data, options); - - expect(ax._min).toEqual([{val: -8, pad: 10, extrapad: false}]); - expect(ax._max).toEqual([{val: 0, pad: 0, extrapad: false}]); + out = findExtremes(ax, data, options); + expect(out.min).toEqual([{val: -8, pad: 10, extrapad: false}]); + expect(out.max).toEqual([{val: 0, pad: 0, extrapad: false}]); }); it('sets neither bound to zero with positive and negative data if tozero is set', function() { ax = getDefaultAx(); data = [-7, 4]; - options = { - vpad: 1, - ppad: 10, - tozero: true - }; - - expand(ax, data, options); + options = {vpad: 1, ppad: 10, tozero: true}; - expect(ax._min).toEqual([{val: -8, pad: 10, extrapad: false}]); - expect(ax._max).toEqual([{val: 5, pad: 10, extrapad: false}]); + out = findExtremes(ax, data, options); + expect(out.min).toEqual([{val: -8, pad: 10, extrapad: false}]); + expect(out.max).toEqual([{val: 5, pad: 10, extrapad: false}]); }); it('overrides padded with tozero', function() { @@ -1852,27 +1788,25 @@ describe('Test axes', function() { padded: true }; - expand(ax, data, options); - - expect(ax._min).toEqual([{val: 0, pad: 0, extrapad: false}]); - expect(ax._max).toEqual([{val: 6, pad: 10, extrapad: true}]); + out = findExtremes(ax, data, options); + expect(out.min).toEqual([{val: 0, pad: 0, extrapad: false}]); + expect(out.max).toEqual([{val: 6, pad: 10, extrapad: true}]); }); it('should fail if no data is given', function() { ax = getDefaultAx(); - expect(function() { expand(ax); }).toThrow(); + expect(function() { findExtremes(ax); }).toThrow(); }); it('should return even if `autorange` is false', function() { ax = getDefaultAx(); - data = [2, 5]; - ax.autorange = false; ax.rangeslider = { autorange: false }; + data = [2, 5]; - expand(ax, data, {}); - expect(ax._min).toEqual([{val: 2, pad: 0, extrapad: false}]); - expect(ax._max).toEqual([{val: 5, pad: 0, extrapad: false}]); + out = findExtremes(ax, data, {}); + expect(out.min).toEqual([{val: 2, pad: 0, extrapad: false}]); + expect(out.max).toEqual([{val: 5, pad: 0, extrapad: false}]); }); }); diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index a1c242da8c4..123137b7829 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -9,7 +9,7 @@ var Axes = require('@src/plots/cartesian/axes'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); -var fail = require('../assets/fail_test'); +var failTest = require('../assets/fail_test'); var checkTicks = require('../assets/custom_assertions').checkTicks; var supplyAllDefaults = require('../assets/supply_defaults'); @@ -652,8 +652,8 @@ describe('Bar.setPositions', function() { var xa = gd._fullLayout.xaxis, ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(xa)).toBeCloseToArray([-5, 14], undefined, '(xa.range)'); - expect(Axes.getAutoRange(ya)).toBeCloseToArray([-3.33, 3.33], undefined, '(ya.range)'); + expect(Axes.getAutoRange(gd, xa)).toBeCloseToArray([-5, 14], undefined, '(xa.range)'); + expect(Axes.getAutoRange(gd, ya)).toBeCloseToArray([-3.33, 3.33], undefined, '(ya.range)'); }); it('should expand size axis (overlay case)', function() { @@ -679,8 +679,8 @@ describe('Bar.setPositions', function() { var xa = gd._fullLayout.xaxis, ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); - expect(Axes.getAutoRange(ya)).toBeCloseToArray([-11.11, 11.11], undefined, '(ya.range)'); + expect(Axes.getAutoRange(gd, xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); + expect(Axes.getAutoRange(gd, ya)).toBeCloseToArray([-11.11, 11.11], undefined, '(ya.range)'); }); it('should expand size axis (relative case)', function() { @@ -702,8 +702,8 @@ describe('Bar.setPositions', function() { var xa = gd._fullLayout.xaxis, ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); - expect(Axes.getAutoRange(ya)).toBeCloseToArray([-4.44, 4.44], undefined, '(ya.range)'); + expect(Axes.getAutoRange(gd, xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); + expect(Axes.getAutoRange(gd, ya)).toBeCloseToArray([-4.44, 4.44], undefined, '(ya.range)'); }); it('should expand size axis (barnorm case)', function() { @@ -725,8 +725,8 @@ describe('Bar.setPositions', function() { var xa = gd._fullLayout.xaxis, ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); - expect(Axes.getAutoRange(ya)).toBeCloseToArray([-1.11, 1.11], undefined, '(ya.range)'); + expect(Axes.getAutoRange(gd, xa)).toBeCloseToArray([-0.5, 2.5], undefined, '(xa.range)'); + expect(Axes.getAutoRange(gd, ya)).toBeCloseToArray([-1.11, 1.11], undefined, '(ya.range)'); }); it('should include explicit base in size axis range', function() { @@ -739,7 +739,7 @@ describe('Bar.setPositions', function() { }); var ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(ya)).toBeCloseToArray([-2.5, 7.5]); + expect(Axes.getAutoRange(gd, ya)).toBeCloseToArray([-2.5, 7.5]); }); }); @@ -753,7 +753,7 @@ describe('Bar.setPositions', function() { }); var ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(ya)).toEqual(['2016-12-31', '2017-01-20']); + expect(Axes.getAutoRange(gd, ya)).toEqual(['2016-12-31', '2017-01-20']); }); }); @@ -767,7 +767,7 @@ describe('Bar.setPositions', function() { }); var ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(ya)).toBeCloseToArray([-0.572, 10.873], undefined, '(ya.range)'); + expect(Axes.getAutoRange(gd, ya)).toBeCloseToArray([-0.572, 10.873], undefined, '(ya.range)'); }); it('works with log axes (stacked bars)', function() { @@ -780,7 +780,7 @@ describe('Bar.setPositions', function() { }); var ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(ya)).toBeCloseToArray([-0.582, 11.059], undefined, '(ya.range)'); + expect(Axes.getAutoRange(gd, ya)).toBeCloseToArray([-0.582, 11.059], undefined, '(ya.range)'); }); it('works with log axes (normalized bars)', function() { @@ -795,7 +795,7 @@ describe('Bar.setPositions', function() { }); var ya = gd._fullLayout.yaxis; - expect(Axes.getAutoRange(ya)).toBeCloseToArray([1.496, 2.027], undefined, '(ya.range)'); + expect(Axes.getAutoRange(gd, ya)).toBeCloseToArray([1.496, 2.027], undefined, '(ya.range)'); }); }); @@ -897,7 +897,7 @@ describe('A bar plot', function() { expect(foundTextNodes).toBe(true); }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -930,7 +930,7 @@ describe('A bar plot', function() { expect(foundTextNodes).toBe(true); }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -961,7 +961,7 @@ describe('A bar plot', function() { expect(foundTextNodes).toBe(true); }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -995,7 +995,7 @@ describe('A bar plot', function() { expect(foundTextNodes).toBe(true); }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -1144,7 +1144,7 @@ describe('A bar plot', function() { assertTextIsInsidePath(text20, path20); // inside assertTextIsInsidePath(text30, path30); // inside }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -1225,7 +1225,7 @@ describe('A bar plot', function() { assertTextFont(textNodes[1], expected.outsidetextfont, 1); assertTextFont(textNodes[2], expected.insidetextfont, 2); }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -1291,7 +1291,7 @@ describe('A bar plot', function() { checkBarsMatch(['bottom', 'width'], 'final'); }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -1328,7 +1328,93 @@ describe('A bar plot', function() { .then(function() { _assertNumberOfBarTextNodes(3); }) - .catch(fail) + .catch(failTest) + .then(done); + }); +}); + +describe('bar visibility toggling:', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + function _assert(msg, xrng, yrng, calls) { + var fullLayout = gd._fullLayout; + expect(fullLayout.xaxis.range).toBeCloseToArray(xrng, 2, msg + ' xrng'); + expect(fullLayout.yaxis.range).toBeCloseToArray(yrng, 2, msg + ' yrng'); + + var setPositions = gd._fullData[0]._module.setPositions; + expect(setPositions).toHaveBeenCalledTimes(calls); + setPositions.calls.reset(); + } + + it('should have the correct edit type', function() { + var schema = Plotly.PlotSchema.get(); + expect(schema.traces.bar.attributes.visible.editType) + .toBe('plot', 'visible editType'); + }); + + it('should update axis range according to visible edits (group case)', function(done) { + Plotly.plot(gd, [ + {type: 'bar', x: [1, 2, 3], y: [1, 2, 1]}, + {type: 'bar', x: [1, 2, 3], y: [-1, -2, -1]} + ]) + .then(function() { + spyOn(gd._fullData[0]._module, 'setPositions').and.callThrough(); + + _assert('base', [0.5, 3.5], [-2.222, 2.222], 0); + return Plotly.restyle(gd, 'visible', false, [1]); + }) + .then(function() { + _assert('visible [true,false]', [0.5, 3.5], [0, 2.105], 1); + return Plotly.restyle(gd, 'visible', false, [0]); + }) + .then(function() { + _assert('both invisible', [0.5, 3.5], [0, 2.105], 0); + return Plotly.restyle(gd, 'visible', true, [1]); + }) + .then(function() { + _assert('visible [false,true]', [0.5, 3.5], [-2.105, 0], 1); + return Plotly.restyle(gd, 'visible', true); + }) + .then(function() { + _assert('back to both visible', [0.5, 3.5], [-2.222, 2.222], 1); + }) + .catch(failTest) + .then(done); + }); + + it('should update axis range according to visible edits (stack case)', function(done) { + Plotly.plot(gd, [ + {type: 'bar', x: [1, 2, 3], y: [1, 2, 1]}, + {type: 'bar', x: [1, 2, 3], y: [2, 3, 2]} + ], {barmode: 'stack'}) + .then(function() { + spyOn(gd._fullData[0]._module, 'setPositions').and.callThrough(); + + _assert('base', [0.5, 3.5], [0, 5.263], 0); + return Plotly.restyle(gd, 'visible', false, [1]); + }) + .then(function() { + _assert('visible [true,false]', [0.5, 3.5], [0, 2.105], 1); + return Plotly.restyle(gd, 'visible', false, [0]); + }) + .then(function() { + _assert('both invisible', [0.5, 3.5], [0, 2.105], 0); + return Plotly.restyle(gd, 'visible', true, [1]); + }) + .then(function() { + _assert('visible [false,true]', [0.5, 3.5], [0, 3.157], 1); + return Plotly.restyle(gd, 'visible', true); + }) + .then(function() { + _assert('back to both visible', [0.5, 3.5], [0, 5.263], 1); + }) + .catch(failTest) .then(done); }); }); @@ -1384,7 +1470,7 @@ describe('bar hover', function() { var mock = Lib.extendDeep({}, require('@mocks/11.json')); Plotly.plot(gd, mock.data, mock.layout) - .catch(fail) + .catch(failTest) .then(done); }); @@ -1410,7 +1496,7 @@ describe('bar hover', function() { var mock = Lib.extendDeep({}, require('@mocks/bar_attrs_group_norm.json')); Plotly.plot(gd, mock.data, mock.layout) - .catch(fail) + .catch(failTest) .then(done); }); @@ -1476,7 +1562,7 @@ describe('bar hover', function() { var out = _hover(gd, -0.25, 0.5, 'closest'); expect(out.text).toEqual('apple', 'hover text'); }) - .catch(fail) + .catch(failTest) .then(done); }); }); @@ -1526,7 +1612,7 @@ describe('bar hover', function() { expect(out).toBe(false, hoverSpec); }); }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -1564,7 +1650,7 @@ describe('bar hover', function() { expect(out.style).toEqual([1, 'red', 200, 1]); assertPos(out.pos, [222, 280, 168, 168]); }) - .catch(fail) + .catch(failTest) .then(done); }); @@ -1594,7 +1680,7 @@ describe('bar hover', function() { out = _hover(gd, 10, 2, 'closest'); assertPos(out.pos, [145, 155, 15, 15]); }) - .catch(fail) + .catch(failTest) .then(done); }); }); @@ -1699,7 +1785,7 @@ describe('bar hover', function() { [true, 3] ); }) - .catch(fail) + .catch(failTest) .then(done); }); }); diff --git a/test/jasmine/tests/contour_test.js b/test/jasmine/tests/contour_test.js index 1ae5858c3d6..4e34458328f 100644 --- a/test/jasmine/tests/contour_test.js +++ b/test/jasmine/tests/contour_test.js @@ -186,6 +186,7 @@ describe('contour calc', function() { supplyAllDefaults(gd); var fullTrace = gd._fullData[0]; + fullTrace._extremes = {}; var out = Contour.calc(gd, fullTrace)[0]; out.trace = fullTrace; diff --git a/test/jasmine/tests/gl2d_pointcloud_test.js b/test/jasmine/tests/gl2d_pointcloud_test.js index b43b7c02827..66f61898f29 100644 --- a/test/jasmine/tests/gl2d_pointcloud_test.js +++ b/test/jasmine/tests/gl2d_pointcloud_test.js @@ -161,43 +161,27 @@ describe('@gl pointcloud traces', function() { var mock = plotData; var scene2d; - var xBaselineMins = [{val: 0, pad: 50, extrapad: false}]; - var xBaselineMaxes = [{val: 9, pad: 50, extrapad: false}]; - - var yBaselineMins = [{val: 0, pad: 50, extrapad: false}]; - var yBaselineMaxes = [{val: 9, pad: 50, extrapad: false}]; - Plotly.plot(gd, mock) .then(function() { scene2d = gd._fullLayout._plots.xy._scene2d; - expect(scene2d.traces[gd._fullData[0].uid].type).toBe('pointcloud'); - expect(scene2d.xaxis._min).toEqual(xBaselineMins); - expect(scene2d.xaxis._max).toEqual(xBaselineMaxes); - - expect(scene2d.yaxis._min).toEqual(yBaselineMins); - expect(scene2d.yaxis._max).toEqual(yBaselineMaxes); - return Plotly.relayout(gd, 'xaxis.range', [3, 6]); }).then(function() { - expect(scene2d.xaxis._min).toEqual(xBaselineMins); - expect(scene2d.xaxis._max).toEqual(xBaselineMaxes); - + expect(scene2d.xaxis.range).toEqual([3, 6]); + expect(scene2d.yaxis.range).toBeCloseToArray([-1.415, 10.415], 2); return Plotly.relayout(gd, 'xaxis.autorange', true); }).then(function() { - expect(scene2d.xaxis._min).toEqual(xBaselineMins); - expect(scene2d.xaxis._max).toEqual(xBaselineMaxes); - + expect(scene2d.xaxis.range).toBeCloseToArray([-0.548, 9.548], 2); + expect(scene2d.yaxis.range).toBeCloseToArray([-1.415, 10.415], 2); return Plotly.relayout(gd, 'yaxis.range', [8, 20]); }).then(function() { - expect(scene2d.yaxis._min).toEqual(yBaselineMins); - expect(scene2d.yaxis._max).toEqual(yBaselineMaxes); - + expect(scene2d.xaxis.range).toBeCloseToArray([-0.548, 9.548], 2); + expect(scene2d.yaxis.range).toEqual([8, 20]); return Plotly.relayout(gd, 'yaxis.autorange', true); }).then(function() { - expect(scene2d.yaxis._min).toEqual(yBaselineMins); - expect(scene2d.yaxis._max).toEqual(yBaselineMaxes); + expect(scene2d.xaxis.range).toBeCloseToArray([-0.548, 9.548], 2); + expect(scene2d.yaxis.range).toBeCloseToArray([-1.415, 10.415], 2); }) .catch(failTest) .then(done); diff --git a/test/jasmine/tests/gl2d_scatterplot_contour_test.js b/test/jasmine/tests/gl2d_scatterplot_contour_test.js index 96ad17c4562..853969b14d1 100644 --- a/test/jasmine/tests/gl2d_scatterplot_contour_test.js +++ b/test/jasmine/tests/gl2d_scatterplot_contour_test.js @@ -222,8 +222,6 @@ describe('@gl contourgl plots', function() { scene2d = gd._fullLayout._plots.xy._scene2d; expect(scene2d.traces[mock.data[0].uid].type).toEqual('contourgl'); - expect(scene2d.xaxis._min).toEqual([{ val: -1, pad: 0, extrapad: false}]); - expect(scene2d.xaxis._max).toEqual([{ val: 1, pad: 0, extrapad: false}]); expect(scene2d.xaxis.range).toEqual([-1, 1]); return Plotly.relayout(gd, 'xaxis.range', [0, -10]); @@ -237,8 +235,6 @@ describe('@gl contourgl plots', function() { return Plotly.restyle(gd, 'type', 'heatmapgl'); }).then(function() { expect(scene2d.traces[mock.data[0].uid].type).toEqual('heatmapgl'); - expect(scene2d.xaxis._min).toEqual([{ val: -1, pad: 0, extrapad: false}]); - expect(scene2d.xaxis._max).toEqual([{ val: 1, pad: 0, extrapad: false}]); expect(scene2d.xaxis.range).toEqual([1, -1]); return Plotly.relayout(gd, 'xaxis.range', [0, -10]); diff --git a/test/jasmine/tests/heatmap_test.js b/test/jasmine/tests/heatmap_test.js index c65a7987043..692a6513d88 100644 --- a/test/jasmine/tests/heatmap_test.js +++ b/test/jasmine/tests/heatmap_test.js @@ -301,6 +301,8 @@ describe('heatmap calc', function() { var fullTrace = gd._fullData[0]; var fullLayout = gd._fullLayout; + fullTrace._extremes = {}; + var out = Heatmap.calc(gd, fullTrace)[0]; out._xcategories = fullLayout.xaxis._categories; out._ycategories = fullLayout.yaxis._categories; diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index 7ea0fa9cad9..fdb9e7753bd 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -879,15 +879,16 @@ describe('end-to-end scatter tests', function() { .then(done); }); - it('should update axis range accordingly on marker.size edits', function(done) { - function _assert(msg, xrng, yrng) { - var fullLayout = gd._fullLayout; - expect(fullLayout.xaxis.range).toBeCloseToArray(xrng, 2, msg + ' xrng'); - expect(fullLayout.yaxis.range).toBeCloseToArray(yrng, 2, msg + ' yrng'); - } + function assertAxisRanges(msg, xrng, yrng) { + var fullLayout = gd._fullLayout; + expect(fullLayout.xaxis.range).toBeCloseToArray(xrng, 2, msg + ' xrng'); + expect(fullLayout.yaxis.range).toBeCloseToArray(yrng, 2, msg + ' yrng'); + } + var schema = Plotly.PlotSchema.get(); + + it('should update axis range accordingly on marker.size edits', function(done) { // edit types are important to this test - var schema = Plotly.PlotSchema.get(); expect(schema.traces.scatter.attributes.marker.size.editType) .toBe('calc', 'marker.size editType'); expect(schema.layout.layoutAttributes.xaxis.autorange.editType) @@ -895,29 +896,60 @@ describe('end-to-end scatter tests', function() { Plotly.plot(gd, [{ y: [1, 2, 1] }]) .then(function() { - _assert('auto rng / base marker.size', [-0.13, 2.13], [0.93, 2.07]); + assertAxisRanges('auto rng / base marker.size', [-0.13, 2.13], [0.93, 2.07]); return Plotly.relayout(gd, { 'xaxis.range': [0, 2], 'yaxis.range': [0, 2] }); }) .then(function() { - _assert('set rng / base marker.size', [0, 2], [0, 2]); + assertAxisRanges('set rng / base marker.size', [0, 2], [0, 2]); return Plotly.restyle(gd, 'marker.size', 50); }) .then(function() { - _assert('set rng / big marker.size', [0, 2], [0, 2]); + assertAxisRanges('set rng / big marker.size', [0, 2], [0, 2]); return Plotly.relayout(gd, { 'xaxis.autorange': true, 'yaxis.autorange': true }); }) .then(function() { - _assert('auto rng / big marker.size', [-0.28, 2.28], [0.75, 2.25]); + assertAxisRanges('auto rng / big marker.size', [-0.28, 2.28], [0.75, 2.25]); return Plotly.restyle(gd, 'marker.size', null); }) .then(function() { - _assert('auto rng / base marker.size', [-0.13, 2.13], [0.93, 2.07]); + assertAxisRanges('auto rng / base marker.size', [-0.13, 2.13], [0.93, 2.07]); + }) + .catch(failTest) + .then(done); + }); + + it('should update axis range according to visible edits', function(done) { + expect(schema.traces.scatter.attributes.visible.editType) + .toBe('plot', 'visible editType'); + + Plotly.plot(gd, [ + {x: [1, 2, 3], y: [1, 2, 1]}, + {x: [4, 5, 6], y: [-1, -2, -1]} + ]) + .then(function() { + assertAxisRanges('both visible', [0.676, 6.323], [-2.29, 2.29]); + return Plotly.restyle(gd, 'visible', false, [1]); + }) + .then(function() { + assertAxisRanges('visible [true,false]', [0.87, 3.128], [0.926, 2.07]); + return Plotly.restyle(gd, 'visible', false, [0]); + }) + .then(function() { + assertAxisRanges('both invisible', [0.87, 3.128], [0.926, 2.07]); + return Plotly.restyle(gd, 'visible', true, [1]); + }) + .then(function() { + assertAxisRanges('visible [false,true]', [3.871, 6.128], [-2.07, -0.926]); + return Plotly.restyle(gd, 'visible', true); + }) + .then(function() { + assertAxisRanges('back to both visible', [0.676, 6.323], [-2.29, 2.29]); }) .catch(failTest) .then(done); diff --git a/test/jasmine/tests/shapes_test.js b/test/jasmine/tests/shapes_test.js index f73a628de14..807efd92f65 100644 --- a/test/jasmine/tests/shapes_test.js +++ b/test/jasmine/tests/shapes_test.js @@ -86,10 +86,10 @@ describe('Test shapes defaults:', function() { it('should provide the right defaults on all axis types', function() { var fullLayout = { - xaxis: {type: 'linear', range: [0, 20]}, - yaxis: {type: 'log', range: [1, 5]}, - xaxis2: {type: 'date', range: ['2006-06-05', '2006-06-09']}, - yaxis2: {type: 'category', range: [-0.5, 7.5]}, + xaxis: {type: 'linear', range: [0, 20], _shapeIndices: []}, + yaxis: {type: 'log', range: [1, 5], _shapeIndices: []}, + xaxis2: {type: 'date', range: ['2006-06-05', '2006-06-09'], _shapeIndices: []}, + yaxis2: {type: 'category', range: [-0.5, 7.5], _shapeIndices: []}, _subplots: {xaxis: ['x', 'x2'], yaxis: ['y', 'y2']} };