diff --git a/src/components/grid/index.js b/src/components/grid/index.js index 064bbd93272..5def46b284e 100644 --- a/src/components/grid/index.js +++ b/src/components/grid/index.js @@ -54,7 +54,7 @@ var gridAttrs = { valType: 'info_array', freeLength: true, dimensions: 2, - items: {valType: 'enumerated', values: [counterRegex('xy').toString(), '']}, + items: {valType: 'enumerated', values: [counterRegex('xy').toString(), ''], editType: 'plot'}, role: 'info', editType: 'plot', description: [ @@ -69,7 +69,7 @@ var gridAttrs = { xaxes: { valType: 'info_array', freeLength: true, - items: {valType: 'enumerated', values: [cartesianIdRegex.x.toString(), '']}, + items: {valType: 'enumerated', values: [cartesianIdRegex.x.toString(), ''], editType: 'plot'}, role: 'info', editType: 'plot', description: [ @@ -83,7 +83,7 @@ var gridAttrs = { yaxes: { valType: 'info_array', freeLength: true, - items: {valType: 'enumerated', values: [cartesianIdRegex.y.toString(), '']}, + items: {valType: 'enumerated', values: [cartesianIdRegex.y.toString(), ''], editType: 'plot'}, role: 'info', editType: 'plot', description: [ diff --git a/src/components/rangeslider/calc_autorange.js b/src/components/rangeslider/calc_autorange.js index 1c8f83fd780..c722836a453 100644 --- a/src/components/rangeslider/calc_autorange.js +++ b/src/components/rangeslider/calc_autorange.js @@ -8,11 +8,12 @@ 'use strict'; -var Axes = require('../../plots/cartesian/axes'); +var listAxes = require('../../plots/cartesian/axis_ids').list; +var getAutoRange = require('../../plots/cartesian/autorange').getAutoRange; var constants = require('./constants'); module.exports = function calcAutorange(gd) { - var axes = Axes.list(gd, 'x', true); + var axes = listAxes(gd, 'x', true); // Compute new slider range using axis autorange if necessary. // @@ -28,7 +29,7 @@ module.exports = function calcAutorange(gd) { if(opts && opts.visible && opts.autorange && ax._min.length && ax._max.length) { opts._input.autorange = true; - opts._input.range = opts.range = Axes.getAutoRange(ax); + opts._input.range = opts.range = getAutoRange(ax); } } }; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index f1d088ed38f..eb8323aca96 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -40,7 +40,7 @@ var cartesianConstants = require('../plots/cartesian/constants'); var axisConstraints = require('../plots/cartesian/constraints'); var enforceAxisConstraints = axisConstraints.enforce; var cleanAxisConstraints = axisConstraints.clean; -var axisIds = require('../plots/cartesian/axis_ids'); +var doAutoRange = require('../plots/cartesian/autorange').doAutoRange; var numericNameWarningCount = 0; var numericNameWarningCountLimit = 5; @@ -283,7 +283,7 @@ exports.plot = function(gd, data, layout, config) { function positionAndAutorange() { if(!recalc) { - enforceAxisConstraints(gd); + doAutoRangeAndConstraints(); return; } @@ -332,7 +332,7 @@ exports.plot = function(gd, data, layout, config) { var ax = axList[i]; cleanAxisConstraints(gd, ax); - Axes.doAutoRange(ax); + doAutoRange(ax); } enforceAxisConstraints(gd); @@ -1846,7 +1846,7 @@ function _relayout(gd, aobj) { var axId; function recordAlteredAxis(pleafPlus) { - var axId = axisIds.name2id(pleafPlus.split('.')[0]); + var axId = Axes.name2id(pleafPlus.split('.')[0]); rangesAltered[axId] = 1; return axId; } @@ -1857,20 +1857,21 @@ function _relayout(gd, aobj) { throw new Error('cannot set ' + ai + 'and a parent attribute simultaneously'); } - var p = Lib.nestedProperty(layout, ai), - vi = aobj[ai], - plen = p.parts.length, - // p.parts may end with an index integer if the property is an array - pend = typeof p.parts[plen - 1] === 'string' ? (plen - 1) : (plen - 2), - // last property in chain (leaf node) - pleaf = p.parts[pend], - // leaf plus immediate parent - pleafPlus = p.parts[pend - 1] + '.' + pleaf, - // trunk nodes (everything except the leaf) - ptrunk = p.parts.slice(0, pend).join('.'), - parentIn = Lib.nestedProperty(gd.layout, ptrunk).get(), - parentFull = Lib.nestedProperty(fullLayout, ptrunk).get(), - vOld = p.get(); + var p = Lib.nestedProperty(layout, ai); + var vi = aobj[ai]; + 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--; } + // last property in chain (leaf node) + var pleaf = p.parts[pend]; + // leaf plus immediate parent + var pleafPlus = p.parts[pend - 1] + '.' + pleaf; + // trunk nodes (everything except the leaf) + var ptrunk = p.parts.slice(0, pend).join('.'); + var parentIn = Lib.nestedProperty(gd.layout, ptrunk).get(); + var parentFull = Lib.nestedProperty(fullLayout, ptrunk).get(); + var vOld = p.get(); if(vi === undefined) continue; @@ -2093,25 +2094,18 @@ function _relayout(gd, aobj) { flags.calc = true; for(var groupAxId in group) { if(!rangesAltered[groupAxId]) { - axisIds.getFromId(gd, groupAxId)._constraintShrinkable = true; + Axes.getFromId(gd, groupAxId)._constraintShrinkable = true; } } } } } - var oldWidth = fullLayout.width, - oldHeight = fullLayout.height; - - // calculate autosizing - if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, fullLayout); - - // avoid unnecessary redraws - var hasSizechanged = aobj.height || aobj.width || - (fullLayout.width !== oldWidth) || - (fullLayout.height !== oldHeight); - - if(hasSizechanged) flags.calc = true; + // If the autosize changed or height or width was explicitly specified, + // this triggers a redraw + // TODO: do we really need special aobj.height/width handling here? + // couldn't editType do this? + if(updateAutosize(gd) || aobj.height || aobj.width) flags.plot = true; if(flags.plot || flags.calc) { flags.layoutReplot = true; @@ -2128,6 +2122,22 @@ function _relayout(gd, aobj) { }; } +/* + * updateAutosize: we made a change, does it change the autosize result? + * puts the new size into fullLayout + * returns true if either height or width changed + */ +function updateAutosize(gd) { + var fullLayout = gd._fullLayout; + var oldWidth = fullLayout.width; + var oldHeight = fullLayout.height; + + // calculate autosizing + if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, fullLayout); + + return (fullLayout.width !== oldWidth) || (fullLayout.height !== oldHeight); +} + // for editing annotations or shapes - is it on autoscaled axes? function refAutorange(gd, obj, axLetter) { if(!Lib.isPlainObject(obj)) return false; @@ -2313,6 +2323,17 @@ exports.react = function(gd, data, layout, config) { var restyleFlags = diffData(gd, oldFullData, newFullData, immutable); var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable); + // TODO: how to translate this part of relayout to Plotly.react? + // // Setting width or height to null must reset the graph's width / height + // // back to its initial value as computed during the first pass in Plots.plotAutoSize. + // // + // // To do so, we must manually set them back here using the _initialAutoSize cache. + // if(['width', 'height'].indexOf(ai) !== -1 && vi === null) { + // fullLayout[ai] = gd._initialAutoSize[ai]; + // } + + if(updateAutosize(gd)) relayoutFlags.layoutReplot = true; + // clear calcdata if required if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined; if(relayoutFlags.margins) helpers.clearAxisAutomargins(gd); diff --git a/src/plots/cartesian/autorange.js b/src/plots/cartesian/autorange.js new file mode 100644 index 00000000000..f7639b5b7aa --- /dev/null +++ b/src/plots/cartesian/autorange.js @@ -0,0 +1,362 @@ +/** +* Copyright 2012-2018, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +var isNumeric = require('fast-isnumeric'); + +var Lib = require('../../lib'); +var FP_SAFE = require('../../constants/numerical').FP_SAFE; + +module.exports = { + getAutoRange: getAutoRange, + makePadFn: makePadFn, + doAutoRange: doAutoRange, + expand: expand +}; + +// 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) { + 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 i, j, minpt, maxpt, minbest, maxbest, dp, dv; + + for(i = 1; i < ax._min.length; i++) { + if(minmin !== maxmax) break; + minmin = Math.min(minmin, ax._min[i].val); + } + for(i = 1; i < ax._max.length; i++) { + if(minmin !== maxmax) break; + maxmax = Math.max(maxmax, ax._max[i].val); + } + + 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') { + axReverse = true; + 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]; + dv = maxpt.val - minpt.val; + dp = ax._length - getPad(minpt) - getPad(maxpt); + if(dv > 0 && dp > 0 && dv / dp > mbest) { + minbest = minpt; + maxbest = maxpt; + mbest = dv / dp; + } + } + } + + if(minmin === maxmax) { + var lower = minmin - 1; + var upper = minmin + 1; + if(ax.rangemode === 'tozero') { + newRange = minmin < 0 ? [lower, 0] : [0, upper]; + } + else if(ax.rangemode === 'nonnegative') { + newRange = [Math.max(0, lower), Math.max(0, upper)]; + } + else { + newRange = [lower, upper]; + } + } + else if(mbest) { + if(ax.type === 'linear' || ax.type === '-') { + if(ax.rangemode === 'tozero') { + if(minbest.val >= 0) { + minbest = {val: 0, pad: 0}; + } + if(maxbest.val <= 0) { + maxbest = {val: 0, pad: 0}; + } + } + else if(ax.rangemode === 'nonnegative') { + if(minbest.val - mbest * getPad(minbest) < 0) { + minbest = {val: 0, pad: 0}; + } + if(maxbest.val < 0) { + maxbest = {val: 1, pad: 0}; + } + } + + // in case it changed again... + mbest = (maxbest.val - minbest.val) / + (ax._length - getPad(minbest) - getPad(maxbest)); + + } + + newRange = [ + minbest.val - mbest * getPad(minbest), + maxbest.val + mbest * getPad(maxbest) + ]; + } + + // don't let axis have zero size, while still respecting tozero and nonnegative + if(newRange[0] === newRange[1]) { + if(ax.rangemode === 'tozero') { + if(newRange[0] < 0) { + newRange = [newRange[0], 0]; + } + else if(newRange[0] > 0) { + newRange = [0, newRange[0]]; + } + else { + newRange = [0, 1]; + } + } + else { + newRange = [newRange[0] - 1, newRange[0] + 1]; + if(ax.rangemode === 'nonnegative') { + newRange[0] = Math.max(0, newRange[0]); + } + } + } + + // maintain reversal + if(axReverse) newRange.reverse(); + + return Lib.simpleMap(newRange, ax.l2r || Number); +} + +/* + * calculate the pixel padding for ax._min and ax._max entries with + * optional extrapad as 5% of the total axis length + */ +function makePadFn(ax) { + // 5% padding for points that specify extrapad: true + var extrappad = ax._length / 20; + + // domain-constrained axes: base extrappad on the unconstrained + // domain so it's consistent as the domain changes + if((ax.constrain === 'domain') && ax._inputDomain) { + extrappad *= (ax._inputDomain[1] - ax._inputDomain[0]) / + (ax.domain[1] - ax.domain[0]); + } + + return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); }; +} + +function doAutoRange(ax) { + ax.setScale(); + + // TODO do we really need this? + var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length); + + if(ax.autorange && hasDeps) { + ax.range = getAutoRange(ax); + + ax._r = ax.range.slice(); + ax._rl = Lib.simpleMap(ax._r, ax.r2l); + + // doAutoRange will get called on fullLayout, + // but we want to report its results back to layout + + var axIn = ax._input; + axIn.range = ax.range.slice(); + axIn.autorange = ax.autorange; + } +} + +function needsAutorange(ax) { + return ax.autorange || !!(ax.rangeslider || {}).autorange; +} + +/* + * 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 + * length; all the inputs should be based solely on the trace data, nothing + * about the axis layout. + * + * @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: + * vpad: (number or number array) pad values (data value +-vpad) + * ppad: (number or number array) pad pixels (pixel location +-ppad) + * ppadplus, ppadminus, vpadplus, vpadminus: + * separate padding for each side, overrides symmetric + * padded: (boolean) add 5% padding to both ends + * (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 + */ +function expand(ax, data, options) { + if(!needsAutorange(ax) || !data) return; + + if(!ax._min) ax._min = []; + if(!ax._max) ax._max = []; + if(!options) options = {}; + if(!ax._m) ax.setScale(); + + 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 hasArrayOption = false; + + function makePadAccessor(item) { + if(Array.isArray(item)) { + hasArrayOption = true; + return function(i) { return Math.max(Number(item[i]||0), 0); }; + } + else { + var v = Math.max(Number(item||0), 0); + return function() { return v; }; + } + } + + var ppadplus = makePadAccessor((ax._m > 0 ? + options.ppadplus : options.ppadminus) || options.ppad || 0); + var ppadminus = makePadAccessor((ax._m > 0 ? + options.ppadminus : options.ppadplus) || options.ppad || 0); + var vpadplus = makePadAccessor(options.vpadplus || options.vpad); + var vpadminus = makePadAccessor(options.vpadminus || options.vpad); + + if(!hasArrayOption) { + // with no arrays other than `data` we don't need to consider + // every point, only the extreme data points + vmin = Infinity; + vmax = -Infinity; + + if(isLog) { + for(i = 0; i < len; i++) { + v = data[i]; + // data is not linearized yet so we still have to filter out negative logs + if(v < vmin && v > 0) vmin = v; + if(v > vmax && v < FP_SAFE) vmax = v; + } + } + else { + for(i = 0; i < len; i++) { + v = data[i]; + if(v < vmin && v > -FP_SAFE) vmin = v; + if(v > vmax && v < FP_SAFE) vmax = v; + } + } + + data = [vmin, vmax]; + len = 2; + } + + function addItem(i) { + di = data[i]; + if(!isNumeric(di)) return; + ppadiplus = ppadplus(i); + ppadiminus = ppadminus(i); + vmin = di - vpadminus(i); + vmax = di + vpadplus(i); + // special case for log axes: if vpad makes this object span + // more than an order of mag, clip it to one order. This is so + // we don't have non-positive errors or absurdly large lower + // range due to rounding errors + if(isLog && vmin < vmax / 10) vmin = vmax / 10; + + dmin = ax.c2l(vmin); + dmax = ax.c2l(vmax); + + if(tozero) { + 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 + }); + } + } + } + } + + // For efficiency covering monotonic or near-monotonic data, + // check a few points at both ends first and then sweep + // through the middle + var iMax = Math.min(6, len); + for(i = 0; i < iMax; i++) addItem(i); + for(i = len - 1; i >= iMax; i--) addItem(i); +} + +// In order to stop overflow errors, don't consider points +// too close to the limits of js floating point +function goodNumber(v) { + return isNumeric(v) && Math.abs(v) < FP_SAFE; +} + +function lessOrEqual(v0, v1) { return v0 <= v1; } +function greaterOrEqual(v0, v1) { return v0 >= v1; } diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 287fe2c5662..0e3eb681ce2 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -21,7 +21,6 @@ var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var constants = require('../../constants/numerical'); -var FP_SAFE = constants.FP_SAFE; var ONEAVGYEAR = constants.ONEAVGYEAR; var ONEAVGMONTH = constants.ONEAVGMONTH; var ONEDAY = constants.ONEDAY; @@ -41,12 +40,17 @@ var autoType = require('./axis_autotype'); var axisIds = require('./axis_ids'); axes.id2name = axisIds.id2name; +axes.name2id = axisIds.name2id; axes.cleanId = axisIds.cleanId; axes.list = axisIds.list; axes.listIds = axisIds.listIds; axes.getFromId = axisIds.getFromId; axes.getFromTrace = axisIds.getFromTrace; +var autorange = require('./autorange'); +axes.expand = autorange.expand; +axes.getAutoRange = autorange.getAutoRange; + /* * find the list of possible axes to reference with an xref or yref attribute * and coerce it to that list @@ -219,157 +223,6 @@ axes.minDtick = function(ax, newDiff, newFirst, allow) { } }; -// 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: -// {val: calcdata value, pad: extra pixels beyond this value} -// -// 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. -axes.getAutoRange = function(ax) { - var newRange = []; - - var minmin = ax._min[0].val, - maxmax = ax._max[0].val, - i; - - for(i = 1; i < ax._min.length; i++) { - if(minmin !== maxmax) break; - minmin = Math.min(minmin, ax._min[i].val); - } - for(i = 1; i < ax._max.length; i++) { - if(minmin !== maxmax) break; - maxmax = Math.max(maxmax, ax._max[i].val); - } - - var j, minpt, maxpt, minbest, maxbest, dp, dv, - mbest = 0, - 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') { - axReverse = true; - 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]; - dv = maxpt.val - minpt.val; - dp = ax._length - minpt.pad - maxpt.pad; - if(dv > 0 && dp > 0 && dv / dp > mbest) { - minbest = minpt; - maxbest = maxpt; - mbest = dv / dp; - } - } - } - - if(minmin === maxmax) { - var lower = minmin - 1; - var upper = minmin + 1; - if(ax.rangemode === 'tozero') { - newRange = minmin < 0 ? [lower, 0] : [0, upper]; - } - else if(ax.rangemode === 'nonnegative') { - newRange = [Math.max(0, lower), Math.max(0, upper)]; - } - else { - newRange = [lower, upper]; - } - } - else if(mbest) { - if(ax.type === 'linear' || ax.type === '-') { - if(ax.rangemode === 'tozero') { - if(minbest.val >= 0) { - minbest = {val: 0, pad: 0}; - } - if(maxbest.val <= 0) { - maxbest = {val: 0, pad: 0}; - } - } - else if(ax.rangemode === 'nonnegative') { - if(minbest.val - mbest * minbest.pad < 0) { - minbest = {val: 0, pad: 0}; - } - if(maxbest.val < 0) { - maxbest = {val: 1, pad: 0}; - } - } - - // in case it changed again... - mbest = (maxbest.val - minbest.val) / - (ax._length - minbest.pad - maxbest.pad); - - } - - newRange = [ - minbest.val - mbest * minbest.pad, - maxbest.val + mbest * maxbest.pad - ]; - } - - // don't let axis have zero size, while still respecting tozero and nonnegative - if(newRange[0] === newRange[1]) { - if(ax.rangemode === 'tozero') { - if(newRange[0] < 0) { - newRange = [newRange[0], 0]; - } - else if(newRange[0] > 0) { - newRange = [0, newRange[0]]; - } - else { - newRange = [0, 1]; - } - } - else { - newRange = [newRange[0] - 1, newRange[0] + 1]; - if(ax.rangemode === 'nonnegative') { - newRange[0] = Math.max(0, newRange[0]); - } - } - } - - // maintain reversal - if(axReverse) newRange.reverse(); - - return Lib.simpleMap(newRange, ax.l2r || Number); -}; - -axes.doAutoRange = function(ax) { - if(!ax._length) ax.setScale(); - - // TODO do we really need this? - var hasDeps = (ax._min && ax._max && ax._min.length && ax._max.length); - - if(ax.autorange && hasDeps) { - ax.range = axes.getAutoRange(ax); - - ax._r = ax.range.slice(); - ax._rl = Lib.simpleMap(ax._r, ax.r2l); - - // doAutoRange will get called on fullLayout, - // but we want to report its results back to layout - - var axIn = ax._input; - axIn.range = ax.range.slice(); - axIn.autorange = ax.autorange; - } -}; - // save a copy of the initial axis ranges in fullLayout // use them in mode bar and dblclick events axes.saveRangeInitial = function(gd, overwrite) { @@ -425,142 +278,6 @@ axes.saveShowSpikeInitial = function(gd, overwrite) { return hasOneAxisChanged; }; -axes.doesAxisNeedAutoRange = function(ax) { - return ( - ax.autorange || - !!Lib.nestedProperty(ax, 'rangeslider.autorange').get() - ); -}; - -// axes.expand: if autoranging, include new data in the outer limits -// for this axis -// data is an array of numbers (ie already run through ax.d2c) -// available options: -// vpad: (number or number array) pad values (data value +-vpad) -// ppad: (number or number array) pad pixels (pixel location +-ppad) -// ppadplus, ppadminus, vpadplus, vpadminus: -// separate padding for each side, overrides symmetric -// padded: (boolean) add 5% padding to both ends -// (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 -axes.expand = function(ax, data, options) { - if(!axes.doesAxisNeedAutoRange(ax) || !data) return; - - if(!ax._min) ax._min = []; - if(!ax._max) ax._max = []; - if(!options) options = {}; - if(!ax._m) ax.setScale(); - - var len = data.length, - extrappad = options.padded ? ax._length * 0.05 : 0, - tozero = options.tozero && (ax.type === 'linear' || ax.type === '-'), - i, j, v, di, dmin, dmax, - ppadiplus, ppadiminus, includeThis, vmin, vmax; - - // domain-constrained axes: base extrappad on the unconstrained - // domain so it's consistent as the domain changes - if(extrappad && (ax.constrain === 'domain') && ax._inputDomain) { - extrappad *= (ax._inputDomain[1] - ax._inputDomain[0]) / - (ax.domain[1] - ax.domain[0]); - } - - function getPad(item) { - if(Array.isArray(item)) { - return function(i) { return Math.max(Number(item[i]||0), 0); }; - } - else { - var v = Math.max(Number(item||0), 0); - return function() { return v; }; - } - } - var ppadplus = getPad((ax._m > 0 ? - options.ppadplus : options.ppadminus) || options.ppad || 0), - ppadminus = getPad((ax._m > 0 ? - options.ppadminus : options.ppadplus) || options.ppad || 0), - vpadplus = getPad(options.vpadplus || options.vpad), - vpadminus = getPad(options.vpadminus || options.vpad); - - function addItem(i) { - di = data[i]; - if(!isNumeric(di)) return; - ppadiplus = ppadplus(i) + extrappad; - ppadiminus = ppadminus(i) + extrappad; - vmin = di - vpadminus(i); - vmax = di + vpadplus(i); - // special case for log axes: if vpad makes this object span - // more than an order of mag, clip it to one order. This is so - // we don't have non-positive errors or absurdly large lower - // range due to rounding errors - if(ax.type === 'log' && vmin < vmax / 10) { vmin = vmax / 10; } - - dmin = ax.c2l(vmin); - dmax = ax.c2l(vmax); - - if(tozero) { - dmin = Math.min(0, dmin); - dmax = Math.max(0, dmax); - } - - // In order to stop overflow errors, don't consider points - // too close to the limits of js floating point - function goodNumber(v) { - return isNumeric(v) && Math.abs(v) < FP_SAFE; - } - - if(goodNumber(dmin)) { - includeThis = true; - // take items v from ax._min and compare them to the - // presently active point: - // - if the item supercedes the new point, set includethis false - // - if the new pt supercedes the item, delete it from ax._min - for(j = 0; j < ax._min.length && includeThis; j++) { - v = ax._min[j]; - if(v.val <= dmin && v.pad >= ppadiminus) { - includeThis = false; - } - else if(v.val >= dmin && v.pad <= ppadiminus) { - ax._min.splice(j, 1); - j--; - } - } - if(includeThis) { - ax._min.push({ - val: dmin, - pad: (tozero && dmin === 0) ? 0 : ppadiminus - }); - } - } - - if(goodNumber(dmax)) { - includeThis = true; - for(j = 0; j < ax._max.length && includeThis; j++) { - v = ax._max[j]; - if(v.val >= dmax && v.pad >= ppadiplus) { - includeThis = false; - } - else if(v.val <= dmax && v.pad <= ppadiplus) { - ax._max.splice(j, 1); - j--; - } - } - if(includeThis) { - ax._max.push({ - val: dmax, - pad: (tozero && dmax === 0) ? 0 : ppadiplus - }); - } - } - } - - // For efficiency covering monotonic or near-monotonic data, - // check a few points at both ends first and then sweep - // through the middle - for(i = 0; i < 6; i++) addItem(i); - for(i = len - 1; i > 5; i--) addItem(i); - -}; - axes.autoBin = function(data, ax, nbins, is2d, calendar) { var dataMin = Lib.aggNums(Math.min, null, data), dataMax = Lib.aggNums(Math.max, null, data); diff --git a/src/plots/cartesian/constraints.js b/src/plots/cartesian/constraints.js index adaee15d4b8..f88bb397c10 100644 --- a/src/plots/cartesian/constraints.js +++ b/src/plots/cartesian/constraints.js @@ -11,6 +11,7 @@ var id2name = require('./axis_ids').id2name; var scaleZoom = require('./scale_zoom'); +var makePadFn = require('./autorange').makePadFn; var ALMOST_EQUAL = require('../../constants/numerical').ALMOST_EQUAL; @@ -134,6 +135,7 @@ exports.enforce = function enforceAxisConstraints(gd) { // *are* expanding to the full domain var outerMin = rangeCenter - halfRange * factor * 1.0001; var outerMax = rangeCenter + halfRange * factor * 1.0001; + var getPad = makePadFn(ax); updateDomain(ax, factor); ax.setScale(); @@ -142,14 +144,14 @@ exports.enforce = function enforceAxisConstraints(gd) { var k; for(k = 0; k < ax._min.length; k++) { - newVal = ax._min[k].val - ax._min[k].pad / m; + newVal = ax._min[k].val - getPad(ax._min[k]) / m; if(newVal > outerMin && newVal < rangeMin) { rangeMin = newVal; } } for(k = 0; k < ax._max.length; k++) { - newVal = ax._max[k].val + ax._max[k].pad / m; + newVal = ax._max[k].val + getPad(ax._max[k]) / m; if(newVal < outerMax && newVal > rangeMax) { rangeMax = newVal; } diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index 4d7dfe34d8a..8334ab5d0ec 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -86,7 +86,7 @@ module.exports = { values: ['normal', 'tozero', 'nonnegative'], dflt: 'normal', role: 'style', - editType: 'calc', + editType: 'plot', description: [ 'If *normal*, the range is computed in relation to the extrema', 'of the input data.', @@ -137,7 +137,7 @@ module.exports = { constants.idRegex.y.toString() ], role: 'info', - editType: 'calc', + editType: 'plot', description: [ 'If set to another axis id (e.g. `x2`, `y`), the range of this axis', 'changes together with the range of the corresponding axis', @@ -159,7 +159,7 @@ module.exports = { min: 0, dflt: 1, role: 'info', - editType: 'calc', + editType: 'plot', description: [ 'If this axis is linked to another by `scaleanchor`, this determines the pixel', 'to unit scale ratio. For example, if this value is 10, then every unit on', @@ -173,7 +173,7 @@ module.exports = { values: ['range', 'domain'], dflt: 'range', role: 'info', - editType: 'calc', + editType: 'plot', description: [ 'If this axis needs to be compressed (either due to its own `scaleanchor` and', '`scaleratio` or those of the other axis), determines how that happens:', @@ -185,7 +185,7 @@ module.exports = { valType: 'enumerated', values: ['left', 'center', 'right', 'top', 'middle', 'bottom'], role: 'info', - editType: 'calc', + editType: 'plot', description: [ 'If this axis needs to be compressed (either due to its own `scaleanchor` and', '`scaleratio` or those of the other axis), determines which direction we push', @@ -669,7 +669,7 @@ module.exports = { constants.idRegex.y.toString() ], role: 'info', - editType: 'calc', + editType: 'plot', description: [ 'If set a same-letter axis id, this axis is overlaid on top of', 'the corresponding same-letter axis.', diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index 7a761a00e36..16a9f571ef7 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -455,11 +455,26 @@ module.exports = function setConvert(ax, fullLayout) { ); }; - // for autoranging: arrays of objects: - // {val: axis value, pad: pixel padding} - // on the low and high sides - ax._min = []; - ax._max = []; + 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(); + + // Build the lookup map for initialized categories + ax._categoriesMap = {}; + for(var j = 0; j < ax._categories.length; j++) { + ax._categoriesMap[ax._categories[j]] = j; + } + }; // Propagate localization into the axis so that // methods in Axes can use it w/o having to pass fullLayout diff --git a/src/plots/domain.js b/src/plots/domain.js index 3c6dc828780..4726f8f8dca 100644 --- a/src/plots/domain.js +++ b/src/plots/domain.js @@ -40,8 +40,8 @@ exports.attributes = function(opts, extra) { role: 'info', editType: opts.editType, items: [ - {valType: 'number', min: 0, max: 1}, - {valType: 'number', min: 0, max: 1} + {valType: 'number', min: 0, max: 1, editType: opts.editType}, + {valType: 'number', min: 0, max: 1, editType: opts.editType} ], dflt: [0, 1] }; diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index 26fd39b9146..1ab225ded52 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -25,6 +25,7 @@ var showNoWebGlMsg = require('../../lib/show_no_webgl_msg'); var axisConstraints = require('../cartesian/constraints'); var enforceAxisConstraints = axisConstraints.enforce; var cleanAxisConstraints = axisConstraints.clean; +var doAutoRange = require('../cartesian/autorange').doAutoRange; var AXES = ['xaxis', 'yaxis']; var STATIC_CANVAS, STATIC_CONTEXT; @@ -77,8 +78,6 @@ function Scene2D(options, fullLayout) { // when we get a mouseout we set it to false before handling this.isMouseOver = true; - this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; - // flag to stop render loop this.stopped = false; @@ -260,9 +259,6 @@ proto.updateSize = function(canvas) { canvas.height = pixelHeight; } - // make sure plots render right thing - if(this.redraw) this.redraw(); - return canvas; }; @@ -394,6 +390,8 @@ proto.plot = function(fullData, calcData, fullLayout) { var glplot = this.glplot; this.updateRefs(fullLayout); + this.xaxis.clearCalc(); + this.yaxis.clearCalc(); this.updateTraces(fullData, calcData); this.updateFx(fullLayout.dragmode); @@ -432,32 +430,13 @@ proto.plot = function(fullData, calcData, fullLayout) { this.mouseContainer.style.left = size.l + domainX[0] * size.w + 'px'; this.mouseContainer.style.top = size.t + (1 - domainY[1]) * size.h + 'px'; - var bounds = this.bounds; - bounds[0] = bounds[1] = Infinity; - bounds[2] = bounds[3] = -Infinity; - - var traceIds = Object.keys(this.traces); var ax, i; - for(i = 0; i < traceIds.length; ++i) { - var traceObj = this.traces[traceIds[i]]; - - for(var k = 0; k < 2; ++k) { - bounds[k] = Math.min(bounds[k], traceObj.bounds[k]); - bounds[k + 2] = Math.max(bounds[k + 2], traceObj.bounds[k + 2]); - } - } - for(i = 0; i < 2; ++i) { - if(bounds[i] > bounds[i + 2]) { - bounds[i] = -1; - bounds[i + 2] = 1; - } - ax = this[AXES[i]]; ax._length = options.viewBox[i + 2] - options.viewBox[i]; - Axes.doAutoRange(ax); + doAutoRange(ax); ax.setScale(); } diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index 1dfee32e8f7..8add2156942 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -59,7 +59,7 @@ module.exports = { role: 'info', min: 10, dflt: 700, - editType: 'none', + editType: 'plot', description: [ 'Sets the plot\'s width (in px).' ].join(' ') @@ -69,7 +69,7 @@ module.exports = { role: 'info', min: 10, dflt: 450, - editType: 'none', + editType: 'plot', description: [ 'Sets the plot\'s height (in px).' ].join(' ') @@ -80,7 +80,7 @@ module.exports = { role: 'info', min: 0, dflt: 80, - editType: 'calc', + editType: 'plot', description: 'Sets the left margin (in px).' }, r: { @@ -88,7 +88,7 @@ module.exports = { role: 'info', min: 0, dflt: 80, - editType: 'calc', + editType: 'plot', description: 'Sets the right margin (in px).' }, t: { @@ -96,7 +96,7 @@ module.exports = { role: 'info', min: 0, dflt: 100, - editType: 'calc', + editType: 'plot', description: 'Sets the top margin (in px).' }, b: { @@ -104,7 +104,7 @@ module.exports = { role: 'info', min: 0, dflt: 80, - editType: 'calc', + editType: 'plot', description: 'Sets the bottom margin (in px).' }, pad: { @@ -112,7 +112,7 @@ module.exports = { role: 'info', min: 0, dflt: 0, - editType: 'calc', + editType: 'plot', description: [ 'Sets the amount of padding (in px)', 'between the plotting area and the axis lines' @@ -122,9 +122,9 @@ module.exports = { valType: 'boolean', role: 'info', dflt: true, - editType: 'calc' + editType: 'plot' }, - editType: 'calc' + editType: 'plot' }, paper_bgcolor: { valType: 'color', diff --git a/src/plots/plots.js b/src/plots/plots.js index bc91864c548..745e4e29bcf 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2337,7 +2337,7 @@ plots.doCalcdata = function(gd, traces) { ); } - initCategories(axList); + clearAxesCalc(axList); var hasCalcTransform = false; @@ -2367,15 +2367,7 @@ plots.doCalcdata = function(gd, traces) { } // clear stuff that should recomputed in 'regular' loop - if(hasCalcTransform) { - for(i = 0; i < axList.length; i++) { - axList[i]._min = []; - axList[i]._max = []; - axList[i]._categories = []; - axList[i]._categoriesMap = {}; - } - initCategories(axList); - } + if(hasCalcTransform) clearAxesCalc(axList); // 'regular' loop for(i = 0; i < fullData.length; i++) { @@ -2422,17 +2414,9 @@ plots.doCalcdata = function(gd, traces) { Registry.getComponentMethod('fx', 'calc')(gd); }; -// initialize the category list, if there is one, so we start over -// to be filled in later by ax.d2c -function initCategories(axList) { +function clearAxesCalc(axList) { for(var i = 0; i < axList.length; i++) { - axList[i]._categories = axList[i]._initialCategories.slice(); - - // Build the lookup map for initialized categories - axList[i]._categoriesMap = {}; - for(var j = 0; j < axList[i]._categories.length; j++) { - axList[i]._categoriesMap[axList[i]._categories[j]] = j; - } + axList[i].clearCalc(); } } diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 62ffd7691a3..df9d53a9589 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -17,6 +17,7 @@ var Color = require('../../components/color'); var Drawing = require('../../components/drawing'); var Plots = require('../plots'); var Axes = require('../cartesian/axes'); +var doAutoRange = require('../cartesian/autorange').doAutoRange; var dragElement = require('../../components/dragelement'); var dragBox = require('../cartesian/dragbox'); var Fx = require('../../components/fx'); @@ -292,7 +293,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) { }); setScale(ax, radialLayout, fullLayout); - Axes.doAutoRange(ax); + doAutoRange(ax); radialLayout.range = ax.range.slice(); radialLayout._input.range = ax.range.slice(); _this.fillViewInitialKey('radialaxis.range', ax.range.slice()); @@ -448,7 +449,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) { } setScale(ax, angularLayout, fullLayout); - Axes.doAutoRange(ax); + doAutoRange(ax); // wrapper around c2rad from setConvertAngular // note that linear ranges are always set in degrees for Axes.doTicks diff --git a/src/traces/pointcloud/convert.js b/src/traces/pointcloud/convert.js index b75cf5448d9..721b964f5a5 100644 --- a/src/traces/pointcloud/convert.js +++ b/src/traces/pointcloud/convert.js @@ -11,10 +11,9 @@ var createPointCloudRenderer = require('gl-pointcloud2d'); var str2RGBArray = require('../../lib/str2rgbarray'); +var expandAxis = require('../../plots/cartesian/autorange').expand; var getTraceColor = require('../scatter/get_trace_color'); -var AXES = ['xaxis', 'yaxis']; - function Pointcloud(scene, uid) { this.scene = scene; this.uid = uid; @@ -201,19 +200,9 @@ proto.updateFast = function(options) { proto.expandAxesFast = function(bounds, markerSize) { var pad = markerSize || 0.5; - var ax, min, max; - - for(var i = 0; i < 2; i++) { - ax = this.scene[AXES[i]]; - min = ax._min; - if(!min) min = []; - min.push({ val: bounds[i], pad: pad }); - - max = ax._max; - if(!max) max = []; - max.push({ val: bounds[i + 2], pad: pad }); - } + expandAxis(this.scene.xaxis, [bounds[0], bounds[2]], {ppad: pad}); + expandAxis(this.scene.yaxis, [bounds[1], bounds[3]], {ppad: pad}); }; proto.dispose = function() { diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 208495ba8f6..e9550515014 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -19,7 +19,7 @@ var arrayRange = require('array-range'); var Registry = require('../../registry'); var Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); +var AxisIDs = require('../../plots/cartesian/axis_ids'); var Drawing = require('../../components/drawing'); var formatColor = require('../../lib/gl_format_color'); @@ -45,8 +45,8 @@ var OPEN_RE = /-open/; function calc(gd, trace) { var fullLayout = gd._fullLayout; - var xa = Axes.getFromId(gd, trace.xaxis); - var ya = Axes.getFromId(gd, trace.yaxis); + var xa = AxisIDs.getFromId(gd, trace.xaxis); + var ya = AxisIDs.getFromId(gd, trace.yaxis); var subplot = fullLayout._plots[trace.xaxis + trace.yaxis]; var count = trace._length; var count2 = count * 2; @@ -103,14 +103,10 @@ function calc(gd, trace) { // and an average size for array marker.size inputs. if(count < TOO_MANY_POINTS) { ppad = calcMarkerSize(trace, count); - calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); - } else { - if(markerOptions) { - ppad = 2 * (markerOptions.sizeAvg || Math.max(markerOptions.size, 3)); - } - fastAxisExpand(xa, x, ppad); - fastAxisExpand(ya, y, ppad); + } else if(markerOptions) { + ppad = 2 * (markerOptions.sizeAvg || Math.max(markerOptions.size, 3)); } + calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); // set flags to create scene renderers if(options.fill && !scene.fill2d) scene.fill2d = true; @@ -140,33 +136,13 @@ function calc(gd, trace) { return [{x: false, y: false, t: stash, trace: trace}]; } -// Approximate Axes.expand results with speed -function fastAxisExpand(ax, vals, ppad) { - if(!Axes.doesAxisNeedAutoRange(ax) || !vals) return; - - var b0 = Infinity; - var b1 = -Infinity; - - for(var i = 0; i < vals.length; i += 2) { - var v = vals[i]; - if(v < b0) b0 = v; - if(v > b1) b1 = v; - } - - if(ax._min) ax._min = []; - ax._min.push({val: b0, pad: ppad}); - - if(ax._max) ax._max = []; - ax._max.push({val: b1, pad: ppad}); -} - // create scene options function sceneOptions(gd, subplot, trace, positions) { var fullLayout = gd._fullLayout; var count = positions.length / 2; var markerOpts = trace.marker; - var xaxis = Axes.getFromId(gd, trace.xaxis); - var yaxis = Axes.getFromId(gd, trace.yaxis); + var xaxis = AxisIDs.getFromId(gd, trace.xaxis); + var yaxis = AxisIDs.getFromId(gd, trace.yaxis); var ptrX = 0; var ptrY = 0; var i; @@ -872,8 +848,8 @@ function plot(gd, subplot, cdata) { var x = stash.x; var y = stash.y; - var xaxis = subplot.xaxis || Axes.getFromId(gd, trace.xaxis || 'x'); - var yaxis = subplot.yaxis || Axes.getFromId(gd, trace.yaxis || 'y'); + var xaxis = subplot.xaxis || AxisIDs.getFromId(gd, trace.xaxis || 'x'); + var yaxis = subplot.yaxis || AxisIDs.getFromId(gd, trace.yaxis || 'y'); var i; var range = [ diff --git a/test/image/baselines/19.png b/test/image/baselines/19.png index 83bb484d031..fc65db8d341 100644 Binary files a/test/image/baselines/19.png and b/test/image/baselines/19.png differ diff --git a/test/image/baselines/24.png b/test/image/baselines/24.png index 9412925e6e9..5f5ef817bc6 100644 Binary files a/test/image/baselines/24.png and b/test/image/baselines/24.png differ diff --git a/test/image/baselines/autorange-tozero-rangemode.png b/test/image/baselines/autorange-tozero-rangemode.png index 152e96cbff2..8f52a059a31 100644 Binary files a/test/image/baselines/autorange-tozero-rangemode.png and b/test/image/baselines/autorange-tozero-rangemode.png differ diff --git a/test/image/baselines/axes-ticks.png b/test/image/baselines/axes-ticks.png index d4867a0ffc8..cb7a83c6a8f 100644 Binary files a/test/image/baselines/axes-ticks.png and b/test/image/baselines/axes-ticks.png differ diff --git a/test/image/baselines/axes_booleans.png b/test/image/baselines/axes_booleans.png index 01cfaaa0db7..a286547a420 100644 Binary files a/test/image/baselines/axes_booleans.png and b/test/image/baselines/axes_booleans.png differ diff --git a/test/image/baselines/axes_labels.png b/test/image/baselines/axes_labels.png index 9bb71bed75c..e21752587cc 100644 Binary files a/test/image/baselines/axes_labels.png and b/test/image/baselines/axes_labels.png differ diff --git a/test/image/baselines/axes_lines.png b/test/image/baselines/axes_lines.png index 290d6b57754..aa570e3aead 100644 Binary files a/test/image/baselines/axes_lines.png and b/test/image/baselines/axes_lines.png differ diff --git a/test/image/baselines/axes_range_type.png b/test/image/baselines/axes_range_type.png index 2abd177735a..de4d196f356 100644 Binary files a/test/image/baselines/axes_range_type.png and b/test/image/baselines/axes_range_type.png differ diff --git a/test/image/baselines/basic_line.png b/test/image/baselines/basic_line.png index fa4ffb0f9bb..67dd2909f09 100644 Binary files a/test/image/baselines/basic_line.png and b/test/image/baselines/basic_line.png differ diff --git a/test/image/baselines/binding.png b/test/image/baselines/binding.png index e527da8f9d6..aaef2610968 100644 Binary files a/test/image/baselines/binding.png and b/test/image/baselines/binding.png differ diff --git a/test/image/baselines/box_grouped_horz.png b/test/image/baselines/box_grouped_horz.png index cac2a0b8934..4c55500cd07 100644 Binary files a/test/image/baselines/box_grouped_horz.png and b/test/image/baselines/box_grouped_horz.png differ diff --git a/test/image/baselines/bubble_markersize0.png b/test/image/baselines/bubble_markersize0.png index 86f376da04c..7b0c62949d4 100644 Binary files a/test/image/baselines/bubble_markersize0.png and b/test/image/baselines/bubble_markersize0.png differ diff --git a/test/image/baselines/bubble_nonnumeric-sizes.png b/test/image/baselines/bubble_nonnumeric-sizes.png index 2b9918aa5a3..2520fc37ac4 100644 Binary files a/test/image/baselines/bubble_nonnumeric-sizes.png and b/test/image/baselines/bubble_nonnumeric-sizes.png differ diff --git a/test/image/baselines/cheater.png b/test/image/baselines/cheater.png index 895cbe9509f..aa6517788ec 100644 Binary files a/test/image/baselines/cheater.png and b/test/image/baselines/cheater.png differ diff --git a/test/image/baselines/cheater_constraints.png b/test/image/baselines/cheater_constraints.png index c2bdeae65cf..c79c53351c8 100644 Binary files a/test/image/baselines/cheater_constraints.png and b/test/image/baselines/cheater_constraints.png differ diff --git a/test/image/baselines/cheater_fully_filled.png b/test/image/baselines/cheater_fully_filled.png index feef44b389c..0c2cedc4919 100644 Binary files a/test/image/baselines/cheater_fully_filled.png and b/test/image/baselines/cheater_fully_filled.png differ diff --git a/test/image/baselines/cheater_smooth.png b/test/image/baselines/cheater_smooth.png index 1edb6600a13..7d4b479bf24 100644 Binary files a/test/image/baselines/cheater_smooth.png and b/test/image/baselines/cheater_smooth.png differ diff --git a/test/image/baselines/colorscale_constraint.png b/test/image/baselines/colorscale_constraint.png index d755bcdfbae..b83fbb1635e 100644 Binary files a/test/image/baselines/colorscale_constraint.png and b/test/image/baselines/colorscale_constraint.png differ diff --git a/test/image/baselines/colorscale_opacity.png b/test/image/baselines/colorscale_opacity.png index 55a0c07f3cd..bc300734de0 100644 Binary files a/test/image/baselines/colorscale_opacity.png and b/test/image/baselines/colorscale_opacity.png differ diff --git a/test/image/baselines/custom_size_subplot.png b/test/image/baselines/custom_size_subplot.png index afdd56ddd41..05d7a2d7786 100644 Binary files a/test/image/baselines/custom_size_subplot.png and b/test/image/baselines/custom_size_subplot.png differ diff --git a/test/image/baselines/error_bar_layers.png b/test/image/baselines/error_bar_layers.png index 63ad61d3ee8..495516d6cc2 100644 Binary files a/test/image/baselines/error_bar_layers.png and b/test/image/baselines/error_bar_layers.png differ diff --git a/test/image/baselines/gl2d_axes_booleans.png b/test/image/baselines/gl2d_axes_booleans.png index 7e523efca3b..e6e72b6b9b2 100644 Binary files a/test/image/baselines/gl2d_axes_booleans.png and b/test/image/baselines/gl2d_axes_booleans.png differ diff --git a/test/image/baselines/gl2d_axes_labels.png b/test/image/baselines/gl2d_axes_labels.png index decf839b993..302d41630e8 100644 Binary files a/test/image/baselines/gl2d_axes_labels.png and b/test/image/baselines/gl2d_axes_labels.png differ diff --git a/test/image/baselines/gl2d_axes_lines.png b/test/image/baselines/gl2d_axes_lines.png index de668a0dc13..5804eba3781 100644 Binary files a/test/image/baselines/gl2d_axes_lines.png and b/test/image/baselines/gl2d_axes_lines.png differ diff --git a/test/image/baselines/gl2d_axes_range_type.png b/test/image/baselines/gl2d_axes_range_type.png index 5e02124eb9a..e9a6474b843 100644 Binary files a/test/image/baselines/gl2d_axes_range_type.png and b/test/image/baselines/gl2d_axes_range_type.png differ diff --git a/test/image/baselines/gl2d_error_bars.png b/test/image/baselines/gl2d_error_bars.png index 0cd99081555..9a84c77630a 100644 Binary files a/test/image/baselines/gl2d_error_bars.png and b/test/image/baselines/gl2d_error_bars.png differ diff --git a/test/image/baselines/gl2d_layout_image.png b/test/image/baselines/gl2d_layout_image.png index fa45cb7793a..2c3850c52a7 100644 Binary files a/test/image/baselines/gl2d_layout_image.png and b/test/image/baselines/gl2d_layout_image.png differ diff --git a/test/image/baselines/gl2d_multiple_subplots.png b/test/image/baselines/gl2d_multiple_subplots.png index f0a814a8a6b..cd55920d1df 100644 Binary files a/test/image/baselines/gl2d_multiple_subplots.png and b/test/image/baselines/gl2d_multiple_subplots.png differ diff --git a/test/image/baselines/gl2d_scatter-colorscale-colorbar.png b/test/image/baselines/gl2d_scatter-colorscale-colorbar.png index e2ebc54e4a8..aab8a01c961 100644 Binary files a/test/image/baselines/gl2d_scatter-colorscale-colorbar.png and b/test/image/baselines/gl2d_scatter-colorscale-colorbar.png differ diff --git a/test/image/baselines/gl2d_scatter_fill_self_next.png b/test/image/baselines/gl2d_scatter_fill_self_next.png index 41bfa5171f6..b6a3ffab794 100644 Binary files a/test/image/baselines/gl2d_scatter_fill_self_next.png and b/test/image/baselines/gl2d_scatter_fill_self_next.png differ diff --git a/test/image/baselines/gl2d_simple_inset.png b/test/image/baselines/gl2d_simple_inset.png index c0005789be9..7cddf9095a9 100644 Binary files a/test/image/baselines/gl2d_simple_inset.png and b/test/image/baselines/gl2d_simple_inset.png differ diff --git a/test/image/baselines/gl2d_stacked_coupled_subplots.png b/test/image/baselines/gl2d_stacked_coupled_subplots.png index c67933ea7e3..a43f92cdba6 100644 Binary files a/test/image/baselines/gl2d_stacked_coupled_subplots.png and b/test/image/baselines/gl2d_stacked_coupled_subplots.png differ diff --git a/test/image/baselines/gl2d_stacked_subplots.png b/test/image/baselines/gl2d_stacked_subplots.png index c7d26103984..14727b64ff7 100644 Binary files a/test/image/baselines/gl2d_stacked_subplots.png and b/test/image/baselines/gl2d_stacked_subplots.png differ diff --git a/test/image/baselines/grid_subplot_types.png b/test/image/baselines/grid_subplot_types.png index 970b25cca38..3627f6c26fd 100644 Binary files a/test/image/baselines/grid_subplot_types.png and b/test/image/baselines/grid_subplot_types.png differ diff --git a/test/image/baselines/layout-colorway.png b/test/image/baselines/layout-colorway.png index 332db30b056..87f397d1041 100644 Binary files a/test/image/baselines/layout-colorway.png and b/test/image/baselines/layout-colorway.png differ diff --git a/test/image/baselines/layout_image.png b/test/image/baselines/layout_image.png index afbe9ad177c..f09ebdccb2a 100644 Binary files a/test/image/baselines/layout_image.png and b/test/image/baselines/layout_image.png differ diff --git a/test/image/baselines/legend_horizontal_groups.png b/test/image/baselines/legend_horizontal_groups.png index 4651f8e5e03..29536dc9b0f 100644 Binary files a/test/image/baselines/legend_horizontal_groups.png and b/test/image/baselines/legend_horizontal_groups.png differ diff --git a/test/image/baselines/legend_labels.png b/test/image/baselines/legend_labels.png index dcb3857a802..42cf8be2adc 100644 Binary files a/test/image/baselines/legend_labels.png and b/test/image/baselines/legend_labels.png differ diff --git a/test/image/baselines/legend_negative_y.png b/test/image/baselines/legend_negative_y.png index 5b32d355a00..a7517cfeaf9 100644 Binary files a/test/image/baselines/legend_negative_y.png and b/test/image/baselines/legend_negative_y.png differ diff --git a/test/image/baselines/legend_outside.png b/test/image/baselines/legend_outside.png index d71185dd549..f6aa26a33d1 100644 Binary files a/test/image/baselines/legend_outside.png and b/test/image/baselines/legend_outside.png differ diff --git a/test/image/baselines/line_scatter.png b/test/image/baselines/line_scatter.png index 016c887521f..adeb1143d83 100644 Binary files a/test/image/baselines/line_scatter.png and b/test/image/baselines/line_scatter.png differ diff --git a/test/image/baselines/line_style.png b/test/image/baselines/line_style.png index 9c4e5a3adca..1ca745a9632 100644 Binary files a/test/image/baselines/line_style.png and b/test/image/baselines/line_style.png differ diff --git a/test/image/baselines/long_axis_labels.png b/test/image/baselines/long_axis_labels.png index 7c5c5605654..77ba28cc14a 100644 Binary files a/test/image/baselines/long_axis_labels.png and b/test/image/baselines/long_axis_labels.png differ diff --git a/test/image/baselines/multiple_axes_double.png b/test/image/baselines/multiple_axes_double.png index a003e3754e7..881054c41fc 100644 Binary files a/test/image/baselines/multiple_axes_double.png and b/test/image/baselines/multiple_axes_double.png differ diff --git a/test/image/baselines/multiple_axes_multiple.png b/test/image/baselines/multiple_axes_multiple.png index d4bd58f54a1..d079a9c597d 100644 Binary files a/test/image/baselines/multiple_axes_multiple.png and b/test/image/baselines/multiple_axes_multiple.png differ diff --git a/test/image/baselines/multiple_subplots.png b/test/image/baselines/multiple_subplots.png index 53f92b568d9..758c5a99ff2 100644 Binary files a/test/image/baselines/multiple_subplots.png and b/test/image/baselines/multiple_subplots.png differ diff --git a/test/image/baselines/overlaying-axis-lines.png b/test/image/baselines/overlaying-axis-lines.png index 8d5a6d95ca5..1f9bb7017ac 100644 Binary files a/test/image/baselines/overlaying-axis-lines.png and b/test/image/baselines/overlaying-axis-lines.png differ diff --git a/test/image/baselines/pseudo_html.png b/test/image/baselines/pseudo_html.png index 2701f536651..a3b74e18888 100644 Binary files a/test/image/baselines/pseudo_html.png and b/test/image/baselines/pseudo_html.png differ diff --git a/test/image/baselines/range_slider_axes_double.png b/test/image/baselines/range_slider_axes_double.png index 719c6573668..5b604a29766 100644 Binary files a/test/image/baselines/range_slider_axes_double.png and b/test/image/baselines/range_slider_axes_double.png differ diff --git a/test/image/baselines/scatter-colorscale-colorbar.png b/test/image/baselines/scatter-colorscale-colorbar.png index 2e0543171c3..2ddf0dd7334 100644 Binary files a/test/image/baselines/scatter-colorscale-colorbar.png and b/test/image/baselines/scatter-colorscale-colorbar.png differ diff --git a/test/image/baselines/scatter_fill_corner_cases.png b/test/image/baselines/scatter_fill_corner_cases.png index c4c59769692..a1a0f823451 100644 Binary files a/test/image/baselines/scatter_fill_corner_cases.png and b/test/image/baselines/scatter_fill_corner_cases.png differ diff --git a/test/image/baselines/scatter_fill_no_opacity.png b/test/image/baselines/scatter_fill_no_opacity.png index 74e47098f1a..4bf8cd022a0 100644 Binary files a/test/image/baselines/scatter_fill_no_opacity.png and b/test/image/baselines/scatter_fill_no_opacity.png differ diff --git a/test/image/baselines/scatter_fill_self_next.png b/test/image/baselines/scatter_fill_self_next.png index d7234007675..d3f7d725e63 100644 Binary files a/test/image/baselines/scatter_fill_self_next.png and b/test/image/baselines/scatter_fill_self_next.png differ diff --git a/test/image/baselines/scattercarpet.png b/test/image/baselines/scattercarpet.png index 0de047bf419..3280b772845 100644 Binary files a/test/image/baselines/scattercarpet.png and b/test/image/baselines/scattercarpet.png differ diff --git a/test/image/baselines/shared_axes_subplots.png b/test/image/baselines/shared_axes_subplots.png index ae8d7ecc758..2085aca7e59 100644 Binary files a/test/image/baselines/shared_axes_subplots.png and b/test/image/baselines/shared_axes_subplots.png differ diff --git a/test/image/baselines/show_legend.png b/test/image/baselines/show_legend.png index 9f4091a2d91..b6f58199b47 100644 Binary files a/test/image/baselines/show_legend.png and b/test/image/baselines/show_legend.png differ diff --git a/test/image/baselines/simple_inset.png b/test/image/baselines/simple_inset.png index 83bb484d031..fc65db8d341 100644 Binary files a/test/image/baselines/simple_inset.png and b/test/image/baselines/simple_inset.png differ diff --git a/test/image/baselines/simple_subplot.png b/test/image/baselines/simple_subplot.png index 35bf6bc71c8..7d31e4c8232 100644 Binary files a/test/image/baselines/simple_subplot.png and b/test/image/baselines/simple_subplot.png differ diff --git a/test/image/baselines/sliders.png b/test/image/baselines/sliders.png index 2271954c2cf..9f1ecd2338c 100644 Binary files a/test/image/baselines/sliders.png and b/test/image/baselines/sliders.png differ diff --git a/test/image/baselines/stacked_coupled_subplots.png b/test/image/baselines/stacked_coupled_subplots.png index a44b8df9206..c93292fe44c 100644 Binary files a/test/image/baselines/stacked_coupled_subplots.png and b/test/image/baselines/stacked_coupled_subplots.png differ diff --git a/test/image/baselines/stacked_subplots.png b/test/image/baselines/stacked_subplots.png index 9c813ebc557..6dc0e46a2da 100644 Binary files a/test/image/baselines/stacked_subplots.png and b/test/image/baselines/stacked_subplots.png differ diff --git a/test/image/baselines/stacked_subplots_shared_yaxis.png b/test/image/baselines/stacked_subplots_shared_yaxis.png index 8783f79fbd0..5e3f6461feb 100644 Binary files a/test/image/baselines/stacked_subplots_shared_yaxis.png and b/test/image/baselines/stacked_subplots_shared_yaxis.png differ diff --git a/test/image/baselines/styling_names.png b/test/image/baselines/styling_names.png index bd5545994b9..f78f6679339 100644 Binary files a/test/image/baselines/styling_names.png and b/test/image/baselines/styling_names.png differ diff --git a/test/image/baselines/text_chart_arrays.png b/test/image/baselines/text_chart_arrays.png index d34b06de5b7..35aeedc3843 100644 Binary files a/test/image/baselines/text_chart_arrays.png and b/test/image/baselines/text_chart_arrays.png differ diff --git a/test/image/baselines/text_chart_invalid-arrays.png b/test/image/baselines/text_chart_invalid-arrays.png index bfea64ed5ed..b639e31f0d4 100644 Binary files a/test/image/baselines/text_chart_invalid-arrays.png and b/test/image/baselines/text_chart_invalid-arrays.png differ diff --git a/test/image/baselines/updatemenus.png b/test/image/baselines/updatemenus.png index 4cd63cf3296..bc97f9a1a10 100644 Binary files a/test/image/baselines/updatemenus.png and b/test/image/baselines/updatemenus.png differ diff --git a/test/image/baselines/updatemenus_positioning.png b/test/image/baselines/updatemenus_positioning.png index a93e32a1a61..4536dc78f99 100644 Binary files a/test/image/baselines/updatemenus_positioning.png and b/test/image/baselines/updatemenus_positioning.png differ diff --git a/test/image/baselines/world-cals.png b/test/image/baselines/world-cals.png index cb846f1df20..dd263b2c21e 100644 Binary files a/test/image/baselines/world-cals.png and b/test/image/baselines/world-cals.png differ diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 77006d1a1fe..de8c216ff2b 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -1419,7 +1419,9 @@ describe('Test axes', function() { it('returns reasonable range without explicit rangemode or autorange', function() { ax = { _min: [ - {val: 1, pad: 20}, + // 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} ], @@ -1540,7 +1542,8 @@ describe('Test axes', function() { {val: 2, pad: 10} ], _max: [ - {val: 6, pad: 20}, + // 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}, ], @@ -1653,7 +1656,6 @@ describe('Test axes', function() { autorange: true, c2l: Number, type: 'linear', - _length: 100, _m: 1 }; } @@ -1664,8 +1666,8 @@ describe('Test axes', function() { expand(ax, data); - expect(ax._min).toEqual([{val: 1, pad: 0}]); - expect(ax._max).toEqual([{val: 7, pad: 0}]); + expect(ax._min).toEqual([{val: 1, pad: 0, extrapad: false}]); + expect(ax._max).toEqual([{val: 7, pad: 0, extrapad: false}]); }); it('calls ax.setScale if necessary', function() { @@ -1692,8 +1694,8 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: -1, pad: 10}]); - expect(ax._max).toEqual([{val: 9, pad: 10}]); + expect(ax._min).toEqual([{val: -1, pad: 10, extrapad: false}]); + expect(ax._max).toEqual([{val: 9, pad: 10, extrapad: false}]); }); it('handles symmetric pads as number arrays', function() { @@ -1706,8 +1708,14 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: -6, pad: 15}, {val: -4, pad: 20}]); - expect(ax._max).toEqual([{val: 14, pad: 15}, {val: 8, pad: 20}]); + expect(ax._min).toEqual([ + {val: -6, pad: 15, extrapad: false}, + {val: -4, pad: 20, extrapad: false} + ]); + expect(ax._max).toEqual([ + {val: 14, pad: 15, extrapad: false}, + {val: 8, pad: 20, extrapad: false} + ]); }); it('handles separate pads as numbers', function() { @@ -1722,8 +1730,8 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: -4, pad: 10}]); - expect(ax._max).toEqual([{val: 11, pad: 20}]); + expect(ax._min).toEqual([{val: -4, pad: 10, extrapad: false}]); + expect(ax._max).toEqual([{val: 11, pad: 20, extrapad: false}]); }); it('handles separate pads as number arrays', function() { @@ -1738,8 +1746,15 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: 1, pad: 30}, {val: -3, pad: 10}]); - expect(ax._max).toEqual([{val: 9, pad: 0}, {val: 3, pad: 40}, {val: 8, pad: 20}]); + expect(ax._min).toEqual([ + {val: 1, pad: 30, extrapad: false}, + {val: -3, pad: 10, extrapad: false} + ]); + expect(ax._max).toEqual([ + {val: 9, pad: 0, extrapad: false}, + {val: 3, pad: 40, extrapad: false}, + {val: 8, pad: 20, extrapad: false} + ]); }); it('overrides symmetric pads with separate pads', function() { @@ -1756,8 +1771,8 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: -1, pad: 20}]); - expect(ax._max).toEqual([{val: 9, pad: 40}]); + expect(ax._min).toEqual([{val: -1, pad: 20, extrapad: false}]); + expect(ax._max).toEqual([{val: 9, pad: 40, extrapad: false}]); }); it('adds 5% padding if specified by flag', function() { @@ -1771,8 +1786,8 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: 0, pad: 15}]); - expect(ax._max).toEqual([{val: 6, pad: 15}]); + expect(ax._min).toEqual([{val: 0, pad: 10, extrapad: true}]); + expect(ax._max).toEqual([{val: 6, pad: 10, extrapad: true}]); }); it('has lower bound zero with all positive data if tozero is sset', function() { @@ -1786,8 +1801,8 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: 0, pad: 0}]); - expect(ax._max).toEqual([{val: 6, pad: 10}]); + expect(ax._min).toEqual([{val: 0, pad: 0, extrapad: false}]); + expect(ax._max).toEqual([{val: 6, pad: 10, extrapad: false}]); }); it('has upper bound zero with all negative data if tozero is set', function() { @@ -1801,8 +1816,8 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: -8, pad: 10}]); - expect(ax._max).toEqual([{val: 0, pad: 0}]); + expect(ax._min).toEqual([{val: -8, pad: 10, extrapad: false}]); + expect(ax._max).toEqual([{val: 0, pad: 0, extrapad: false}]); }); it('sets neither bound to zero with positive and negative data if tozero is set', function() { @@ -1816,8 +1831,8 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: -8, pad: 10}]); - expect(ax._max).toEqual([{val: 5, pad: 10}]); + expect(ax._min).toEqual([{val: -8, pad: 10, extrapad: false}]); + expect(ax._max).toEqual([{val: 5, pad: 10, extrapad: false}]); }); it('overrides padded with tozero', function() { @@ -1832,8 +1847,8 @@ describe('Test axes', function() { expand(ax, data, options); - expect(ax._min).toEqual([{val: 0, pad: 0}]); - expect(ax._max).toEqual([{val: 6, pad: 15}]); + expect(ax._min).toEqual([{val: 0, pad: 0, extrapad: false}]); + expect(ax._max).toEqual([{val: 6, pad: 10, extrapad: true}]); }); it('should return early if no data is given', function() { @@ -1864,8 +1879,8 @@ describe('Test axes', function() { ax.rangeslider = { autorange: true }; expand(ax, data, {}); - expect(ax._min).toEqual([{val: 2, pad: 0}]); - expect(ax._max).toEqual([{val: 5, pad: 0}]); + expect(ax._min).toEqual([{val: 2, pad: 0, extrapad: false}]); + expect(ax._max).toEqual([{val: 5, pad: 0, extrapad: false}]); }); }); diff --git a/test/jasmine/tests/cartesian_test.js b/test/jasmine/tests/cartesian_test.js index 6de808302f8..2bcf763642a 100644 --- a/test/jasmine/tests/cartesian_test.js +++ b/test/jasmine/tests/cartesian_test.js @@ -266,6 +266,41 @@ describe('relayout', function() { .then(done); }); + it('should autorange correctly with margin pushers', function(done) { + // lock down https://github.com/plotly/plotly.js/issues/2428 + var expectedXRange = [-0.3068, 1.3068]; + var expectedYRange = [0.5184, 2.4816]; + var foundXRange, foundYRange; + Plotly.newPlot(gd, [{ + // really long name, so legend pushes margins and decreases xaxis._length + name: 'loooooooooooongloooooooooooong', + y: [1, 2], + // really big markers, so autorange depends on length + // and with markers x range is padded (and 5% padding depends on length) + marker: {size: 100} + }], { + showlegend: true, + width: 800, + height: 500 + }) + .then(function() { + foundXRange = gd.layout.xaxis.range; + foundYRange = gd.layout.yaxis.range; + // less stringent test at first - for some reason I get a slightly different + // legend size even in my regular browser from when I run the tests locally + expect(foundXRange).toBeCloseToArray(expectedXRange, 1.5); + expect(foundYRange).toBeCloseToArray(expectedYRange, 1.5); + + return Plotly.relayout(gd, {'xaxis.autorange': true, 'yaxis.autorange': true}); + }) + .then(function() { + // the most important thing is that the ranges don't change when you re-autorange + expect(gd.layout.xaxis.range).toBeCloseToArray(foundXRange, 5); + expect(gd.layout.yaxis.range).toBeCloseToArray(foundYRange, 5); + }) + .catch(failTest) + .then(done); + }); }); describe('axis line visibility', function() { diff --git a/test/jasmine/tests/config_test.js b/test/jasmine/tests/config_test.js index 951d1b4dba2..7b7f74ffbec 100644 --- a/test/jasmine/tests/config_test.js +++ b/test/jasmine/tests/config_test.js @@ -5,18 +5,20 @@ var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var click = require('../assets/click'); var mouseEvent = require('../assets/mouse_event'); +var failTest = require('../assets/fail_test'); describe('config argument', function() { describe('attribute layout.autosize', function() { - var layoutWidth = 1111, - relayoutWidth = 555, - containerWidthBeforePlot = 888, - containerWidthBeforeRelayout = 666, - containerHeightBeforePlot = 543, - containerHeightBeforeRelayout = 321, - data = [], - gd; + var layoutWidth = 1111; + var relayoutWidth = 555; + var containerWidthBeforePlot = 888; + var containerWidthBeforeRelayout = 666; + var containerHeightBeforePlot = 543; + var containerHeightBeforeRelayout = 321; + var data = []; + + var gd; beforeEach(function() { gd = createGraphDiv(); @@ -40,106 +42,135 @@ describe('config argument', function() { function testAutosize(autosize, config, layoutHeight, relayoutHeight, done) { var layout = { - autosize: autosize, - width: layoutWidth + autosize: autosize, + width: layoutWidth + }; + var relayout = { + width: relayoutWidth + }; - }, - relayout = { - width: relayoutWidth - }; + var layout2 = Lib.extendDeep({}, layout); - var container = document.getElementById('graph'); - container.style.width = containerWidthBeforePlot + 'px'; - container.style.height = containerHeightBeforePlot + 'px'; + gd.style.width = containerWidthBeforePlot + 'px'; + gd.style.height = containerHeightBeforePlot + 'px'; - Plotly.plot(gd, data, layout, config).then(function() { + function beforeResize() { checkLayoutSize(layoutWidth, layoutHeight); if(!autosize) compareLayoutAndFullLayout(gd); - container.style.width = containerWidthBeforeRelayout + 'px'; - container.style.height = containerHeightBeforeRelayout + 'px'; + gd.style.width = containerWidthBeforeRelayout + 'px'; + gd.style.height = containerHeightBeforeRelayout + 'px'; + } - Plotly.relayout(gd, relayout).then(function() { - checkLayoutSize(relayoutWidth, relayoutHeight); - if(!autosize) compareLayoutAndFullLayout(gd); - done(); - }); - }); + function afterResize() { + checkLayoutSize(relayoutWidth, relayoutHeight); + if(!autosize) compareLayoutAndFullLayout(gd); + } + + Plotly.plot(gd, data, layout, config).then(function() { + beforeResize(); + + return Plotly.relayout(gd, relayout); + }) + .then(afterResize) + // now redo with Plotly.react + .then(function() { + gd.style.width = containerWidthBeforePlot + 'px'; + gd.style.height = containerHeightBeforePlot + 'px'; + + return Plotly.newPlot(gd, data, layout2, config); + }) + .then(function() { + beforeResize(); + + layout2.width = relayoutWidth; + return Plotly.react(gd, data, layout2, config); + }) + .then(afterResize) + .catch(failTest) + .then(done); } it('should fill the frame when autosize: false, fillFrame: true, frameMargins: undefined', function(done) { - var autosize = false, - config = { - autosizable: true, - fillFrame: true - }, - layoutHeight = window.innerHeight, - relayoutHeight = layoutHeight; + var autosize = false; + var config = { + autosizable: true, + fillFrame: true + }; + var layoutHeight = window.innerHeight; + var relayoutHeight = layoutHeight; + testAutosize(autosize, config, layoutHeight, relayoutHeight, done); }); it('should fill the frame when autosize: true, fillFrame: true and frameMargins: undefined', function(done) { - var autosize = true, - config = { - fillFrame: true - }, - layoutHeight = window.innerHeight, - relayoutHeight = window.innerHeight; + var autosize = true; + var config = { + fillFrame: true + }; + var layoutHeight = window.innerHeight; + var relayoutHeight = window.innerHeight; + testAutosize(autosize, config, layoutHeight, relayoutHeight, done); }); it('should fill the container when autosize: false, fillFrame: false and frameMargins: undefined', function(done) { - var autosize = false, - config = { - autosizable: true, - fillFrame: false - }, - layoutHeight = containerHeightBeforePlot, - relayoutHeight = layoutHeight; + var autosize = false; + var config = { + autosizable: true, + fillFrame: false + }; + var layoutHeight = containerHeightBeforePlot; + var relayoutHeight = layoutHeight; + testAutosize(autosize, config, layoutHeight, relayoutHeight, done); }); it('should fill the container when autosize: true, fillFrame: false and frameMargins: undefined', function(done) { - var autosize = true, - config = { - fillFrame: false - }, - layoutHeight = containerHeightBeforePlot, - relayoutHeight = containerHeightBeforeRelayout; + var autosize = true; + var config = { + fillFrame: false + }; + var layoutHeight = containerHeightBeforePlot; + var relayoutHeight = containerHeightBeforeRelayout; + testAutosize(autosize, config, layoutHeight, relayoutHeight, done); }); it('should fill the container when autosize: false, fillFrame: false and frameMargins: 0.1', function(done) { - var autosize = false, - config = { - autosizable: true, - fillFrame: false, - frameMargins: 0.1 - }, - layoutHeight = 360, - relayoutHeight = layoutHeight; + var autosize = false; + var config = { + autosizable: true, + fillFrame: false, + frameMargins: 0.1 + }; + var layoutHeight = 360; + var relayoutHeight = layoutHeight; + testAutosize(autosize, config, layoutHeight, relayoutHeight, done); }); it('should fill the container when autosize: true, fillFrame: false and frameMargins: 0.1', function(done) { - var autosize = true, - config = { - fillFrame: false, - frameMargins: 0.1 - }, - layoutHeight = 360, - relayoutHeight = 288; + var autosize = true; + var config = { + fillFrame: false, + frameMargins: 0.1 + }; + var layoutHeight = 360; + var relayoutHeight = 288; + testAutosize(autosize, config, layoutHeight, relayoutHeight, done); }); it('should respect attribute autosizable: false', function(done) { - var autosize = false, - config = { - autosizable: false, - fillFrame: true - }, - layoutHeight = Plots.layoutAttributes.height.dflt, - relayoutHeight = layoutHeight; + var autosize = false; + var config = { + autosizable: false, + fillFrame: true + }; + var layoutHeight = Plots.layoutAttributes.height.dflt; + var relayoutHeight = layoutHeight; + testAutosize(autosize, config, layoutHeight, relayoutHeight, done); }); }); @@ -148,9 +179,8 @@ describe('config argument', function() { var gd; - beforeEach(function(done) { + beforeEach(function() { gd = createGraphDiv(); - done(); }); afterEach(destroyGraphDiv); @@ -185,6 +215,8 @@ describe('config argument', function() { gd = createGraphDiv(); }); + afterEach(destroyGraphDiv); + function initPlot(editFlag) { var edits = {}; edits[editFlag] = true; @@ -201,17 +233,15 @@ describe('config argument', function() { }, { editable: false, edits: edits }); } - afterEach(destroyGraphDiv); - function checkIfEditable(elClass, text) { return function() { var label = document.getElementsByClassName(elClass)[0]; expect(label.textContent).toBe(text); - var labelBox = label.getBoundingClientRect(), - labelX = labelBox.left + labelBox.width / 2, - labelY = labelBox.top + labelBox.height / 2; + var labelBox = label.getBoundingClientRect(); + var labelX = labelBox.left + labelBox.width / 2; + var labelY = labelBox.top + labelBox.height / 2; mouseEvent('click', labelX, labelY); @@ -226,9 +256,9 @@ describe('config argument', function() { return function() { var el = document.getElementsByClassName(elClass)[0]; - var elBox = el.getBoundingClientRect(), - elX = elBox.left + elBox.width / 2, - elY = elBox.top + elBox.height / 2; + var elBox = el.getBoundingClientRect(); + var elX = elBox.left + elBox.width / 2; + var elY = elBox.top + elBox.height / 2; mouseEvent('mousedown', elX, elY); mouseEvent('mousemove', elX - 20, elY + 20); @@ -300,54 +330,63 @@ describe('config argument', function() { titleText: false }); }) + .catch(failTest) .then(done); }); it('should make titles editable', function(done) { initPlot('titleText') .then(checkIfEditable('gtitle', 'Click to enter Plot title')) + .catch(failTest) .then(done); }); it('should make x axes labels editable', function(done) { initPlot('axisTitleText') .then(checkIfEditable('g-xtitle', 'Click to enter X axis title')) + .catch(failTest) .then(done); }); it('should make y axes labels editable', function(done) { initPlot('axisTitleText') .then(checkIfEditable('g-ytitle', 'Click to enter Y axis title')) + .catch(failTest) .then(done); }); it('should make legend labels editable', function(done) { initPlot('legendText') .then(checkIfEditable('legendtext', 'trace 0')) + .catch(failTest) .then(done); }); it('should make annotation labels editable', function(done) { initPlot('annotationText') .then(checkIfEditable('annotation-text-g', 'testing')) + .catch(failTest) .then(done); }); it('should make annotation labels draggable', function(done) { initPlot('annotationTail') .then(checkIfDraggable('annotation-text-g')) + .catch(failTest) .then(done); }); it('should make annotation arrows draggable', function(done) { initPlot('annotationPosition') .then(checkIfDraggable('annotation-arrow-g')) + .catch(failTest) .then(done); }); it('should make legends draggable', function(done) { initPlot('legendPosition') .then(checkIfDraggable('legend')) + .catch(failTest) .then(done); }); @@ -367,55 +406,25 @@ describe('config argument', function() { afterEach(destroyGraphDiv); + function testDraggers(len) { + [ + 'nw', 'ne', 'sw', 'se', 'ew', 'w', 'e', 'ns', 'n', 's' + ].forEach(function(dir) { + var draggers = document.getElementsByClassName('drag ' + dir + 'drag'); + expect(draggers.length).toBe(len, dir); + }); + } + it('should have drag rectangles cursors by default', function() { Plotly.plot(gd, mockCopy.data, {}); - var nwdrag = document.getElementsByClassName('drag nwdrag'); - expect(nwdrag.length).toBe(1); - var nedrag = document.getElementsByClassName('drag nedrag'); - expect(nedrag.length).toBe(1); - var swdrag = document.getElementsByClassName('drag swdrag'); - expect(swdrag.length).toBe(1); - var sedrag = document.getElementsByClassName('drag sedrag'); - expect(sedrag.length).toBe(1); - var ewdrag = document.getElementsByClassName('drag ewdrag'); - expect(ewdrag.length).toBe(1); - var wdrag = document.getElementsByClassName('drag wdrag'); - expect(wdrag.length).toBe(1); - var edrag = document.getElementsByClassName('drag edrag'); - expect(edrag.length).toBe(1); - var nsdrag = document.getElementsByClassName('drag nsdrag'); - expect(nsdrag.length).toBe(1); - var sdrag = document.getElementsByClassName('drag sdrag'); - expect(sdrag.length).toBe(1); - var ndrag = document.getElementsByClassName('drag ndrag'); - expect(ndrag.length).toBe(1); - + testDraggers(1); }); it('should not have drag rectangles when disabled', function() { Plotly.plot(gd, mockCopy.data, {}, { showAxisDragHandles: false }); - var nwdrag = document.getElementsByClassName('drag nwdrag'); - expect(nwdrag.length).toBe(0); - var nedrag = document.getElementsByClassName('drag nedrag'); - expect(nedrag.length).toBe(0); - var swdrag = document.getElementsByClassName('drag swdrag'); - expect(swdrag.length).toBe(0); - var sedrag = document.getElementsByClassName('drag sedrag'); - expect(sedrag.length).toBe(0); - var ewdrag = document.getElementsByClassName('drag ewdrag'); - expect(ewdrag.length).toBe(0); - var wdrag = document.getElementsByClassName('drag wdrag'); - expect(wdrag.length).toBe(0); - var edrag = document.getElementsByClassName('drag edrag'); - expect(edrag.length).toBe(0); - var nsdrag = document.getElementsByClassName('drag nsdrag'); - expect(nsdrag.length).toBe(0); - var sdrag = document.getElementsByClassName('drag sdrag'); - expect(sdrag.length).toBe(0); - var ndrag = document.getElementsByClassName('drag ndrag'); - expect(ndrag.length).toBe(0); + testDraggers(0); }); }); @@ -423,8 +432,7 @@ describe('config argument', function() { describe('axis range entry attribute', function() { var mock = require('@mocks/14.json'); - var gd; - var mockCopy; + var gd, mockCopy; beforeEach(function(done) { gd = createGraphDiv(); @@ -434,14 +442,13 @@ describe('config argument', function() { afterEach(destroyGraphDiv); - it('show allow axis range entry by default', function() { + it('allows axis range entry by default', function() { Plotly.plot(gd, mockCopy.data, {}); var corner = document.getElementsByClassName('edrag')[0]; - - var cornerBox = corner.getBoundingClientRect(), - cornerX = cornerBox.left + cornerBox.width / 2, - cornerY = cornerBox.top + cornerBox.height / 2; + var cornerBox = corner.getBoundingClientRect(); + var cornerX = cornerBox.left + cornerBox.width / 2; + var cornerY = cornerBox.top + cornerBox.height / 2; click(cornerX, cornerY); @@ -450,14 +457,13 @@ describe('config argument', function() { expect(editBox.getAttribute('contenteditable')).toBe('true'); }); - it('show not allow axis range entry when', function() { + it('disallows axis range entry when disabled', function() { Plotly.plot(gd, mockCopy.data, {}, { showAxisRangeEntryBoxes: false }); var corner = document.getElementsByClassName('edrag')[0]; - - var cornerBox = corner.getBoundingClientRect(), - cornerX = cornerBox.left + cornerBox.width / 2, - cornerY = cornerBox.top + cornerBox.height / 2; + var cornerBox = corner.getBoundingClientRect(); + var cornerX = cornerBox.left + cornerBox.width / 2; + var cornerY = cornerBox.top + cornerBox.height / 2; click(cornerX, cornerY); diff --git a/test/jasmine/tests/gl2d_plot_interact_test.js b/test/jasmine/tests/gl2d_plot_interact_test.js index ffcda67b908..372524acb01 100644 --- a/test/jasmine/tests/gl2d_plot_interact_test.js +++ b/test/jasmine/tests/gl2d_plot_interact_test.js @@ -907,8 +907,8 @@ describe('Test scattergl autorange:', function() { marker: {size: 10} }]) .then(function() { - expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.02, 1.02], 'x range'); - expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.04, 1.04], 'y range'); + expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.079, 1.079], 2, 'x range'); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.105, 1.105], 2, 'y range'); }) .catch(fail) .then(done); @@ -925,8 +925,8 @@ describe('Test scattergl autorange:', function() { marker: {size: ms} }]) .then(function() { - expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.05, 1.05], 'x range'); - expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.11, 1.11], 'y range'); + expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.119, 1.119], 2, 'x range'); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.199, 1.199], 2, 'y range'); }) .catch(fail) .then(done); diff --git a/test/jasmine/tests/gl2d_pointcloud_test.js b/test/jasmine/tests/gl2d_pointcloud_test.js index 8544357730d..ebe0e414f8c 100644 --- a/test/jasmine/tests/gl2d_pointcloud_test.js +++ b/test/jasmine/tests/gl2d_pointcloud_test.js @@ -165,11 +165,11 @@ describe('@gl pointcloud traces', function() { var mock = plotData; var scene2d; - var xBaselineMins = [{'val': 0, 'pad': 50}, {'val': 0, 'pad': 50}, {'val': 3, 'pad': 50}, {'val': 1, 'pad': 50}, {'val': 1, 'pad': 50}, {'val': 1, 'pad': 50}, {'val': 1, 'pad': 50}]; - var xBaselineMaxes = [{'val': 9, 'pad': 50}, {'val': 9, 'pad': 50}, {'val': 6, 'pad': 50}, {'val': 9, 'pad': 50}, {'val': 9, 'pad': 50}, {'val': 9, 'pad': 50}, {'val': 9, 'pad': 50}]; + var xBaselineMins = [{val: 0, pad: 50, extrapad: false}]; + var xBaselineMaxes = [{val: 9, pad: 50, extrapad: false}]; - var yBaselineMins = [{'val': 0, 'pad': 50}, {'val': 0, 'pad': 50}, {'val': 9, 'pad': 50}, {'val': 3, 'pad': 50}, {'val': 4, 'pad': 50}, {'val': 5, 'pad': 50}, {'val': 6, 'pad': 50}]; - var yBaselineMaxes = [{'val': 9, 'pad': 50}, {'val': 9, 'pad': 50}, {'val': 9, 'pad': 50}, {'val': 3, 'pad': 50}, {'val': 4, 'pad': 50}, {'val': 5, 'pad': 50}, {'val': 6, 'pad': 50}]; + var yBaselineMins = [{val: 0, pad: 50, extrapad: false}]; + var yBaselineMaxes = [{val: 9, pad: 50, extrapad: false}]; Plotly.plot(gd, mock.data, mock.layout).then(function() { scene2d = gd._fullLayout._plots.xy._scene2d; @@ -185,8 +185,8 @@ describe('@gl pointcloud traces', function() { 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._min).toEqual([]); + expect(scene2d.xaxis._max).toEqual([]); return Plotly.relayout(gd, 'xaxis.autorange', true); }).then(function() { @@ -197,8 +197,8 @@ describe('@gl pointcloud traces', function() { return Plotly.relayout(gd, 'yaxis.range', [8, 20]); }).then(function() { - expect(scene2d.yaxis._min).toEqual(yBaselineMins); - expect(scene2d.yaxis._max).toEqual(yBaselineMaxes); + expect(scene2d.yaxis._min).toEqual([]); + expect(scene2d.yaxis._max).toEqual([]); return Plotly.relayout(gd, 'yaxis.autorange', true); }).then(function() { diff --git a/test/jasmine/tests/gl2d_scatterplot_contour_test.js b/test/jasmine/tests/gl2d_scatterplot_contour_test.js index e9c49f84eb5..96ad17c4562 100644 --- a/test/jasmine/tests/gl2d_scatterplot_contour_test.js +++ b/test/jasmine/tests/gl2d_scatterplot_contour_test.js @@ -222,34 +222,32 @@ 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}]); - expect(scene2d.xaxis._max).toEqual([{ val: 1, pad: 0}]); + 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]); }).then(function() { - expect(scene2d.xaxis._min).toEqual([]); - expect(scene2d.xaxis._max).toEqual([]); + expect(scene2d.xaxis.range).toEqual([0, -10]); return Plotly.relayout(gd, 'xaxis.autorange', true); }).then(function() { - expect(scene2d.xaxis._min).toEqual([{ val: -1, pad: 0}]); - expect(scene2d.xaxis._max).toEqual([{ val: 1, pad: 0}]); + expect(scene2d.xaxis.range).toEqual([1, -1]); // autorange keeps its reversal 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}]); - expect(scene2d.xaxis._max).toEqual([{ val: 1, pad: 0}]); + 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]); }).then(function() { - expect(scene2d.xaxis._min).toEqual([]); - expect(scene2d.xaxis._max).toEqual([]); + expect(scene2d.xaxis.range).toEqual([0, -10]); return Plotly.relayout(gd, 'xaxis.autorange', true); }).then(function() { - expect(scene2d.xaxis._min).toEqual([{ val: -1, pad: 0}]); - expect(scene2d.xaxis._max).toEqual([{ val: 1, pad: 0}]); + expect(scene2d.xaxis.range).toEqual([1, -1]); done(); }); diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index 6f65a6ce049..085f9fbc961 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -507,6 +507,8 @@ describe('Test plot api', function() { 'doCamera' ]; + var gd; + beforeAll(function() { mockedMethods.forEach(function(m) { spyOn(subroutines, m); @@ -523,8 +525,20 @@ describe('Test plot api', function() { return gd; } + function expectModeBarOnly(msg) { + expect(gd.calcdata).toBeDefined(msg); + expect(subroutines.doModeBar.calls.count()).toBeGreaterThan(0, msg); + expect(subroutines.layoutReplot.calls.count()).toBe(0, msg); + } + + function expectReplot(msg) { + expect(gd.calcdata).toBeDefined(msg); + expect(subroutines.doModeBar.calls.count()).toBe(0, msg); + expect(subroutines.layoutReplot.calls.count()).toBeGreaterThan(0, msg); + } + it('should trigger replot (but not recalc) when switching into select or lasso dragmode for scattergl traces', function() { - var gd = mock({ + gd = mock({ data: [{ type: 'scattergl', x: [1, 2, 3], @@ -535,35 +549,69 @@ describe('Test plot api', function() { } }); - function expectModeBarOnly() { - expect(gd.calcdata).toBeDefined(); - expect(subroutines.doModeBar).toHaveBeenCalled(); - expect(subroutines.layoutReplot).not.toHaveBeenCalled(); - } - - function expectReplot() { - expect(gd.calcdata).toBeDefined(); - expect(subroutines.doModeBar).not.toHaveBeenCalled(); - expect(subroutines.layoutReplot).toHaveBeenCalled(); - } - Plotly.relayout(gd, 'dragmode', 'pan'); - expectModeBarOnly(); + expectModeBarOnly('pan'); Plotly.relayout(mock(gd), 'dragmode', 'lasso'); - expectReplot(); + expectReplot('lasso 1'); Plotly.relayout(mock(gd), 'dragmode', 'select'); - expectModeBarOnly(); + expectModeBarOnly('select 1'); Plotly.relayout(mock(gd), 'dragmode', 'lasso'); - expectModeBarOnly(); + expectModeBarOnly('lasso 2'); Plotly.relayout(mock(gd), 'dragmode', 'zoom'); - expectModeBarOnly(); + expectModeBarOnly('zoom'); Plotly.relayout(mock(gd), 'dragmode', 'select'); - expectReplot(); + expectReplot('select 2'); + }); + + it('should trigger replot (but not recalc) when changing attributes that affect axis length/range', function() { + // but axis.autorange itself is NOT here, because setting it from false to true requires an + // autorange so that we calculate _min and _max, which we ignore if autorange is off. + var axLayoutEdits = { + 'xaxis.rangemode': 'tozero', + 'xaxis.domain': [0.2, 0.8], + 'xaxis.domain[1]': 0.7, + 'yaxis.domain': [0.1, 0.9], + 'yaxis.domain[0]': 0.3, + 'yaxis.overlaying': 'y2', + 'margin.l': 50, + 'margin.r': 20, + 'margin.t': 1, + 'margin.b': 5, + 'margin.autoexpand': false, + height: 567, + width: 432, + 'grid.rows': 2, + 'grid.columns': 3, + 'grid.xgap': 0.5, + 'grid.ygap': 0, + 'grid.roworder': 'bottom to top', + 'grid.pattern': 'independent', + 'grid.yaxes': ['y2', 'y'], + 'grid.xaxes[0]': 'x2', + 'grid.domain': {x: [0, 0.4], y: [0.6, 1]}, + 'grid.domain.x': [0.01, 0.99], + 'grid.domain.y[0]': 0.33, + 'grid.subplots': [['', 'xy'], ['x2y2', '']], + 'grid.subplots[1][1]': 'xy' + }; + + for(var attr in axLayoutEdits) { + gd = mock({ + data: [{y: [1, 2]}, {y: [4, 3], xaxis: 'x2', yaxis: 'y2'}], + layout: { + xaxis2: {domain: [0.6, 0.9]}, + yaxis2: {domain: [0.6, 0.9]} + } + }); + + Plotly.relayout(gd, attr, axLayoutEdits[attr]); + expectReplot(attr); + } }); }); @@ -2785,6 +2833,43 @@ describe('Test plot api', function() { }); }); }); + + describe('resizing with Plotly.relayout and Plotly.react', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + it('recalculates autoranges when height/width change', function(done) { + Plotly.newPlot(gd, + [{y: [1, 2], marker: {size: 100}}], + {width: 400, height: 400, margin: {l: 100, r: 100, t: 100, b: 100}} + ) + .then(function() { + expect(gd.layout.xaxis.range).toBeCloseToArray([-1.31818, 2.31818], 3); + expect(gd.layout.yaxis.range).toBeCloseToArray([-0.31818, 3.31818], 3); + + return Plotly.relayout(gd, {height: 800, width: 800}); + }) + .then(function() { + expect(gd.layout.xaxis.range).toBeCloseToArray([-0.22289, 1.22289], 3); + expect(gd.layout.yaxis.range).toBeCloseToArray([0.77711, 2.22289], 3); + + gd.layout.width = 500; + gd.layout.height = 500; + return Plotly.react(gd, gd.data, gd.layout); + }) + .then(function() { + expect(gd.layout.xaxis.range).toBeCloseToArray([-0.53448, 1.53448], 3); + expect(gd.layout.yaxis.range).toBeCloseToArray([0.46552, 2.53448], 3); + }) + .catch(fail) + .then(done); + }); + }); }); describe('plot_api helpers', function() {