Skip to content

Commit 6cf8b64

Browse files
authored
Merge pull request #4529 from plotly/axis-matches-with-trace-less-axis
Handle 'missing' matching axes
2 parents 3cb1652 + 2589451 commit 6cf8b64

File tree

10 files changed

+654
-42
lines changed

10 files changed

+654
-42
lines changed

src/plot_api/subroutines.js

+6
Original file line numberDiff line numberDiff line change
@@ -670,13 +670,15 @@ exports.doAutoRangeAndConstraints = function(gd) {
670670
var fullLayout = gd._fullLayout;
671671
var axList = Axes.list(gd, '', true);
672672
var matchGroups = fullLayout._axisMatchGroups || [];
673+
var axLookup = {};
673674
var ax;
674675
var axRng;
675676

676677
for(var i = 0; i < axList.length; i++) {
677678
ax = axList[i];
678679
cleanAxisConstraints(gd, ax);
679680
doAutoRange(gd, ax);
681+
axLookup[ax._id] = 1;
680682
}
681683

682684
enforceAxisConstraints(gd);
@@ -689,6 +691,10 @@ exports.doAutoRangeAndConstraints = function(gd) {
689691

690692
for(id in group) {
691693
ax = Axes.getFromId(gd, id);
694+
695+
// skip over 'missing' axes which do not pass through doAutoRange
696+
if(!axLookup[ax._id]) continue;
697+
// if one axis has autorange false, we're done
692698
if(ax.autorange === false) continue groupLoop;
693699

694700
axRng = Lib.simpleMap(ax.range, ax.r2l);

src/plots/cartesian/axes.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -1669,10 +1669,14 @@ axes.drawOne = function(gd, ax, opts) {
16691669
var axId = ax._id;
16701670
var axLetter = axId.charAt(0);
16711671
var counterLetter = axes.counterLetter(axId);
1672-
var mainLinePosition = ax._mainLinePosition;
1673-
var mainMirrorPosition = ax._mainMirrorPosition;
16741672
var mainPlotinfo = fullLayout._plots[ax._mainSubplot];
1673+
1674+
// this happens when updating matched group with 'missing' axes
1675+
if(!mainPlotinfo) return;
1676+
16751677
var mainAxLayer = mainPlotinfo[axLetter + 'axislayer'];
1678+
var mainLinePosition = ax._mainLinePosition;
1679+
var mainMirrorPosition = ax._mainMirrorPosition;
16761680

16771681
var vals = ax._vals = axes.calcTicks(ax);
16781682

src/plots/cartesian/constants.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@
77
*/
88

99
'use strict';
10-
var counterRegex = require('../../lib/regex').counter;
1110

11+
var counterRegex = require('../../lib/regex').counter;
1212

1313
module.exports = {
14-
1514
idRegex: {
1615
x: counterRegex('x'),
1716
y: counterRegex('y')

src/plots/cartesian/layout_defaults.js

+112-27
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ var axisIds = require('./axis_ids');
2424
var id2name = axisIds.id2name;
2525
var name2id = axisIds.name2id;
2626

27+
var AX_ID_PATTERN = require('./constants').AX_ID_PATTERN;
28+
2729
var Registry = require('../../registry');
2830
var traceIs = Registry.traceIs;
2931
var getComponentMethod = Registry.getComponentMethod;
@@ -133,7 +135,28 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
133135

134136
var bgColor = Color.combine(plotBgColor, layoutOut.paper_bgcolor);
135137

136-
var axName, axLetter, axLayoutIn, axLayoutOut;
138+
// name of single axis (e.g. 'xaxis', 'yaxis2')
139+
var axName;
140+
// id of single axis (e.g. 'y', 'x5')
141+
var axId;
142+
// 'x' or 'y'
143+
var axLetter;
144+
// input layout axis container
145+
var axLayoutIn;
146+
// full layout axis container
147+
var axLayoutOut;
148+
149+
function newAxLayoutOut() {
150+
var traces = ax2traces[axName] || [];
151+
axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; });
152+
axLayoutOut._annIndices = [];
153+
axLayoutOut._shapeIndices = [];
154+
axLayoutOut._imgIndices = [];
155+
axLayoutOut._subplotsWith = [];
156+
axLayoutOut._counterAxes = [];
157+
axLayoutOut._name = axLayoutOut._attr = axName;
158+
axLayoutOut._id = axId;
159+
}
137160

138161
function coerce(attr, dflt) {
139162
return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt);
@@ -147,9 +170,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
147170
return (axLetter === 'x') ? yIds : xIds;
148171
}
149172

150-
var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')};
151-
var allAxisIds = counterAxes.x.concat(counterAxes.y);
152-
153173
function getOverlayableAxes(axLetter, axName) {
154174
var list = (axLetter === 'x') ? xNames : yNames;
155175
var out = [];
@@ -165,9 +185,30 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
165185
return out;
166186
}
167187

188+
// list of available counter axis names
189+
var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')};
190+
// list of all x AND y axis ids
191+
var allAxisIds = counterAxes.x.concat(counterAxes.y);
192+
// lookup and list of axis ids that axes in axNames have a reference to,
193+
// even though they are missing from allAxisIds
194+
var missingMatchedAxisIdsLookup = {};
195+
var missingMatchedAxisIds = [];
196+
197+
// fill in 'missing' axis lookup when an axis is set to match an axis
198+
// not part of the allAxisIds list, save axis type so that we can propagate
199+
// it to the missing axes
200+
function addMissingMatchedAxis() {
201+
var matchesIn = axLayoutIn.matches;
202+
if(AX_ID_PATTERN.test(matchesIn) && allAxisIds.indexOf(matchesIn) === -1) {
203+
missingMatchedAxisIdsLookup[matchesIn] = axLayoutIn.type;
204+
missingMatchedAxisIds = Object.keys(missingMatchedAxisIdsLookup);
205+
}
206+
}
207+
168208
// first pass creates the containers, determines types, and handles most of the settings
169209
for(i = 0; i < axNames.length; i++) {
170210
axName = axNames[i];
211+
axId = name2id(axName);
171212
axLetter = axName.charAt(0);
172213

173214
if(!Lib.isPlainObject(layoutIn[axName])) {
@@ -176,20 +217,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
176217

177218
axLayoutIn = layoutIn[axName];
178219
axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis');
179-
180-
var traces = ax2traces[axName] || [];
181-
axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; });
182-
axLayoutOut._annIndices = [];
183-
axLayoutOut._shapeIndices = [];
184-
axLayoutOut._imgIndices = [];
185-
axLayoutOut._subplotsWith = [];
186-
axLayoutOut._counterAxes = [];
187-
188-
// set up some private properties
189-
axLayoutOut._name = axLayoutOut._attr = axName;
190-
var id = axLayoutOut._id = name2id(axName);
191-
192-
var overlayableAxes = getOverlayableAxes(axLetter, axName);
220+
newAxLayoutOut();
193221

194222
var visibleDflt =
195223
(axLetter === 'x' && !xaMustDisplay[axName] && xaMayHide[axName]) ||
@@ -207,13 +235,13 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
207235
font: layoutOut.font,
208236
outerTicks: outerTicks[axName],
209237
showGrid: !noGrids[axName],
210-
data: traces,
238+
data: ax2traces[axName] || [],
211239
bgColor: bgColor,
212240
calendar: layoutOut.calendar,
213241
automargin: true,
214242
visibleDflt: visibleDflt,
215243
reverseDflt: reverseDflt,
216-
splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[id]
244+
splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
217245
};
218246

219247
coerce('uirevision', layoutOut.uirevision);
@@ -239,12 +267,63 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
239267
handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
240268
letter: axLetter,
241269
counterAxes: counterAxes[axLetter],
242-
overlayableAxes: overlayableAxes,
270+
overlayableAxes: getOverlayableAxes(axLetter, axName),
243271
grid: layoutOut.grid
244272
});
245273

