Skip to content

Commit dee136d

Browse files
committed
forbid scale constraints along with fixedrange
1 parent 13f848e commit dee136d

File tree

4 files changed

+112
-55
lines changed

4 files changed

+112
-55
lines changed

Diff for: src/plot_api/plot_api.js

+1
Original file line numberDiff line numberDiff line change
@@ -2107,6 +2107,7 @@ function _relayout(gd, aobj) {
21072107
pp1 === 'rangemode' ||
21082108
pp1 === 'type' ||
21092109
pp1 === 'domain' ||
2110+
pp1 === 'fixedrange' ||
21102111
ai.indexOf('calendar') !== -1 ||
21112112
ai.match(/^(bar|box|font)/)) {
21122113
flags.docalc = true;

Diff for: src/plots/cartesian/constraint_defaults.js

+19-12
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var id2name = require('./axis_ids').id2name;
1616
module.exports = function handleConstraintDefaults(containerIn, containerOut, coerce, counterAxes, layoutOut) {
1717
var constraintGroups = layoutOut._axisConstraintGroups;
1818

19-
if(!containerIn.scaleanchor) return;
19+
if(containerOut.fixedrange || !containerIn.scaleanchor) return;
2020

2121
var constraintOpts = getConstraintOpts(constraintGroups, containerOut._id, counterAxes, layoutOut);
2222

@@ -41,8 +41,9 @@ module.exports = function handleConstraintDefaults(containerIn, containerOut, co
4141
}
4242
else if(counterAxes.indexOf(containerIn.scaleanchor) !== -1) {
4343
Lib.warn('ignored ' + containerOut._name + '.scaleanchor: "' +
44-
containerIn.scaleanchor + '" to avoid an infinite loop ' +
45-
'and possibly inconsistent scaleratios.');
44+
containerIn.scaleanchor + '" to avoid either an infinite loop ' +
45+
'and possibly inconsistent scaleratios, or because the target' +
46+
'axis has fixed range.');
4647
}
4748
};
4849

@@ -53,23 +54,29 @@ function getConstraintOpts(constraintGroups, thisID, counterAxes, layoutOut) {
5354

5455
var thisType = layoutOut[id2name(thisID)].type;
5556

56-
var i, j, idj;
57+
var i, j, idj, axj;
58+
59+
var linkableAxes = [];
60+
for(j = 0; j < counterAxes.length; j++) {
61+
idj = counterAxes[j];
62+
axj = layoutOut[id2name(idj)];
63+
if(axj.type === thisType && !axj.fixedrange) linkableAxes.push(idj);
64+
}
65+
5766
for(i = 0; i < constraintGroups.length; i++) {
5867
if(constraintGroups[i][thisID]) {
5968
var thisGroup = constraintGroups[i];
6069

61-
var linkableAxes = [];
62-
for(j = 0; j < counterAxes.length; j++) {
63-
idj = counterAxes[j];
64-
if(!thisGroup[idj] && layoutOut[id2name(idj)].type === thisType) {
65-
linkableAxes.push(idj);
66-
}
70+
var linkableAxesNoLoops = [];
71+
for(j = 0; j < linkableAxes.length; j++) {
72+
idj = linkableAxes[j];
73+
if(!thisGroup[idj]) linkableAxesNoLoops.push(idj);
6774
}
68-
return {linkableAxes: linkableAxes, thisGroup: thisGroup};
75+
return {linkableAxes: linkableAxesNoLoops, thisGroup: thisGroup};
6976
}
7077
}
7178

72-
return {linkableAxes: counterAxes, thisGroup: null};
79+
return {linkableAxes: linkableAxes, thisGroup: null};
7380
}
7481

7582

Diff for: src/plots/cartesian/layout_defaults.js

+25-21
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
117117

118118
var bgColor = Color.combine(plot_bgcolor, layoutOut.paper_bgcolor);
119119

120-
var axName, axLayoutIn, axLayoutOut;
120+
var axName, axLetter, axLayoutIn, axLayoutOut;
121121

122122
function coerce(attr, dflt) {
123123
return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt);
@@ -128,6 +128,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
128128
return Lib.simpleMap(list, axisIds.name2id);
129129
}
130130

131+
var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')};
132+
131133
function getOverlayableAxes(axLetter, axName) {
132134
var list = {x: xaList, y: yaList}[axLetter];
133135
var out = [];
@@ -143,12 +145,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
143145
return out;
144146
}
145147

146-
// sets of axes linked by `scaleanchor` along with the scaleratios compounded
147-
// together, populated in handleConstraintDefaults
148-
layoutOut._axisConstraintGroups = [];
149-
150-
// first pass creates the containers and determines types, because
151-
// we need to have all types predetermined before setting constraints
148+
// first pass creates the containers, determines types, and handles most of the settings
152149
for(i = 0; i < axesList.length; i++) {
153150
axName = axesList[i];
154151

@@ -160,17 +157,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
160157
axLayoutOut = layoutOut[axName] = {};
161158

162159
handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, fullData, axName);
163-
}
164160

