| 1 | /**
|
|---|
| 2 | * @fileOverview calculate tick values of scale
|
|---|
| 3 | * @author xile611, arcthur
|
|---|
| 4 | * @date 2015-09-17
|
|---|
| 5 | */
|
|---|
| 6 | import Decimal from 'decimal.js-light';
|
|---|
| 7 | import { getDigitCount, rangeStep } from './util/arithmetic';
|
|---|
| 8 | /**
|
|---|
| 9 | * Calculate a interval of a minimum value and a maximum value
|
|---|
| 10 | *
|
|---|
| 11 | * @param {Number} min The minimum value
|
|---|
| 12 | * @param {Number} max The maximum value
|
|---|
| 13 | * @return {Array} An interval
|
|---|
| 14 | */
|
|---|
| 15 | export var getValidInterval = _ref => {
|
|---|
| 16 | var [min, max] = _ref;
|
|---|
| 17 | var [validMin, validMax] = [min, max];
|
|---|
| 18 |
|
|---|
| 19 | // exchange
|
|---|
| 20 | if (min > max) {
|
|---|
| 21 | [validMin, validMax] = [max, min];
|
|---|
| 22 | }
|
|---|
| 23 | return [validMin, validMax];
|
|---|
| 24 | };
|
|---|
| 25 |
|
|---|
| 26 | /**
|
|---|
| 27 | * Calculate the step which is easy to understand between ticks, like 10, 20, 25
|
|---|
| 28 | *
|
|---|
| 29 | * @param roughStep The rough step calculated by dividing the difference by the tickCount
|
|---|
| 30 | * @param allowDecimals Allow the ticks to be decimals or not
|
|---|
| 31 | * @param correctionFactor A correction factor
|
|---|
| 32 | * @return The step which is easy to understand between two ticks
|
|---|
| 33 | */
|
|---|
| 34 | export var getFormatStep = (roughStep, allowDecimals, correctionFactor) => {
|
|---|
| 35 | if (roughStep.lte(0)) {
|
|---|
| 36 | return new Decimal(0);
|
|---|
| 37 | }
|
|---|
| 38 | var digitCount = getDigitCount(roughStep.toNumber());
|
|---|
| 39 | // The ratio between the rough step and the smallest number which has a bigger
|
|---|
| 40 | // order of magnitudes than the rough step
|
|---|
| 41 | var digitCountValue = new Decimal(10).pow(digitCount);
|
|---|
| 42 | var stepRatio = roughStep.div(digitCountValue);
|
|---|
| 43 | // When an integer and a float multiplied, the accuracy of result may be wrong
|
|---|
| 44 | var stepRatioScale = digitCount !== 1 ? 0.05 : 0.1;
|
|---|
| 45 | var amendStepRatio = new Decimal(Math.ceil(stepRatio.div(stepRatioScale).toNumber())).add(correctionFactor).mul(stepRatioScale);
|
|---|
| 46 | var formatStep = amendStepRatio.mul(digitCountValue);
|
|---|
| 47 | return allowDecimals ? new Decimal(formatStep.toNumber()) : new Decimal(Math.ceil(formatStep.toNumber()));
|
|---|
| 48 | };
|
|---|
| 49 |
|
|---|
| 50 | /**
|
|---|
| 51 | * calculate the ticks when the minimum value equals to the maximum value
|
|---|
| 52 | *
|
|---|
| 53 | * @param value The minimum value which is also the maximum value
|
|---|
| 54 | * @param tickCount The count of ticks
|
|---|
| 55 | * @param allowDecimals Allow the ticks to be decimals or not
|
|---|
| 56 | * @return array of ticks
|
|---|
| 57 | */
|
|---|
| 58 | export var getTickOfSingleValue = (value, tickCount, allowDecimals) => {
|
|---|
| 59 | var step = new Decimal(1);
|
|---|
| 60 | // calculate the middle value of ticks
|
|---|
| 61 | var middle = new Decimal(value);
|
|---|
| 62 | if (!middle.isint() && allowDecimals) {
|
|---|
| 63 | var absVal = Math.abs(value);
|
|---|
| 64 | if (absVal < 1) {
|
|---|
| 65 | // The step should be a float number when the difference is smaller than 1
|
|---|
| 66 | step = new Decimal(10).pow(getDigitCount(value) - 1);
|
|---|
| 67 | middle = new Decimal(Math.floor(middle.div(step).toNumber())).mul(step);
|
|---|
| 68 | } else if (absVal > 1) {
|
|---|
| 69 | // Return the maximum integer which is smaller than 'value' when 'value' is greater than 1
|
|---|
| 70 | middle = new Decimal(Math.floor(value));
|
|---|
| 71 | }
|
|---|
| 72 | } else if (value === 0) {
|
|---|
| 73 | middle = new Decimal(Math.floor((tickCount - 1) / 2));
|
|---|
| 74 | } else if (!allowDecimals) {
|
|---|
| 75 | middle = new Decimal(Math.floor(value));
|
|---|
| 76 | }
|
|---|
| 77 | var middleIndex = Math.floor((tickCount - 1) / 2);
|
|---|
| 78 | var ticks = [];
|
|---|
| 79 | for (var i = 0; i < tickCount; i++) {
|
|---|
| 80 | ticks.push(middle.add(new Decimal(i - middleIndex).mul(step)).toNumber());
|
|---|
| 81 | }
|
|---|
| 82 | return ticks;
|
|---|
| 83 | };
|
|---|
| 84 |
|
|---|
| 85 | /**
|
|---|
| 86 | * Calculate the step
|
|---|
| 87 | *
|
|---|
| 88 | * @param min The minimum value of an interval
|
|---|
| 89 | * @param max The maximum value of an interval
|
|---|
| 90 | * @param tickCount The count of ticks
|
|---|
| 91 | * @param allowDecimals Allow the ticks to be decimals or not
|
|---|
| 92 | * @param correctionFactor A correction factor
|
|---|
| 93 | * @return The step, minimum value of ticks, maximum value of ticks
|
|---|
| 94 | */
|
|---|
| 95 | var _calculateStep = function calculateStep(min, max, tickCount, allowDecimals) {
|
|---|
| 96 | var correctionFactor = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
|
|---|
| 97 | // dirty hack (for recharts' test)
|
|---|
| 98 | if (!Number.isFinite((max - min) / (tickCount - 1))) {
|
|---|
| 99 | return {
|
|---|
| 100 | step: new Decimal(0),
|
|---|
| 101 | tickMin: new Decimal(0),
|
|---|
| 102 | tickMax: new Decimal(0)
|
|---|
| 103 | };
|
|---|
| 104 | }
|
|---|
| 105 |
|
|---|
| 106 | // The step which is easy to understand between two ticks
|
|---|
| 107 | var step = getFormatStep(new Decimal(max).sub(min).div(tickCount - 1), allowDecimals, correctionFactor);
|
|---|
| 108 |
|
|---|
| 109 | // A medial value of ticks
|
|---|
| 110 | var middle;
|
|---|
| 111 |
|
|---|
| 112 | // When 0 is inside the interval, 0 should be a tick
|
|---|
| 113 | if (min <= 0 && max >= 0) {
|
|---|
| 114 | middle = new Decimal(0);
|
|---|
| 115 | } else {
|
|---|
| 116 | // calculate the middle value
|
|---|
| 117 | middle = new Decimal(min).add(max).div(2);
|
|---|
| 118 | // minus modulo value
|
|---|
| 119 | middle = middle.sub(new Decimal(middle).mod(step));
|
|---|
| 120 | }
|
|---|
| 121 | var belowCount = Math.ceil(middle.sub(min).div(step).toNumber());
|
|---|
| 122 | var upCount = Math.ceil(new Decimal(max).sub(middle).div(step).toNumber());
|
|---|
| 123 | var scaleCount = belowCount + upCount + 1;
|
|---|
| 124 | if (scaleCount > tickCount) {
|
|---|
| 125 | // When more ticks need to cover the interval, step should be bigger.
|
|---|
| 126 | return _calculateStep(min, max, tickCount, allowDecimals, correctionFactor + 1);
|
|---|
| 127 | }
|
|---|
| 128 | if (scaleCount < tickCount) {
|
|---|
| 129 | // When less ticks can cover the interval, we should add some additional ticks
|
|---|
| 130 | upCount = max > 0 ? upCount + (tickCount - scaleCount) : upCount;
|
|---|
| 131 | belowCount = max > 0 ? belowCount : belowCount + (tickCount - scaleCount);
|
|---|
| 132 | }
|
|---|
| 133 | return {
|
|---|
| 134 | step,
|
|---|
| 135 | tickMin: middle.sub(new Decimal(belowCount).mul(step)),
|
|---|
| 136 | tickMax: middle.add(new Decimal(upCount).mul(step))
|
|---|
| 137 | };
|
|---|
| 138 | };
|
|---|
| 139 |
|
|---|
| 140 | /**
|
|---|
| 141 | * Calculate the ticks of an interval. Ticks can appear outside the interval
|
|---|
| 142 | * if it makes them more rounded and nice.
|
|---|
| 143 | *
|
|---|
| 144 | * @param tuple of [min,max] min: The minimum value, max: The maximum value
|
|---|
| 145 | * @param tickCount The count of ticks
|
|---|
| 146 | * @param allowDecimals Allow the ticks to be decimals or not
|
|---|
| 147 | * @return array of ticks
|
|---|
| 148 | */
|
|---|
| 149 | export { _calculateStep as calculateStep };
|
|---|
| 150 | export var getNiceTickValues = function getNiceTickValues(_ref2) {
|
|---|
| 151 | var [min, max] = _ref2;
|
|---|
| 152 | var tickCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 6;
|
|---|
| 153 | var allowDecimals = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|---|
| 154 | // More than two ticks should be return
|
|---|
| 155 | var count = Math.max(tickCount, 2);
|
|---|
| 156 | var [cormin, cormax] = getValidInterval([min, max]);
|
|---|
| 157 | if (cormin === -Infinity || cormax === Infinity) {
|
|---|
| 158 | var _values = cormax === Infinity ? [cormin, ...Array(tickCount - 1).fill(Infinity)] : [...Array(tickCount - 1).fill(-Infinity), cormax];
|
|---|
| 159 | return min > max ? _values.reverse() : _values;
|
|---|
| 160 | }
|
|---|
| 161 | if (cormin === cormax) {
|
|---|
| 162 | return getTickOfSingleValue(cormin, tickCount, allowDecimals);
|
|---|
| 163 | }
|
|---|
| 164 |
|
|---|
| 165 | // Get the step between two ticks
|
|---|
| 166 | var {
|
|---|
| 167 | step,
|
|---|
| 168 | tickMin,
|
|---|
| 169 | tickMax
|
|---|
| 170 | } = _calculateStep(cormin, cormax, count, allowDecimals, 0);
|
|---|
| 171 | var values = rangeStep(tickMin, tickMax.add(new Decimal(0.1).mul(step)), step);
|
|---|
| 172 | return min > max ? values.reverse() : values;
|
|---|
| 173 | };
|
|---|
| 174 |
|
|---|
| 175 | /**
|
|---|
| 176 | * Calculate the ticks of an interval.
|
|---|
| 177 | * Ticks will be constrained to the interval [min, max] even if it makes them less rounded and nice.
|
|---|
| 178 | *
|
|---|
| 179 | * @param tuple of [min,max] min: The minimum value, max: The maximum value
|
|---|
| 180 | * @param tickCount The count of ticks. This function may return less than tickCount ticks if the interval is too small.
|
|---|
| 181 | * @param allowDecimals Allow the ticks to be decimals or not
|
|---|
| 182 | * @return array of ticks
|
|---|
| 183 | */
|
|---|
| 184 | export var getTickValuesFixedDomain = function getTickValuesFixedDomain(_ref3, tickCount) {
|
|---|
| 185 | var [min, max] = _ref3;
|
|---|
| 186 | var allowDecimals = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|---|
| 187 | // More than two ticks should be return
|
|---|
| 188 | var [cormin, cormax] = getValidInterval([min, max]);
|
|---|
| 189 | if (cormin === -Infinity || cormax === Infinity) {
|
|---|
| 190 | return [min, max];
|
|---|
| 191 | }
|
|---|
| 192 | if (cormin === cormax) {
|
|---|
| 193 | return [cormin];
|
|---|
| 194 | }
|
|---|
| 195 | var count = Math.max(tickCount, 2);
|
|---|
| 196 | var step = getFormatStep(new Decimal(cormax).sub(cormin).div(count - 1), allowDecimals, 0);
|
|---|
| 197 | var values = [...rangeStep(new Decimal(cormin), new Decimal(cormax), step), cormax];
|
|---|
| 198 | if (allowDecimals === false) {
|
|---|
| 199 | /*
|
|---|
| 200 | * allowDecimals is false means that we want to have integer ticks.
|
|---|
| 201 | * The step is guaranteed to be an integer in the code above which is great start
|
|---|
| 202 | * but when the first step is not an integer, it will start stepping from a decimal value anyway.
|
|---|
| 203 | * So we need to round all the values to integers after the fact.
|
|---|
| 204 | */
|
|---|
| 205 | values = values.map(value => Math.round(value));
|
|---|
| 206 | }
|
|---|
| 207 | return min > max ? values.reverse() : values;
|
|---|
| 208 | }; |
|---|