246274
coerce('title.standoff');
247275

276+
addMissingMatchedAxis();
277+
278+
axLayoutOut._input = axLayoutIn;
279+
}
280+
281+
// coerce the 'missing' axes
282+
i = 0;
283+
while(i < missingMatchedAxisIds.length) {
284+
axId = missingMatchedAxisIds[i++];
285+
axName = id2name(axId);
286+
axLetter = axName.charAt(0);
287+
288+
if(!Lib.isPlainObject(layoutIn[axName])) {
289+
layoutIn[axName] = {};
290+
}
291+
292+
axLayoutIn = layoutIn[axName];
293+
axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis');
294+
newAxLayoutOut();
295+
296+
var defaultOptions2 = {
297+
letter: axLetter,
298+
font: layoutOut.font,
299+
outerTicks: outerTicks[axName],
300+
showGrid: !noGrids[axName],
301+
data: [],
302+
bgColor: bgColor,
303+
calendar: layoutOut.calendar,
304+
automargin: true,
305+
visibleDflt: false,
306+
reverseDflt: false,
307+
splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
308+
};
309+
310+
coerce('uirevision', layoutOut.uirevision);
311+
312+
axLayoutOut.type = missingMatchedAxisIdsLookup[axId] || 'linear';
313+
314+
handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2, layoutOut);
315+
316+
handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
317+
letter: axLetter,
318+
counterAxes: counterAxes[axLetter],
319+
overlayableAxes: getOverlayableAxes(axLetter, axName),
320+
grid: layoutOut.grid
321+
});
322+
323+
coerce('fixedrange');
324+
325+
addMissingMatchedAxis();
326+
248327
axLayoutOut._input = axLayoutIn;
249328
}
250329

