From: Jukka Kurkela Date: Mon, 16 Nov 2020 18:19:59 +0000 (+0200) Subject: Refactor/cleanup range option handling (#8057) X-Git-Tag: v3.0.0-beta.7~40 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1b6226342a6425a5926662b127ee17a7809952e6;p=thirdparty%2FChart.js.git Refactor/cleanup range option handling (#8057) --- diff --git a/src/core/core.scale.js b/src/core/core.scale.js index eac35448a..bad77d4c7 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1,7 +1,7 @@ import defaults from './core.defaults'; import Element from './core.element'; import {_alignPixel, _measureText} from '../helpers/helpers.canvas'; -import {callback as call, each, isArray, isFinite, isNullOrUndef, isObject, valueOrDefault} from '../helpers/helpers.core'; +import {callback as call, each, finiteOrDefault, isArray, isFinite, isNullOrUndef, isObject, valueOrDefault} from '../helpers/helpers.core'; import {_factorize, toDegrees, toRadians, _int16Range, HALF_PI} from '../helpers/helpers.math'; import {toFont, resolve, toPadding} from '../helpers/helpers.options'; import Ticks from './core.ticks'; @@ -367,6 +367,8 @@ export default class Scale extends Element { this._reversePixels = false; this._userMax = undefined; this._userMin = undefined; + this._suggestedMax = undefined; + this._suggestedMin = undefined; this._ticksLength = 0; this._borderValue = 0; this._cache = {}; @@ -386,6 +388,8 @@ export default class Scale extends Element { // parse min/max value, so we can properly determine min/max for other scales me._userMin = me.parse(options.min); me._userMax = me.parse(options.max); + me._suggestedMin = me.parse(options.suggestedMin); + me._suggestedMax = me.parse(options.suggestedMax); } /** @@ -404,15 +408,17 @@ export default class Scale extends Element { * @since 3.0 */ getUserBounds() { - let min = this._userMin; - let max = this._userMax; - if (isNullOrUndef(min) || isNaN(min)) { - min = Number.POSITIVE_INFINITY; - } - if (isNullOrUndef(max) || isNaN(max)) { - max = Number.NEGATIVE_INFINITY; - } - return {min, max, minDefined: isFinite(min), maxDefined: isFinite(max)}; + let {_userMin, _userMax, _suggestedMin, _suggestedMax} = this; + _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY); + _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY); + _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY); + _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY); + return { + min: finiteOrDefault(_userMin, _suggestedMin), + max: finiteOrDefault(_userMax, _suggestedMax), + minDefined: isFinite(_userMin), + maxDefined: isFinite(_userMax) + }; } /** @@ -442,7 +448,10 @@ export default class Scale extends Element { } } - return {min, max}; + return { + min: finiteOrDefault(min, finiteOrDefault(max, min)), + max: finiteOrDefault(max, finiteOrDefault(min, max)) + }; } invalidateCaches() { diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 7066e578e..5a4da3489 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -65,6 +65,16 @@ export { isNumberFinite as isFinite, }; +/** + * Returns `value` if finite, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is not finite. + * @returns {*} + */ +export function finiteOrDefault(value, defaultValue) { + return isNumberFinite(value) ? value : defaultValue; +} + /** * Returns `value` if defined, else returns `defaultValue`. * @param {*} value - The value to return if defined. diff --git a/src/scales/scale.linear.js b/src/scales/scale.linear.js index bd3fc5af6..8c14d0e22 100644 --- a/src/scales/scale.linear.js +++ b/src/scales/scale.linear.js @@ -1,4 +1,4 @@ -import {isFinite, valueOrDefault} from '../helpers/helpers.core'; +import {isFinite} from '../helpers/helpers.core'; import LinearScaleBase from './scale.linearbase'; import Ticks from '../core/core.ticks'; @@ -6,16 +6,10 @@ export default class LinearScale extends LinearScaleBase { determineDataLimits() { const me = this; - const options = me.options; const {min, max} = me.getMinMax(true); - me.min = isFinite(min) ? min : valueOrDefault(options.suggestedMin, 0); - me.max = isFinite(max) ? max : valueOrDefault(options.suggestedMax, 1); - - // Backward compatible inconsistent min for stacked - if (options.stacked && min > 0) { - me.min = 0; - } + me.min = isFinite(min) ? min : 0; + me.max = isFinite(max) ? max : 1; // Common base implementation to handle min, max, beginAtZero me.handleTickRangeOptions(); @@ -37,8 +31,7 @@ export default class LinearScale extends LinearScaleBase { // Utils getPixelForValue(value) { - const me = this; - return me.getPixelForDecimal((value - me._startValue) / me._valueRange); + return this.getPixelForDecimal((value - this._startValue) / this._valueRange); } getValueForPixel(pixel) { diff --git a/src/scales/scale.linearbase.js b/src/scales/scale.linearbase.js index 621482910..8155240d6 100644 --- a/src/scales/scale.linearbase.js +++ b/src/scales/scale.linearbase.js @@ -125,68 +125,33 @@ export default class LinearScaleBase extends Scale { handleTickRangeOptions() { const me = this; - const opts = me.options; + const {beginAtZero, stacked} = me.options; + const {minDefined, maxDefined} = me.getUserBounds(); + let {min, max} = me; + + const setMin = v => (min = minDefined ? min : v); + const setMax = v => (max = maxDefined ? max : v); - // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, - // do nothing since that would make the chart weird. If the user really wants a weird chart - // axis, they can manually override it - if (opts.beginAtZero) { - const minSign = sign(me.min); - const maxSign = sign(me.max); + if (beginAtZero || stacked) { + const minSign = sign(min); + const maxSign = sign(max); if (minSign < 0 && maxSign < 0) { - // move the top up to 0 - me.max = 0; + setMax(0); } else if (minSign > 0 && maxSign > 0) { - // move the bottom down to 0 - me.min = 0; - } - } - - const setMin = opts.min !== undefined || opts.suggestedMin !== undefined; - const setMax = opts.max !== undefined || opts.suggestedMax !== undefined; - - if (opts.min !== undefined) { - me.min = opts.min; - } else if (opts.suggestedMin !== undefined) { - if (me.min === null) { - me.min = opts.suggestedMin; - } else { - me.min = Math.min(me.min, opts.suggestedMin); - } - } - - if (opts.max !== undefined) { - me.max = opts.max; - } else if (opts.suggestedMax !== undefined) { - if (me.max === null) { - me.max = opts.suggestedMax; - } else { - me.max = Math.max(me.max, opts.suggestedMax); - } - } - - if (setMin !== setMax) { - // We set the min or the max but not both. - // So ensure that our range is good - // Inverted or 0 length range can happen when - // min is set, and no datasets are visible - if (me.min >= me.max) { - if (setMin) { - me.max = me.min + 1; - } else { - me.min = me.max - 1; - } + setMin(0); } } - if (me.min === me.max) { - me.max++; + if (min === max) { + setMax(max + 1); - if (!opts.beginAtZero) { - me.min--; + if (!beginAtZero) { + setMin(min - 1); } } + me.min = min; + me.max = max; } getTickLimit() { diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index cee47b7f1..849084f06 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -1,4 +1,4 @@ -import {isFinite, isNullOrUndef} from '../helpers/helpers.core'; +import {finiteOrDefault, isFinite} from '../helpers/helpers.core'; import {_setMinAndMaxByKey, log10} from '../helpers/helpers.math'; import Scale from '../core/core.scale'; import LinearScaleBase from './scale.linearbase'; @@ -9,10 +9,6 @@ function isMajor(tickVal) { return remain === 1; } -function finiteOrDefault(value, def) { - return isFinite(value) ? value : def; -} - /** * Generate a set of logarithmic ticks * @param generationOptions the options used to generate the ticks @@ -86,38 +82,33 @@ export default class LogarithmicScale extends Scale { handleTickRangeOptions() { const me = this; - const {suggestedMax, suggestedMin} = me.options; - const DEFAULT_MIN = 1; - const DEFAULT_MAX = 10; + const {minDefined, maxDefined} = me.getUserBounds(); let min = me.min; let max = me.max; - if (!isNullOrUndef(suggestedMin)) { - min = Math.min(min, suggestedMin); - } - if (!isNullOrUndef(suggestedMax)) { - max = Math.max(max, suggestedMax); - } + const setMin = v => (min = minDefined ? min : v); + const setMax = v => (max = maxDefined ? max : v); + const exp = (v, m) => Math.pow(10, Math.floor(log10(v)) + m); if (min === max) { if (min <= 0) { // includes null - min = DEFAULT_MIN; - max = DEFAULT_MAX; + setMin(1); + setMax(10); } else { - min = Math.pow(10, Math.floor(log10(min)) - 1); - max = Math.pow(10, Math.floor(log10(max)) + 1); + setMin(exp(min, -1)); + setMax(exp(max, +1)); } } if (min <= 0) { - min = Math.pow(10, Math.floor(log10(max)) - 1); + setMin(exp(max, -1)); } if (max <= 0) { - max = Math.pow(10, Math.floor(log10(min)) + 1); + setMax(exp(min, +1)); } - // if data has `0` in it or `beginAtZero` is true, and min (non zero) value is at bottom - // of scale, lower the min bound by one exp. - if (!me._userMin && me._zero && min === Math.pow(10, Math.floor(log10(me.min)))) { - min = Math.pow(10, Math.floor(log10(min)) - 1); + // if data has `0` in it or `beginAtZero` is true, min (non zero) value is at bottom + // of scale, and it does not equal suggestedMin, lower the min bound by one exp. + if (me._zero && me.min !== me._suggestedMin && min === exp(me.min, 0)) { + setMin(exp(min, -1)); } me.min = min; me.max = max;