Skip to content

Commit 91431ec

Browse files
committed
drop *scaleanchor* constraints for axes under *matches* constraint
... for now, until we found a way to apply domain scaleanchor constraints on matching axes (which is theoretically possible).
1 parent 99a9edb commit 91431ec

File tree

6 files changed

+120
-57
lines changed

6 files changed

+120
-57
lines changed

src/plots/cartesian/constraints.js

+31-37
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ exports.handleConstraintDefaults = function(containerIn, containerOut, coerce, a
2929
// coerce the constraint mechanics even if this axis has no scaleanchor
3030
// because it may be the anchor of another axis.
3131
var constrain = coerce('constrain');
32-
3332
Lib.coerce(containerIn, containerOut, {
3433
constraintoward: {
3534
valType: 'enumerated',
@@ -38,38 +37,41 @@ exports.handleConstraintDefaults = function(containerIn, containerOut, coerce, a
3837
}
3938
}, 'constraintoward');
4039

41-
var scaleOpts = containerIn.scaleanchor && !(containerOut.fixedrange && constrain !== 'domain') ?
42-
getConstraintOpts(constraintGroups, thisID, allAxisIds, layoutOut, constrain) :
43-
{};
40+
var matches, matchOpts;
4441

45-
var matchOpts = (containerIn.matches || splomStash.matches) && !containerOut.fixedrange ?
46-
getConstraintOpts(matchGroups, thisID, allAxisIds, layoutOut) :
47-
{};
42+
if((containerIn.matches || splomStash.matches) && !containerOut.fixedrange) {
43+
matchOpts = getConstraintOpts(matchGroups, thisID, allAxisIds, layoutOut);
44+
matches = Lib.coerce(containerIn, containerOut, {
45+
matches: {
46+
valType: 'enumerated',
47+
values: matchOpts.linkableAxes || [],
48+
dflt: splomStash.matches
49+
}
50+
}, 'matches');
51+
}
4852

49-
var scaleanchor = Lib.coerce(containerIn, containerOut, {
50-
scaleanchor: {
51-
valType: 'enumerated',
52-
values: scaleOpts.linkableAxes || []
53-
}
54-
}, 'scaleanchor');
53+
// 'matches' wins over 'scaleanchor' (for now)
54+
var scaleanchor, scaleOpts;
5555

56-
var matches = Lib.coerce(containerIn, containerOut, {
57-
matches: {
58-
valType: 'enumerated',
59-
values: matchOpts.linkableAxes || [],
60-
dflt: splomStash.matches
61-
}
62-
}, 'matches');
56+
if(!matches && containerIn.scaleanchor && !(containerOut.fixedrange && constrain !== 'domain')) {
57+
scaleOpts = getConstraintOpts(constraintGroups, thisID, allAxisIds, layoutOut, constrain);
58+
scaleanchor = Lib.coerce(containerIn, containerOut, {
59+
scaleanchor: {
60+
valType: 'enumerated',
61+
values: scaleOpts.linkableAxes || []
62+
}
63+
}, 'scaleanchor');
64+
}
6365

64-
// disallow constraining AND matching range
65-
if(constrain === 'range' && scaleanchor && matches && scaleanchor === matches) {
66-
delete containerOut.scaleanchor;
66+
if(matches) {
6767
delete containerOut.constrain;
68-
scaleanchor = null;
68+
updateConstraintGroups(matchGroups, matchOpts.thisGroup, thisID, matches, 1);
69+
} else if(allAxisIds.indexOf(containerIn.matches) !== -1) {
70+
Lib.warn('ignored ' + containerOut._name + '.matches: "' +
71+
containerIn.matches + '" to avoid either an infinite loop ' +
72+
'or because the target axis has fixed range.');
6973
}
7074

71-
var found = false;
72-
7375
if(scaleanchor) {
7476
var scaleratio = coerce('scaleratio');
7577

@@ -81,19 +83,11 @@ exports.handleConstraintDefaults = function(containerIn, containerOut, coerce, a
8183
if(!scaleratio) scaleratio = containerOut.scaleratio = 1;
8284

8385
updateConstraintGroups(constraintGroups, scaleOpts.thisGroup, thisID, scaleanchor, scaleratio);
84-
found = true;
85-
}
86-
87-
if(matches) {
88-
updateConstraintGroups(matchGroups, matchOpts.thisGroup, thisID, matches, 1);
89-
found = true;
90-
}
91-
92-
if(!found && allAxisIds.indexOf(containerIn.scaleanchor) !== -1) {
86+
} else if(allAxisIds.indexOf(containerIn.scaleanchor) !== -1) {
9387
Lib.warn('ignored ' + containerOut._name + '.scaleanchor: "' +
9488
containerIn.scaleanchor + '" to avoid either an infinite loop ' +
95-
'and possibly inconsistent scaleratios, or because the target' +
96-
'axis has fixed range.');
89+
'and possibly inconsistent scaleratios, or because the target ' +
90+
'axis has fixed range or this axis declares a *matches* constraint.');
9791
}
9892
};
9993

src/plots/cartesian/layout_attributes.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ module.exports = {
169169
'or the same letter (to match scales across subplots).',
170170
'Loops (`yaxis: {scaleanchor: *x*}, xaxis: {scaleanchor: *y*}` or longer) are redundant',
171171
'and the last constraint encountered will be ignored to avoid possible',
172-
'inconsistent constraints via `scaleratio`.'
172+
'inconsistent constraints via `scaleratio`.',
173+
'Note that setting axes simultaneously in both a `scaleanchor` and a `matches` constraint',
174+
'is currently forbidden.'
173175
].join(' ')
174176
},
175177
scaleratio: {
@@ -224,8 +226,8 @@ module.exports = {
224226
'will match the range of the corresponding axis in data-coordinates space.',
225227
'Moreover, matching axes share auto-range values, category lists and',
226228
'histogram auto-bins.',
227-
'Note that setting `matches` and `scaleratio` under a *range* `constrain`',
228-
'to the same axis id is forbidden.'
229+
'Note that setting axes simultaneously in both a `scaleanchor` and a `matches` constraint',
230+
'is currently forbidden.'
229231
].join(' ')
230232
},
231233
// ticks

src/plots/cartesian/layout_defaults.js

+29-6
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,9 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
254254

255255
// sets of axes linked by `scaleanchor` along with the scaleratios compounded
256256
// together, populated in handleConstraintDefaults
257-
layoutOut._axisConstraintGroups = [];
257+
var constraintGroups = layoutOut._axisConstraintGroups = [];
258258
// similar to _axisConstraintGroups, but for matching axes
259-
layoutOut._axisMatchGroups = [];
259+
var matchGroups = layoutOut._axisMatchGroups = [];
260260

261261
for(i = 0; i < axNames.length; i++) {
262262
axName = axNames[i];
@@ -267,20 +267,22 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
267267
handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, allAxisIds, layoutOut);
268268
}
269269