@@ -295,25 +374,32 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
295374
var constraintGroups = layoutOut._axisConstraintGroups = [];
296375
// similar to _axisConstraintGroups, but for matching axes
297376
var matchGroups = layoutOut._axisMatchGroups = [];
377+
// make sure to include 'missing' axes here
378+
var allAxisIdsIncludingMissing = allAxisIds.concat(missingMatchedAxisIds);
379+
var axNamesIncludingMissing = axNames.concat(Lib.simpleMap(missingMatchedAxisIds, id2name));
298380

299-
for(i = 0; i < axNames.length; i++) {
300-
axName = axNames[i];
381+
for(i = 0; i < axNamesIncludingMissing.length; i++) {
382+
axName = axNamesIncludingMissing[i];
301383
axLetter = axName.charAt(0);
302384
axLayoutIn = layoutIn[axName];
303385
axLayoutOut = layoutOut[axName];
304386

305387
var scaleanchorDflt;
306388
if(axLetter === 'y' && !axLayoutIn.hasOwnProperty('scaleanchor') && axHasImage[axName]) {
307389
scaleanchorDflt = axLayoutOut.anchor;
308-
} else {scaleanchorDflt = undefined;}
390+
} else {
391+
scaleanchorDflt = undefined;
392+
}
309393

310394
var constrainDflt;
311395
if(!axLayoutIn.hasOwnProperty('constrain') && axHasImage[axName]) {
312396
constrainDflt = 'domain';
313-
} else {constrainDflt = undefined;}
397+
} else {
398+
constrainDflt = undefined;
399+
}
314400

