Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle 'missing' matching axes #4529

Merged
merged 11 commits into from Jan 31, 2020
6 changes: 6 additions & 0 deletions src/plot_api/subroutines.js
Expand Up @@ -670,13 +670,15 @@ exports.doAutoRangeAndConstraints = function(gd) {
var fullLayout = gd._fullLayout;
var axList = Axes.list(gd, '', true);
var matchGroups = fullLayout._axisMatchGroups || [];
var axLookup = {};
var ax;
var axRng;

for(var i = 0; i < axList.length; i++) {
ax = axList[i];
cleanAxisConstraints(gd, ax);
doAutoRange(gd, ax);
axLookup[ax._id] = 1;
}

enforceAxisConstraints(gd);
Expand All @@ -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);
Expand Down
8 changes: 6 additions & 2 deletions src/plots/cartesian/axes.js
Expand Up @@ -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);

Expand Down
3 changes: 1 addition & 2 deletions src/plots/cartesian/constants.js
Expand Up @@ -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')
Expand Down
139 changes: 112 additions & 27 deletions src/plots/cartesian/layout_defaults.js
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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 = [];
Expand All @@ -165,9 +185,30 @@ 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);
// 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 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) {
missingMatchedAxisIdsLookup[matchesIn] = axLayoutIn.type;
missingMatchedAxisIds = Object.keys(missingMatchedAxisIdsLookup);
}
}

// 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])) {
Expand All @@ -176,20 +217,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]) ||
Expand All @@ -207,13 +235,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);
Expand All @@ -239,12 +267,63 @@ 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();

axLayoutOut._input = axLayoutIn;
}

// coerce the 'missing' axes
i = 0;
while(i < missingMatchedAxisIds.length) {
archmoj marked this conversation as resolved.
Show resolved Hide resolved
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);

axLayoutOut.type = missingMatchedAxisIdsLookup[axId] || 'linear';

handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2, layoutOut);

handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
letter: axLetter,
counterAxes: counterAxes[axLetter],
overlayableAxes: getOverlayableAxes(axLetter, axName),
grid: layoutOut.grid
});

coerce('fixedrange');

addMissingMatchedAxis();

axLayoutOut._input = axLayoutIn;
}

Expand Down Expand Up @@ -295,25 +374,32 @@ 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];

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,
allAxisIds: allAxisIdsIncludingMissing,
layoutOut: layoutOut,
scaleanchorDflt: scaleanchorDflt,
constrainDflt: constrainDflt
Expand All @@ -324,7 +410,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) {
Expand Down
39 changes: 30 additions & 9 deletions src/plots/plots.js
Expand Up @@ -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];
Expand Down Expand Up @@ -2934,15 +2937,15 @@ 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
for(i = 0; i < fullData.length; i++) calci(i, true);
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)
Expand Down Expand Up @@ -3147,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();
}
}
}
}

Expand Down
Binary file added test/image/baselines/matching-missing-axes.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/image/compare_pixels_test.js
Expand Up @@ -104,6 +104,7 @@ var FLAKY_LIST = [
'treemap_coffee',
'treemap_textposition',
'treemap_with-without_values_template',
'treemap_with-without_values',
archmoj marked this conversation as resolved.
Show resolved Hide resolved
'trace_metatext',
'gl3d_directions-streamtube1'
];
Expand Down