270-
for(i = 0; i < layoutOut._axisMatchGroups.length; i++) {
271-
var group = layoutOut._axisMatchGroups[i];
270+
for(i = 0; i < matchGroups.length; i++) {
271+
var group = matchGroups[i];
272272
var rng = null;
273273
var autorange = null;
274274
var axId;
275275

276+
// find 'matching' range attrs
276277
for(axId in group) {
277278
axLayoutOut = layoutOut[id2name(axId)];
278279
if(!axLayoutOut.matches) {
279280
rng = axLayoutOut.range;
280281
autorange = axLayoutOut.autorange;
281282
}
282283
}
283-
284+
// if `ax.matches` values are reciprocal,
285+
// pick values of first axis in group
284286
if(rng === null || autorange === null) {
285287
for(axId in group) {
286288
axLayoutOut = layoutOut[id2name(axId)];
@@ -289,7 +291,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
289291
break;
290292
}
291293
}
292-
294+
// apply matching range attrs
293295
for(axId in group) {
294296
axLayoutOut = layoutOut[id2name(axId)];
295297
if(axLayoutOut.matches) {
@@ -298,5 +300,26 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
298300
}
299301
axLayoutOut._matchGroup = group;
300302
}
303+
304+
// remove matching axis from scaleanchor constraint groups (for now)
305+
if(constraintGroups.length) {
306+
for(axId in group) {
307+
for(j = 0; j < constraintGroups.length; j++) {
308+
var group2 = constraintGroups[j];
309+
for(var axId2 in group2) {
310+
if(axId === axId2) {
311+
Lib.warn('Axis ' + axId2 + ' is set with both ' +
312+
'a *scaleanchor* and *matches* constraint; ' +
313+
'ignoring the scale constraint.');
314+
315+
delete group2[axId2];
316+
if(Object.keys(group2).length < 2) {
317+
constraintGroups.splice(j, 1);
318+
}
319+
}
320+
}
321+
}
322+
}
323+
}
301324
}
302325
};
Loading