315401
handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, {
316-
allAxisIds: allAxisIds,
402+
allAxisIds: allAxisIdsIncludingMissing,
317403
layoutOut: layoutOut,
318404
scaleanchorDflt: scaleanchorDflt,
319405
constrainDflt: constrainDflt
@@ -324,7 +410,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
324410
var group = matchGroups[i];
325411
var rng = null;
326412
var autorange = null;
327-
var axId;
328413

329414
// find 'matching' range attrs
330415
for(axId in group) {

src/plots/plots.js

+30-9
Original file line numberDiff line numberDiff line change
@@ -1272,10 +1272,13 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac
12721272
var subplots = layout._subplots;
12731273
var subplotId = '';
12741274

1275-
// TODO - currently if we draw an empty gl2d subplot, it draws
1276-
// nothing then gets stuck and you can't get it back without newPlot
1277-
// sort this out in the regl refactor? but for now just drop empty gl2d subplots
1278-
if(basePlotModule.name !== 'gl2d' || visible) {
1275+
if(
1276+
visible ||
1277+
basePlotModule.name !== 'gl2d' // for now just drop empty gl2d subplots
1278+
// TODO - currently if we draw an empty gl2d subplot, it draws
1279+
// nothing then gets stuck and you can't get it back without newPlot
1280+
// sort this out in the regl refactor?
1281+
) {
12791282
if(Array.isArray(subplotAttr)) {
12801283
for(i = 0; i < subplotAttr.length; i++) {
12811284
var attri = subplotAttr[i];
@@ -2934,15 +2937,15 @@ plots.doCalcdata = function(gd, traces) {
29342937
calcdata[i] = cd;
29352938
}
29362939

2937-
setupAxisCategories(axList, fullData);
2940+
setupAxisCategories(axList, fullData, fullLayout);
29382941

29392942
// 'transform' loop - must calc container traces first
29402943
// so that if their dependent traces can get transform properly
29412944
for(i = 0; i < fullData.length; i++) calci(i, true);
29422945
for(i = 0; i < fullData.length; i++) transformCalci(i);
29432946

29442947
// clear stuff that should recomputed in 'regular' loop
2945-
if(hasCalcTransform) setupAxisCategories(axList, fullData);
2948+
if(hasCalcTransform) setupAxisCategories(axList, fullData, fullLayout);
29462949

29472950
// 'regular' loop - make sure container traces (eg carpet) calc before
29482951
// contained traces (eg contourcarpet)
@@ -3147,13 +3150,31 @@ function sortAxisCategoriesByValue(axList, gd) {
31473150
return affectedTraces;
31483151
}
31493152

3150-
function setupAxisCategories(axList, fullData) {
3151-
for(var i = 0; i < axList.length; i++) {
3152-
var ax = axList[i];
3153+
function setupAxisCategories(axList, fullData, fullLayout) {
3154+
var axLookup = {};
3155+
var i, ax, axId;
3156+
3157+
for(i = 0; i < axList.length; i++) {
3158+
ax = axList[i];
3159+
axId = ax._id;
3160+
31533161
ax.clearCalc();
31543162
if(ax.type === 'multicategory') {
31553163
ax.setupMultiCategory(fullData);
31563164
}
3165+
3166+
axLookup[ax._id] = 1;
3167+
}
3168+
3169+
// look into match groups for 'missing' axes
3170+
var matchGroups = fullLayout._axisMatchGroups || [];
3171+
for(i = 0; i < matchGroups.length; i++) {
3172+
for(axId in matchGroups[i]) {
3173+
if(!axLookup[axId]) {
3174+
ax = fullLayout[axisIDs.id2name(axId)];
3175+
ax.clearCalc();
3176+
}
3177+
}
31573178
}
31583179
}
31593180

35.2 KB
Loading

test/image/compare_pixels_test.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,8 @@ if(allMock || argv.filter) {
101101
}
102102

103103
var FLAKY_LIST = [
104-
'treemap_coffee',
105104
'treemap_textposition',
106-
'treemap_with-without_values_template',
105+
'treemap_with-without_values',
107106
'trace_metatext',
108107
'gl3d_directions-streamtube1'
109108
];

0 commit comments

Comments
 (0)