@oliasoft-open-source/charts-library 2.5.27 → 2.6.0-beta-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/release-notes.md +7 -0
- package/src/components/line-chart/axis-scales/axis-scales.js +165 -0
- package/src/components/line-chart/line-chart-prop-types.js +3 -0
- package/src/components/line-chart/line-chart-utils.js +9 -0
- package/src/components/line-chart/line-chart.interface.ts +1 -0
- package/src/components/line-chart/line-chart.jsx +2 -2
- package/src/helpers/chart-consts.js +2 -0
- package/src/helpers/numbers/numbers.js +44 -0
- package/src/helpers/range/estimate-data-series-have-close-values.js +54 -0
- package/src/helpers/range/range.js +95 -0
package/package.json
CHANGED
package/release-notes.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Charts Library Release Notes
|
|
2
2
|
|
|
3
|
+
## 2.6.0
|
|
4
|
+
|
|
5
|
+
- Add support for optional `autoAxisPadding` prop, which autoscales 5% padding around data ([OW-10398](https://oliasoft.atlassian.net/browse/OW-10398))
|
|
6
|
+
- Fix bug in default scales when all data points similar values ([OW-4327](https://oliasoft.atlassian.net/browse/OW-4327))
|
|
7
|
+
|
|
8
|
+
- Add guard to useEffect in line-chart
|
|
9
|
+
|
|
3
10
|
## 2.5.27
|
|
4
11
|
|
|
5
12
|
- Add guard to useEffect in line-chart
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import getLineChartScales from '../get-line-chart-scales';
|
|
2
|
+
import { AxisType } from '../../../helpers/enums';
|
|
3
|
+
import { getAxisTypeFromKey } from '../line-chart-utils';
|
|
4
|
+
import { estimateDataSeriesHaveCloseValues } from '../../../helpers/range/estimate-data-series-have-close-values';
|
|
5
|
+
import { getSuggestedAxisRange } from '../../../helpers/range/range';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
Function that counts the number of occurences of "x" and "y" in an array and returns the one with the highest count.
|
|
9
|
+
@param {Array} scalesKeys - An array of strings that may contain "x" or "y".
|
|
10
|
+
@return {string} - Returns "x" if "x" occurs more times, "y" if "y" occurs more times, or null if they occur the same number of times.
|
|
11
|
+
*/
|
|
12
|
+
const checkMultiAxis = (scalesKeys) => {
|
|
13
|
+
let counts = {
|
|
14
|
+
x: 0,
|
|
15
|
+
y: 0,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
scalesKeys.forEach((axeKey) => {
|
|
19
|
+
const key = getAxisTypeFromKey(axeKey);
|
|
20
|
+
counts[key]++;
|
|
21
|
+
});
|
|
22
|
+
const res =
|
|
23
|
+
(counts.x > counts.y && AxisType.X) ||
|
|
24
|
+
(counts.y > counts.x && AxisType.Y) ||
|
|
25
|
+
(counts.x === counts.y && null);
|
|
26
|
+
|
|
27
|
+
return res;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
Function that returns an object containing all the values for a specific key in an array of objects,
|
|
32
|
+
grouped by the unique values of another key in the same objects.
|
|
33
|
+
@param {Array} data - An array of objects to search for the keys.
|
|
34
|
+
@return {Object} - Returns an object with keys representing the unique values for the annotationAxis key in the data array,
|
|
35
|
+
and values representing an array of all values for the value key in the data array.
|
|
36
|
+
*/
|
|
37
|
+
const getAnnotationsData = (data) => {
|
|
38
|
+
return data.reduce((acc, obj) => {
|
|
39
|
+
return {
|
|
40
|
+
...acc,
|
|
41
|
+
[obj.annotationAxis]: [...(acc[obj.annotationAxis] || []), obj.value],
|
|
42
|
+
};
|
|
43
|
+
}, {});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* return data for each axes
|
|
48
|
+
* @function getAxesData
|
|
49
|
+
* @return {object}
|
|
50
|
+
*/
|
|
51
|
+
const getAxesData = (scalesKeys, datasets, annotationsData) => {
|
|
52
|
+
const allData =
|
|
53
|
+
datasets?.reduce((acc, item) => [...acc, ...item.data], []) ?? [];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* return data by key and depends on axes count
|
|
57
|
+
* @function getData
|
|
58
|
+
* @return {object}
|
|
59
|
+
*/
|
|
60
|
+
const getData = () => {
|
|
61
|
+
return scalesKeys.reduce((acc, axeKey) => {
|
|
62
|
+
const key = getAxisTypeFromKey(axeKey);
|
|
63
|
+
const data = getAnnotationsData(annotationsData);
|
|
64
|
+
const formData = allData
|
|
65
|
+
?.map((val) => val?.[key])
|
|
66
|
+
.concat(data[key] || []);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
...acc,
|
|
70
|
+
[axeKey]: [...new Set(formData)],
|
|
71
|
+
};
|
|
72
|
+
}, {});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
Function that returns an object containing the unique values for a multi axis key in an array of objects.
|
|
77
|
+
@return {Object} - Returns an object with key representing the found key in the scalesKeys array,
|
|
78
|
+
and values representing an array of unique values for that key in the datasets array. If no key is found, an empty object is returned.
|
|
79
|
+
*/
|
|
80
|
+
const getDataForMultiAxes = () => {
|
|
81
|
+
const multiAxesKey = checkMultiAxis(scalesKeys);
|
|
82
|
+
const axes = scalesKeys?.filter((key) => key?.includes(multiAxesKey)) ?? [];
|
|
83
|
+
const data = getAnnotationsData(annotationsData);
|
|
84
|
+
const [first, second] = axes;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Reduces an array of objects and returns an array of values from the specified key
|
|
88
|
+
* @param {Array} arr - The array of objects to be reduced
|
|
89
|
+
* @param {string} key - The key to extract the values from
|
|
90
|
+
* @returns {Array} - An array of values from the specified key
|
|
91
|
+
*/
|
|
92
|
+
const reduceData = (arr, key) =>
|
|
93
|
+
arr.reduce((acc, { [key]: value }) => [...acc, value], []);
|
|
94
|
+
|
|
95
|
+
return datasets.reduce(
|
|
96
|
+
(acc, obj) => {
|
|
97
|
+
const include = Object.values(obj).some((val) => axes.includes(val));
|
|
98
|
+
const key = include ? second : first;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
...acc,
|
|
102
|
+
[key]: [
|
|
103
|
+
...new Set([
|
|
104
|
+
...acc[key],
|
|
105
|
+
...reduceData(obj?.data, multiAxesKey),
|
|
106
|
+
...((!include && data?.[multiAxesKey]) || []),
|
|
107
|
+
]),
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
{ [first]: [], [second]: [] },
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
...getData(),
|
|
117
|
+
...getDataForMultiAxes(),
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Auto scales axis ranges (mix, max) if no explicit range is set
|
|
123
|
+
* - overrides some edge cases not handled well by default chart.js
|
|
124
|
+
* - supports optional padding when `autoAxisPadding` is set
|
|
125
|
+
* - otherwise does not set min/max (falls back to chart.js default)
|
|
126
|
+
*
|
|
127
|
+
* @function autoScale
|
|
128
|
+
* @return {object} scales
|
|
129
|
+
*/
|
|
130
|
+
export const autoScale = (options, state, generatedDatasets) => {
|
|
131
|
+
const { additionalAxesOptions = {}, annotations = {} } = options || {};
|
|
132
|
+
const { annotationsData = [] } = annotations || {};
|
|
133
|
+
const scales = getLineChartScales(options, state) || {};
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
!additionalAxesOptions?.autoAxisPadding &&
|
|
137
|
+
!estimateDataSeriesHaveCloseValues(generatedDatasets)
|
|
138
|
+
) {
|
|
139
|
+
return scales;
|
|
140
|
+
}
|
|
141
|
+
const scalesKeys = Object.keys(scales) ?? [];
|
|
142
|
+
const data =
|
|
143
|
+
getAxesData(scalesKeys, generatedDatasets, annotationsData) ?? {};
|
|
144
|
+
|
|
145
|
+
const adjustedScales = scalesKeys?.reduce((acc, key) => {
|
|
146
|
+
const { min: propMin = undefined, max: propMax = undefined } = scales[key];
|
|
147
|
+
const { min, max } = getSuggestedAxisRange({
|
|
148
|
+
data: data[key],
|
|
149
|
+
beginAtZero: additionalAxesOptions?.beginAtZero,
|
|
150
|
+
autoAxisPadding: additionalAxesOptions?.autoAxisPadding,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const res = {
|
|
154
|
+
[key]: {
|
|
155
|
+
...scales[key],
|
|
156
|
+
min: propMin ?? min,
|
|
157
|
+
max: propMax ?? max,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return { ...acc, ...res };
|
|
162
|
+
}, {});
|
|
163
|
+
|
|
164
|
+
return adjustedScales ?? scales;
|
|
165
|
+
};
|
|
@@ -44,6 +44,7 @@ export const LineChartPropTypes = {
|
|
|
44
44
|
max: PropTypes.number,
|
|
45
45
|
}),
|
|
46
46
|
}),
|
|
47
|
+
autoAxisPadding: PropTypes.bool,
|
|
47
48
|
}),
|
|
48
49
|
chartStyling: PropTypes.shape({
|
|
49
50
|
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
@@ -141,6 +142,8 @@ export const getDefaultProps = (props) => {
|
|
|
141
142
|
suggestedMin: props.chart.options.additionalAxesOptions.suggestedMin,
|
|
142
143
|
suggestedMax: props.chart.options.additionalAxesOptions.suggestedMax,
|
|
143
144
|
range: props.chart.options.additionalAxesOptions.range,
|
|
145
|
+
autoAxisPadding:
|
|
146
|
+
props.chart.options.additionalAxesOptions.autoAxisPadding ?? false,
|
|
144
147
|
},
|
|
145
148
|
chartStyling: {
|
|
146
149
|
width: props.chart.options.chartStyling.width,
|
|
@@ -181,3 +181,12 @@ export const generateAxisId = (axisType, index = 0, hasMultiAxes = false) => {
|
|
|
181
181
|
const i = hasMultiAxes ? index + 1 : '';
|
|
182
182
|
return `${axisType}${i}`;
|
|
183
183
|
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
Get axis type from a key string.
|
|
187
|
+
@param {string} string - The key string to extract from.
|
|
188
|
+
@returns {string} e.g. x1 => x
|
|
189
|
+
*/
|
|
190
|
+
export const getAxisTypeFromKey = (string) => {
|
|
191
|
+
return string.match(/[^0-9/]+/gi)[0];
|
|
192
|
+
};
|
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
} from './state/action-types';
|
|
35
35
|
import Controls from '../controls/controls';
|
|
36
36
|
import { getDefaultProps, LineChartPropTypes } from './line-chart-prop-types';
|
|
37
|
-
import getLineChartScales from './get-line-chart-scales';
|
|
38
37
|
import getLineChartToolTips from './get-line-chart-tooltips';
|
|
39
38
|
import getLineChartDataLabels from './get-line-chart-data-labels';
|
|
40
39
|
import {
|
|
@@ -72,6 +71,7 @@ import {
|
|
|
72
71
|
} from '../../helpers/enums';
|
|
73
72
|
import { getAxesRangesFromChart } from './get-axes-ranges-from-chart';
|
|
74
73
|
import { generateAxisId } from './line-chart-utils';
|
|
74
|
+
import { autoScale } from './axis-scales/axis-scales';
|
|
75
75
|
|
|
76
76
|
ChartJS.register(
|
|
77
77
|
LinearScale,
|
|
@@ -479,7 +479,7 @@ const LineChart = (props) => {
|
|
|
479
479
|
showLine: state.lineEnabled,
|
|
480
480
|
},
|
|
481
481
|
},
|
|
482
|
-
scales:
|
|
482
|
+
scales: autoScale(options, state, generatedDatasets),
|
|
483
483
|
plugins: {
|
|
484
484
|
// title: getTitle(options),
|
|
485
485
|
datalabels: getLineChartDataLabels(options),
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DECIMAL_POINT_TOLERANCE } from '../chart-consts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rounds a number to N decimal places
|
|
5
|
+
*
|
|
6
|
+
* @param {number} v value
|
|
7
|
+
* @param {number} n decimal count
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
export const roundN = (v, n) => {
|
|
11
|
+
const factor = 10 ** n;
|
|
12
|
+
return Math.round(v * factor) / factor;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Rounds a number to DECIMAL_POINT_TOLERANCE decimal places
|
|
17
|
+
*
|
|
18
|
+
* @param {number} v value
|
|
19
|
+
* @returns {number}
|
|
20
|
+
*/
|
|
21
|
+
export const round = (v) => roundN(v, DECIMAL_POINT_TOLERANCE);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines whether two numbers are close in value with a tolerance
|
|
25
|
+
* (mitigates excess JavaScript floating point precision quirks)
|
|
26
|
+
*
|
|
27
|
+
* Inspired by WellDesign implementations:
|
|
28
|
+
* - isCloseTo in rounding.js
|
|
29
|
+
* - isEqual in TDHYDutils.js (recommended by Truls, but quirks comparing values close to 0 need testing)
|
|
30
|
+
* - TODO: replace this with a universal Oliasoft implementation when ready
|
|
31
|
+
*
|
|
32
|
+
* @param {number} a
|
|
33
|
+
* @param {number} b
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
export const isEqualWithTolerance = (a, b) => {
|
|
37
|
+
if (typeof a == 'number' && typeof b === typeof a) {
|
|
38
|
+
const tolerance = 10 ** -DECIMAL_POINT_TOLERANCE;
|
|
39
|
+
const difference = Math.abs(b - a);
|
|
40
|
+
const roundedDifference = roundN(difference, DECIMAL_POINT_TOLERANCE);
|
|
41
|
+
return roundedDifference <= tolerance;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { isEqualWithTolerance } from '../numbers/numbers';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Estimates whether any of the data series has values that are all close together
|
|
5
|
+
* - checks only the first and last values in each series (i.e. assumes they are ordered)
|
|
6
|
+
* - uses an equality check with tolerance for decimal precision noise
|
|
7
|
+
* - this is just an inexpensive "guesstimate" (full min/max detection can be used afterwards)
|
|
8
|
+
*
|
|
9
|
+
* @param {Array} generatedDatasets chart dataset series with x, y points
|
|
10
|
+
* @return {boolean} - at least one series has values that seem close together
|
|
11
|
+
*/
|
|
12
|
+
export const estimateDataSeriesHaveCloseValues = (generatedDatasets) => {
|
|
13
|
+
if (!Array.isArray(generatedDatasets) || !generatedDatasets.length) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const axesFirstLast = generatedDatasets.reduce((acc, dataset) => {
|
|
17
|
+
const xAxisId = dataset?.xAxisID ?? 'defaultX';
|
|
18
|
+
const yAxisId = dataset?.yAxisID ?? 'defaultY';
|
|
19
|
+
const data = dataset?.data;
|
|
20
|
+
if (data && data.length) {
|
|
21
|
+
const { x: xFirstCurrent, y: yFirstCurrent } = data?.[0] ?? {};
|
|
22
|
+
const { x: xLastCurrent, y: yLastCurrent } = data.at(-1) ?? {};
|
|
23
|
+
const xFirstAcc = acc?.[xAxisId]?.xFirst ?? xFirstCurrent;
|
|
24
|
+
const xLastAcc = acc?.[xAxisId]?.xLast ?? xLastCurrent;
|
|
25
|
+
const yFirstAcc = acc?.[yAxisId]?.yFirst ?? yFirstCurrent;
|
|
26
|
+
const yLastAcc = acc?.[yAxisId]?.yLast ?? yLastCurrent;
|
|
27
|
+
const xFirst = Math.min(xFirstCurrent, xFirstAcc);
|
|
28
|
+
const xLast = Math.max(xLastCurrent, xLastAcc);
|
|
29
|
+
const yFirst = Math.min(yFirstCurrent, yFirstAcc);
|
|
30
|
+
const yLast = Math.max(yLastCurrent, yLastAcc);
|
|
31
|
+
acc = {
|
|
32
|
+
...acc,
|
|
33
|
+
[xAxisId]: {
|
|
34
|
+
...acc[xAxisId],
|
|
35
|
+
xFirst,
|
|
36
|
+
xLast,
|
|
37
|
+
},
|
|
38
|
+
[yAxisId]: {
|
|
39
|
+
yFirst,
|
|
40
|
+
yLast,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return acc;
|
|
45
|
+
}, {});
|
|
46
|
+
return Object.values(axesFirstLast).some(
|
|
47
|
+
({ xFirst, xLast, yFirst, yLast }) => {
|
|
48
|
+
return (
|
|
49
|
+
isEqualWithTolerance(xFirst, xLast) ||
|
|
50
|
+
isEqualWithTolerance(yFirst, yLast)
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isEqualWithTolerance, round } from '../numbers/numbers';
|
|
2
|
+
|
|
3
|
+
const whiteSpacePercentage = 0.05; // relative amount of white space on each "side" of the data points
|
|
4
|
+
|
|
5
|
+
const defaultRange = { min: -1, max: 1 };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Overrides the default chart.js axis range for some edge-cases:
|
|
9
|
+
* - when no data -> default range
|
|
10
|
+
* - when all values are close to zero -> default range
|
|
11
|
+
* - when all values are close to each other -> custom 5% padding
|
|
12
|
+
* - when autoAxisPadding is set -> custom 5% padding
|
|
13
|
+
* - all other cases fall back to chart.js default behaviour
|
|
14
|
+
*
|
|
15
|
+
* `autoAxisPadding` feature requirements:
|
|
16
|
+
* - specified by Truls and ported by Mark+Oleg
|
|
17
|
+
* - numbers that are equal (within tolerance) shall be presented as a straight line
|
|
18
|
+
* - all other data series shall use 90% of width of axis (5% padding each side)
|
|
19
|
+
* - the padding on each side shall be symmetric
|
|
20
|
+
*
|
|
21
|
+
* @param {object} args
|
|
22
|
+
* @param {array<number|null>} args.data
|
|
23
|
+
* @param {boolean} [args.beginAtZero]
|
|
24
|
+
* @param {boolean>} [args.autoAxisPadding]
|
|
25
|
+
* @returns {object} returns {min, max} pair
|
|
26
|
+
*/
|
|
27
|
+
export const getSuggestedAxisRange = ({
|
|
28
|
+
data,
|
|
29
|
+
beginAtZero = false,
|
|
30
|
+
autoAxisPadding = false,
|
|
31
|
+
}) => {
|
|
32
|
+
const dataMin = Math.min(
|
|
33
|
+
...data.filter((v) => v !== null && v !== undefined && !isNaN(v)),
|
|
34
|
+
);
|
|
35
|
+
const dataMax = Math.max(
|
|
36
|
+
...data.filter((v) => v !== null && v !== undefined && !isNaN(v)),
|
|
37
|
+
);
|
|
38
|
+
const isNegative = Math.sign(dataMin) === -1 || Math.sign(dataMax) === -1;
|
|
39
|
+
const isCloseToZeroWithTolerance =
|
|
40
|
+
isEqualWithTolerance(dataMin, 0) && isEqualWithTolerance(dataMax, 0);
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
Use default range upon no data or when all values are close to 0
|
|
44
|
+
*/
|
|
45
|
+
if (!data.length || isCloseToZeroWithTolerance) {
|
|
46
|
+
return defaultRange;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
When all values are close to the same, always add some padding OW-4327
|
|
51
|
+
*/
|
|
52
|
+
if (isEqualWithTolerance(dataMin, dataMax)) {
|
|
53
|
+
const point = dataMax;
|
|
54
|
+
const padding = point * whiteSpacePercentage;
|
|
55
|
+
const minAxisValue = beginAtZero && !isNegative ? 0 : point - padding;
|
|
56
|
+
const maxAxisValue = beginAtZero && isNegative ? 0 : point + padding;
|
|
57
|
+
return {
|
|
58
|
+
min: round(minAxisValue),
|
|
59
|
+
max: round(maxAxisValue),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
Else fall back to native chart.js implementation when autoAxisPadding is off
|
|
65
|
+
*/
|
|
66
|
+
if (!autoAxisPadding) {
|
|
67
|
+
return {
|
|
68
|
+
min: undefined,
|
|
69
|
+
max: undefined,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
Else use custom auto scaling with 5% padding (only when autoAxisPadding is set)
|
|
75
|
+
*/
|
|
76
|
+
const rangeBeginAtZero = dataMin === 0 || dataMax === 0 || beginAtZero;
|
|
77
|
+
const positiveAndNegative =
|
|
78
|
+
Math.sign(dataMin) === -1 && Math.sign(dataMax) === 1;
|
|
79
|
+
|
|
80
|
+
const range = Math.abs(dataMax - dataMin);
|
|
81
|
+
const padding = autoAxisPadding ? range * whiteSpacePercentage : 0;
|
|
82
|
+
const minAxisValue =
|
|
83
|
+
!positiveAndNegative && rangeBeginAtZero && beginAtZero && !isNegative
|
|
84
|
+
? 0
|
|
85
|
+
: dataMin - padding;
|
|
86
|
+
const maxAxisValue =
|
|
87
|
+
!positiveAndNegative && rangeBeginAtZero && beginAtZero && isNegative
|
|
88
|
+
? 0
|
|
89
|
+
: dataMax + padding;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
min: round(minAxisValue),
|
|
93
|
+
max: round(maxAxisValue),
|
|
94
|
+
};
|
|
95
|
+
};
|