test/image/mocks/axes_scaleanchor-with-matches.json

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
{
22
"data":[
33
{"x": [0,1,1,0,0,1,1,2,2,3,3,2,2,3], "y": [0,0,1,1,3,3,2,2,3,3,1,1,0,0]},
4-
{"x": [0,1,2,3], "y": [1,2,4,8], "yaxis":"y2"}
4+
{"x": [0,1,2,3], "y": [1,2,4,8], "xaxis": "x2", "yaxis":"y2"}
55
],
66
"layout":{
77
"width": 500,
88
"height": 500,
9+
"title": {"text": "Bottom subplot has scaleanchor constrained axes<br>Top subplot has matching axes"},
910
"xaxis": {
10-
"constrain": "domain"
11+
"constrain": "range"
1112
},
1213
"yaxis": {
1314
"scaleanchor": "x",
14-
"constrain": "domain",
15+
"constrain": "range",
1516
"domain": [0, 0.45],
16-
"title": {"text": "1:1<br>matching y range above"}
17+
"title": {"text": "1:1<br>x|y scale constrain range"}
18+
},
19+
"xaxis2": {
20+
"anchor": "y2"
1721
},
1822
"yaxis2": {
19-
"matches": "y",
20-
"constrain": "domain",
21-
"scaleanchor": "x",
22-
"scaleratio": 0.2,
23+
"anchor": "x2",
24+
"matches": "x2",
2325
"domain": [0.55, 1],
24-
"title": {"text": "1:5<br>matching y range below"}
26+
"title": {"text": "matching x|y"}
2527
},
2628
"showlegend": false
2729
}

test/jasmine/tests/axes_test.js

+44-2
Original file line numberDiff line numberDiff line change
@@ -588,8 +588,8 @@ describe('Test axes', function() {
588588
});
589589

590590
var warnTxt = ' to avoid either an infinite loop and possibly ' +
591-
'inconsistent scaleratios, or because the targetaxis has ' +
592-
'fixed range.';
591+
'inconsistent scaleratios, or because the target axis has ' +
592+
'fixed range or this axis declares a *matches* constraint.';
593593

594594
it('breaks scaleanchor loops and drops conflicting ratios', function() {
595595
var warnings = [];
@@ -707,6 +707,48 @@ describe('Test axes', function() {
707707
expect(layoutOut._axisMatchGroups).toEqual([{x: 1, x2: 1}]);
708708
});
709709

710+
it('remove axes from constraint groups if they are in a match group', function() {
711+
layoutIn = {
712+
// this one is ok
713+
xaxis: {},
714+
yaxis: {scaleanchor: 'x'},
715+
// this one too
716+
xaxis2: {},
717+
yaxis2: {matches: 'x2'},
718+
// not these ones
719+
xaxis3: {scaleanchor: 'x2'},
720+
yaxis3: {scaleanchor: 'y2'}
721+
};
722+
layoutOut._subplots.cartesian.push('x2y2, x3y3');
723+
layoutOut._subplots.xaxis.push('x2', 'x3');
724+
layoutOut._subplots.yaxis.push('y2', 'y3');
725+
726+
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
727+
728+
expect(layoutOut._axisMatchGroups.length).toBe(1);
729+
expect(layoutOut._axisMatchGroups).toContain({x2: 1, y2: 1});
730+
731+
expect(layoutOut._axisConstraintGroups.length).toBe(1);
732+
expect(layoutOut._axisConstraintGroups).toContain({x: 1, y: 1});
733+
});
734+
735+
it('remove constraint group if they are one or zero items left in it', function() {
736+
layoutIn = {
737+
xaxis: {},
738+
yaxis: {matches: 'x'},
739+
xaxis2: {scaleanchor: 'y'}
740+
};
741+
layoutOut._subplots.cartesian.push('x2y');
742+
layoutOut._subplots.xaxis.push('x2');
743+
744+
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
745+
746+
expect(layoutOut._axisMatchGroups.length).toBe(1);
747+
expect(layoutOut._axisMatchGroups).toContain({x: 1, y: 1});
748+
749+
expect(layoutOut._axisConstraintGroups.length).toBe(0);
750+
});
751+
710752
it('drops scaleanchor settings if either the axis or target has fixedrange', function() {
711753
// some of these will create warnings... not too important, so not going to test,
712754
// just want to keep the output clean

0 commit comments

Comments
 (0)