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';
this._reversePixels = false;
this._userMax = undefined;
this._userMin = undefined;
+ this._suggestedMax = undefined;
+ this._suggestedMin = undefined;
this._ticksLength = 0;
this._borderValue = 0;
this._cache = {};
// 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);
}
/**
* @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)
+ };
}
/**
}
}
- return {min, max};
+ return {
+ min: finiteOrDefault(min, finiteOrDefault(max, min)),
+ max: finiteOrDefault(max, finiteOrDefault(min, max))
+ };
}
invalidateCaches() {
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.
-import {isFinite, valueOrDefault} from '../helpers/helpers.core';
+import {isFinite} from '../helpers/helpers.core';
import LinearScaleBase from './scale.linearbase';
import Ticks from '../core/core.ticks';
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();
// Utils
getPixelForValue(value) {
- const me = this;
- return me.getPixelForDecimal((value - me._startValue) / me._valueRange);
+ return this.getPixelForDecimal((value - this._startValue) / this._valueRange);
}
getValueForPixel(pixel) {
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() {
-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';
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
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;