diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index c483db44931..e43c0daf850 100644
--- a/src/plot_api/plot_api.js
+++ b/src/plot_api/plot_api.js
@@ -2055,7 +2055,7 @@ function _relayout(gd, aobj) {
// we're editing the (auto)range of, so we can tell the others constrained
// to scale with them that it's OK for them to shrink
var rangesAltered = {};
- var axId;
+ var axId, ax;
function recordAlteredAxis(pleafPlus) {
var axId = Axes.name2id(pleafPlus.split('.')[0]);
@@ -2145,7 +2145,7 @@ function _relayout(gd, aobj) {
// previously we did this for log <-> not-log, but now only do it
// for log <-> linear
if(pleaf === 'type') {
- var ax = parentIn;
+ ax = parentIn;
var toLog = parentFull.type === 'linear' && vi === 'log';
var fromLog = parentFull.type === 'log' && vi === 'linear';
@@ -2283,21 +2283,19 @@ function _relayout(gd, aobj) {
}
// figure out if we need to recalculate axis constraints
- var constraints = fullLayout._axisConstraintGroups || [];
for(axId in rangesAltered) {
- for(i = 0; i < constraints.length; i++) {
- var group = constraints[i];
- if(group[axId]) {
- // Always recalc if we're changing constrained ranges.
- // Otherwise it's possible to violate the constraints by
- // specifying arbitrary ranges for all axes in the group.
- // this way some ranges may expand beyond what's specified,
- // as they do at first draw, to satisfy the constraints.
- flags.calc = true;
- for(var groupAxId in group) {
- if(!rangesAltered[groupAxId]) {
- Axes.getFromId(gd, groupAxId)._constraintShrinkable = true;
- }
+ ax = Axes.getFromId(gd, axId);
+ var group = ax && ax._constraintGroup;
+ if(group) {
+ // Always recalc if we're changing constrained ranges.
+ // Otherwise it's possible to violate the constraints by
+ // specifying arbitrary ranges for all axes in the group.
+ // this way some ranges may expand beyond what's specified,
+ // as they do at first draw, to satisfy the constraints.
+ flags.calc = true;
+ for(var groupAxId in group) {
+ if(!rangesAltered[groupAxId]) {
+ Axes.getFromId(gd, groupAxId)._constraintShrinkable = true;
}
}
}
diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js
index 9769f214392..36d66ac20b9 100644
--- a/src/plot_api/subroutines.js
+++ b/src/plot_api/subroutines.js
@@ -667,57 +667,34 @@ exports.redrawReglTraces = function(gd) {
};
exports.doAutoRangeAndConstraints = function(gd) {
- var fullLayout = gd._fullLayout;
var axList = Axes.list(gd, '', true);
- var matchGroups = fullLayout._axisMatchGroups || [];
- var axLookup = {};
var ax;
- var axRng;
+
+ var autoRangeDone = {};
for(var i = 0; i < axList.length; i++) {
ax = axList[i];
- cleanAxisConstraints(gd, ax);
- doAutoRange(gd, ax);
- axLookup[ax._id] = 1;
- }
-
- enforceAxisConstraints(gd);
-
- groupLoop:
- for(var j = 0; j < matchGroups.length; j++) {
- var group = matchGroups[j];
- var rng = null;
- var id;
-
- for(id in group) {
- ax = Axes.getFromId(gd, id);
-
- // skip over 'missing' axes which do not pass through doAutoRange
- if(!axLookup[ax._id]) continue;
- // if one axis has autorange false, we're done
- if(ax.autorange === false) continue groupLoop;
-
- axRng = Lib.simpleMap(ax.range, ax.r2l);
- if(rng) {
- if(rng[0] < rng[1]) {
- rng[0] = Math.min(rng[0], axRng[0]);
- rng[1] = Math.max(rng[1], axRng[1]);
- } else {
- rng[0] = Math.max(rng[0], axRng[0]);
- rng[1] = Math.min(rng[1], axRng[1]);
+ if(!autoRangeDone[ax._id]) {
+ autoRangeDone[ax._id] = 1;
+ cleanAxisConstraints(gd, ax);
+ doAutoRange(gd, ax);
+
+ // For matching axes, just propagate this autorange to the group.
+ // The extra arg to doAutoRange avoids recalculating the range,
+ // since doAutoRange by itself accounts for all matching axes. but
+ // there are other side-effects of doAutoRange that we still want.
+ var matchGroup = ax._matchGroup;
+ if(matchGroup) {
+ for(var id2 in matchGroup) {
+ var ax2 = Axes.getFromId(gd, id2);
+ doAutoRange(gd, ax2, ax.range);
+ autoRangeDone[id2] = 1;
}
- } else {
- rng = axRng;
}
}
-
- for(id in group) {
- ax = Axes.getFromId(gd, id);
- ax.range = Lib.simpleMap(rng, ax.l2r);
- ax._input.range = ax.range.slice();
- ax.setScale();
- }
}
+
+ enforceAxisConstraints(gd);
};
// An initial paint must be completed before these components can be
diff --git a/src/plots/cartesian/autorange.js b/src/plots/cartesian/autorange.js
index d2c90fcb271..28c44197a0c 100644
--- a/src/plots/cartesian/autorange.js
+++ b/src/plots/cartesian/autorange.js
@@ -14,6 +14,8 @@ var Lib = require('../../lib');
var FP_SAFE = require('../../constants/numerical').FP_SAFE;
var Registry = require('../../registry');
+var getFromId = require('./axis_ids').getFromId;
+
module.exports = {
getAutoRange: getAutoRange,
makePadFn: makePadFn,
@@ -213,7 +215,7 @@ function makePadFn(ax) {
return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); };
}
-function concatExtremes(gd, ax) {
+function concatExtremes(gd, ax, noMatch) {
var axId = ax._id;
var fullData = gd._fullData;
var fullLayout = gd._fullLayout;
@@ -242,14 +244,34 @@ function concatExtremes(gd, ax) {
_concat(fullLayout.annotations || [], ax._annIndices || []);
_concat(fullLayout.shapes || [], ax._shapeIndices || []);
+ // Include the extremes from other matched axes with this one
+ if(ax._matchGroup && !noMatch) {
+ for(var axId2 in ax._matchGroup) {
+ if(axId2 !== ax._id) {
+ var ax2 = getFromId(gd, axId2);
+ var extremes2 = concatExtremes(gd, ax2, true);
+ // convert padding on the second axis to the first with lenRatio
+ var lenRatio = ax._length / ax2._length;
+ for(j = 0; j < extremes2.min.length; j++) {
+ d = extremes2.min[j];
+ collapseMinArray(minArray, d.val, d.pad * lenRatio, {extrapad: d.extrapad});
+ }
+ for(j = 0; j < extremes2.max.length; j++) {
+ d = extremes2.max[j];
+ collapseMaxArray(maxArray, d.val, d.pad * lenRatio, {extrapad: d.extrapad});
+ }
+ }
+ }
+ }
+
return {min: minArray, max: maxArray};
}
-function doAutoRange(gd, ax) {
+function doAutoRange(gd, ax, presetRange) {
ax.setScale();
if(ax.autorange) {
- ax.range = getAutoRange(gd, ax);
+ ax.range = presetRange ? presetRange.slice() : getAutoRange(gd, ax);
ax._r = ax.range.slice();
ax._rl = Lib.simpleMap(ax._r, ax.r2l);
diff --git a/src/plots/cartesian/axis_ids.js b/src/plots/cartesian/axis_ids.js
index dff56784fe3..db2c3705275 100644
--- a/src/plots/cartesian/axis_ids.js
+++ b/src/plots/cartesian/axis_ids.js
@@ -123,16 +123,6 @@ exports.idSort = function(id1, id2) {
return +(id1.substr(1) || 1) - +(id2.substr(1) || 1);
};
-exports.getAxisGroup = function getAxisGroup(fullLayout, axId) {
- var matchGroups = fullLayout._axisMatchGroups;
-
- for(var i = 0; i < matchGroups.length; i++) {
- var group = matchGroups[i];
- if(group[axId]) return 'g' + i;
- }
- return axId;
-};
-
/*
* An axis reference (e.g., the contents at the 'xref' key of an object) might
* have extra information appended. Extract the axis ID only.
diff --git a/src/plots/cartesian/constraints.js b/src/plots/cartesian/constraints.js
index d3c0c138152..85a4db0edf6 100644
--- a/src/plots/cartesian/constraints.js
+++ b/src/plots/cartesian/constraints.js
@@ -9,74 +9,280 @@
'use strict';
var Lib = require('../../lib');
+
+var autorange = require('./autorange');
var id2name = require('./axis_ids').id2name;
+var layoutAttributes = require('./layout_attributes');
var scaleZoom = require('./scale_zoom');
-var makePadFn = require('./autorange').makePadFn;
-var concatExtremes = require('./autorange').concatExtremes;
+var setConvert = require('./set_convert');
var ALMOST_EQUAL = require('../../constants/numerical').ALMOST_EQUAL;
var FROM_BL = require('../../constants/alignment').FROM_BL;
-exports.handleConstraintDefaults = function(containerIn, containerOut, coerce, opts) {
- var allAxisIds = opts.allAxisIds;
+exports.handleDefaults = function(layoutIn, layoutOut, opts) {
+ var axIds = opts.axIds;
+ var axHasImage = opts.axHasImage;
+
+ // sets of axes linked by `scaleanchor` OR `matches` along with the
+ // scaleratios compounded together, populated in handleConstraintDefaults
+ var constraintGroups = layoutOut._axisConstraintGroups = [];
+ // similar to _axisConstraintGroups, but only matching axes
+ var matchGroups = layoutOut._axisMatchGroups = [];
+
+ var i, group, axId, axName, axIn, axOut, attr, val;
+
+ for(i = 0; i < axIds.length; i++) {
+ axName = id2name(axIds[i]);
+ axIn = layoutIn[axName];
+ axOut = layoutOut[axName];
+
+ handleOneAxDefaults(axIn, axOut, {
+ axIds: axIds,
+ layoutOut: layoutOut,
+ hasImage: axHasImage[axName]
+ });
+ }
+
+ // save matchGroup on each matching axis
+ function stash(groups, stashAttr) {
+ for(i = 0; i < groups.length; i++) {
+ group = groups[i];
+ for(axId in group) {
+ layoutOut[id2name(axId)][stashAttr] = group;
+ }
+ }
+ }
+ stash(matchGroups, '_matchGroup');
+
+ // If any axis in a constraint group is fixedrange, they all get fixed
+ // This covers matches axes, as they're now in the constraintgroup too
+ // and have not yet been removed (if the group is *only* matching)
+ for(i = 0; i < constraintGroups.length; i++) {
+ group = constraintGroups[i];
+ for(axId in group) {
+ axOut = layoutOut[id2name(axId)];
+ if(axOut.fixedrange) {
+ for(var axId2 in group) {
+ var axName2 = id2name(axId2);
+ if((layoutIn[axName2] || {}).fixedrange === false) {
+ Lib.warn(
+ 'fixedrange was specified as false for axis ' +
+ axName2 + ' but was overridden because another ' +
+ 'axis in its constraint group has fixedrange true'
+ );
+ }
+ layoutOut[axName2].fixedrange = true;
+ }
+ break;
+ }
+ }
+ }
+
+ // remove constraint groups that simply duplicate match groups
+ i = 0;
+ while(i < constraintGroups.length) {
+ group = constraintGroups[i];
+ for(axId in group) {
+ axOut = layoutOut[id2name(axId)];
+ if(axOut._matchGroup && Object.keys(axOut._matchGroup).length === Object.keys(group).length) {
+ constraintGroups.splice(i, 1);
+ i--;
+ }
+ break;
+ }
+ i++;
+ }
+
+ // save constraintGroup on each constrained axis
+ stash(constraintGroups, '_constraintGroup');
+
+ // make sure `matching` axes share values of necessary attributes
+ // Precedence (base axis is the one that doesn't list a `matches`, ie others
+ // all point to it):
+ // (1) explicitly defined value in the base axis
+ // (2) explicitly defined in another axis (arbitrary order)
+ // (3) default in the base axis
+ var matchAttrs = [
+ 'constrain',
+ 'range',
+ 'autorange',
+ 'rangemode',
+ 'rangebreaks',
+ 'categoryorder',
+ 'categoryarray'
+ ];
+ var hasRange = false;
+ var hasDayOfWeekBreaks = false;
+
+ function setAttrVal() {
+ val = axOut[attr];
+ if(attr === 'rangebreaks') {
+ hasDayOfWeekBreaks = axOut._hasDayOfWeekBreaks;
+ }
+ }
+
+ for(i = 0; i < matchGroups.length; i++) {
+ group = matchGroups[i];
+
+ // find 'matching' range attrs
+ for(var j = 0; j < matchAttrs.length; j++) {
+ attr = matchAttrs[j];
+ val = null;
+ var baseAx;
+ for(axId in group) {
+ axName = id2name(axId);
+ axIn = layoutIn[axName];
+ axOut = layoutOut[axName];
+ if(!(attr in axOut)) {
+ continue;
+ }
+ if(!axOut.matches) {
+ baseAx = axOut;
+ // top priority: explicit value in base axis
+ if(attr in axIn) {
+ setAttrVal();
+ break;
+ }
+ }
+ if(val === null && attr in axIn) {
+ // second priority: first explicit value in another axis
+ setAttrVal();
+ }
+ }
+
+ // special logic for coupling of range and autorange
+ // if nobody explicitly specifies autorange, but someone does
+ // explicitly specify range, autorange must be disabled.
+ if(attr === 'range' && val) {
+ hasRange = true;
+ }
+ if(attr === 'autorange' && val === null && hasRange) {
+ val = false;
+ }
+
+ if(val === null && attr in baseAx) {
+ // fallback: default value in base axis
+ val = baseAx[attr];
+ }
+ // but we still might not have a value, which is fine.
+ if(val !== null) {
+ for(axId in group) {
+ axOut = layoutOut[id2name(axId)];
+ axOut[attr] = attr === 'range' ? val.slice() : val;
+
+ if(attr === 'rangebreaks') {
+ axOut._hasDayOfWeekBreaks = hasDayOfWeekBreaks;
+ setConvert(axOut, layoutOut);
+ }
+ }
+ }
+ }
+ }
+};
+
+function handleOneAxDefaults(axIn, axOut, opts) {
+ var axIds = opts.axIds;
var layoutOut = opts.layoutOut;
- var scaleanchorDflt = opts.scaleanchorDflt;
- var constrainDflt = opts.constrainDflt;
+ var hasImage = opts.hasImage;
var constraintGroups = layoutOut._axisConstraintGroups;
var matchGroups = layoutOut._axisMatchGroups;
- var axId = containerOut._id;
+ var axId = axOut._id;
var axLetter = axId.charAt(0);
var splomStash = ((layoutOut._splomAxes || {})[axLetter] || {})[axId] || {};
- var thisID = containerOut._id;
- var letter = thisID.charAt(0);
+ var thisID = axOut._id;
+ var isX = thisID.charAt(0) === 'x';
+
+ // Clear _matchGroup & _constraintGroup so relinkPrivateKeys doesn't keep
+ // an old one around. If this axis is in a group we'll set this again later
+ axOut._matchGroup = null;
+ axOut._constraintGroup = null;
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(axIn, axOut, layoutAttributes, attr, dflt);
+ }
// coerce the constraint mechanics even if this axis has no scaleanchor
// because it may be the anchor of another axis.
- var constrain = coerce('constrain', constrainDflt);
- Lib.coerce(containerIn, containerOut, {
+ coerce('constrain', hasImage ? 'domain' : 'range');
+ Lib.coerce(axIn, axOut, {
constraintoward: {
valType: 'enumerated',
- values: letter === 'x' ? ['left', 'center', 'right'] : ['bottom', 'middle', 'top'],
- dflt: letter === 'x' ? 'center' : 'middle'
+ values: isX ? ['left', 'center', 'right'] : ['bottom', 'middle', 'top'],
+ dflt: isX ? 'center' : 'middle'
}
}, 'constraintoward');
- var matches, matchOpts;
+ // If this axis is already part of a constraint group, we can't
+ // scaleanchor any other axis in that group, or we'd make a loop.
+ // Filter axIds to enforce this, also matching axis types.
+ var thisType = axOut.type;
+ var i, idi;
- if((containerIn.matches || splomStash.matches) && !containerOut.fixedrange) {
- matchOpts = getConstraintOpts(matchGroups, thisID, allAxisIds, layoutOut);
- matches = Lib.coerce(containerIn, containerOut, {
+ var linkableAxes = [];
+ for(i = 0; i < axIds.length; i++) {
+ idi = axIds[i];
+ if(idi === thisID) continue;
+
+ var axi = layoutOut[id2name(idi)];
+ if(axi.type === thisType) {
+ linkableAxes.push(idi);
+ }
+ }
+
+ var thisGroup = getConstraintGroup(constraintGroups, thisID);
+ if(thisGroup) {
+ var linkableAxesNoLoops = [];
+ for(i = 0; i < linkableAxes.length; i++) {
+ idi = linkableAxes[i];
+ if(!thisGroup[idi]) linkableAxesNoLoops.push(idi);
+ }
+ linkableAxes = linkableAxesNoLoops;
+ }
+
+ var canLink = linkableAxes.length;
+
+ var matches, scaleanchor;
+
+ if(canLink && (axIn.matches || splomStash.matches)) {
+ matches = Lib.coerce(axIn, axOut, {
matches: {
valType: 'enumerated',
- values: matchOpts.linkableAxes || [],
- dflt: splomStash.matches
+ values: linkableAxes,
+ dflt: linkableAxes.indexOf(splomStash.matches) !== -1 ? splomStash.matches : undefined
}
}, 'matches');
}
- // 'matches' wins over 'scaleanchor' (for now)
- var scaleanchor, scaleOpts;
-
- if(!matches &&
- !(containerOut.fixedrange && constrain !== 'domain') &&
- (containerIn.scaleanchor || scaleanchorDflt)
- ) {
- scaleOpts = getConstraintOpts(constraintGroups, thisID, allAxisIds, layoutOut, constrain);
- scaleanchor = Lib.coerce(containerIn, containerOut, {
+ // 'matches' wins over 'scaleanchor' - each axis can only specify one
+ // constraint, but you can chain matches and scaleanchor constraints by
+ // specifying them in separate axes.
+ var scaleanchorDflt = hasImage && !isX ? axOut.anchor : undefined;
+ if(canLink && !matches && (axIn.scaleanchor || scaleanchorDflt)) {
+ scaleanchor = Lib.coerce(axIn, axOut, {
scaleanchor: {
valType: 'enumerated',
- values: scaleOpts.linkableAxes || []
+ values: linkableAxes
}
}, 'scaleanchor', scaleanchorDflt);
}
if(matches) {
- delete containerOut.constrain;
- updateConstraintGroups(matchGroups, matchOpts.thisGroup, thisID, matches, 1);
- } else if(allAxisIds.indexOf(containerIn.matches) !== -1) {
- Lib.warn('ignored ' + containerOut._name + '.matches: "' +
- containerIn.matches + '" to avoid either an infinite loop ' +
- 'or because the target axis has fixed range.');
+ axOut._matchGroup = updateConstraintGroups(matchGroups, thisID, matches, 1);
+
+ // Also include match constraints in the scale groups
+ var matchedAx = layoutOut[id2name(matches)];
+ var matchRatio = extent(layoutOut, axOut) / extent(layoutOut, matchedAx);
+ if(isX !== (matches.charAt(0) === 'x')) {
+ // We don't yet know the actual scale ratio of x/y matches constraints,
+ // due to possible automargins, so just leave a placeholder for this:
+ // 'x' means "x size over y size", 'y' means the inverse.
+ // in principle in the constraint group you could get multiple of these.
+ matchRatio = (isX ? 'x' : 'y') + matchRatio;
+ }
+ updateConstraintGroups(constraintGroups, thisID, matches, matchRatio);
+ } else if(axIn.matches && axIds.indexOf(axIn.matches) !== -1) {
+ Lib.warn('ignored ' + axOut._name + '.matches: "' +
+ axIn.matches + '" to avoid an infinite loop');
}
if(scaleanchor) {
@@ -87,64 +293,39 @@ exports.handleConstraintDefaults = function(containerIn, containerOut, coerce, o
// Of course if you use several super-tiny values you could eventually
// force a product of these to zero and all hell would break loose...
// Likewise with super-huge values.
- if(!scaleratio) scaleratio = containerOut.scaleratio = 1;
-
- updateConstraintGroups(constraintGroups, scaleOpts.thisGroup, thisID, scaleanchor, scaleratio);
- } else if(allAxisIds.indexOf(containerIn.scaleanchor) !== -1) {
- Lib.warn('ignored ' + containerOut._name + '.scaleanchor: "' +
- containerIn.scaleanchor + '" to avoid either an infinite loop ' +
- 'and possibly inconsistent scaleratios, or because the target ' +
- 'axis has fixed range or this axis declares a *matches* constraint.');
+ if(!scaleratio) scaleratio = axOut.scaleratio = 1;
+
+ updateConstraintGroups(constraintGroups, thisID, scaleanchor, scaleratio);
+ } else if(axIn.scaleanchor && axIds.indexOf(axIn.scaleanchor) !== -1) {
+ Lib.warn('ignored ' + axOut._name + '.scaleanchor: "' +
+ axIn.scaleanchor + '" to avoid either an infinite loop ' +
+ 'and possibly inconsistent scaleratios, or because this axis ' +
+ 'declares a *matches* constraint.');
}
-};
-
-// If this axis is already part of a constraint group, we can't
-// scaleanchor any other axis in that group, or we'd make a loop.
-// Filter allAxisIds to enforce this, also matching axis types.
-function getConstraintOpts(groups, thisID, allAxisIds, layoutOut, constrain) {
- var doesNotConstrainRange = constrain !== 'range';
- var thisType = layoutOut[id2name(thisID)].type;
- var i, j, idj, axj;
+}
- var linkableAxes = [];
- for(j = 0; j < allAxisIds.length; j++) {
- idj = allAxisIds[j];
- if(idj === thisID) continue;
-
- axj = layoutOut[id2name(idj)];
- if(axj.type === thisType) {
- if(!axj.fixedrange) {
- linkableAxes.push(idj);
- } else if(doesNotConstrainRange && axj.anchor) {
- // allow domain constraints on subplots where
- // BOTH axes have fixedrange:true and constrain:domain
- var counterAxj = layoutOut[id2name(axj.anchor)];
- if(counterAxj.fixedrange) {
- linkableAxes.push(idj);
- }
- }
- }
+function extent(layoutOut, ax) {
+ var domain = ax.domain;
+ if(!domain) {
+ // at this point overlaying axes haven't yet inherited their main domains
+ // TODO: constrain: domain with overlaying axes is likely a bug.
+ domain = layoutOut[id2name(ax.overlaying)].domain;
}
+ return domain[1] - domain[0];
+}
- for(i = 0; i < groups.length; i++) {
+function getConstraintGroup(groups, thisID) {
+ for(var i = 0; i < groups.length; i++) {
if(groups[i][thisID]) {
- var thisGroup = groups[i];
-
- var linkableAxesNoLoops = [];
- for(j = 0; j < linkableAxes.length; j++) {
- idj = linkableAxes[j];
- if(!thisGroup[idj]) linkableAxesNoLoops.push(idj);
- }
- return {linkableAxes: linkableAxesNoLoops, thisGroup: thisGroup};
+ return groups[i];
}
}
-
- return {linkableAxes: linkableAxes, thisGroup: null};
+ return null;
}
/*
* Add this axis to the axis constraint groups, which is the collection
- * of axes that are all constrained together on scale.
+ * of axes that are all constrained together on scale (or matching).
*
* constraintGroups: a list of objects. each object is
* {axis_id: scale_within_group}, where scale_within_group is
@@ -153,12 +334,14 @@ function getConstraintOpts(groups, thisID, allAxisIds, layoutOut, constrain) {
*
* thisGroup: the group the current axis is already in
* thisID: the id if the current axis
- * scaleanchor: the id of the axis to scale it with
- * scaleratio: the ratio of this axis to the scaleanchor axis
+ * thatID: the id of the axis to scale it with
+ * scaleratio: the ratio of this axis to the thatID axis
*/
-function updateConstraintGroups(constraintGroups, thisGroup, thisID, scaleanchor, scaleratio) {
+function updateConstraintGroups(constraintGroups, thisID, thatID, scaleratio) {
var i, j, groupi, keyj, thisGroupIndex;
+ var thisGroup = getConstraintGroup(constraintGroups, thisID);
+
if(thisGroup === null) {
thisGroup = {};
thisGroup[thisID] = 1;
@@ -171,38 +354,109 @@ function updateConstraintGroups(constraintGroups, thisGroup, thisID, scaleanchor
var thisGroupKeys = Object.keys(thisGroup);
// we know that this axis isn't in any other groups, but we don't know
- // about the scaleanchor axis. If it is, we need to merge the groups.
+ // about the thatID axis. If it is, we need to merge the groups.
for(i = 0; i < constraintGroups.length; i++) {
groupi = constraintGroups[i];
- if(i !== thisGroupIndex && groupi[scaleanchor]) {
- var baseScale = groupi[scaleanchor];
+ if(i !== thisGroupIndex && groupi[thatID]) {
+ var baseScale = groupi[thatID];
for(j = 0; j < thisGroupKeys.length; j++) {
keyj = thisGroupKeys[j];
- groupi[keyj] = baseScale * scaleratio * thisGroup[keyj];
+ groupi[keyj] = multiplyScales(baseScale, multiplyScales(scaleratio, thisGroup[keyj]));
}
constraintGroups.splice(thisGroupIndex, 1);
return;
}
}
- // otherwise, we insert the new scaleanchor axis as the base scale (1)
+ // otherwise, we insert the new thatID axis as the base scale (1)
// in its group, and scale the rest of the group to it
if(scaleratio !== 1) {
for(j = 0; j < thisGroupKeys.length; j++) {
- thisGroup[thisGroupKeys[j]] *= scaleratio;
+ var key = thisGroupKeys[j];
+ thisGroup[key] = multiplyScales(scaleratio, thisGroup[key]);
}
}
- thisGroup[scaleanchor] = 1;
+ thisGroup[thatID] = 1;
+}
+
+// scales may be numbers or 'x1.3', 'yy4.5' etc to multiply by as-yet-unknown
+// ratios between x and y plot sizes n times
+function multiplyScales(a, b) {
+ var aPrefix = '';
+ var bPrefix = '';
+ var aLen, bLen;
+
+ if(typeof a === 'string') {
+ aPrefix = a.match(/^[xy]*/)[0];
+ aLen = aPrefix.length;
+ a = +a.substr(aLen);
+ }
+
+ if(typeof b === 'string') {
+ bPrefix = b.match(/^[xy]*/)[0];
+ bLen = bPrefix.length;
+ b = +b.substr(bLen);
+ }
+
+ var c = a * b;
+
+ // just two numbers
+ if(!aLen && !bLen) {
+ return c;
+ }
+
+ // one or more prefixes of the same type
+ if(!aLen || !bLen || aPrefix.charAt(0) === bPrefix.charAt(0)) {
+ return aPrefix + bPrefix + (a * b);
+ }
+
+ // x and y cancel each other out exactly - back to a number
+ if(aLen === bLen) {
+ return c;
+ }
+
+ // partial cancelation of prefixes
+ return (aLen > bLen ? aPrefix.substr(bLen) : bPrefix.substr(aLen)) + c;
+}
+
+function finalRatios(group, fullLayout) {
+ var size = fullLayout._size;
+ var yRatio = size.h / size.w;
+ var out = {};
+ var keys = Object.keys(group);
+ for(var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var val = group[key];
+
+ if(typeof val === 'string') {
+ var prefix = val.match(/^[xy]*/)[0];
+ var pLen = prefix.length;
+ val = +val.substr(pLen);
+ var mult = prefix.charAt(0) === 'y' ? yRatio : (1 / yRatio);
+ for(var j = 0; j < pLen; j++) {
+ val *= mult;
+ }
+ }
+
+ out[key] = val;
+ }
+ return out;
}
exports.enforce = function enforce(gd) {
var fullLayout = gd._fullLayout;
var constraintGroups = fullLayout._axisConstraintGroups || [];
- var i, j, axisID, ax, normScale, mode, factor;
+ var i, j, group, axisID, ax, normScale, mode, factor;
+ // matching constraints are handled in the autorange code when autoranged,
+ // or in the supplyDefaults code when explicitly ranged.
+ // now we just need to handle scaleanchor constraints
+ // matches constraints that chain with scaleanchor constraints are included
+ // here too, but because matches has already been satisfied,
+ // any changes here should preserve that.
for(i = 0; i < constraintGroups.length; i++) {
- var group = constraintGroups[i];
+ group = finalRatios(constraintGroups[i], fullLayout);
var axisIDs = Object.keys(group);
var minScale = Infinity;
@@ -311,11 +565,11 @@ exports.enforce = function enforce(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);
+ var getPad = autorange.makePadFn(ax);
updateDomain(ax, factor);
var m = Math.abs(ax._m);
- var extremes = concatExtremes(gd, ax);
+ var extremes = autorange.concatExtremes(gd, ax);
var minArray = extremes.min;
var maxArray = extremes.max;
var newVal;
@@ -351,6 +605,16 @@ exports.enforce = function enforce(gd) {
}
};
+exports.getAxisGroup = function getAxisGroup(fullLayout, axId) {
+ var matchGroups = fullLayout._axisMatchGroups;
+
+ for(var i = 0; i < matchGroups.length; i++) {
+ var group = matchGroups[i];
+ if(group[axId]) return 'g' + i;
+ }
+ return axId;
+};
+
// For use before autoranging, check if this axis was previously constrained
// by domain but no longer is
exports.clean = function clean(gd, ax) {
diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js
index 8a77be91f93..e27c98c6063 100644
--- a/src/plots/cartesian/dragbox.js
+++ b/src/plots/cartesian/dragbox.js
@@ -125,10 +125,11 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
yActive = isDirectionActive(yaxes, ns);
allFixedRanges = !yActive && !xActive;
- links = calcLinks(gd, gd._fullLayout._axisConstraintGroups, xaHash, yaHash);
matches = calcLinks(gd, gd._fullLayout._axisMatchGroups, xaHash, yaHash);
- editX = ew || links.isSubplotConstrained || matches.isSubplotConstrained;
- editY = ns || links.isSubplotConstrained || matches.isSubplotConstrained;
+ links = calcLinks(gd, gd._fullLayout._axisConstraintGroups, xaHash, yaHash, matches);
+ var spConstrained = links.isSubplotConstrained || matches.isSubplotConstrained;
+ editX = ew || spConstrained;
+ editY = ns || spConstrained;
var fullLayout = gd._fullLayout;
hasScatterGl = fullLayout._has('scattergl');
@@ -567,6 +568,22 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
gd._fullLayout._replotting = true;
if(xActive === 'ew' || yActive === 'ns') {
+ var spDx = xActive ? -dx : 0;
+ var spDy = yActive ? -dy : 0;
+ if(matches.isSubplotConstrained) {
+ if(xActive && yActive) {
+ var frac = (dx / pw - dy / ph) / 2;
+ dx = frac * pw;
+ dy = -frac * ph;
+ spDx = -dx;
+ spDy = -dy;
+ }
+ if(yActive) {
+ spDx = -spDy * pw / ph;
+ } else {
+ spDy = -spDx * ph / pw;
+ }
+ }
if(xActive) {
dragAxList(xaxes, dx);
updateMatchedAxRange('x');
@@ -575,7 +592,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
dragAxList(yaxes, dy);
updateMatchedAxRange('y');
}
- updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]);
+ updateSubplots([spDx, spDy, pw, ph]);
ticksAndAnnotations();
gd.emit('plotly_relayouting', updates);
return;
@@ -606,15 +623,17 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
(movedAx._rl[end] - movedAx._rl[otherEnd]);
}
- if(links.isSubplotConstrained && xActive && yActive) {
+ var dxySign = ((xActive === 'w') === (yActive === 'n')) ? 1 : -1;
+ if(xActive && yActive && (links.isSubplotConstrained || matches.isSubplotConstrained)) {
// dragging a corner of a constrained subplot:
// respect the fixed corner, but harmonize dx and dy
- var dxySign = ((xActive === 'w') === (yActive === 'n')) ? 1 : -1;
var dxyFraction = (dx / pw + dxySign * dy / ph) / 2;
dx = dxyFraction * pw;
dy = dxySign * dxyFraction * ph;
}
+ var xStart, yStart;
+
if(xActive === 'w') dx = dz(xaxes, 0, dx);
else if(xActive === 'e') dx = dz(xaxes, 1, -dx);
else if(!xActive) dx = 0;
@@ -623,12 +642,16 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
else if(yActive === 's') dy = dz(yaxes, 0, -dy);
else if(!yActive) dy = 0;
- var xStart = (xActive === 'w') ? dx : 0;
- var yStart = (yActive === 'n') ? dy : 0;
+ xStart = (xActive === 'w') ? dx : 0;
+ yStart = (yActive === 'n') ? dy : 0;
- if(links.isSubplotConstrained) {
+ if(
+ (links.isSubplotConstrained && !matches.isSubplotConstrained) ||
+ // NW or SE on matching axes - create a symmetric zoom
+ (matches.isSubplotConstrained && xActive && yActive && dxySign > 0)
+ ) {
var i;
- if(!xActive && yActive.length === 1) {
+ if(matches.isSubplotConstrained || (!xActive && yActive.length === 1)) {
// dragging one end of the y axis of a constrained subplot
// scale the other axis the same about its middle
for(i = 0; i < xaxes.length; i++) {
@@ -638,7 +661,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
dx = dy * pw / ph;
xStart = dx / 2;
}
- if(!yActive && xActive.length === 1) {
+ if(matches.isSubplotConstrained || (!yActive && xActive.length === 1)) {
for(i = 0; i < yaxes.length; i++) {
yaxes[i].range = yaxes[i]._r.slice();
scaleZoom(yaxes[i], 1 - dx / pw);
@@ -648,9 +671,24 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
}
}
- updateMatchedAxRange('x');
- updateMatchedAxRange('y');
- updateSubplots([xStart, yStart, pw - dx, ph - dy]);
+ if(!matches.isSubplotConstrained || !yActive) {
+ updateMatchedAxRange('x');
+ }
+ if(!matches.isSubplotConstrained || !xActive) {
+ updateMatchedAxRange('y');
+ }
+ var xSize = pw - dx;
+ var ySize = ph - dy;
+ if(matches.isSubplotConstrained && !(xActive && yActive)) {
+ if(xActive) {
+ yStart = xStart ? 0 : (dx * ph / pw);
+ ySize = xSize * ph / pw;
+ } else {
+ xStart = yStart ? 0 : (dy * pw / ph);
+ xSize = ySize * pw / ph;
+ }
+ }
+ updateSubplots([xStart, yStart, xSize, ySize]);
ticksAndAnnotations();
gd.emit('plotly_relayouting', updates);
}
@@ -850,15 +888,15 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
xa = sp.xaxis;
ya = sp.yaxis;
- var editX2 = editX && !xa.fixedrange && xaHash[xa._id];
- var editY2 = editY && !ya.fixedrange && yaHash[ya._id];
+ var editX2 = (editX || matches.isSubplotConstrained) && !xa.fixedrange && xaHash[xa._id];
+ var editY2 = (editY || matches.isSubplotConstrained) && !ya.fixedrange && yaHash[ya._id];
var xScaleFactor2, yScaleFactor2;
var clipDx, clipDy;
if(editX2) {
xScaleFactor2 = xScaleFactor;
- clipDx = ew ? viewBox[0] : getShift(xa, xScaleFactor2);
+ clipDx = ew || matches.isSubplotConstrained ? viewBox[0] : getShift(xa, xScaleFactor2);
} else if(matches.xaHash[xa._id]) {
xScaleFactor2 = xScaleFactor;
clipDx = viewBox[0] * xa._length / xa0._length;
@@ -874,7 +912,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
if(editY2) {
yScaleFactor2 = yScaleFactor;
- clipDy = ns ? viewBox[1] : getShift(ya, yScaleFactor2);
+ clipDy = ns || matches.isSubplotConstrained ? viewBox[1] : getShift(ya, yScaleFactor2);
} else if(matches.yaHash[ya._id]) {
yScaleFactor2 = yScaleFactor;
clipDy = viewBox[1] * ya._length / ya0._length;
@@ -1164,11 +1202,13 @@ function xyCorners(box) {
'h' + clen + 'v3h-' + (clen + 3) + 'Z';
}
-function calcLinks(gd, groups, xaHash, yaHash) {
+function calcLinks(gd, groups, xaHash, yaHash, exclude) {
var isSubplotConstrained = false;
var xLinks = {};
var yLinks = {};
var xID, yID, xLinkID, yLinkID;
+ var xExclude = (exclude || {}).xaHash;
+ var yExclude = (exclude || {}).yaHash;
for(var i = 0; i < groups.length; i++) {
var group = groups[i];
@@ -1179,14 +1219,22 @@ function calcLinks(gd, groups, xaHash, yaHash) {
// dragging them, so we know to scale these axes automatically too
// to match the changes in the dragged x axes
for(xLinkID in group) {
- if(!(xLinkID.charAt(0) === 'x' ? xaHash : yaHash)[xLinkID]) {
+ if(
+ !(exclude && (xExclude[xLinkID] || yExclude[xLinkID])) &&
+ !(xLinkID.charAt(0) === 'x' ? xaHash : yaHash)[xLinkID]
+ ) {
xLinks[xLinkID] = xID;
}
}
// check if the x and y axes of THIS drag are linked
for(yID in yaHash) {
- if(group[yID]) isSubplotConstrained = true;
+ if(
+ !(exclude && (xExclude[yID] || yExclude[yID])) &&
+ group[yID]
+ ) {
+ isSubplotConstrained = true;
+ }
}
}
}
@@ -1196,7 +1244,10 @@ function calcLinks(gd, groups, xaHash, yaHash) {
for(yID in yaHash) {
if(group[yID]) {
for(yLinkID in group) {
- if(!(yLinkID.charAt(0) === 'x' ? xaHash : yaHash)[yLinkID]) {
+ if(
+ !(exclude && (xExclude[yLinkID] || yExclude[yLinkID])) &&
+ !(yLinkID.charAt(0) === 'x' ? xaHash : yaHash)[yLinkID]
+ ) {
yLinks[yLinkID] = yID;
}
}
diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js
index 43d8d2c2fdc..6c92547f86e 100644
--- a/src/plots/cartesian/index.js
+++ b/src/plots/cartesian/index.js
@@ -335,6 +335,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
purgeSubplotLayers(oldFullLayout._cartesianlayer.selectAll('.subplot'), oldFullLayout);
oldFullLayout._defs.selectAll('.axesclip').remove();
delete oldFullLayout._axisConstraintGroups;
+ delete oldFullLayout._axisMatchGroups;
} else if(oldSubplotList.cartesian) {
// otherwise look for subplots we need to remove
diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js
index 9251e2cd50b..ef6b4205e15 100644
--- a/src/plots/cartesian/layout_attributes.js
+++ b/src/plots/cartesian/layout_attributes.js
@@ -223,13 +223,13 @@ module.exports = {
constrain: {
valType: 'enumerated',
values: ['range', 'domain'],
- dflt: 'range',
role: 'info',
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:',
- 'by increasing the *range* (default), or by decreasing the *domain*.'
+ 'by increasing the *range*, or by decreasing the *domain*.',
+ 'Default is *domain* for axes containing image traces, *range* otherwise.'
].join(' ')
},
// constraintoward: not used directly, just put here for reference
diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js
index 6ce3c069db1..bc43f6fcde4 100644
--- a/src/plots/cartesian/layout_defaults.js
+++ b/src/plots/cartesian/layout_defaults.js
@@ -19,7 +19,7 @@ var basePlotLayoutAttributes = require('../layout_attributes');
var layoutAttributes = require('./layout_attributes');
var handleTypeDefaults = require('./type_defaults');
var handleAxisDefaults = require('./axis_defaults');
-var handleConstraintDefaults = require('./constraints').handleConstraintDefaults;
+var constraints = require('./constraints');
var handlePositionDefaults = require('./position_defaults');
var axisIds = require('./axis_ids');
@@ -378,96 +378,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
// We need to do this after all axes have coerced both `type`
// (so we link only axes of the same type) and
// `fixedrange` (so we can avoid linking from OR TO a fixed axis).
-
- // sets of axes linked by `scaleanchor` along with the scaleratios compounded
- // together, populated in handleConstraintDefaults
- var constraintGroups = layoutOut._axisConstraintGroups = [];
- // similar to _axisConstraintGroups, but for matching axes
- var matchGroups = layoutOut._axisMatchGroups = [];
- // make sure to include 'missing' axes here
- var allAxisIdsIncludingMissing = allAxisIds.concat(missingMatchedAxisIds);
- var axNamesIncludingMissing = axNames.concat(Lib.simpleMap(missingMatchedAxisIds, id2name));
-
- for(i = 0; i < axNamesIncludingMissing.length; i++) {
- axName = axNamesIncludingMissing[i];
- axLetter = axName.charAt(0);
- axLayoutIn = layoutIn[axName];
- axLayoutOut = layoutOut[axName];
-
- var scaleanchorDflt;
- if(axLetter === 'y' && !axLayoutIn.hasOwnProperty('scaleanchor') && axHasImage[axName]) {
- scaleanchorDflt = axLayoutOut.anchor;
- } else {
- scaleanchorDflt = undefined;
- }
-
- var constrainDflt;
- if(!axLayoutIn.hasOwnProperty('constrain') && axHasImage[axName]) {
- constrainDflt = 'domain';
- } else {
- constrainDflt = undefined;
- }
-
- handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, {
- allAxisIds: allAxisIdsIncludingMissing,
- layoutOut: layoutOut,
- scaleanchorDflt: scaleanchorDflt,
- constrainDflt: constrainDflt
- });
- }
-
- for(i = 0; i < matchGroups.length; i++) {
- var group = matchGroups[i];
- var rng = null;
- var autorange = null;
-
- // find 'matching' range attrs
- for(axId in group) {
- axLayoutOut = layoutOut[id2name(axId)];
- if(!axLayoutOut.matches) {
- rng = axLayoutOut.range;
- autorange = axLayoutOut.autorange;
- }
- }
- // if `ax.matches` values are reciprocal,
- // pick values of first axis in group
- if(rng === null || autorange === null) {
- for(axId in group) {
- axLayoutOut = layoutOut[id2name(axId)];
- rng = axLayoutOut.range;
- autorange = axLayoutOut.autorange;
- break;
- }
- }
- // apply matching range attrs
- for(axId in group) {
- axLayoutOut = layoutOut[id2name(axId)];
- if(axLayoutOut.matches) {
- axLayoutOut.range = rng.slice();
- axLayoutOut.autorange = autorange;
- }
- axLayoutOut._matchGroup = group;
- }
-
- // remove matching axis from scaleanchor constraint groups (for now)
- if(constraintGroups.length) {
- for(axId in group) {
- for(j = 0; j < constraintGroups.length; j++) {
- var group2 = constraintGroups[j];
- for(var axId2 in group2) {
- if(axId === axId2) {
- Lib.warn('Axis ' + axId2 + ' is set with both ' +
- 'a *scaleanchor* and *matches* constraint; ' +
- 'ignoring the scale constraint.');
-
- delete group2[axId2];
- if(Object.keys(group2).length < 2) {
- constraintGroups.splice(j, 1);
- }
- }
- }
- }
- }
- }
- }
+ constraints.handleDefaults(layoutIn, layoutOut, {
+ axIds: allAxisIds.concat(missingMatchedAxisIds).sort(axisIds.idSort),
+ axHasImage: axHasImage
+ });
};
diff --git a/src/plots/cartesian/scale_zoom.js b/src/plots/cartesian/scale_zoom.js
index 3cdc8b4495f..db6fbfad9a4 100644
--- a/src/plots/cartesian/scale_zoom.js
+++ b/src/plots/cartesian/scale_zoom.js
@@ -23,4 +23,5 @@ module.exports = function scaleZoom(ax, factor, centerFraction) {
ax.l2r(center + (rangeLinear[0] - center) * factor),
ax.l2r(center + (rangeLinear[1] - center) * factor)
];
+ ax.setScale();
};
diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js
index af34c0febe1..5d55e06fd73 100644
--- a/src/plots/cartesian/set_convert.js
+++ b/src/plots/cartesian/set_convert.js
@@ -368,17 +368,12 @@ module.exports = function setConvert(ax, fullLayout) {
var traceIndices = ax._traceIndices;
var i, j;
- var matchGroups = fullLayout._axisMatchGroups;
- if(matchGroups && matchGroups.length && ax._categories.length === 0) {
- for(i = 0; i < matchGroups.length; i++) {
- var group = matchGroups[i];
- if(group[axId]) {
- for(var axId2 in group) {
- if(axId2 !== axId) {
- var ax2 = fullLayout[axisIds.id2name(axId2)];
- traceIndices = traceIndices.concat(ax2._traceIndices);
- }
- }
+ var group = ax._matchGroup;
+ if(group && ax._categories.length === 0) {
+ for(var axId2 in group) {
+ if(axId2 !== axId) {
+ var ax2 = fullLayout[axisIds.id2name(axId2)];
+ traceIndices = traceIndices.concat(ax2._traceIndices);
}
}
}
@@ -882,38 +877,26 @@ module.exports = function setConvert(ax, fullLayout) {
// should skip if not category nor multicategory
ax.clearCalc = function() {
- var matchGroups = fullLayout._axisMatchGroups;
-
- if(matchGroups && matchGroups.length) {
- var found = false;
-
- for(var i = 0; i < matchGroups.length; i++) {
- var group = matchGroups[i];
-
- if(group[axId]) {
- found = true;
- var categories = null;
- var categoriesMap = null;
-
- for(var axId2 in group) {
- var ax2 = fullLayout[axisIds.id2name(axId2)];
- if(ax2._categories) {
- categories = ax2._categories;
- categoriesMap = ax2._categoriesMap;
- break;
- }
- }
-
- if(categories && categoriesMap) {
- ax._categories = categories;
- ax._categoriesMap = categoriesMap;
- } else {
- ax._emptyCategories();
- }
+ var group = ax._matchGroup;
+ if(group) {
+ var categories = null;
+ var categoriesMap = null;
+
+ for(var axId2 in group) {
+ var ax2 = fullLayout[axisIds.id2name(axId2)];
+ if(ax2._categories) {
+ categories = ax2._categories;
+ categoriesMap = ax2._categoriesMap;
break;
}
}
- if(!found) ax._emptyCategories();
+
+ if(categories && categoriesMap) {
+ ax._categories = categories;
+ ax._categoriesMap = categoriesMap;
+ } else {
+ ax._emptyCategories();
+ }
} else {
ax._emptyCategories();
}
diff --git a/src/plots/plots.js b/src/plots/plots.js
index bccb37b4130..4d26e8e9331 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -3234,12 +3234,8 @@ function sortAxisCategoriesByValue(axList, gd) {
function setupAxisCategories(axList, fullData, fullLayout) {
var axLookup = {};
- var i, ax, axId;
-
- for(i = 0; i < axList.length; i++) {
- ax = axList[i];
- axId = ax._id;
+ function setupOne(ax) {
ax.clearCalc();
if(ax.type === 'multicategory') {
ax.setupMultiCategory(fullData);
@@ -3248,13 +3244,14 @@ function setupAxisCategories(axList, fullData, fullLayout) {
axLookup[ax._id] = 1;
}
+ Lib.simpleMap(axList, setupOne);
+
// look into match groups for 'missing' axes
var matchGroups = fullLayout._axisMatchGroups || [];
- for(i = 0; i < matchGroups.length; i++) {
- for(axId in matchGroups[i]) {
+ for(var i = 0; i < matchGroups.length; i++) {
+ for(var axId in matchGroups[i]) {
if(!axLookup[axId]) {
- ax = fullLayout[axisIDs.id2name(axId)];
- ax.clearCalc();
+ setupOne(fullLayout[axisIDs.id2name(axId)]);
}
}
}
diff --git a/src/traces/bar/cross_trace_calc.js b/src/traces/bar/cross_trace_calc.js
index 1bb5dda2f81..719174b555d 100644
--- a/src/traces/bar/cross_trace_calc.js
+++ b/src/traces/bar/cross_trace_calc.js
@@ -14,7 +14,7 @@ var BADNUM = require('../../constants/numerical').BADNUM;
var Registry = require('../../registry');
var Axes = require('../../plots/cartesian/axes');
-var getAxisGroup = require('../../plots/cartesian/axis_ids').getAxisGroup;
+var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup;
var Sieve = require('./sieve.js');
/*
diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js
index f9393cf766d..593a1670cff 100644
--- a/src/traces/bar/defaults.js
+++ b/src/traces/bar/defaults.js
@@ -15,7 +15,7 @@ var Registry = require('../../registry');
var handleXYDefaults = require('../scatter/xy_defaults');
var handlePeriodDefaults = require('../scatter/period_defaults');
var handleStyleDefaults = require('./style_defaults');
-var getAxisGroup = require('../../plots/cartesian/axis_ids').getAxisGroup;
+var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup;
var attributes = require('./attributes');
var coerceFont = Lib.coerceFont;
diff --git a/src/traces/box/cross_trace_calc.js b/src/traces/box/cross_trace_calc.js
index 72b57856cfb..165ef398627 100644
--- a/src/traces/box/cross_trace_calc.js
+++ b/src/traces/box/cross_trace_calc.js
@@ -10,7 +10,7 @@
var Axes = require('../../plots/cartesian/axes');
var Lib = require('../../lib');
-var getAxisGroup = require('../../plots/cartesian/axis_ids').getAxisGroup;
+var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup;
var orientations = ['v', 'h'];
diff --git a/src/traces/histogram/cross_trace_defaults.js b/src/traces/histogram/cross_trace_defaults.js
index 5a9edd34cdf..d900b44f95c 100644
--- a/src/traces/histogram/cross_trace_defaults.js
+++ b/src/traces/histogram/cross_trace_defaults.js
@@ -15,7 +15,7 @@ var traceIs = require('../../registry').traceIs;
var handleGroupingDefaults = require('../bar/defaults').handleGroupingDefaults;
var nestedProperty = Lib.nestedProperty;
-var getAxisGroup = axisIds.getAxisGroup;
+var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup;
var BINATTRS = [
{aStr: {x: 'xbins.start', y: 'ybins.start'}, name: 'start'},
diff --git a/test/image/baselines/axes_breaks-matches.png b/test/image/baselines/axes_breaks-matches.png
new file mode 100644
index 00000000000..7d5fb86addc
Binary files /dev/null and b/test/image/baselines/axes_breaks-matches.png differ
diff --git a/test/image/baselines/axes_chain_scaleanchor_matches.png b/test/image/baselines/axes_chain_scaleanchor_matches.png
new file mode 100644
index 00000000000..026c906cb55
Binary files /dev/null and b/test/image/baselines/axes_chain_scaleanchor_matches.png differ
diff --git a/test/image/baselines/axes_chain_scaleanchor_matches2.png b/test/image/baselines/axes_chain_scaleanchor_matches2.png
new file mode 100644
index 00000000000..33bed3a95a6
Binary files /dev/null and b/test/image/baselines/axes_chain_scaleanchor_matches2.png differ
diff --git a/test/image/baselines/axes_linked_date_autorange.png b/test/image/baselines/axes_linked_date_autorange.png
index 46b9e97cb94..3a2da78fe9f 100644
Binary files a/test/image/baselines/axes_linked_date_autorange.png and b/test/image/baselines/axes_linked_date_autorange.png differ
diff --git a/test/image/baselines/axes_scaleanchor-with-matches.png b/test/image/baselines/axes_scaleanchor-with-matches.png
index 56599d8d2ce..29e4065ffd2 100644
Binary files a/test/image/baselines/axes_scaleanchor-with-matches.png and b/test/image/baselines/axes_scaleanchor-with-matches.png differ
diff --git a/test/image/baselines/gl2d_marker_coloraxis.png b/test/image/baselines/gl2d_marker_coloraxis.png
index e1ec515186a..4724448d777 100644
Binary files a/test/image/baselines/gl2d_marker_coloraxis.png and b/test/image/baselines/gl2d_marker_coloraxis.png differ
diff --git a/test/image/baselines/matching-categories.png b/test/image/baselines/matching-categories.png
index e5c61632275..31d2a3c52c7 100644
Binary files a/test/image/baselines/matching-categories.png and b/test/image/baselines/matching-categories.png differ
diff --git a/test/image/mocks/axes_breaks-matches.json b/test/image/mocks/axes_breaks-matches.json
new file mode 100644
index 00000000000..4e51c5ed55c
--- /dev/null
+++ b/test/image/mocks/axes_breaks-matches.json
@@ -0,0 +1,71 @@
+{
+ "data": [
+ {
+ "mode": "lines+markers",
+ "x": [
+ "2020-05-09",
+ "2020-05-10",
+ "2020-05-11",
+ "2020-05-12",
+ "2020-05-13",
+ "2020-05-14",
+ "2020-05-15",
+ "2020-05-16",
+ "2020-05-17",
+ "2020-05-18",
+ "2020-05-19",
+ "2020-05-20",
+ "2020-05-21",
+ "2020-05-22",
+ "2020-05-23",
+ "2020-05-24",
+ "2020-05-25",
+ "2020-05-26",
+ "2020-05-27",
+ "2020-05-28"
+ ]
+ },
+ {
+ "mode": "lines+markers",
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "x": [
+ "2020-05-09",
+ "2020-05-10",
+ "2020-05-11",
+ "2020-05-12",
+ "2020-05-13",
+ "2020-05-14",
+ "2020-05-15",
+ "2020-05-16",
+ "2020-05-17",
+ "2020-05-18",
+ "2020-05-19",
+ "2020-05-20",
+ "2020-05-21",
+ "2020-05-22",
+ "2020-05-23",
+ "2020-05-24",
+ "2020-05-25",
+ "2020-05-26",
+ "2020-05-27",
+ "2020-05-28"
+ ],
+ "y0": 10
+ }
+ ],
+ "layout": {
+ "showlegend": false,
+ "width": 600,
+ "height": 400,
+ "xaxis": {"domain": [0, 0.45]},
+ "xaxis2": {
+ "rangebreaks": [{"bounds": ["sat", "mon"]}],
+ "matches": "x",
+ "anchor": "y2",
+ "domain": [0.55, 1]
+ },
+ "yaxis2": {"anchor": "x2"},
+ "title": {"text": "matches + rangebreaks"}
+ }
+}
diff --git a/test/image/mocks/axes_chain_scaleanchor_matches.json b/test/image/mocks/axes_chain_scaleanchor_matches.json
new file mode 100644
index 00000000000..36d95c91580
--- /dev/null
+++ b/test/image/mocks/axes_chain_scaleanchor_matches.json
@@ -0,0 +1,26 @@
+{
+ "data": [
+ {"z": [[1, 2], [3, 4], [5, 6]], "type": "heatmap", "showscale": false},
+ {"z": [[1, 2, 3], [6, 5, 4]], "type": "heatmap", "xaxis": "x2", "yaxis": "y2", "showscale": false},
+ {"z": [[1, 6], [2, 5], [3, 4]], "type": "heatmap", "xaxis": "x3", "yaxis": "y3", "showscale": false},
+ {"z": [[1, 2, 3], [4, 5, 6]], "type": "heatmap", "xaxis": "x4", "yaxis": "y4", "showscale": false}
+ ],
+ "layout": {
+ "xaxis": {"domain": [0, 0.4], "constrain": "domain"},
+ "yaxis": {"domain": [0, 0.3], "constrain": "domain", "scaleanchor": "x", "title": {"text": "constrain domain"}},
+ "xaxis2": {"domain": [0.6, 1], "matches": "x", "anchor": "y2", "title": {"text": "<- each right subplot matches
the axes left and below
and all are constrained
to square bricks."}},
+ "yaxis2": {"domain": [0.2, 0.5], "matches": "y", "anchor": "x2"},
+ "xaxis3": {"domain": [0, 0.4], "constrain": "range", "anchor": "y3"},
+ "yaxis3": {"domain": [0.5, 0.8], "constrain": "range", "scaleanchor": "x3", "anchor": "x3", "title": {"text": "constrain range"}},
+ "xaxis4": {"domain": [0.6, 1], "matches": "x3", "anchor": "y4"},
+ "yaxis4": {"domain": [0.7, 1], "matches": "y3", "anchor": "x4"},
+ "shapes": [
+ {"x0": 0, "x1": 0.4, "y0": 0, "y1": 0.3, "xref": "paper", "yref": "paper", "type": "rect", "line": {"color": "#888", "dash": "dot"}},
+ {"x0": 0.6, "x1": 1, "y0": 0.2, "y1": 0.5, "xref": "paper", "yref": "paper", "type": "rect", "line": {"color": "#888", "dash": "dot"}},
+ {"x0": 0, "x1": 0.4, "y0": 0.5, "y1": 0.8, "xref": "paper", "yref": "paper", "type": "rect", "line": {"color": "#888", "dash": "dot"}},
+ {"x0": 0.6, "x1": 1, "y0": 0.7, "y1": 1, "xref": "paper", "yref": "paper", "type": "rect", "line": {"color": "#888", "dash": "dot"}}
+ ],
+ "width": 700,
+ "height": 600
+ }
+}
diff --git a/test/image/mocks/axes_chain_scaleanchor_matches2.json b/test/image/mocks/axes_chain_scaleanchor_matches2.json
new file mode 100644
index 00000000000..aee211fd4c8
--- /dev/null
+++ b/test/image/mocks/axes_chain_scaleanchor_matches2.json
@@ -0,0 +1,82 @@
+{
+ "data": [
+ {"y":[1,2], "marker": {"color": "red"}},
+ {"y":[1,2],"xaxis":"x2","yaxis":"y2", "marker": {"color": "red"}},
+ {"y":[1,2],"xaxis":"x3","yaxis":"y3", "marker": {"color": "red"}},
+ {"y":[1,2],"xaxis":"x4","yaxis":"y4", "marker": {"color": "red"}},
+ {"y":[1,2],"xaxis":"x5","yaxis":"y5", "marker": {"color": "green"}},
+ {"y":[1,2],"xaxis":"x6","yaxis":"y6", "marker": {"color": "green"}},
+ {"y":[1,2],"xaxis":"x7","yaxis":"y7", "marker": {"color": "green"}},
+ {"y":[1,2],"xaxis":"x8","yaxis":"y8", "marker": {"color": "green"}},
+ {"y":[1,2],"xaxis":"x9","yaxis":"y9", "marker": {"color": "blue"}},
+ {"y":[1,2],"xaxis":"x10","yaxis":"y10", "marker": {"color": "blue"}},
+ {"y":[1,2],"xaxis":"x11","yaxis":"y11", "marker": {"color": "blue"}},
+ {"y":[1,2],"xaxis":"x12","yaxis":"y12", "marker": {"color": "blue"}},
+ {"y":[1,2],"xaxis":"x13","yaxis":"y13", "marker": {"color": "black"}},
+ {"y":[1,2],"xaxis":"x14","yaxis":"y14", "marker": {"color": "black"}},
+ {"y":[1,2],"xaxis":"x15","yaxis":"y15", "marker": {"color": "black"}},
+ {"y":[1,2],"xaxis":"x16","yaxis":"y16", "marker": {"color": "black"}}
+ ],
+ "layout": {
+ "xaxis": {"domain": [0, 0.2], "anchor": "y"},
+ "yaxis": {"domain": [0, 0.15], "anchor": "x", "matches": "x"},
+ "xaxis2": {"domain": [0.25, 0.45], "anchor": "y2", "scaleanchor": "y"},
+ "yaxis2": {"domain": [0, 0.15], "anchor": "x2", "matches": "x2"},
+ "xaxis3": {"domain": [0.5, 0.7], "anchor": "y3", "scaleanchor": "y2"},
+ "yaxis3": {"domain": [0, 0.15], "anchor": "x3", "matches": "x3"},
+ "xaxis4": {"domain": [0.75, 0.95], "anchor": "y4", "scaleanchor": "y3"},
+ "yaxis4": {"domain": [0, 0.15], "anchor": "x4", "matches": "x4"},
+
+ "xaxis5": {"domain": [0, 0.2], "anchor": "y5", "constrain": "domain"},
+ "yaxis5": {"domain": [0.25, 0.4], "anchor": "x5", "matches": "x5", "constrain": "domain"},
+ "xaxis6": {"domain": [0.25, 0.45], "anchor": "y6", "scaleanchor": "y5", "constrain": "domain"},
+ "yaxis6": {"domain": [0.25, 0.4], "anchor": "x6", "matches": "x6", "constrain": "domain"},
+ "xaxis7": {"domain": [0.5, 0.7], "anchor": "y7", "scaleanchor": "y6", "constrain": "domain"},
+ "yaxis7": {"domain": [0.25, 0.4], "anchor": "x7", "matches": "x7", "constrain": "domain"},
+ "xaxis8": {"domain": [0.75, 0.95], "anchor": "y8", "scaleanchor": "y7", "constrain": "domain"},
+ "yaxis8": {"domain": [0.25, 0.4], "anchor": "x8", "matches": "x8", "constrain": "domain"},
+
+ "xaxis9": {"domain": [0, 0.15], "anchor": "y9"},
+ "yaxis9": {"domain": [0.5, 0.7], "anchor": "x9", "matches": "x9"},
+ "xaxis10": {"domain": [0.25, 0.4], "anchor": "y10", "scaleanchor": "y9"},
+ "yaxis10": {"domain": [0.5, 0.7], "anchor": "x10", "matches": "x10"},
+ "xaxis11": {"domain": [0.5, 0.65], "anchor": "y11", "scaleanchor": "y10"},
+ "yaxis11": {"domain": [0.5, 0.7], "anchor": "x11", "matches": "x11"},
+ "xaxis12": {"domain": [0.75, 0.9], "anchor": "y12", "scaleanchor": "y11"},
+ "yaxis12": {"domain": [0.5, 0.7], "anchor": "x12", "matches": "x12"},
+
+ "xaxis13": {"domain": [0, 0.15], "anchor": "y13", "constrain": "domain"},
+ "yaxis13": {"domain": [0.75, 0.95], "anchor": "x13", "matches": "x13", "constrain": "domain"},
+ "xaxis14": {"domain": [0.25, 0.4], "anchor": "y14", "scaleanchor": "y13", "constrain": "domain"},
+ "yaxis14": {"domain": [0.75, 0.95], "anchor": "x14", "matches": "x14", "constrain": "domain"},
+ "xaxis15": {"domain": [0.5, 0.65], "anchor": "y15", "scaleanchor": "y14", "constrain": "domain"},
+ "yaxis15": {"domain": [0.75, 0.95], "anchor": "x15", "matches": "x15", "constrain": "domain"},
+ "xaxis16": {"domain": [0.75, 0.9], "anchor": "y16", "scaleanchor": "y15", "constrain": "domain"},
+ "yaxis16": {"domain": [0.75, 0.95], "anchor": "x16", "matches": "x16", "constrain": "domain"},
+
+ "shapes": [
+ {"x0": 0, "x1": 0.2, "y0": 0, "y1": 0.15, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.25, "x1": 0.45, "y0": 0, "y1": 0.15, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.5, "x1": 0.7, "y0": 0, "y1": 0.15, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.75, "x1": 0.95, "y0": 0, "y1": 0.15, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0, "x1": 0.2, "y0": 0.25, "y1": 0.4, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.25, "x1": 0.45, "y0": 0.25, "y1": 0.4, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.5, "x1": 0.7, "y0": 0.25, "y1": 0.4, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.75, "x1": 0.95, "y0": 0.25, "y1": 0.4, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0, "x1": 0.15, "y0": 0.5, "y1": 0.7, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.25, "x1": 0.4, "y0": 0.5, "y1": 0.7, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.5, "x1": 0.65, "y0": 0.5, "y1": 0.7, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.75, "x1": 0.9, "y0": 0.5, "y1": 0.7, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0, "x1": 0.15, "y0": 0.75, "y1": 0.95, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.25, "x1": 0.4, "y0": 0.75, "y1": 0.95, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.5, "x1": 0.65, "y0": 0.75, "y1": 0.95, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}},
+ {"x0": 0.75, "x1": 0.9, "y0": 0.75, "y1": 0.95, "type": "rect", "xref": "paper", "yref": "paper", "line": {"color": "#ccc", "dash": "dot"}}
+ ],
+ "annotations": [{"x": 0, "y": 1, "xanchor": "left", "yanchor": "top", "xref": "paper", "yref": "paper", "showarrow": false, "align": "left", "text": "y matches same x, x scales to the previous y. Subplot aspect ratios compound"}],
+
+ "width": 500,
+ "height": 500,
+ "margin": {"l": 50, "r": 0, "t": 0, "b": 50},
+ "showlegend": false
+ }
+}
diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js
index 009bd26e37a..d38894dd782 100644
--- a/test/jasmine/tests/axes_test.js
+++ b/test/jasmine/tests/axes_test.js
@@ -21,6 +21,7 @@ var ONEWEEK = numerical.ONEWEEK;
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var failTest = require('../assets/fail_test');
+var negateIf = require('../assets/negate_if');
var selectButton = require('../assets/modebar_button');
var supplyDefaults = require('../assets/supply_defaults');
@@ -677,14 +678,10 @@ describe('Test axes', function() {
]);
});
- var warnTxt = ' to avoid either an infinite loop and possibly ' +
- 'inconsistent scaleratios, or because the target axis has ' +
- 'fixed range or this axis declares a *matches* constraint.';
-
it('breaks scaleanchor loops and drops conflicting ratios', function() {
var warnings = [];
spyOn(Lib, 'warn').and.callFake(function(msg) {
- warnings.push(msg);
+ warnings.push(msg.substr(0, msg.indexOf(' to avoid')));
});
layoutIn = {
@@ -700,7 +697,8 @@ describe('Test axes', function() {
yaxis4: {scaleanchor: 'y', scaleratio: 17}, // y<->y is OK now
};
layoutOut._subplots.cartesian.push('x2y2', 'x3y3', 'x4y4');
- layoutOut._subplots.yaxis.push('x2', 'x3', 'x4', 'y2', 'y3', 'y4');
+ layoutOut._subplots.xaxis.push('x2', 'x3', 'x4');
+ layoutOut._subplots.yaxis.push('y2', 'y3', 'y4');
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
@@ -710,15 +708,15 @@ describe('Test axes', function() {
]);
expect(warnings).toEqual([
- 'ignored yaxis.scaleanchor: "x"' + warnTxt,
- 'ignored yaxis3.scaleanchor: "x2"' + warnTxt
+ 'ignored yaxis.scaleanchor: "x"',
+ 'ignored yaxis3.scaleanchor: "x2"'
]);
});
it('silently drops invalid scaleanchor values', function() {
var warnings = [];
spyOn(Lib, 'warn').and.callFake(function(msg) {
- warnings.push(msg);
+ warnings.push(msg.substr(0, msg.indexOf(' to avoid')));
});
layoutIn = {
@@ -732,7 +730,7 @@ describe('Test axes', function() {
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
expect(layoutOut._axisConstraintGroups).toEqual([]);
- expect(warnings).toEqual(['ignored xaxis.scaleanchor: "x"' + warnTxt]);
+ expect(warnings).toEqual(['ignored xaxis.scaleanchor: "x"']);
['xaxis', 'yaxis', 'xaxis2'].forEach(function(axName) {
expect(layoutOut[axName].scaleanchor).toBeUndefined(axName);
@@ -791,23 +789,23 @@ describe('Test axes', function() {
expect(layoutOut.xaxis2.matches).toBe('x');
expect(layoutOut.xaxis2.scaleanchor).toBe(undefined);
- expect(layoutOut.xaxis2.constrain).toBe(undefined);
+ // constrain is still coerced in case someone else scales to xaxis2
+ expect(layoutOut.xaxis2.constrain).toBe('range');
expect(layoutOut._axisConstraintGroups).toEqual([]);
expect(layoutOut._axisMatchGroups).toEqual([{x: 1, x2: 1}]);
});
- it('remove axes from constraint groups if they are in a match group', function() {
+ it('combines all chained scaled/matched axes into a group but drops match-only groups from constraintGroups', function() {
layoutIn = {
- // this one is ok
+ // this one big group
xaxis: {},
yaxis: {scaleanchor: 'x'},
- // this one too
- xaxis2: {},
- yaxis2: {matches: 'x2'},
- // not these ones
- xaxis3: {scaleanchor: 'x2'},
- yaxis3: {scaleanchor: 'y2'}
+ xaxis2: {matches: 'x'},
+ yaxis2: {matches: 'y'},
+ // this is another group but only shows up in matchGroups
+ xaxis3: {},
+ yaxis3: {matches: 'x3'}
};
layoutOut._subplots.cartesian.push('x2y2, x3y3');
layoutOut._subplots.xaxis.push('x2', 'x3');
@@ -815,74 +813,76 @@ describe('Test axes', function() {
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
- expect(layoutOut._axisMatchGroups.length).toBe(1);
- expect(layoutOut._axisMatchGroups).toContain({x2: 1, y2: 1});
+ expect(layoutOut._axisMatchGroups).toEqual([{x: 1, x2: 1}, {y: 1, y2: 1}, {x3: 1, y3: 1}]);
- expect(layoutOut._axisConstraintGroups.length).toBe(1);
- expect(layoutOut._axisConstraintGroups).toContain({x: 1, y: 1});
+ expect(layoutOut._axisConstraintGroups).toEqual([{x: 1, y: 1, x2: 1, y2: 1}]);
});
- it('remove constraint group if they are one or zero items left in it', function() {
+ it('includes matches in constraintGroup when combined with scaleanchor', function() {
layoutIn = {
xaxis: {},
yaxis: {matches: 'x'},
- xaxis2: {scaleanchor: 'y'}
+ xaxis2: {scaleanchor: 'x'}
};
layoutOut._subplots.cartesian.push('x2y');
layoutOut._subplots.xaxis.push('x2');
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
- expect(layoutOut._axisMatchGroups.length).toBe(1);
- expect(layoutOut._axisMatchGroups).toContain({x: 1, y: 1});
-
- expect(layoutOut._axisConstraintGroups.length).toBe(0);
+ expect(layoutOut._axisMatchGroups).toEqual([{x: 1, y: 1}]);
+ expect(layoutOut._axisConstraintGroups).toEqual([{x: 1, y: 'y1', x2: 1}]);
});
- it('drops scaleanchor settings if either the axis or target has fixedrange', function() {
+ it('turns all scaled axes fixedrange if any is fixedrange', function() {
// some of these will create warnings... not too important, so not going to test,
// just want to keep the output clean
// spyOn(Lib, 'warn');
layoutIn = {
xaxis: {fixedrange: true, scaleanchor: 'y', scaleratio: 2},
- yaxis: {scaleanchor: 'x2', scaleratio: 3}, // only this one should survive
+ yaxis: {scaleanchor: 'x2', scaleratio: 3},
xaxis2: {},
yaxis2: {scaleanchor: 'x', scaleratio: 5}
};
layoutOut._subplots.cartesian.push('x2y2');
- layoutOut._subplots.yaxis.push('x2', 'y2');
+ layoutOut._subplots.xaxis.push('x2');
+ layoutOut._subplots.yaxis.push('y2');
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
- expect(layoutOut._axisConstraintGroups).toEqual([{x2: 1, y: 3}]);
+ expect(layoutOut._axisConstraintGroups).toEqual([{x2: 1, y: 3, x: 6, y2: 30}]);
+ expect(layoutOut.xaxis.scaleanchor).toBe('y');
+ expect(layoutOut.xaxis.scaleratio).toBe(2);
expect(layoutOut.yaxis.scaleanchor).toBe('x2');
expect(layoutOut.yaxis.scaleratio).toBe(3);
+ expect(layoutOut.yaxis2.scaleanchor).toBe('x');
+ expect(layoutOut.yaxis2.scaleratio).toBe(5);
- ['xaxis', 'yaxis2', 'xaxis2'].forEach(function(axName) {
- expect(layoutOut[axName].scaleanchor).toBeUndefined();
- expect(layoutOut[axName].scaleratio).toBeUndefined();
+ ['xaxis', 'yaxis', 'yaxis2', 'xaxis2'].forEach(function(axName) {
+ expect(layoutOut[axName].fixedrange).toBe(true, axName);
});
});
- it('drops *matches* settings if either the axis or target has fixedrange', function() {
+ it('turns all matching axes fixedrange if any is fixedrange', function() {
layoutIn = {
xaxis: {fixedrange: true, matches: 'y'},
- yaxis: {matches: 'x2'}, // only this one should survive
+ yaxis: {matches: 'x2'},
xaxis2: {},
yaxis2: {matches: 'x'}
};
layoutOut._subplots.cartesian.push('x2y2');
- layoutOut._subplots.yaxis.push('x2', 'y2');
+ layoutOut._subplots.xaxis.push('x2');
+ layoutOut._subplots.yaxis.push('y2');
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
- expect(layoutOut._axisMatchGroups).toEqual([{x2: 1, y: 1}]);
+ expect(layoutOut._axisMatchGroups).toEqual([{x: 1, x2: 1, y: 1, y2: 1}]);
expect(layoutOut.yaxis.matches).toBe('x2');
- ['xaxis', 'yaxis2', 'xaxis2'].forEach(function(axName) {
- expect(layoutOut[axName].matches).toBeUndefined();
+ ['xaxis', 'xaxis2', 'yaxis', 'yaxis2'].forEach(function(axName) {
+ negateIf(axName !== 'xaxis2', expect(layoutOut[axName].matches)).toBeUndefined(axName);
+ expect(layoutOut[axName].fixedrange).toBe(true, axName);
});
});
@@ -990,7 +990,7 @@ describe('Test axes', function() {
// matchee ax has range
yaxis: {range: [0, 1]},
yaxis2: {matches: 'y'},
- // matcher ax has range (gets ignored)
+ // first explicit range gets copied to both
xaxis3: {},
yaxis3: {range: [-1, 1], matches: 'x3'},
// both ax have range
@@ -1013,13 +1013,14 @@ describe('Test axes', function() {
names.forEach(function(n) {
var ax = layoutOut[n];
expect(ax.autorange).toBe(autorange, n);
- expect(ax.range).toEqual(rng);
+ expect(ax.range[0]).toBe(rng[0], n);
+ expect(ax.range[1]).toBe(rng[1], n);
});
}
_assertMatchingAxes(['xaxis', 'xaxis2'], true, [-1, 6]);
_assertMatchingAxes(['yaxis', 'yaxis2'], false, [0, 1]);
- _assertMatchingAxes(['xaxis3', 'yaxis3'], true, [-1, 6]);
+ _assertMatchingAxes(['xaxis3', 'yaxis3'], false, [-1, 1]);
_assertMatchingAxes(['xaxis4', 'yaxis4'], false, [-1, 3]);
});
@@ -1718,7 +1719,7 @@ describe('Test axes', function() {
})
.then(function() {
assertRanges('base (autoranged)', [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.245, 3.245], true],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.285, 3.245], true],
[['yaxis'], [-0.211, 3.211], true]
]);
})
@@ -1732,7 +1733,7 @@ describe('Test axes', function() {
.then(function() { return Plotly.relayout(gd, 'xaxis2.autorange', true); })
.then(function() {
assertRanges('back to autorange', [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.245, 3.245], true],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.285, 3.245], true],
[['yaxis'], [-0.211, 3.211], true]
]);
})
diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js
index 819708b2de3..a351246fe22 100644
--- a/test/jasmine/tests/cartesian_interact_test.js
+++ b/test/jasmine/tests/cartesian_interact_test.js
@@ -872,7 +872,7 @@ describe('axis zoom/pan and main plot zoom', function() {
var msgi = n + ' - ' + msg;
if(opts.autorange) {
expect(eventData[n + '.autorange']).toBe(true, 2, msgi + '|event data');
- } else if(!opts.noChange) {
+ } else if(!opts.noChange && !opts.noEventData) {
expect(eventData[n + '.range[0]']).toBeCloseTo(rng[0], TOL, msgi + '|event data [0]');
expect(eventData[n + '.range[1]']).toBeCloseTo(rng[1], TOL, msgi + '|event data [1]');
}
@@ -970,14 +970,14 @@ describe('axis zoom/pan and main plot zoom', function() {
dragmode: 'zoom'
};
- var xr0 = [-0.245, 3.245];
+ var xr0 = [-0.285, 3.246];
var yr0 = [-0.211, 3.211];
var specs = [{
desc: 'zoombox on xy',
drag: ['xy', 'nsew', 30, 30],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [1.494, 2.350]],
+ [['xaxis', 'xaxis2', 'xaxis3'], [1.457, 2.328]],
[['yaxis'], [1.179, 1.50]]
],
dblclickSubplot: 'xy'
@@ -985,7 +985,7 @@ describe('axis zoom/pan and main plot zoom', function() {
desc: 'x-only zoombox on xy',
drag: ['xy', 'nsew', 30, 0],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [1.494, 2.350]],
+ [['xaxis', 'xaxis2', 'xaxis3'], [1.457, 2.328]],
[['yaxis'], yr0, {noChange: true}]
],
dblclickSubplot: 'x2y'
@@ -1003,7 +1003,7 @@ describe('axis zoom/pan and main plot zoom', function() {
exp: [
// N.B. slightly different range result
// due difference in ax._length
- [['xaxis', 'xaxis2', 'xaxis3'], [1.492, 2.062]],
+ [['xaxis', 'xaxis2', 'xaxis3'], [1.468, 2.049]],
[['yaxis'], [1.179, 1.50]]
],
dblclickSubplot: 'x3y'
@@ -1013,7 +1013,7 @@ describe('axis zoom/pan and main plot zoom', function() {
exp: [
// Similarly here slightly different range result
// due difference in ax._length
- [['xaxis', 'xaxis2', 'xaxis3'], [1.485, 1.974]],
+ [['xaxis', 'xaxis2', 'xaxis3'], [1.470, 1.974]],
[['yaxis'], [1.179, 1.50]]
],
dblclickSubplot: 'xy'
@@ -1021,7 +1021,7 @@ describe('axis zoom/pan and main plot zoom', function() {
desc: 'drag ew on x2y',
drag: ['x2y', 'ew', 30, 0],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.816, 2.675], {dragged: true}],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.866, 2.665], {dragged: true}],
[['yaxis'], yr0, {noChange: true}]
],
dblclickSubplot: 'x3y'
@@ -1029,7 +1029,7 @@ describe('axis zoom/pan and main plot zoom', function() {
desc: 'drag ew on x3y',
drag: ['x3y', 'ew', 30, 0],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.734, 2.756], {dragged: true}],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.783, 2.748], {dragged: true}],
[['yaxis'], yr0, {noChange: true}]
],
dblclickSubplot: 'xy'
@@ -1037,7 +1037,8 @@ describe('axis zoom/pan and main plot zoom', function() {
desc: 'drag e on xy',
drag: ['xy', 'e', 30, 30],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [xr0[0], 1.366], {dragged: true}],
+ // FIXME On CI we need 1.359 but locally it's 1.317 ??
+ [['xaxis', 'xaxis2', 'xaxis3'], [xr0[0], 1.359], {dragged: true}],
[['yaxis'], yr0, {noChange: true}]
],
dblclickSubplot: 'x3y'
@@ -1045,7 +1046,8 @@ describe('axis zoom/pan and main plot zoom', function() {
desc: 'drag nw on x3y',
drag: ['xy', 'nw', 30, 30],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-1.379, 3.245], {dragged: true}],
+ // FIXME On CI we need -1.425 but locally it's -1.442 ??
+ [['xaxis', 'xaxis2', 'xaxis3'], [-1.425, xr0[1]], {dragged: true}],
[['yaxis'], [-0.211, 3.565], {dragged: true}]
],
dblclickSubplot: 'x3y'
@@ -1054,7 +1056,7 @@ describe('axis zoom/pan and main plot zoom', function() {
dragmode: 'pan',
drag: ['xy', 'nsew', 30, 30],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-1.101, 2.390], {dragged: true}],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-1.157, 2.374], {dragged: true}],
[['yaxis'], [0.109, 3.532], {dragged: true}]
],
dblclickSubplot: 'x3y'
@@ -1063,7 +1065,7 @@ describe('axis zoom/pan and main plot zoom', function() {
dragmode: 'pan',
drag: ['x2y', 'nsew', 30, 30],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.816, 2.675], {dragged: true}],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.866, 2.665], {dragged: true}],
[['yaxis'], [0.109, 3.532], {dragged: true}]
],
dblclickSubplot: 'x2y'
@@ -1072,7 +1074,7 @@ describe('axis zoom/pan and main plot zoom', function() {
dragmode: 'pan',
drag: ['x3y', 'nsew', 30, 30],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.734, 2.756], {dragged: true}],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.783, 2.748], {dragged: true}],
[['yaxis'], [0.109, 3.532], {dragged: true}]
],
dblclickSubplot: 'xy'
@@ -1080,7 +1082,7 @@ describe('axis zoom/pan and main plot zoom', function() {
desc: 'scrolling on x3y subplot',
scroll: ['x3y', 20],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.613, 3.245], {dragged: true}],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.655, 3.247], {dragged: true}],
[['yaxis'], [-0.211, 3.571], {dragged: true}]
],
dblclickSubplot: 'xy'
@@ -1088,7 +1090,7 @@ describe('axis zoom/pan and main plot zoom', function() {
desc: 'scrolling on x2y subplot',
scroll: ['x2y', 20],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.613, 3.245], {dragged: true}],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.655, 3.247], {dragged: true}],
[['yaxis'], [-0.211, 3.571], {dragged: true}]
],
dblclickSubplot: 'xy'
@@ -1096,7 +1098,7 @@ describe('axis zoom/pan and main plot zoom', function() {
desc: 'scrolling on xy subplot',
scroll: ['xy', 20],
exp: [
- [['xaxis', 'xaxis2', 'xaxis3'], [-0.613, 3.245], {dragged: true}],
+ [['xaxis', 'xaxis2', 'xaxis3'], [-0.655, 3.247], {dragged: true}],
[['yaxis'], [-0.211, 3.571], {dragged: true}]
],
dblclickSubplot: 'x2y'
@@ -1150,14 +1152,14 @@ describe('axis zoom/pan and main plot zoom', function() {
};
var xr0 = [-0.211, 3.211];
- var yr0 = [-0.077, 3.163];
+ var yr0 = [-0.234, 3.244];
var specs = [{
desc: 'pan on xy',
drag: ['xy', 'nsew', 30, 30],
exp: [
[['xaxis'], [-0.534, 2.888], {dragged: true}],
- [['yaxis', 'yaxis2', 'yaxis3'], [0.706, 3.947], {dragged: true}],
+ [['yaxis', 'yaxis2', 'yaxis3'], [0.607, 4.085], {dragged: true}],
],
trans: [-30, -30, -30, -45, -30, -52.5]
}, {
@@ -1165,7 +1167,7 @@ describe('axis zoom/pan and main plot zoom', function() {
drag: ['xy2', 'nsew', 30, 30],
exp: [
[['xaxis'], [-0.534, 2.888], {dragged: true}],
- [['yaxis', 'yaxis2', 'yaxis3'], [0.444, 3.685], {dragged: true}],
+ [['yaxis', 'yaxis2', 'yaxis3'], [0.327, 3.805], {dragged: true}],
],
trans: [-30, -20, -30, -30, -30, -35]
}, {
@@ -1173,7 +1175,7 @@ describe('axis zoom/pan and main plot zoom', function() {
drag: ['xy3', 'nsew', 30, 30],
exp: [
[['xaxis'], [-0.534, 2.888], {dragged: true}],
- [['yaxis', 'yaxis2', 'yaxis3'], [0.370, 3.611], {dragged: true}],
+ [['yaxis', 'yaxis2', 'yaxis3'], [0.247, 3.725], {dragged: true}],
],
trans: [-30, -17.142, -30, -25.71, -30, -30]
}, {
@@ -1181,7 +1183,7 @@ describe('axis zoom/pan and main plot zoom', function() {
drag: ['xy2', 'ns', 0, 30],
exp: [
[['xaxis'], xr0, {noChange: true}],
- [['yaxis', 'yaxis2', 'yaxis3'], [0.444, 3.685], {dragged: true}],
+ [['yaxis', 'yaxis2', 'yaxis3'], [0.327, 3.805], {dragged: true}],
],
trans: [0, -20, 0, -30, 0, -35]
}, {
@@ -1189,7 +1191,7 @@ describe('axis zoom/pan and main plot zoom', function() {
drag: ['xy3', 'n', 0, 30],
exp: [
[['xaxis'], xr0, {noChange: true}],
- [['yaxis', 'yaxis2', 'yaxis3'], [yr0[0], 3.683], {dragged: true}],
+ [['yaxis', 'yaxis2', 'yaxis3'], [yr0[0], 3.802], {dragged: true}],
],
trans: [0, -19.893, 0, -29.839, 0, -34.812],
scale: [1, 1.160, 1, 1.160, 1, 1.160]
@@ -1198,7 +1200,7 @@ describe('axis zoom/pan and main plot zoom', function() {
drag: ['xy', 's', 0, 30],
exp: [
[['xaxis'], xr0, {noChange: true}],
- [['yaxis', 'yaxis2', 'yaxis3'], [1.617, yr0[1]], {dragged: true}],
+ [['yaxis', 'yaxis2', 'yaxis3'], [1.586, yr0[1]], {dragged: true}],
],
trans: [0, 0, 0, 0, 0, 0],
scale: [1, 0.476, 1, 0.476, 1, 0.476]
@@ -1588,6 +1590,130 @@ describe('axis zoom/pan and main plot zoom', function() {
.catch(failTest)
.then(done);
});
+
+ it('matching and constrained subplots play nice together', function(done) {
+ var data = [
+ {x: [0, 3], y: [0, 3]},
+ {x: [0, 3], y: [1, 8], xaxis: 'x2', yaxis: 'y2'}
+ ];
+
+ var layout = {
+ width: 400, height: 350, margin: {l: 50, r: 50, t: 50, b: 50},
+ yaxis: {domain: [0, 0.4], scaleanchor: 'x'},
+ xaxis2: {anchor: 'y2'},
+ yaxis2: {domain: [0.6, 1], matches: 'x2'},
+ showlegend: false
+ };
+ var x2y2, mx, my;
+
+ makePlot(data, layout).then(function() {
+ assertRanges('base', [
+ [['xaxis'], [-3.955, 6.955]],
+ [['yaxis'], [-0.318, 3.318]],
+ [['xaxis2', 'yaxis2'], [-0.588, 8.824]]
+ ]);
+ x2y2 = d3.select('.subplot.x2y2 .plot');
+ expect(x2y2.attr('transform')).toBe('translate(50,50)');
+ mx = gd._fullLayout.xaxis._m;
+ my = gd._fullLayout.yaxis._m;
+ })
+ .then(function() {
+ var drag = makeDragFns('x2y2', 'ns', 30, 30);
+ return drag.start().then(function() {
+ assertRanges('during drag', [
+ [['xaxis'], [-3.955, 6.955]],
+ [['yaxis'], [-0.318, 3.318]],
+ [['xaxis2', 'yaxis2'], [2.236, 11.648], {skipInput: true}]
+ ]);
+ // Check that the data container moves as it should with the axes
+ expect(x2y2.attr('transform')).toBe('translate(-40,80)scale(1,1)');
+ })
+ .then(drag.end);
+ })
+ .then(_assert('after drag on x2y2 subplot', [
+ [['xaxis'], [-3.955, 6.955], {noChange: true}],
+ [['yaxis'], [-0.318, 3.318], {noChange: true}],
+ [['xaxis2', 'yaxis2'], [2.236, 11.648], {dragged: true}]
+ ]))
+ .then(function() {
+ // make sure the ranges were correct when xy was redrawn
+ expect(gd._fullLayout.xaxis._m).toBe(mx);
+ expect(gd._fullLayout.yaxis._m).toBe(my);
+ })
+ .then(doDblClick('x2y2', 'ew'))
+ .then(_assert('after double-click on x2', [
+ [['xaxis'], [-3.955, 6.955], {noChange: true}],
+ [['yaxis'], [-0.318, 3.318], {noChange: true}],
+ [['xaxis2'], [-0.588, 8.824], {autorange: true}],
+ [['yaxis2'], [-0.588, 8.824], {noEventData: true}]
+ ]))
+ .then(function() {
+ expect(gd._fullLayout.xaxis._m).toBe(mx);
+ expect(gd._fullLayout.yaxis._m).toBe(my);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('handles matching & scaleanchor chained together', function(done) {
+ var data = [
+ {y: [1, 2]},
+ {y: [0, 1], xaxis: 'x2', yaxis: 'y2'}
+ ];
+
+ var layout = {
+ width: 350,
+ height: 300,
+ margin: {l: 50, r: 50, t: 50, b: 50},
+ showlegend: false,
+ xaxis: {domain: [0, 0.4]},
+ yaxis: {domain: [0, 0.5], matches: 'x'},
+ xaxis2: {domain: [0.6, 1], scaleanchor: 'x', anchor: 'y2'},
+ yaxis2: {domain: [0.5, 1], matches: 'x2', anchor: 'x2'}
+ };
+
+ makePlot(data, layout).then(function() {
+ assertRanges('base', [
+ [['xaxis', 'yaxis'], [-0.212, 2.212]],
+ [['xaxis2', 'yaxis2'], [-0.712, 1.712]]
+ ]);
+ })
+ .then(function() {
+ var drag = makeDragFns('xy', 'sw', 30, -30);
+ return drag.start().then(function() {
+ assertRanges('during drag sw', [
+ [['xaxis', 'yaxis'], [-1.251, 2.212], {skipInput: true}],
+ [['xaxis2', 'yaxis2'], [-1.232, 2.232], {skipInput: true}]
+ ]);
+ })
+ .then(drag.end);
+ })
+ .then(_assert('after drag sw on xy subplot', [
+ [['xaxis', 'yaxis'], [-1.251, 2.212], {dragged: true}],
+ [['xaxis2', 'yaxis2'], [-1.232, 2.232], {dragged: true}]
+ ]))
+ .then(doDblClick('x2y2', 'nsew'))
+ .then(_assert('after double-click on x2', [
+ [['xaxis', 'yaxis'], [-0.212, 2.212], {autorange: true}],
+ [['xaxis2', 'yaxis2'], [-0.712, 1.712], {autorange: true}]
+ ]))
+ .then(function() {
+ var drag = makeDragFns('xy', 'nw', 30, 30);
+ return drag.start().then(function() {
+ assertRanges('during drag nw', [
+ [['xaxis', 'yaxis'], [-0.732, 2.732], {skipInput: true}],
+ [['xaxis2', 'yaxis2'], [-1.232, 2.232], {skipInput: true}]
+ ]);
+ })
+ .then(drag.end);
+ })
+ .then(_assert('after drag nw on xy subplot', [
+ [['xaxis', 'yaxis'], [-0.732, 2.732], {dragged: true}],
+ [['xaxis2', 'yaxis2'], [-1.232, 2.232], {dragged: true}]
+ ]))
+ .catch(failTest)
+ .then(done);
+ });
});
describe('redrag behavior', function() {
diff --git a/test/jasmine/tests/image_test.js b/test/jasmine/tests/image_test.js
index 9363a730ffa..879f3eb47d7 100644
--- a/test/jasmine/tests/image_test.js
+++ b/test/jasmine/tests/image_test.js
@@ -208,11 +208,11 @@ describe('image smart layout defaults', function() {
expect(gd._fullLayout.yaxis.scaleanchor).toBe('x');
});
- it('should NOT set scaleanchor if it\'s already defined', function() {
- gd.data = [{type: 'image', z: [[[255, 0, 0]]]}];
+ it('should NOT reset scaleanchor if it\'s already defined', function() {
+ gd.data = [{type: 'image', z: [[[255, 0, 0]]]}, {y: [5, 3, 2], xaxis: 'x3'}];
gd.layout = {yaxis: {scaleanchor: 'x3'}};
supplyAllDefaults(gd);
- expect(gd._fullLayout.yaxis.scaleanchor).toBe(undefined);
+ expect(gd._fullLayout.yaxis.scaleanchor).toBe('x3');
});
it('should constrain axes to domain if images are present', function() {
@@ -233,7 +233,7 @@ describe('image smart layout defaults', function() {
it('should NOT constrain axes to domain if it\'s already defined', function() {
gd.data = [{type: 'image', z: [[[255, 0, 0]]]}];
- gd.layout = {yaxis: {constrain: false}, xaxis: {constrain: false}};
+ gd.layout = {yaxis: {constrain: 'range'}, xaxis: {constrain: 'range'}};
supplyAllDefaults(gd);
expect(gd._fullLayout.xaxis.constrain).toBe('range');
expect(gd._fullLayout.yaxis.constrain).toBe('range');
diff --git a/test/jasmine/tests/splom_test.js b/test/jasmine/tests/splom_test.js
index 7052124d76e..2b43f198418 100644
--- a/test/jasmine/tests/splom_test.js
+++ b/test/jasmine/tests/splom_test.js
@@ -537,9 +537,10 @@ describe('Test splom trace defaults:', function() {
var fullLayout = gd._fullLayout;
expect(fullLayout.xaxis.matches).toBe('y');
- expect(fullLayout.yaxis.matches).toBe('x');
expect(fullLayout.xaxis2.matches).toBe('y2');
- expect(fullLayout.yaxis2.matches).toBe('x2');
+ // not necessary to set y axes matching x, since x already matches y
+ expect(fullLayout.yaxis.matches).toBe(undefined);
+ expect(fullLayout.yaxis2.matches).toBe(undefined);
var groups = fullLayout._axisMatchGroups;
expect(groups.length).toBe(2);
@@ -562,7 +563,7 @@ describe('Test splom trace defaults:', function() {
expect(fullLayout.xaxis).toBe(undefined);
expect(fullLayout.yaxis.matches).toBe(undefined);
expect(fullLayout.xaxis2.matches).toBe('y2');
- expect(fullLayout.yaxis2.matches).toBe('x2');
+ expect(fullLayout.yaxis2.matches).toBe(undefined);
expect(fullLayout.xaxis3.matches).toBe(undefined);
expect(fullLayout.yaxis3).toBe(undefined);
@@ -586,7 +587,7 @@ describe('Test splom trace defaults:', function() {
expect(fullLayout.xaxis.matches).toBe(undefined);
expect(fullLayout.yaxis).toBe(undefined);
expect(fullLayout.xaxis2.matches).toBe('y2');
- expect(fullLayout.yaxis2.matches).toBe('x2');
+ expect(fullLayout.yaxis2.matches).toBe(undefined);
expect(fullLayout.xaxis3).toBe(undefined);
expect(fullLayout.yaxis3.matches).toBe(undefined);
@@ -608,7 +609,7 @@ describe('Test splom trace defaults:', function() {
var fullLayout = gd._fullLayout;
expect(fullLayout.xaxis.matches).toBe('y');
- expect(fullLayout.yaxis.matches).toBe('x');
+ expect(fullLayout.yaxis.matches).toBe(undefined);
expect(fullLayout.xaxis2.matches).toBe('x');
expect(fullLayout.yaxis2.matches).toBe('x2');