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

Add bounds to range and autorange of cartesian, gl3d and radial axes #6547

Merged
merged 43 commits into from Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2b470ba
add autorange min max to cartesian, gl3d & radial axes
archmoj Mar 31, 2023
e981da5
test autorange min max on cartesian gl3d & radial axes
archmoj Mar 31, 2023
c7b57f5
add range min and max to cartesian, gl3d and radial axes
archmoj Apr 3, 2023
8c2320d
draft log for PR 6547
archmoj Apr 3, 2023
3cda56a
Merge remote-tracking branch 'origin/master' into autorange-bounds
archmoj May 2, 2023
3f2e6e7
rename autorange clip min max
archmoj May 2, 2023
cf7b264
add autorange exact min and max
archmoj May 2, 2023
78b5160
centralized function
archmoj May 2, 2023
8c61f11
add autorange include
archmoj May 2, 2023
c528781
revisit autorange include options
archmoj May 2, 2023
113ec69
centralize autorange options defaults
archmoj May 2, 2023
c7153ca
ensure min < max in case both defined
archmoj May 2, 2023
ab9d1cc
limit range during scattergl update
archmoj May 3, 2023
16a8213
limit corner draggers considering rangemin or rangemax
archmoj May 5, 2023
4d891b3
ensure ranges stay in bound after setting in dragAxList
archmoj May 10, 2023
d787b9f
revise attributes
archmoj May 17, 2023
cb55c08
introduce null range and new autorange options
archmoj May 18, 2023
595a00c
use null in ranges to set defaults
archmoj May 18, 2023
fc79461
declare isReversed() function
archmoj May 18, 2023
c38ed70
use null ranges as default values for autorange eoptions min/max allowed
archmoj May 23, 2023
f8a0501
Merge branch 'master' into autorange-bounds
archmoj May 30, 2023
14ea726
Merge remote-tracking branch 'origin/master' into autorange-bounds
archmoj Jul 19, 2023
13dfe31
update draftlog
archmoj Jul 20, 2023
308ae20
Merge remote-tracking branch 'origin/master' into autorange-bounds
archmoj Jul 25, 2023
e4f906c
Update src/plots/cartesian/layout_attributes.js
archmoj Jul 25, 2023
04b8efa
update schema test
archmoj Jul 25, 2023
086282c
fix hasValidMinAndMax for autorangeoptions allowed attrs
archmoj Jul 26, 2023
699fb78
keep null items in range when calling cleanRange
archmoj Aug 1, 2023
36cb0fa
do not set autorange to false in constraints.handleDefaults for paria…
archmoj Aug 1, 2023
8e707c6
test partial ranges on matching axes
archmoj Aug 1, 2023
c6a147f
Update src/plots/cartesian/layout_attributes.js
archmoj Aug 2, 2023
1f5b819
update schema
archmoj Aug 2, 2023
cc03cf8
Update src/plots/cartesian/layout_attributes.js
archmoj Aug 2, 2023
51ca21e
update schema
archmoj Aug 2, 2023
4527d5d
Merge branch 'master' into autorange-bounds
archmoj Aug 11, 2023
be1d805
keep _rangeInitial0 & _rangeInitial1 instead of _rangeInitial array
archmoj Aug 9, 2023
a7ce254
fix double click interactions for partial ranges
archmoj Aug 18, 2023
fbe1fb9
add jasmine test
archmoj Aug 18, 2023
f0d48e6
revisit axReverse in autorange
archmoj Aug 18, 2023
5d4f665
fix double click on min reversed and max reversed cases
archmoj Aug 18, 2023
faccfc6
validate partial ranges and set autorange true for invalid partial ra…
archmoj Aug 22, 2023
7776305
handle invalid range and reversed autorange
archmoj Aug 23, 2023
30a692e
do not accept partial ranges if autorange is set to true
archmoj Aug 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions draftlogs/6547_add.md
@@ -0,0 +1,2 @@
- Add bounds to range and autorange of cartesian, gl3d and radial axes [[#6547](https://github.com/plotly/plotly.js/pull/6547)]

12 changes: 8 additions & 4 deletions src/components/modebar/buttons.js
Expand Up @@ -271,12 +271,16 @@ function handleCartesian(gd, ev) {
if(val === 'auto') {
aobj[axName + '.autorange'] = true;
} else if(val === 'reset') {
if(ax._rangeInitial === undefined) {
if(ax._rangeInitial0 === undefined && ax._rangeInitial1 === undefined) {
aobj[axName + '.autorange'] = true;
} else if(ax._rangeInitial0 === undefined) {
aobj[axName + '.autorange'] = ax._autorangeInitial;
aobj[axName + '.range'] = [null, ax._rangeInitial1];
} else if(ax._rangeInitial1 === undefined) {
aobj[axName + '.range'] = [ax._rangeInitial0, null];
aobj[axName + '.autorange'] = ax._autorangeInitial;
} else {
var rangeInitial = ax._rangeInitial.slice();
aobj[axName + '.range[0]'] = rangeInitial[0];
aobj[axName + '.range[1]'] = rangeInitial[1];
aobj[axName + '.range'] = [ax._rangeInitial0, ax._rangeInitial1];
}

// N.B. "reset" also resets showspikes
Expand Down
11 changes: 11 additions & 0 deletions src/plot_api/plot_api.js
Expand Up @@ -1843,6 +1843,17 @@ function axRangeSupplyDefaultsByPass(gd, flags, specs) {
var axIn = gd.layout[axName];
var axOut = fullLayout[axName];
axOut.autorange = axIn.autorange;

var r0 = axOut._rangeInitial0;
var r1 = axOut._rangeInitial1;
// partial range needs supplyDefaults
if(
(r0 === undefined && r1 !== undefined) ||
(r0 !== undefined && r1 === undefined)
) {
return false;
}

if(axIn.range) {
axOut.range = axIn.range.slice();
}
Expand Down
105 changes: 101 additions & 4 deletions src/plots/cartesian/autorange.js
Expand Up @@ -13,6 +13,7 @@ var getFromId = axIds.getFromId;
var isLinked = axIds.isLinked;

module.exports = {
applyAutorangeOptions: applyAutorangeOptions,
getAutoRange: getAutoRange,
makePadFn: makePadFn,
doAutoRange: doAutoRange,
Expand Down Expand Up @@ -75,16 +76,20 @@ function getAutoRange(gd, ax) {
maxmax = Math.max(maxmax, maxArray[i].val);
}

var axReverse = false;
var autorange = ax.autorange;
var axReverse =
autorange === 'reversed' ||
autorange === 'min reversed' ||
autorange === 'max reversed';

if(ax.range) {
if(!axReverse && ax.range) {
var rng = Lib.simpleMap(ax.range, ax.r2l);
axReverse = rng[1] < rng[0];
}

// one-time setting to easily reverse the axis
// when plotting from code
if(ax.autorange === 'reversed') {
axReverse = true;
ax.autorange = true;
}

Expand Down Expand Up @@ -176,6 +181,10 @@ function getAutoRange(gd, ax) {
];
}

newRange = applyAutorangeOptions(newRange, ax);

if(ax.limitRange) ax.limitRange();

// maintain reversal
if(axReverse) newRange.reverse();

Expand Down Expand Up @@ -209,7 +218,7 @@ function makePadFn(fullLayout, ax, max) {
(ax.ticklabelposition || '').indexOf('inside') !== -1 ||
(anchorAxis.ticklabelposition || '').indexOf('inside') !== -1
) {
var axReverse = ax.autorange === 'reversed';
var axReverse = ax.isReversed();
if(!axReverse) {
var rng = Lib.simpleMap(ax.range, ax.r2l);
axReverse = rng[1] < rng[0];
Expand Down Expand Up @@ -623,3 +632,91 @@ function goodNumber(v) {

function lessOrEqual(v0, v1) { return v0 <= v1; }
function greaterOrEqual(v0, v1) { return v0 >= v1; }

function applyAutorangeMinOptions(v, ax) {
var autorangeoptions = ax.autorangeoptions;
if(
autorangeoptions &&
autorangeoptions.minallowed !== undefined &&
hasValidMinAndMax(ax, autorangeoptions.minallowed, autorangeoptions.maxallowed)
) {
return autorangeoptions.minallowed;
}

if(
autorangeoptions &&
autorangeoptions.clipmin !== undefined &&
hasValidMinAndMax(ax, autorangeoptions.clipmin, autorangeoptions.clipmax)
) {
return Math.max(v, ax.d2l(autorangeoptions.clipmin));
}
return v;
}

function applyAutorangeMaxOptions(v, ax) {
var autorangeoptions = ax.autorangeoptions;

if(
autorangeoptions &&
autorangeoptions.maxallowed !== undefined &&
hasValidMinAndMax(ax, autorangeoptions.minallowed, autorangeoptions.maxallowed)
) {
return autorangeoptions.maxallowed;
}

if(
autorangeoptions &&
autorangeoptions.clipmax !== undefined &&
hasValidMinAndMax(ax, autorangeoptions.clipmin, autorangeoptions.clipmax)
) {
return Math.min(v, ax.d2l(autorangeoptions.clipmax));
}

return v;
}

function hasValidMinAndMax(ax, min, max) {
// in case both min and max are defined, ensure min < max
if(
min !== undefined &&
max !== undefined
) {
min = ax.d2l(min);
max = ax.d2l(max);
return min < max;
}
return true;
}

// this function should be (and is) called before reversing the range
// so range[0] is the minimum and range[1] is the maximum
function applyAutorangeOptions(range, ax) {
if(!ax || !ax.autorangeoptions) return range;

var min = range[0];
var max = range[1];

var include = ax.autorangeoptions.include;
if(include !== undefined) {
var lMin = ax.d2l(min);
var lMax = ax.d2l(max);

if(!Lib.isArrayOrTypedArray(include)) include = [include];
for(var i = 0; i < include.length; i++) {
var v = ax.d2l(include[i]);
if(lMin >= v) {
lMin = v;
min = v;
}
if(lMax <= v) {
lMax = v;
max = v;
}
}
}

min = applyAutorangeMinOptions(min, ax);
max = applyAutorangeMaxOptions(max, ax);

return [min, max];
}
23 changes: 23 additions & 0 deletions src/plots/cartesian/autorange_options_defaults.js
@@ -0,0 +1,23 @@
'use strict';

module.exports = function handleAutorangeOptionsDefaults(coerce, autorange, range) {
var minRange, maxRange;
if(range) {
var isReversed = (
autorange === 'reversed' ||
autorange === 'min reversed' ||
autorange === 'max reversed'
);

minRange = range[isReversed ? 1 : 0];
maxRange = range[isReversed ? 0 : 1];
}

var minallowed = coerce('autorangeoptions.minallowed', maxRange === null ? minRange : undefined);
var maxallowed = coerce('autorangeoptions.maxallowed', minRange === null ? maxRange : undefined);

if(minallowed === undefined) coerce('autorangeoptions.clipmin');
if(maxallowed === undefined) coerce('autorangeoptions.clipmax');

coerce('autorangeoptions.include');
};
18 changes: 12 additions & 6 deletions src/plots/cartesian/axes.js
Expand Up @@ -321,14 +321,20 @@ axes.saveRangeInitial = function(gd, overwrite) {

for(var i = 0; i < axList.length; i++) {
var ax = axList[i];
var isNew = (ax._rangeInitial === undefined);
var hasChanged = isNew || !(
ax.range[0] === ax._rangeInitial[0] &&
ax.range[1] === ax._rangeInitial[1]
var isNew =
ax._rangeInitial0 === undefined &&
ax._rangeInitial1 === undefined;

var hasChanged = isNew || (
ax.range[0] !== ax._rangeInitial0 ||
ax.range[1] !== ax._rangeInitial1
);

if((isNew && ax.autorange === false) || (overwrite && hasChanged)) {
ax._rangeInitial = ax.range.slice();
var autorange = ax.autorange;
if((isNew && autorange !== true) || (overwrite && hasChanged)) {
ax._rangeInitial0 = (autorange === 'min' || autorange === 'max reversed') ? undefined : ax.range[0];
ax._rangeInitial1 = (autorange === 'max' || autorange === 'min reversed') ? undefined : ax.range[1];
ax._autorangeInitial = autorange;
hasOneAxisChanged = true;
}
}
Expand Down
36 changes: 31 additions & 5 deletions src/plots/cartesian/axis_defaults.js
Expand Up @@ -15,6 +15,7 @@ var handleTickLabelDefaults = require('./tick_label_defaults');
var handlePrefixSuffixDefaults = require('./prefix_suffix_defaults');
var handleCategoryOrderDefaults = require('./category_order_defaults');
var handleLineGridDefaults = require('./line_grid_defaults');
var handleAutorangeOptionsDefaults = require('./autorange_options_defaults');
var setConvert = require('./set_convert');

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

setConvert(containerOut, layoutOut);

var autorangeDflt = !containerOut.isValidRange(containerIn.range);
if(autorangeDflt && options.reverseDflt) autorangeDflt = 'reversed';
var autoRange = coerce('autorange', autorangeDflt);
if(autoRange && (axType === 'linear' || axType === '-')) coerce('rangemode');
coerce('minallowed');
coerce('maxallowed');
var range = coerce('range');
var autorangeDflt = containerOut.getAutorangeDflt(range, options);
var autorange = coerce('autorange', autorangeDflt);

var shouldAutorange;

// validate range and set autorange true for invalid partial ranges
if(range && (
(range[0] === null && range[1] === null) ||
((range[0] === null || range[1] === null) && (autorange === 'reversed' || autorange === true)) ||
(range[0] !== null && (autorange === 'min' || autorange === 'max reversed')) ||
(range[1] !== null && (autorange === 'max' || autorange === 'min reversed'))
)) {
range = undefined;
delete containerOut.range;
containerOut.autorange = true;
shouldAutorange = true;
}

if(!shouldAutorange) {
autorangeDflt = containerOut.getAutorangeDflt(range, options);
autorange = coerce('autorange', autorangeDflt);
}

if(autorange) {
handleAutorangeOptionsDefaults(coerce, autorange, range);
if(axType === 'linear' || axType === '-') coerce('rangemode');
}

coerce('range');
containerOut.cleanRange();

handleCategoryOrderDefaults(containerIn, containerOut, coerce, options);
Expand Down
7 changes: 6 additions & 1 deletion src/plots/cartesian/constraints.js
Expand Up @@ -145,7 +145,12 @@ exports.handleDefaults = function(layoutIn, layoutOut, opts) {
// special logic for coupling of range and autorange
// if nobody explicitly specifies autorange, but someone does
// explicitly specify range, autorange must be disabled.
if(attr === 'range' && val) {
if(attr === 'range' && val &&
axIn.range &&
axIn.range.length === 2 &&
axIn.range[0] !== null &&
axIn.range[1] !== null
) {
hasRange = true;
}
if(attr === 'autorange' && val === null && hasRange) {
Expand Down