From 3666cf00b603ce5c050456d17d097e58518653a0 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 21 Jan 2020 12:21:41 -0500 Subject: [PATCH 01/11] move gl2d comment --- src/plots/plots.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 4ec29c19aa8..03f6b854c54 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1272,10 +1272,13 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac var subplots = layout._subplots; var subplotId = ''; - // TODO - currently if we draw an empty gl2d subplot, it draws - // nothing then gets stuck and you can't get it back without newPlot - // sort this out in the regl refactor? but for now just drop empty gl2d subplots - if(basePlotModule.name !== 'gl2d' || visible) { + if( + visible || + basePlotModule.name !== 'gl2d' // for now just drop empty gl2d subplots + // TODO - currently if we draw an empty gl2d subplot, it draws + // nothing then gets stuck and you can't get it back without newPlot + // sort this out in the regl refactor? + ) { if(Array.isArray(subplotAttr)) { for(i = 0; i < subplotAttr.length; i++) { var attri = subplotAttr[i]; From 09849744dcffe8e7f1ec2abbce42edf7a2f04958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Jan 2020 14:35:49 -0500 Subject: [PATCH 02/11] some linting in plots/cartesian/ --- src/plots/cartesian/constants.js | 3 +-- src/plots/cartesian/layout_defaults.js | 8 ++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js index ed1b0229a2b..a06eff59894 100644 --- a/src/plots/cartesian/constants.js +++ b/src/plots/cartesian/constants.js @@ -7,11 +7,10 @@ */ 'use strict'; -var counterRegex = require('../../lib/regex').counter; +var counterRegex = require('../../lib/regex').counter; module.exports = { - idRegex: { x: counterRegex('x'), y: counterRegex('y') diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 84d518dfc27..71f42f1c401 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -305,12 +305,16 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var scaleanchorDflt; if(axLetter === 'y' && !axLayoutIn.hasOwnProperty('scaleanchor') && axHasImage[axName]) { scaleanchorDflt = axLayoutOut.anchor; - } else {scaleanchorDflt = undefined;} + } else { + scaleanchorDflt = undefined; + } var constrainDflt; if(!axLayoutIn.hasOwnProperty('constrain') && axHasImage[axName]) { constrainDflt = 'domain'; - } else {constrainDflt = undefined;} + } else { + constrainDflt = undefined; + } handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, { allAxisIds: allAxisIds, From 33078b0c12e17b5cee7e7240f430da67bac1a44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Jan 2020 16:04:45 -0500 Subject: [PATCH 03/11] track & coerce 'missing' matching axes - keep list of valid but missing `matches` value - coerce the missing axes in fullLayout - include the missing axes in the list of valid `matches` values - add a few comments about the variables used in the cartesian supplyLayoutDefaults routine. - add jasmine supplyDefaults tests! --- src/plots/cartesian/layout_defaults.js | 124 ++++++++++++++++++++----- test/jasmine/tests/axes_test.js | 107 +++++++++++++++++++++ 2 files changed, 206 insertions(+), 25 deletions(-) diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 71f42f1c401..fcba165d208 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -24,6 +24,8 @@ var axisIds = require('./axis_ids'); var id2name = axisIds.id2name; var name2id = axisIds.name2id; +var AX_ID_PATTERN = require('./constants').AX_ID_PATTERN; + var Registry = require('../../registry'); var traceIs = Registry.traceIs; var getComponentMethod = Registry.getComponentMethod; @@ -133,7 +135,28 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var bgColor = Color.combine(plotBgColor, layoutOut.paper_bgcolor); - var axName, axLetter, axLayoutIn, axLayoutOut; + // name of single axis (e.g. 'xaxis', 'yaxis2') + var axName; + // id of single axis (e.g. 'y', 'x5') + var axId; + // 'x' or 'y' + var axLetter; + // input layout axis container + var axLayoutIn; + // full layout axis container + var axLayoutOut; + + function newAxLayoutOut() { + var traces = ax2traces[axName] || []; + axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; }); + axLayoutOut._annIndices = []; + axLayoutOut._shapeIndices = []; + axLayoutOut._imgIndices = []; + axLayoutOut._subplotsWith = []; + axLayoutOut._counterAxes = []; + axLayoutOut._name = axLayoutOut._attr = axName; + axLayoutOut._id = axId; + } function coerce(attr, dflt) { return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt); @@ -147,9 +170,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { return (axLetter === 'x') ? yIds : xIds; } - var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')}; - var allAxisIds = counterAxes.x.concat(counterAxes.y); - function getOverlayableAxes(axLetter, axName) { var list = (axLetter === 'x') ? xNames : yNames; var out = []; @@ -165,9 +185,26 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { return out; } + // list of available counter axis names + var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')}; + // list of all x AND y axis ids + var allAxisIds = counterAxes.x.concat(counterAxes.y); + // list of axis ids that axes in axNames have a reference to, + // even though they are missing from allAxisIds + var missingMatchedAxisIds = []; + + // fill in 'missing' axis list when an axis is set to match an axis + // not part of the allAxisIds list + function addMissingMatchedAxis(matchesIn) { + if(AX_ID_PATTERN.test(matchesIn) && allAxisIds.indexOf(matchesIn) === -1) { + Lib.pushUnique(missingMatchedAxisIds, matchesIn); + } + } + // first pass creates the containers, determines types, and handles most of the settings for(i = 0; i < axNames.length; i++) { axName = axNames[i]; + axId = name2id(axName); axLetter = axName.charAt(0); if(!Lib.isPlainObject(layoutIn[axName])) { @@ -176,20 +213,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { axLayoutIn = layoutIn[axName]; axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis'); - - var traces = ax2traces[axName] || []; - axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; }); - axLayoutOut._annIndices = []; - axLayoutOut._shapeIndices = []; - axLayoutOut._imgIndices = []; - axLayoutOut._subplotsWith = []; - axLayoutOut._counterAxes = []; - - // set up some private properties - axLayoutOut._name = axLayoutOut._attr = axName; - var id = axLayoutOut._id = name2id(axName); - - var overlayableAxes = getOverlayableAxes(axLetter, axName); + newAxLayoutOut(); var visibleDflt = (axLetter === 'x' && !xaMustDisplay[axName] && xaMayHide[axName]) || @@ -207,13 +231,13 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { font: layoutOut.font, outerTicks: outerTicks[axName], showGrid: !noGrids[axName], - data: traces, + data: ax2traces[axName] || [], bgColor: bgColor, calendar: layoutOut.calendar, automargin: true, visibleDflt: visibleDflt, reverseDflt: reverseDflt, - splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[id] + splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId] }; coerce('uirevision', layoutOut.uirevision); @@ -239,12 +263,60 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, { letter: axLetter, counterAxes: counterAxes[axLetter], - overlayableAxes: overlayableAxes, + overlayableAxes: getOverlayableAxes(axLetter, axName), grid: layoutOut.grid }); coerce('title.standoff'); + addMissingMatchedAxis(axLayoutIn.matches); + + axLayoutOut._input = axLayoutIn; + } + + // coerce the 'missing' axes + i = 0; + while(i < missingMatchedAxisIds.length) { + axId = missingMatchedAxisIds[i++]; + axName = id2name(axId); + axLetter = axName.charAt(0); + + if(!Lib.isPlainObject(layoutIn[axName])) { + layoutIn[axName] = {}; + } + + axLayoutIn = layoutIn[axName]; + axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis'); + newAxLayoutOut(); + + var defaultOptions2 = { + letter: axLetter, + font: layoutOut.font, + outerTicks: outerTicks[axName], + showGrid: !noGrids[axName], + data: [], + bgColor: bgColor, + calendar: layoutOut.calendar, + automargin: true, + visibleDflt: false, + reverseDflt: false, + splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId] + }; + + coerce('uirevision', layoutOut.uirevision); + + handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2); + handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2, layoutOut); + + handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, { + letter: axLetter, + counterAxes: counterAxes[axLetter], + overlayableAxes: getOverlayableAxes(axLetter, axName), + grid: layoutOut.grid + }); + + addMissingMatchedAxis(axLayoutIn.matches); + axLayoutOut._input = axLayoutIn; } @@ -295,9 +367,12 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { 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 < axNames.length; i++) { - axName = axNames[i]; + for(i = 0; i < axNamesIncludingMissing.length; i++) { + axName = axNamesIncludingMissing[i]; axLetter = axName.charAt(0); axLayoutIn = layoutIn[axName]; axLayoutOut = layoutOut[axName]; @@ -317,7 +392,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { } handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, { - allAxisIds: allAxisIds, + allAxisIds: allAxisIdsIncludingMissing, layoutOut: layoutOut, scaleanchorDflt: scaleanchorDflt, constrainDflt: constrainDflt @@ -328,7 +403,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var group = matchGroups[i]; var rng = null; var autorange = null; - var axId; // find 'matching' range attrs for(axId in group) { diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index e51bd397ead..3ba971c16fd 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -830,6 +830,68 @@ describe('Test axes', function() { expect(layoutOut._axisMatchGroups).toContain({y: 1, y2: 1, y3: 1}); }); + it('should find matching group even when matching a *missing* axis', function() { + layoutIn = { + // N.B. xaxis isn't set + xaxis2: {matches: 'x'}, + xaxis3: {matches: 'x'}, + xaxis4: {matches: 'x'}, + // N.B. yaxis isn't set + yaxis2: {matches: 'y'}, + yaxis3: {matches: 'y2'}, + yaxis4: {matches: 'y3'}, + }; + layoutOut._subplots.cartesian.push('x2y2', 'x3y3', 'x4y4'); + layoutOut._subplots.xaxis.push('x2', 'x3', 'x4'); + layoutOut._subplots.yaxis.push('y2', 'y3', 'y4'); + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + + expect(layoutOut._axisMatchGroups.length).toBe(2); + expect(layoutOut._axisMatchGroups).toContain({x: 1, x2: 1, x3: 1, x4: 1}); + expect(layoutOut._axisMatchGroups).toContain({y: 1, y2: 1, y3: 1, y4: 1}); + + // should coerce the 'missing' axes + expect(layoutIn.xaxis).toBeDefined(); + expect(layoutIn.yaxis).toBeDefined(); + expect(layoutOut.xaxis).toBeDefined(); + expect(layoutOut.yaxis).toBeDefined(); + }); + + it('should find matching group even when matching a *missing* axis (nested case)', function() { + layoutIn = { + // N.B. xaxis isn't set + // N.B. xaxis2 is set, but does not correspond to a subplot + xaxis2: {matches: 'x'}, + xaxis3: {matches: 'x2'}, + xaxis4: {matches: 'x3'}, + // N.B. yaxis isn't set + // N.B yaxis2 does not correspond to a subplot and is useless here + yaxis2: {matches: 'y'}, + yaxis3: {matches: 'y'}, + yaxis4: {matches: 'y3'} + }; + layoutOut._subplots.cartesian.push('x3y3', 'x4y4'); + layoutOut._subplots.xaxis.push('x3', 'x4'); + layoutOut._subplots.yaxis.push('y3', 'y4'); + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + + expect(layoutOut._axisMatchGroups.length).toBe(2); + expect(layoutOut._axisMatchGroups).toContain({x: 1, x2: 1, x3: 1, x4: 1}); + expect(layoutOut._axisMatchGroups).toContain({y: 1, y3: 1, y4: 1}); + + // should coerce the 'missing' axes + expect(layoutIn.xaxis).toBeDefined(); + expect(layoutIn.yaxis).toBeDefined(); + expect(layoutOut.xaxis).toBeDefined(); + expect(layoutOut.yaxis).toBeDefined(); + + // should coerce useless axes + expect(layoutIn.yaxis2).toEqual({matches: 'y'}); + expect(layoutOut.yaxis2).toBeUndefined(); + }); + it('should match set axis range value for matching axes', function() { layoutIn = { // autorange case @@ -871,6 +933,51 @@ describe('Test axes', function() { _assertMatchingAxes(['xaxis4', 'yaxis4'], false, [-1, 3]); }); + it('should match set axis range value for matching axes even when matching a *missing* axis', function() { + layoutIn = { + // N.B. xaxis is set, but does not correspond to a subplot + xaxis: {range: [0, 1]}, + xaxis2: {matches: 'x'}, + xaxis4: {matches: 'x'} + }; + layoutOut._subplots.cartesian.push('x2y2', 'x4y4'); + layoutOut._subplots.xaxis.push('x2', 'x4'); + layoutOut._subplots.yaxis.push('y2', 'y4'); + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + + expect(layoutOut._axisMatchGroups.length).toBe(1); + expect(layoutOut._axisMatchGroups).toContain({x: 1, x2: 1, x4: 1}); + + expect(layoutOut.xaxis.range).withContext('xaxis.range').toEqual([0, 1]); + expect(layoutOut.xaxis2.range).withContext('xaxis2.range').toEqual([0, 1]); + expect(layoutOut.xaxis4.range).withContext('xaxis4.range').toEqual([0, 1]); + }); + + it('should match set axis range value for matching axes even when matching a *missing* axis (nested case)', function() { + layoutIn = { + // N.B. xaxis is set, but does not correspond to a subplot + xaxis: {range: [0, 1]}, + // N.B. xaxis2 is set, but does not correspond to a subplot + xaxis2: {matches: 'x'}, + xaxis3: {matches: 'x2'}, + xaxis4: {matches: 'x3'} + }; + layoutOut._subplots.cartesian.push('x3y3', 'x4y4'); + layoutOut._subplots.xaxis.push('x3', 'x4'); + layoutOut._subplots.yaxis.push('y3', 'y4'); + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + + expect(layoutOut._axisMatchGroups.length).toBe(1); + expect(layoutOut._axisMatchGroups).toContain({x: 1, x2: 1, x3: 1, x4: 1}); + + expect(layoutOut.xaxis.range).withContext('xaxis.range').toEqual([0, 1]); + expect(layoutOut.xaxis2.range).withContext('xaxis2.range').toEqual([0, 1]); + expect(layoutOut.xaxis2.range).withContext('xaxis3.range').toEqual([0, 1]); + expect(layoutOut.xaxis4.range).withContext('xaxis4.range').toEqual([0, 1]); + }); + it('should adapt default axis ranges to *rangemode*', function() { layoutIn = { xaxis: {rangemode: 'tozero'}, From df1c59f7ab502b078ed62106d669eaab52aafbdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Jan 2020 16:52:26 -0500 Subject: [PATCH 04/11] make interactions for matched group w/ missing axes work - do not consider 'missing' axes during autorange sync-up operation - do not attempt to update missing axes on drag and other relayout calls that reference missing axes --- src/plot_api/subroutines.js | 6 +++ src/plots/cartesian/axes.js | 8 ++- test/jasmine/tests/cartesian_interact_test.js | 53 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 9806bbbceb1..7b8fc4c1766 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -670,6 +670,7 @@ exports.doAutoRangeAndConstraints = function(gd) { var fullLayout = gd._fullLayout; var axList = Axes.list(gd, '', true); var matchGroups = fullLayout._axisMatchGroups || []; + var axLookup = {}; var ax; var axRng; @@ -677,6 +678,7 @@ exports.doAutoRangeAndConstraints = function(gd) { ax = axList[i]; cleanAxisConstraints(gd, ax); doAutoRange(gd, ax); + axLookup[ax._id] = 1; } enforceAxisConstraints(gd); @@ -689,6 +691,10 @@ exports.doAutoRangeAndConstraints = function(gd) { 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); diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 7bbaf033341..af04c97d90b 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -1669,10 +1669,14 @@ axes.drawOne = function(gd, ax, opts) { var axId = ax._id; var axLetter = axId.charAt(0); var counterLetter = axes.counterLetter(axId); - var mainLinePosition = ax._mainLinePosition; - var mainMirrorPosition = ax._mainMirrorPosition; var mainPlotinfo = fullLayout._plots[ax._mainSubplot]; + + // this happens when updating matched group with 'missing' axes + if(!mainPlotinfo) return; + var mainAxLayer = mainPlotinfo[axLetter + 'axislayer']; + var mainLinePosition = ax._mainLinePosition; + var mainMirrorPosition = ax._mainMirrorPosition; var vals = ax._vals = axes.calcTicks(ax); diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 68ccfa0a4e5..29bb79810ef 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -1535,6 +1535,59 @@ describe('axis zoom/pan and main plot zoom', function() { .catch(failTest) .then(done); }); + + it('panning a matching axes with references to *missing* axes', function(done) { + var data = [ + // N.B. no traces on subplot xy + { x: [1, 2, 3], y: [1, 2, 1], xaxis: 'x2', yaxis: 'y2'}, + { x: [1, 2, 3], y: [1, 2, 1], xaxis: 'x3', yaxis: 'y3'}, + { x: [1, 2, 3], y: [1, 2, 1], xaxis: 'x4', yaxis: 'y4'} + ]; + + var layout = { + xaxis: {domain: [0, 0.48]}, + xaxis2: {anchor: 'y2', domain: [0.52, 1], matches: 'x'}, + xaxis3: {anchor: 'y3', domain: [0, 0.48], matches: 'x'}, + xaxis4: {anchor: 'y4', domain: [0.52, 1], matches: 'x'}, + yaxis: {domain: [0, 0.48]}, + yaxis2: {anchor: 'x2', domain: [0.52, 1], matches: 'y'}, + yaxis3: {anchor: 'x3', domain: [0.52, 1], matches: 'y'}, + yaxis4: {anchor: 'x4', domain: [0, 0.48], matches: 'y'}, + width: 400, + height: 400, + margin: {t: 50, l: 50, b: 50, r: 50}, + showlegend: false, + dragmode: 'pan' + }; + + makePlot(data, layout).then(function() { + assertRanges('base', [ + [['xaxis', 'xaxis2', 'xaxis3', 'xaxis4'], [0.8206, 3.179]], + [['yaxis', 'yaxis2', 'yaxis3', 'yaxis4'], [0.9103, 2.0896]] + ]); + }) + .then(function() { + var drag = makeDragFns('x2y2', 'nsew', 30, 30); + return drag.start().then(function() { + assertRanges('during drag', [ + [['xaxis', 'xaxis2', 'xaxis3', 'xaxis4'], [0.329, 2.687], {skipInput: true}], + [['yaxis', 'yaxis2', 'yaxis3', 'yaxis4'], [1.156, 2.335], {skipInput: true}] + ]); + }) + .then(drag.end); + }) + .then(_assert('after drag on x2y2 subplot', [ + [['xaxis', 'xaxis2', 'xaxis3', 'xaxis4'], [0.329, 2.687], {dragged: true}], + [['yaxis', 'yaxis2', 'yaxis3', 'yaxis4'], [1.156, 2.335], {dragged: true}] + ])) + .then(doDblClick('x3y3', 'nsew')) + .then(_assert('after double-click on x3y3 subplot', [ + [['xaxis', 'xaxis2', 'xaxis3', 'xaxis4'], [0.8206, 3.179], {autorange: true}], + [['yaxis', 'yaxis2', 'yaxis3', 'yaxis4'], [0.9103, 2.0896], {autorange: true}] + ])) + .catch(failTest) + .then(done); + }); }); describe('redrag behavior', function() { From 330bde16ad0da51d5e14c42b0aec50bc84d04e06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 30 Jan 2020 16:53:13 -0500 Subject: [PATCH 05/11] coerce fixedrange in 'missing' axis loop - this does not fix any bugs as the downstream fixedrange check use !fixedrange, but it's probably a good idea to get this into fullLayout to avoid potential bugs down the road. --- src/plots/cartesian/layout_defaults.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index fcba165d208..bdf6097753b 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -315,6 +315,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { grid: layoutOut.grid }); + coerce('fixedrange'); + addMissingMatchedAxis(axLayoutIn.matches); axLayoutOut._input = axLayoutIn; From 22b34c06d8a233d87a52943c0d47cc89221afd77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 30 Jan 2020 17:25:29 -0500 Subject: [PATCH 06/11] fixup new missing matching axis jasmine tests - now they correctly omit subplot 'xy' and axes 'x' and 'y' from the initial fullLayout --- test/jasmine/tests/axes_test.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 3ba971c16fd..3db11cd16c1 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -841,9 +841,9 @@ describe('Test axes', function() { yaxis3: {matches: 'y2'}, yaxis4: {matches: 'y3'}, }; - layoutOut._subplots.cartesian.push('x2y2', 'x3y3', 'x4y4'); - layoutOut._subplots.xaxis.push('x2', 'x3', 'x4'); - layoutOut._subplots.yaxis.push('y2', 'y3', 'y4'); + layoutOut._subplots.cartesian = ['x2y2', 'x3y3', 'x4y4']; + layoutOut._subplots.xaxis = ['x2', 'x3', 'x4']; + layoutOut._subplots.yaxis = ['y2', 'y3', 'y4']; supplyLayoutDefaults(layoutIn, layoutOut, fullData); @@ -871,9 +871,9 @@ describe('Test axes', function() { yaxis3: {matches: 'y'}, yaxis4: {matches: 'y3'} }; - layoutOut._subplots.cartesian.push('x3y3', 'x4y4'); - layoutOut._subplots.xaxis.push('x3', 'x4'); - layoutOut._subplots.yaxis.push('y3', 'y4'); + layoutOut._subplots.cartesian = ['x3y3', 'x4y4']; + layoutOut._subplots.xaxis = ['x3', 'x4']; + layoutOut._subplots.yaxis = ['y3', 'y4']; supplyLayoutDefaults(layoutIn, layoutOut, fullData); @@ -940,9 +940,9 @@ describe('Test axes', function() { xaxis2: {matches: 'x'}, xaxis4: {matches: 'x'} }; - layoutOut._subplots.cartesian.push('x2y2', 'x4y4'); - layoutOut._subplots.xaxis.push('x2', 'x4'); - layoutOut._subplots.yaxis.push('y2', 'y4'); + layoutOut._subplots.cartesian = ['x2y2', 'x4y4']; + layoutOut._subplots.xaxis = ['x2', 'x4']; + layoutOut._subplots.yaxis = ['y2', 'y4']; supplyLayoutDefaults(layoutIn, layoutOut, fullData); @@ -963,9 +963,9 @@ describe('Test axes', function() { xaxis3: {matches: 'x2'}, xaxis4: {matches: 'x3'} }; - layoutOut._subplots.cartesian.push('x3y3', 'x4y4'); - layoutOut._subplots.xaxis.push('x3', 'x4'); - layoutOut._subplots.yaxis.push('y3', 'y4'); + layoutOut._subplots.cartesian = ['x3y3', 'x4y4']; + layoutOut._subplots.xaxis = ['x3', 'x4']; + layoutOut._subplots.yaxis = ['y3', 'y4']; supplyLayoutDefaults(layoutIn, layoutOut, fullData); From 488c35ff75724c6aceb5d327f1d2a186d4b110a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 30 Jan 2020 17:28:21 -0500 Subject: [PATCH 07/11] copy axis type into missing axes ... so that their references are valid during handleConstraintDefaults --- src/plots/cartesian/layout_defaults.js | 21 +++++++++++++-------- test/jasmine/tests/axes_test.js | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index bdf6097753b..21c651c245e 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -189,15 +189,19 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')}; // list of all x AND y axis ids var allAxisIds = counterAxes.x.concat(counterAxes.y); - // list of axis ids that axes in axNames have a reference to, + // lookup and list of axis ids that axes in axNames have a reference to, // even though they are missing from allAxisIds + var missingMatchedAxisIdsLookup = {}; var missingMatchedAxisIds = []; - // fill in 'missing' axis list when an axis is set to match an axis - // not part of the allAxisIds list - function addMissingMatchedAxis(matchesIn) { + // fill in 'missing' axis lookup when an axis is set to match an axis + // not part of the allAxisIds list, save axis type so that we can propagate + // it to the missing axes + function addMissingMatchedAxis() { + var matchesIn = axLayoutIn.matches; if(AX_ID_PATTERN.test(matchesIn) && allAxisIds.indexOf(matchesIn) === -1) { - Lib.pushUnique(missingMatchedAxisIds, matchesIn); + missingMatchedAxisIdsLookup[matchesIn] = axLayoutIn.type; + missingMatchedAxisIds = Object.keys(missingMatchedAxisIdsLookup); } } @@ -269,7 +273,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { coerce('title.standoff'); - addMissingMatchedAxis(axLayoutIn.matches); + addMissingMatchedAxis(); axLayoutOut._input = axLayoutIn; } @@ -305,7 +309,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { coerce('uirevision', layoutOut.uirevision); - handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2); + axLayoutOut.type = missingMatchedAxisIdsLookup[axId] || 'linear'; + handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2, layoutOut); handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, { @@ -317,7 +322,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { coerce('fixedrange'); - addMissingMatchedAxis(axLayoutIn.matches); + addMissingMatchedAxis(); axLayoutOut._input = axLayoutIn; } diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 3db11cd16c1..83a3298876f 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -978,6 +978,27 @@ describe('Test axes', function() { expect(layoutOut.xaxis4.range).withContext('xaxis4.range').toEqual([0, 1]); }); + it('should propagate axis type into *missing* axes', function() { + layoutIn = { + xaxis2: {type: 'date', matches: 'x'}, + yaxis: {type: 'category', matches: 'y2'} + }; + layoutOut._subplots.cartesian = ['x2y']; + layoutOut._subplots.xaxis = ['x2']; + layoutOut._subplots.yaxis = ['y']; + + supplyLayoutDefaults(layoutIn, layoutOut, fullData); + + expect(layoutOut._axisMatchGroups.length).toBe(2); + expect(layoutOut._axisMatchGroups).toContain({x: 1, x2: 1}); + expect(layoutOut._axisMatchGroups).toContain({y: 1, y2: 1}); + + expect(layoutOut.xaxis.type).withContext('xaxis.type').toBe('date'); + expect(layoutOut.xaxis2.type).withContext('xaxis2.type').toBe('date'); + expect(layoutOut.yaxis.type).withContext('yaxis.type').toBe('category'); + expect(layoutOut.yaxis2.type).withContext('yaxis2.type').toBe('category'); + }); + it('should adapt default axis ranges to *rangemode*', function() { layoutIn = { xaxis: {rangemode: 'tozero'}, From 0230b73081b4f2a985060d32dd8fa452614c7c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 30 Jan 2020 17:31:48 -0500 Subject: [PATCH 08/11] link up missing axes during setupAxisCategories ... so that if ever a `categoryarray` is set in a missing axis, it gets correctly linked with the other axes in the match group. ++ add image mock! --- src/plots/plots.js | 28 +- .../image/baselines/matching-missing-axes.png | Bin 0 -> 36016 bytes test/image/mocks/matching-missing-axes.json | 317 ++++++++++++++++++ 3 files changed, 340 insertions(+), 5 deletions(-) create mode 100644 test/image/baselines/matching-missing-axes.png create mode 100644 test/image/mocks/matching-missing-axes.json diff --git a/src/plots/plots.js b/src/plots/plots.js index 03f6b854c54..81de18794c0 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2937,7 +2937,7 @@ plots.doCalcdata = function(gd, traces) { calcdata[i] = cd; } - setupAxisCategories(axList, fullData); + setupAxisCategories(axList, fullData, fullLayout); // 'transform' loop - must calc container traces first // so that if their dependent traces can get transform properly @@ -2945,7 +2945,7 @@ plots.doCalcdata = function(gd, traces) { for(i = 0; i < fullData.length; i++) transformCalci(i); // clear stuff that should recomputed in 'regular' loop - if(hasCalcTransform) setupAxisCategories(axList, fullData); + if(hasCalcTransform) setupAxisCategories(axList, fullData, fullLayout); // 'regular' loop - make sure container traces (eg carpet) calc before // contained traces (eg contourcarpet) @@ -3150,13 +3150,31 @@ function sortAxisCategoriesByValue(axList, gd) { return affectedTraces; } -function setupAxisCategories(axList, fullData) { - for(var i = 0; i < axList.length; i++) { - var ax = axList[i]; +function setupAxisCategories(axList, fullData, fullLayout) { + var axLookup = {}; + var i, ax, axId; + + for(i = 0; i < axList.length; i++) { + ax = axList[i]; + axId = ax._id; + ax.clearCalc(); if(ax.type === 'multicategory') { ax.setupMultiCategory(fullData); } + + axLookup[ax._id] = 1; + } + + // look into match groups for 'missing' axes + var matchGroups = fullLayout._axisMatchGroups; + for(i = 0; i < matchGroups.length; i++) { + for(axId in matchGroups[i]) { + if(!axLookup[axId]) { + ax = fullLayout[axisIDs.id2name(axId)]; + ax.clearCalc(); + } + } } } diff --git a/test/image/baselines/matching-missing-axes.png b/test/image/baselines/matching-missing-axes.png new file mode 100644 index 0000000000000000000000000000000000000000..2d067c0a1cd192eff777476cbe567bf4fc4b1824 GIT binary patch literal 36016 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 literal 0 HcmV?d00001 diff --git a/test/image/mocks/matching-missing-axes.json b/test/image/mocks/matching-missing-axes.json new file mode 100644 index 00000000000..0a11637a56a --- /dev/null +++ b/test/image/mocks/matching-missing-axes.json @@ -0,0 +1,317 @@ +{ + "data": [ + { + "type": "bar", + "name": "smoker=No", + "alignmentgroup": "True", + "offsetgroup": "smoker=No", + "legendgroup": "smoker=No", + "showlegend": true, + "marker": { "color": "#636efa" }, + "x": [ "Female", "Male", "Male", "Male", "Female", "Male", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Male", "Female", "Male", "Female", "Male", "Female", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Female", "Female", "Male", "Male", "Male", "Male", "Male", "Female", "Female", "Male", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Female", "Female", "Male", "Male", "Male", "Female", "Male", "Male", "Male", "Male", "Male" ], + "y": [ 16.99, 10.34, 21.01, 23.68, 24.59, 25.29, 8.77, 26.88, 15.04, 14.78, 10.27, 35.26, 15.42, 18.43, 14.83, 21.58, 10.33, 16.29, 16.97, 17.46, 13.94, 9.68, 30.4, 18.29, 22.23, 32.4, 28.55, 18.04, 12.54, 10.29, 34.81, 9.94, 25.56, 19.49, 38.07, 23.95, 25.71, 17.31, 29.93, 14.07, 13.13, 17.26, 24.55, 19.77, 29.85, 48.17, 25, 13.39, 16.49, 21.5, 12.66, 16.21, 13.81, 24.52, 20.76, 31.71, 20.69 ], + "xaxis": "x5", + "yaxis": "y5" + }, + { + "type": "bar", + "name": "smoker=No", + "alignmentgroup": "True", + "offsetgroup": "smoker=No", + "legendgroup": "smoker=No", + "showlegend": false, + "marker": { "color": "#636efa" }, + "x": [ "Male", "Male", "Female", "Female", "Male", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Male", "Female", "Female", "Male", "Male", "Male", "Female", "Male", "Male", "Male", "Female", "Male", "Male", "Male", "Female", "Male", "Male", "Female", "Female", "Male", "Female", "Male", "Male", "Female", "Male", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Male" ], + "y": [ 20.65, 17.92, 20.29, 15.77, 39.42, 19.82, 17.81, 13.37, 12.69, 21.7, 19.65, 9.55, 18.35, 15.06, 20.69, 17.78, 24.06, 16.31, 16.93, 18.69, 31.27, 16.04, 26.41, 48.27, 17.59, 20.08, 16.45, 20.23, 12.02, 17.07, 14.73, 10.51, 20.92, 18.24, 14, 7.25, 48.33, 20.45, 13.28, 11.61, 10.77, 10.07, 35.83, 29.03, 17.82 ], + "xaxis": "x6", + "yaxis": "y6" + }, + { + "type": "bar", + "name": "smoker=No", + "alignmentgroup": "True", + "offsetgroup": "smoker=No", + "legendgroup": "smoker=No", + "showlegend": false, + "marker": { "color": "#636efa" }, + "x": [ "Female" ], + "y": [ 18.78 ], + "xaxis": "x7", + "yaxis": "y7" + }, + { + "type": "bar", + "name": "smoker=No", + "alignmentgroup": "True", + "offsetgroup": "smoker=No", + "legendgroup": "smoker=No", + "showlegend": false, + "marker": { "color": "#636efa" }, + "x": [ "Male", "Female", "Male" ], + "y": [ 22.49, 22.75, 12.46 ], + "xaxis": "x8", + "yaxis": "y8", + "showlegend": false + }, + { + "type": "bar", + "name": "smoker=No", + "alignmentgroup": "True", + "offsetgroup": "smoker=No", + "legendgroup": "smoker=No", + "showlegend": false, + "marker": { "color": "#636efa" }, + "x": [ "Male", "Male", "Male", "Male", "Female", "Male", "Female", "Male", "Male", "Male", "Male", "Female", "Female", "Female", "Male", "Female", "Male", "Male", "Female", "Female", "Male", "Female", "Female", "Male", "Male", "Female", "Female", "Female", "Female", "Female", "Female", "Female", "Female", "Female", "Male", "Male", "Female", "Female", "Female", "Female", "Female", "Male", "Male", "Male" ], + "y": [ 27.2, 22.76, 17.29, 16.66, 10.07, 15.98, 34.83, 13.03, 18.28, 24.71, 21.16, 10.65, 12.43, 24.08, 11.69, 13.42, 14.26, 15.95, 12.48, 29.8, 8.52, 14.52, 11.38, 22.82, 19.08, 20.27, 11.17, 12.26, 18.26, 8.51, 10.33, 14.15, 13.16, 17.47, 34.3, 41.19, 27.05, 16.43, 8.35, 18.64, 11.87, 9.78, 7.51, 7.56 ], + "xaxis": "x3", + "yaxis": "y3" + }, + { + "type": "bar", + "name": "smoker=No", + "alignmentgroup": "True", + "offsetgroup": "smoker=No", + "legendgroup": "smoker=No", + "showlegend": false, + "marker": { "color": "#636efa" }, + "x": [ "Female" ], + "y": [ 15.98 ], + "xaxis": "x4", + "yaxis": "y4" + }, + { + "type": "bar", + "name": "smoker=Yes", + "alignmentgroup": "True", + "offsetgroup": "smoker=Yes", + "legendgroup": "smoker=Yes", + "showlegend": true, + "marker": { "color": "#EF553B" }, + "x": [ "Female", "Male", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Female", "Male", "Male" ], + "y": [ 17.51, 7.25, 31.85, 16.82, 32.9, 17.89, 14.48, 9.6, 34.63, 34.65, 23.33, 45.35, 23.17, 40.55, 20.9, 30.46, 18.15, 23.1, 15.69 ], + "xaxis": "x5", + "yaxis": "y5" + }, + { + "type": "bar", + "name": "smoker=Yes", + "alignmentgroup": "True", + "offsetgroup": "smoker=Yes", + "legendgroup": "smoker=Yes", + "showlegend": false, + "marker": { "color": "#EF553B" }, + "x": [ "Male", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Female", "Female", "Male", "Female", "Female", "Male", "Male", "Male", "Female", "Female", "Female", "Male", "Male", "Male", "Male", "Male", "Female", "Male", "Male", "Female", "Female", "Female", "Male", "Male", "Male", "Female", "Female", "Male", "Male", "Male", "Male", "Male", "Female", "Male" ], + "y": [ 38.01, 11.24, 20.29, 13.81, 11.02, 18.29, 3.07, 15.01, 26.86, 25.28, 17.92, 44.3, 22.42, 15.36, 20.49, 25.21, 14.31, 10.59, 10.63, 50.81, 15.81, 26.59, 38.73, 24.27, 12.76, 30.06, 25.89, 13.27, 28.17, 12.9, 28.15, 11.59, 7.74, 30.14, 22.12, 24.01, 15.69, 15.53, 12.6, 32.83, 27.18, 22.67 ], + "xaxis": "x6", + "yaxis": "y6" + }, + { + "type": "bar", + "name": "smoker=Yes", + "alignmentgroup": "True", + "offsetgroup": "smoker=Yes", + "legendgroup": "smoker=Yes", + "showlegend": false, + "marker": { "color": "#EF553B" }, + "x": [ "Male", "Female", "Female", "Male", "Male", "Male", "Male", "Female", "Female" ], + "y": [ 28.97, 5.75, 16.32, 40.17, 27.28, 12.03, 21.01, 11.35, 15.38 ], + "xaxis": "x8", + "yaxis": "y8" + }, + { + "type": "bar", + "name": "smoker=Yes", + "alignmentgroup": "True", + "offsetgroup": "smoker=Yes", + "legendgroup": "smoker=Yes", + "showlegend": false, + "marker": { "color": "#EF553B" }, + "x": [ "Male", "Male", "Male", "Female", "Male", "Male", "Male", "Male", "Female", "Female", "Male", "Male", "Female", "Female", "Female", "Male", "Female" ], + "y": [ 19.44, 32.68, 16, 19.81, 28.44, 15.48, 16.58, 10.34, 43.11, 13, 13.51, 18.71, 12.74, 13, 16.4, 20.53, 16.47 ], + "xaxis": "x3", + "yaxis": "y3" + }, + { + "type": "bar", + "name": "smoker=Yes", + "alignmentgroup": "True", + "offsetgroup": "smoker=Yes", + "legendgroup": "smoker=Yes", + "showlegend": false, + "marker": { "color": "#EF553B" }, + "x": [ "Male", "Female", "Male", "Male", "Female", "Female" ], + "y": [ 12.16, 13.42, 8.58, 13.42, 16.27, 10.09 ], + "xaxis": "x4", + "yaxis": "y4" + } + ], + "layout": { + "annotations": [ + { + "showarrow": false, + "text": "day=Sun", + "x": 0.11499999999999999, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "showarrow": false, + "text": "day=Sat", + "x": 0.365, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "showarrow": false, + "text": "day=Thur", + "x": 0.615, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "showarrow": false, + "text": "day=Fri", + "x": 0.865, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "showarrow": false, + "text": "time=Lunch", + "textangle": 90, + "x": 0.98, + "xanchor": "left", + "xref": "paper", + "y": 0.2425, + "yanchor": "middle", + "yref": "paper" + }, + { + "showarrow": false, + "text": "time=Dinner", + "textangle": 90, + "x": 0.98, + "xanchor": "left", + "xref": "paper", + "y": 0.7575000000000001, + "yanchor": "middle", + "yref": "paper" + } + ], + "barmode": "group", + "legend": { "tracegroupgap": 0 }, + "margin": { "t": 60 }, + "xaxis": { + "anchor": "y", + "domain": [ 0, 0.22999999999999998 ], + "title": { "text": "sex" }, + "categoryarray": [ "Male", "Female" ] + }, + "xaxis2": { + "anchor": "y2", + "domain": [ 0.24999999999999997, 0.48 ], + "matches": "x", + "title": { "text": "sex" } + }, + "xaxis3": { + "anchor": "y3", + "domain": [ 0.49999999999999994, 0.73 ], + "matches": "x", + "title": { "text": "sex" } + }, + "xaxis4": { + "anchor": "y4", + "domain": [ 0.75, 0.98 ], + "matches": "x", + "title": { "text": "sex" } + }, + "xaxis5": { + "anchor": "y5", + "domain": [ 0, 0.22999999999999998 ], + "matches": "x", + "showticklabels": false + }, + "xaxis6": { + "anchor": "y6", + "domain": [ 0.24999999999999997, 0.48 ], + "matches": "x", + "showticklabels": false + }, + "xaxis7": { + "anchor": "y7", + "domain": [ 0.49999999999999994, 0.73 ], + "matches": "x", + "showticklabels": false + }, + "xaxis8": { + "anchor": "y8", + "domain": [ 0.75, 0.98 ], + "matches": "x", + "showticklabels": false + }, + "yaxis": { + "anchor": "x", + "domain": [ 0, 0.485 ], + "title": { "text": "total_bill" } + }, + "yaxis2": { + "anchor": "x2", + "domain": [ 0, 0.485 ], + "matches": "y", + "showticklabels": false + }, + "yaxis3": { + "anchor": "x3", + "domain": [ 0, 0.485 ], + "matches": "y", + "showticklabels": false + }, + "yaxis4": { + "anchor": "x4", + "domain": [ 0, 0.485 ], + "matches": "y", + "showticklabels": false + }, + "yaxis5": { + "anchor": "x5", + "domain": [ 0.515, 1 ], + "matches": "y", + "title": { "text": "total_bill" } + }, + "yaxis6": { + "anchor": "x6", + "domain": [ 0.515, 1 ], + "matches": "y", + "showticklabels": false + }, + "yaxis7": { + "anchor": "x7", + "domain": [ 0.515, 1 ], + "matches": "y", + "showticklabels": false + }, + "yaxis8": { + "anchor": "x8", + "domain": [ 0.515, 1 ], + "matches": "y", + "showticklabels": false + }, + "title": { + "text" : "Matching missing axes 'x' and 'y'
with categoryarray set in xaxis", + "x": 0.05, + "y": 0.2 + } + } +} From 6b61e34ead5c39f67b3a7a44b9f66a47e90b85cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 30 Jan 2020 18:04:39 -0500 Subject: [PATCH 09/11] fixup (add fallback for _axisMatchGroups) --- src/plots/plots.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 81de18794c0..55fd49714ca 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -3167,7 +3167,7 @@ function setupAxisCategories(axList, fullData, fullLayout) { } // look into match groups for 'missing' axes - var matchGroups = fullLayout._axisMatchGroups; + var matchGroups = fullLayout._axisMatchGroups || []; for(i = 0; i < matchGroups.length; i++) { for(axId in matchGroups[i]) { if(!axLookup[axId]) { From f151aedcf362ac684c57e3ab9606d721c8c4687c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 30 Jan 2020 18:29:24 -0500 Subject: [PATCH 10/11] add treemap_with-without_values to list of flaky mocks --- test/image/compare_pixels_test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/image/compare_pixels_test.js b/test/image/compare_pixels_test.js index 07848a385ad..7f96cbfaaaf 100644 --- a/test/image/compare_pixels_test.js +++ b/test/image/compare_pixels_test.js @@ -104,6 +104,7 @@ var FLAKY_LIST = [ 'treemap_coffee', 'treemap_textposition', 'treemap_with-without_values_template', + 'treemap_with-without_values', 'trace_metatext', 'gl3d_directions-streamtube1' ]; From 2589451c1c0734de9c0268b7af6093bd6f94d461 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 31 Jan 2020 10:28:55 -0500 Subject: [PATCH 11/11] modify the list of flaky mocks --- test/image/compare_pixels_test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/image/compare_pixels_test.js b/test/image/compare_pixels_test.js index 7f96cbfaaaf..3b08dcbdf20 100644 --- a/test/image/compare_pixels_test.js +++ b/test/image/compare_pixels_test.js @@ -101,9 +101,7 @@ if(allMock || argv.filter) { } var FLAKY_LIST = [ - 'treemap_coffee', 'treemap_textposition', - 'treemap_with-without_values_template', 'treemap_with-without_values', 'trace_metatext', 'gl3d_directions-streamtube1'