8000 Merge pull request #6547 from plotly/autorange-bounds · plotly/plotly.js@0f63668 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0f63668

Browse files
authored
Merge pull request #6547 from plotly/autorange-bounds
Add bounds to range and autorange of cartesian, gl3d and radial axes
2 parents 2aa7e47 + 30a692e commit 0f63668

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1160
-111
lines changed

draftlogs/6547_add.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Add bounds to range and autorange of cartesian, gl3d and radial axes [[#6547](https://github.com/plotly/plotly.js/pull/6547)]
2+

src/components/modebar/buttons.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,16 @@ function handleCartesian(gd, ev) {
271271
if(val === 'auto') {
272272
aobj[axName + '.autorange'] = true;
273273
} else if(val === 'reset') {
274-
if(ax._rangeInitial === undefined) {
274+
if(ax._rangeInitial0 === undefined && ax._rangeInitial1 === undefined) {
275275
aobj[axName + '.autorange'] = true;
276+
} else if(ax._rangeInitial0 === undefined) {
277+
aobj[axName + '.autorange'] = ax._autorangeInitial;
278+
aobj[axName + '.range'] = [null, ax._rangeInitial1];
279+
} else if(ax._rangeInitial1 === undefined) {
280+
aobj[axName + '.range'] = [ax._rangeInitial0, null];
281+
aobj[axName + '.autorange'] = ax._autorangeInitial;
276282
} else {
277-
var rangeInitial = ax._rangeInitial.slice();
278-
aobj[axName + '.range[0]'] = rangeInitial[0];
279-
aobj[axName + '.range[1]'] = rangeInitial[1];
283+
aobj[axName + '.range'] = [ax._rangeInitial0, ax._rangeInitial1];
280284
}
281285

282286
// N.B. "reset" also resets showspikes

src/plot_api/plot_api.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,6 +1843,17 @@ function axRangeSupplyDefaultsByPass(gd, flags, specs) {
18431843
var axIn = gd.layout[axName];
18441844
var axOut = fullLayout[axName];
18451845
axOut.autorange = axIn.autorange;
1846+
1847+
var r0 = axOut._rangeInitial0;
1848+
var r1 = axOut._rangeInitial1;
1849+
// partial range needs supplyDefaults
1850+
if(
1851+
(r0 === undefined && r1 !== undefined) ||
1852+
(r0 !== undefined && r1 === undefined)
1853+
) {
1854+
return false;
1855+
}
1856+
18461857
if(axIn.range) {
18471858
axOut.range = axIn.range.slice();
18481859
}

src/plots/cartesian/autorange.js

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var getFromId = axIds.getFromId;
1313
var isLinked = axIds.isLinked;
1414

1515
module.exports = {
16+
applyAutorangeOptions: applyAutorangeOptions,
1617
getAutoRange: getAutoRange,
1718
makePadFn: makePadFn,
1819
doAutoRange: doAutoRange,
@@ -75,16 +76,20 @@ function getAutoRange(gd, ax) {
7576
maxmax = Math.max(maxmax, maxArray[i].val);
7677
}
7778

78-
var axReverse = false;
79+
var autorange = ax.autorange;
80+
var axReverse =
81+
autorange === 'reversed' ||
82+
autorange === 'min reversed' ||
83+
autorange === 'max reversed';
7984

80-
if(ax.range) {
85+
if(!axReverse && ax.range) {
8186
var rng = Lib.simpleMap(ax.range, ax.r2l);
8287
axReverse = rng[1] < rng[0];
8388
}
89+
8490
// one-time setting to easily reverse the axis
8591
// when plotting from code
8692
if(ax.autorange === 'reversed') {
87-
axReverse = true;
8893
ax.autorange = true;
8994
}
9095

@@ -176,6 +181,10 @@ function getAutoRange(gd, ax) {
176181
];
177182
}
178183

184+
newRange = applyAutorangeOptions(newRange, ax);
185+
186+
if(ax.limitRange) ax.limitRange();
187+
179188
// maintain reversal
180189
if(axReverse) newRange.reverse();
181190

@@ -209,7 +218,7 @@ function makePadFn(fullLayout, ax, max) {
209218
(ax.ticklabelposition || '').indexOf('inside') !== -1 ||
210219
(anchorAxis.ticklabelposition || '').indexOf('inside') !== -1
211220
) {
212-
var axReverse = ax.autorange === 'reversed';
221+
var axReverse = ax.isReversed();
213222
if(!axReverse) {
214223
var rng = Lib.simpleMap(ax.range, ax.r2l);
215224
axReverse = rng[1] < rng[0];
@@ -623,3 +632,91 @@ function goodNumber(v) {
623632

624633
function lessOrEqual(v0, v1) { return v0 <= v1; }
625634
function greaterOrEqual(v0, v1) { return v0 >= v1; }
635+
636+
function applyAutorangeMinOptions(v, ax) {
637+
var autorangeoptions = ax.autorangeoptions;
638+
if(
639+
autorangeoptions &&
640+
autorangeoptions.minallowed !== undefined &&
641+
hasValidMinAndMax(ax, autorangeoptions.minallowed, autorangeoptions.maxallowed)
642+
) {
643+
return autorangeoptions.minallowed;
644+
}
645+
646+
if(
647+
autorangeoptions &&
648+
autorangeoptions.clipmin !== undefined &&
649+
hasValidMinAndMax(ax, autorangeoptions.clipmin, autorangeoptions.clipmax)
650+
) {
651+
return Math.max(v, ax.d2l(autorangeoptions.clipmin));
652+
}
653+
return v;
654+
}
655+
656+
function applyAutorangeMaxOptions(v, ax) {
657+
var autorangeoptions = ax.autorangeoptions;
658+
659+
if(
660+
autorangeoptions &&
661+
autorangeoptions.maxallowed !== undefined &&
662+
hasValidMinAndMax(ax, autorangeoptions.minallowed, autorangeoptions.maxallowed)
663+
) {
664+
return autorangeoptions.maxallowed;
665+
}
666+
667+
if(
668+
autorangeoptions &&
669+
autorangeoptions.clipmax !== undefined &&
670+
hasValidMinAndMax(ax, autorangeoptions.clipmin, autorangeoptions.clipmax)
671+
) {
672+
return Math.min(v, ax.d2l(autorangeoptions.clipmax));
673+
}
674+
675+
return v;
676+
}
677+
678+
function hasValidMinAndMax(ax, min, max) {
679+
// in case both min and max are defined, ensure min < max
680+
if(
681+
min !== undefined &&
682+
max !== undefined
683+
) {
684+
min = ax.d2l(min);
685+
max = ax.d2l(max);
686+
return min < max;
687+
}
688+
return true;
689+
}
690+
691+
// this function should be (and is) called before reversing the range
692+
// so range[0] is the minimum and range[1] is the maximum
693+
function applyAutorangeOptions(range, ax) {
694+
if(!ax || !ax.autorangeoptions) return range;
695+
696+
var min = range[0];
697+
var max = range[1];
698+
699+
var include = ax.autorangeoptions.include;
700+
if(include !== undefined) {
701+
var lMin = ax.d2l(min);
702+
var lMax = ax.d2l(max);
703+
704+
if(!Lib.isArrayOrTypedArray(include)) include = [include];
705+
for(var i = 0; i < include.length; i++) {
706+
var v = ax.d2l(include[i]);
707+
if(lMin >= v) {
708+
lMin = v;
709+
min = v;
710+
}
711+
if(lMax <= v) {
712+
lMax = v;
713+
max = v;
714+
}
715+
}
716+
}
717+
718+
min = applyAutorangeMinOptions(min, ax);
719+
max = applyAutorangeMaxOptions(max, ax);
720+
721+
return [min, max];
722+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
module.exports = function handleAutorangeOptionsDefaults(coerce, autorange, range) {
4+
var minRange, maxRange;
5+
if(range) {
6+
var isReversed = (
7+
autorange === 'reversed' ||
8+
autorange === 'min reversed' ||
9+
autorange === 'max reversed'
10+
);
11+
12+
minRange = range[isReversed ? 1 : 0];
13+
maxRange = range[isReversed ? 0 : 1];
14+
}
15+
16+
var minallowed = coerce('autorangeoptions.minallowed', maxRange === null ? minRange : undefined);
17+
var maxallowed = coerce('autorangeoptions.maxallowed', minRange === null ? maxRange : undefined);
18+
19+
if(minallowed === undefined) coerce('autorangeoptions.clipmin');
20+
if(maxallowed === undefined) coerce('autorangeoptions.clipmax');
21+
22+
coerce('autorangeoptions.include');
23+
};

src/plots/cartesian/axes.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,14 +321,20 @@ axes.saveRangeInitial = function(gd, overwrite) {
321321

322322
for(var i = 0; i < axList.length; i++) {
323323
var ax = axList[i];
324-
var isNew = (ax._rangeInitial === undefined);
325-
var hasChanged = isNew || !(
326-
ax.range[0] === ax._rangeInitial[0] &&
327-
ax.range[1] === ax._rangeInitial[1]
324+
var isNew =
325+
ax._rangeInitial0 === undefined &&
326+
ax._rangeInitial1 === undefined;
327+
328+
var hasChanged = isNew || (
329+
ax.range[0] !== ax._rangeInitial0 ||
330+
ax.range[1] !== ax._rangeInitial1
328331
);
329332

330-
if((isNew && ax.autorange === false) || (overwrite && hasChanged)) {
331-
ax._rangeInitial = ax.range.slice();
333+
var autorange = ax.autorange;
334+
if((isNew && autorange !== true) || (overwrite && hasChanged)) {
335+
ax._rangeInitial0 = (autorange === 'min' || autorange === 'max reversed') ? undefined : ax.range[0];
336+
ax._rangeInitial1 = (autorange === 'max' || autorange === 'min reversed') ? undefined : ax.range[1];
337+
ax._autorangeInitial = autorange;
332338
hasOneAxisChanged = true;
333339
}
334340
}

src/plots/cartesian/axis_defaults.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var handleTickLabelDefaults = require('./tick_label_defaults');
1515
var handlePrefixSuffixDefaults = require('./prefix_suffix_defaults');
1616
var handleCategoryOrderDefaults = require('./category_order_defaults');
1717
var handleLineGridDefaults = require('./line_grid_defaults');
18+
var handleAutorangeOptionsDefaults = require('./autorange_options_defaults');
1819
var setConvert = require('./set_convert');
1920

2021
var DAY_OF_WEEK = require('./constants').WEEKDAY_PATTERN;
@@ -91,12 +92,37 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
9192

9293
setConvert(containerOut, layoutOut);
9394

94-
var autorangeDflt = !containerOut.isValidRange(containerIn.range);
95-
if(autorangeDflt && options.reverseDflt) autorangeDflt = 'reversed';
96-
var autoRange = coerce('autorange', autorangeDflt);
97-
if(autoRange && (axType === 'linear' || axType === '-')) coerce('rangemode');
95+
coerce('minallowed');
96+
coerce('maxallowed');
97+
var range = coerce('range');
98+
var autorangeDflt = containerOut.getAutorangeDflt(range, options);
99+
var autorange = coerce('autorange', autorangeDflt);
100+
101+
var shouldAutorange;
102+
103+
// validate range and set autorange true for invalid partial ranges
104+
if(range && (
105+
(range[0] === null && range[1] === null) ||
106+
((range[0] === null || range[1] === null) && (autorange === 'reversed' || autorange === true)) ||
107+
(range[0] !== null && (autorange === 'min' || autorange === 'max reversed')) ||
108+
(range[1] !== null && (autorange === 'max' || autorange === 'min reversed'))
109+
)) {
110+
range = undefined;
111+
delete containerOut.range;
112+
containerOut.autorange = true;
113+
shouldAutorange = true;
114+
}
115+
116+
if(!shouldAutorange) {
117+
autorangeDflt = containerOut.getAutorangeDflt(range, options);
118+
autorange = coerce('autorange', autorangeDflt);
119+
}
120+
121+
if(autorange) {
122+
handleAutorangeOptionsDefaults(coerce, autorange, range);
123+
if(axType === 'linear' || axType === '-') coerce('rangemode');
124+
}
98125

99-
coerce('range');
100126
containerOut.cleanRange();
101127

102128
handleCategoryOrderDefaults(containerIn, containerOut, coerce, options);

src/plots/cartesian/constraints.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,12 @@ exports.handleDefaults = function(layoutIn, layoutOut, opts) {
145145
// special logic for coupling of range and autorange
146146
// if nobody explicitly specifies autorange, but someone does
147147
// explicitly specify range, autorange must be disabled.
148-
if(attr === 'range' && val) {
148+
if(attr === 'range' && val &&
149+
axIn.range &&
150+
axIn.range.length === 2 &&
151+
axIn.range[0] !== null &&
152+
axIn.range[1] !== null
153+
) {
149154
hasRange = true;
150155
}
151156
if(attr === 'autorange' && val === null && hasRange) {

0 commit comments

Comments
 (0)
0