165-
// second pass handles most of the settings
166-
for(i = 0; i < axesList.length; i++) {
167-
axName = axesList[i];
168-
169-
axLayoutIn = layoutIn[axName];
170-
axLayoutOut = layoutOut[axName];
171-
172-
var axLetter = axName.charAt(0);
173-
var counterAxes = getCounterAxes(axLetter);
161+
axLetter = axName.charAt(0);
174162
var overlayableAxes = getOverlayableAxes(axLetter, axName);
175163

176164
var defaultOptions = {
@@ -185,11 +173,9 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
185173

186174
handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut);
187175

188-
handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, counterAxes, layoutOut);
189-
190176
var positioningOptions = {
191177
letter: axLetter,
192-
counterAxes: counterAxes,
178+
counterAxes: counterAxes[axLetter],
193179
overlayableAxes: overlayableAxes
194180
};
195181

@@ -198,7 +184,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
198184
axLayoutOut._input = axLayoutIn;
199185
}
200186

201-
// quick third pass for range slider and selector defaults
187+
// quick second pass for range slider and selector defaults
202188
var rangeSliderDefaults = Registry.getComponentMethod('rangeslider', 'handleDefaults'),
203189
rangeSelectorDefaults = Registry.getComponentMethod('rangeselector', 'handleDefaults');
204190

@@ -237,4 +223,22 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
237223

238224
coerce('fixedrange', fixedRangeDflt);
239225
}
226+
227+
// Finally, handle scale constraints. We need to do this after all axes have
228+
// coerced both `type` (so we link only axes of the same type) and
229+
// `fixedrange` (so we can avoid linking from OR TO a fixed axis).
230+
231+
// sets of axes linked by `scaleanchor` along with the scaleratios compounded
232+
// together, populated in handleConstraintDefaults
233+
layoutOut._axisConstraintGroups = [];
234+
235+
for(i = 0; i < axesList.length; i++) {
236+
axName = axesList[i];
237+
axLetter = axName.charAt(0);
238+
239+
axLayoutIn = layoutIn[axName];
240+
axLayoutOut = layoutOut[axName];
241+
242+
handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, counterAxes[axLetter], layoutOut);
243+
}
240244
};

Diff for: test/jasmine/tests/axes_test.js

+67-22
Original file line numberDiff line numberDiff line change
@@ -449,14 +449,14 @@ describe('Test axes', function() {
449449
layoutIn = {
450450
// first group: linked in series, scales compound
451451
xaxis: {},
452-
yaxis: {scalewith: 'x', scaleratio: 2},
453-
xaxis2: {scalewith: 'y', scaleratio: 3},
454-
yaxis2: {scalewith: 'x2', scaleratio: 5},
452+
yaxis: {scaleanchor: 'x', scaleratio: 2},
453+
xaxis2: {scaleanchor: 'y', scaleratio: 3},
454+
yaxis2: {scaleanchor: 'x2', scaleratio: 5},
455455
// second group: linked in parallel, scales don't compound
456456
yaxis3: {},
457-
xaxis3: {scalewith: 'y3'}, // default scaleratio: 1
458-
xaxis4: {scalewith: 'y3', scaleratio: 7},
459-
xaxis5: {scalewith: 'y3', scaleratio: 9}
457+
xaxis3: {scaleanchor: 'y3'}, // default scaleratio: 1
458+
xaxis4: {scaleanchor: 'y3', scaleratio: 7},
459+
xaxis5: {scaleanchor: 'y3', scaleratio: 9}
460460
};
461461

462462
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
@@ -467,20 +467,20 @@ describe('Test axes', function() {
467467
]);
468468
});
469469

470-
it('breaks scalewith loops and drops conflicting ratios', function() {
470+
it('breaks scaleanchor loops and drops conflicting ratios', function() {
471471
var warnings = [];
472472
spyOn(Lib, 'warn').and.callFake(function(msg) {
473473
warnings.push(msg);
474474
});
475475

476476
layoutIn = {
477-
xaxis: {scalewith: 'y', scaleratio: 2},
478-
yaxis: {scalewith: 'x', scaleratio: 3},
477+
xaxis: {scaleanchor: 'y', scaleratio: 2},
478+
yaxis: {scaleanchor: 'x', scaleratio: 3},
479479

480-
xaxis2: {scalewith: 'y2', scaleratio: 5},
481-
yaxis2: {scalewith: 'x3', scaleratio: 7},
482-
xaxis3: {scalewith: 'y3', scaleratio: 9},
483-
yaxis3: {scalewith: 'x2', scaleratio: 11}
480+
xaxis2: {scaleanchor: 'y2', scaleratio: 5},
481+
yaxis2: {scaleanchor: 'x3', scaleratio: 7},
482+
xaxis3: {scaleanchor: 'y3', scaleratio: 9},
483+
yaxis3: {scaleanchor: 'x2', scaleratio: 11}
484484
};
485485

486486
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
@@ -490,24 +490,26 @@ describe('Test axes', function() {
490490
{x2: 5 * 7 * 9, y2: 7 * 9, y3: 1, x3: 9}
491491
]);
492492

493+
var warnTxt = ' to avoid either an infinite loop and possibly ' +
494+
'inconsistent scaleratios, or because the targetaxis has ' +
495+
'fixed range.';
496+
493497
expect(warnings).toEqual([
494-
'ignored yaxis.scalewith: "x" to avoid an infinite loop ' +
495-
'and possibly inconsistent scaleratios.',
496-
'ignored yaxis3.scalewith: "x2" to avoid an infinite loop ' +
497-
'and possibly inconsistent scaleratios.'
498+
'ignored yaxis.scaleanchor: "x"' + warnTxt,
499+
'ignored yaxis3.scaleanchor: "x2"' + warnTxt
498500
]);
499501
});
500502

501-
it('silently drops invalid scalewith values', function() {
503+
it('silently drops invalid scaleanchor values', function() {
502504
var warnings = [];
503505
spyOn(Lib, 'warn').and.callFake(function(msg) {
504506
warnings.push(msg);
505507
});
506508

507509
layoutIn = {
508-
xaxis: {scalewith: 'x2', scaleratio: 2}, // must be opposite letter
509-
yaxis: {scalewith: 'x4', scaleratio: 3}, // doesn't exist
510-
xaxis2: {scalewith: 'yaxis', scaleratio: 5} // must be an id, not a name
510+
xaxis: {scaleanchor: 'x2', scaleratio: 2}, // must be opposite letter
511+
yaxis: {scaleanchor: 'x4', scaleratio: 3}, // doesn't exist
512+
xaxis2: {scaleanchor: 'yaxis', scaleratio: 5} // must be an id, not a name
511513
};
512514

513515
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
@@ -516,7 +518,50 @@ describe('Test axes', function() {
516518
expect(warnings).toEqual([]);
517519

518520
['xaxis', 'yaxis', 'xaxis2'].forEach(function(axName) {
519-
expect(layoutOut[axName].scalewith).toBeUndefined();
521+
expect(layoutOut[axName].scaleanchor).toBeUndefined(axName);
522+
expect(layoutOut[axName].scaleratio).toBeUndefined(axName);
523+
});
524+
});
525+
526+
it('will not link axes of different types', function() {
527+
layoutIn = {
528+
xaxis: {type: 'linear'},
529+
yaxis: {type: 'log', scaleanchor: 'x', scaleratio: 2},
530+
xaxis2: {type: 'date', scaleanchor: 'y', scaleratio: 3},
531+
yaxis2: {type: 'category', scaleanchor: 'x2', scaleratio: 5}
532+
};
533+
534+
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
535+
536+
expect(layoutOut._axisConstraintGroups).toEqual([]);
537+
538+
['xaxis', 'yaxis', 'xaxis2', 'yaxis2'].forEach(function(axName) {
539+
expect(layoutOut[axName].scaleanchor).toBeUndefined(axName);
540+
expect(layoutOut[axName].scaleratio).toBeUndefined(axName);
541+
});
542+
});
543+
544+
it('drops scaleanchor settings if either the axis or target has fixedrange', function() {
545+
// some of these will create warnings... not too important, so not going to test,
546+
// just want to keep the output clean
547+
// spyOn(Lib, 'warn');
548+
549+
layoutIn = {
550+
xaxis: {fixedrange: true, scaleanchor: 'y', scaleratio: 2},
551+
yaxis: {scaleanchor: 'x2', scaleratio: 3}, // only this one should survive
552+
xaxis2: {},
553+
yaxis2: {scaleanchor: 'x', scaleratio: 5}
554+
};
555+
556+
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
557+
558+
expect(layoutOut._axisConstraintGroups).toEqual([{x2: 1, y: 3}]);
559+
560+
expect(layoutOut.yaxis.scaleanchor).toBe('x2');
561+
expect(layoutOut.yaxis.scaleratio).toBe(3);
562+
563+
['xaxis', 'yaxis2', 'xaxis2'].forEach(function(axName) {
564+
expect(layoutOut[axName].scaleanchor).toBeUndefined();
520565
expect(layoutOut[axName].scaleratio).toBeUndefined();
521566
});
522567
});

0 commit comments

Comments
 (0)