| /* | 
 | * Licensed to the Apache Software Foundation (ASF) under one | 
 | * or more contributor license agreements.  See the NOTICE file | 
 | * distributed with this work for additional information | 
 | * regarding copyright ownership.  The ASF licenses this file | 
 | * to you under the Apache License, Version 2.0 (the | 
 | * "License"); you may not use this file except in compliance | 
 | * with the License.  You may obtain a copy of the License at | 
 | * | 
 | *   http://www.apache.org/licenses/LICENSE-2.0 | 
 | * | 
 | * Unless required by applicable law or agreed to in writing, | 
 | * software distributed under the License is distributed on an | 
 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
 | * KIND, either express or implied.  See the License for the | 
 | * specific language governing permissions and limitations | 
 | * under the License. | 
 | */ | 
 |  | 
 |  | 
 | import * as numberUtil from '../util/number'; | 
 | import * as formatUtil from '../util/format'; | 
 | import Scale from './Scale'; | 
 | import * as helper from './helper'; | 
 | import {ScaleTick, Dictionary} from '../util/types'; | 
 |  | 
 | const roundNumber = numberUtil.round; | 
 |  | 
 | class IntervalScale<SETTING extends Dictionary<unknown> = Dictionary<unknown>> extends Scale<SETTING> { | 
 |  | 
 |     static type = 'interval'; | 
 |     type = 'interval'; | 
 |  | 
 |     // Step is calculated in adjustExtent. | 
 |     protected _interval: number = 0; | 
 |     protected _niceExtent: [number, number]; | 
 |     private _intervalPrecision: number = 2; | 
 |  | 
 |  | 
 |     parse(val: number): number { | 
 |         return val; | 
 |     } | 
 |  | 
 |     contain(val: number): boolean { | 
 |         return helper.contain(val, this._extent); | 
 |     } | 
 |  | 
 |     normalize(val: number): number { | 
 |         return helper.normalize(val, this._extent); | 
 |     } | 
 |  | 
 |     scale(val: number): number { | 
 |         return helper.scale(val, this._extent); | 
 |     } | 
 |  | 
 |     setExtent(start: number | string, end: number | string): void { | 
 |         const thisExtent = this._extent; | 
 |         // start,end may be a Number like '25',so... | 
 |         if (!isNaN(start as any)) { | 
 |             thisExtent[0] = parseFloat(start as any); | 
 |         } | 
 |         if (!isNaN(end as any)) { | 
 |             thisExtent[1] = parseFloat(end as any); | 
 |         } | 
 |     } | 
 |  | 
 |     unionExtent(other: [number, number]): void { | 
 |         const extent = this._extent; | 
 |         other[0] < extent[0] && (extent[0] = other[0]); | 
 |         other[1] > extent[1] && (extent[1] = other[1]); | 
 |  | 
 |         // unionExtent may called by it's sub classes | 
 |         this.setExtent(extent[0], extent[1]); | 
 |     } | 
 |  | 
 |     getInterval(): number { | 
 |         return this._interval; | 
 |     } | 
 |  | 
 |     setInterval(interval: number): void { | 
 |         this._interval = interval; | 
 |         // Dropped auto calculated niceExtent and use user-set extent. | 
 |         // We assume user wants to set both interval, min, max to get a better result. | 
 |         this._niceExtent = this._extent.slice() as [number, number]; | 
 |  | 
 |         this._intervalPrecision = helper.getIntervalPrecision(interval); | 
 |     } | 
 |  | 
 |     /** | 
 |      * @param expandToNicedExtent Whether expand the ticks to niced extent. | 
 |      */ | 
 |     getTicks(expandToNicedExtent?: boolean): ScaleTick[] { | 
 |         const interval = this._interval; | 
 |         const extent = this._extent; | 
 |         const niceTickExtent = this._niceExtent; | 
 |         const intervalPrecision = this._intervalPrecision; | 
 |  | 
 |         const ticks = [] as ScaleTick[]; | 
 |         // If interval is 0, return []; | 
 |         if (!interval) { | 
 |             return ticks; | 
 |         } | 
 |  | 
 |         // Consider this case: using dataZoom toolbox, zoom and zoom. | 
 |         const safeLimit = 10000; | 
 |  | 
 |         if (extent[0] < niceTickExtent[0]) { | 
 |             if (expandToNicedExtent) { | 
 |                 ticks.push({ | 
 |                     value: roundNumber(niceTickExtent[0] - interval, intervalPrecision) | 
 |                 }); | 
 |             } | 
 |             else { | 
 |                 ticks.push({ | 
 |                     value: extent[0] | 
 |                 }); | 
 |             } | 
 |         } | 
 |         let tick = niceTickExtent[0]; | 
 |  | 
 |         while (tick <= niceTickExtent[1]) { | 
 |             ticks.push({ | 
 |                 value: tick | 
 |             }); | 
 |             // Avoid rounding error | 
 |             tick = roundNumber(tick + interval, intervalPrecision); | 
 |             if (tick === ticks[ticks.length - 1].value) { | 
 |                 // Consider out of safe float point, e.g., | 
 |                 // -3711126.9907707 + 2e-10 === -3711126.9907707 | 
 |                 break; | 
 |             } | 
 |             if (ticks.length > safeLimit) { | 
 |                 return []; | 
 |             } | 
 |         } | 
 |         // Consider this case: the last item of ticks is smaller | 
 |         // than niceTickExtent[1] and niceTickExtent[1] === extent[1]. | 
 |         const lastNiceTick = ticks.length ? ticks[ticks.length - 1].value : niceTickExtent[1]; | 
 |         if (extent[1] > lastNiceTick) { | 
 |             if (expandToNicedExtent) { | 
 |                 ticks.push({ | 
 |                     value: roundNumber(lastNiceTick + interval, intervalPrecision) | 
 |                 }); | 
 |             } | 
 |             else { | 
 |                 ticks.push({ | 
 |                     value: extent[1] | 
 |                 }); | 
 |             } | 
 |         } | 
 |  | 
 |         return ticks; | 
 |     } | 
 |  | 
 |     getMinorTicks(splitNumber: number): number[][] { | 
 |         const ticks = this.getTicks(true); | 
 |         const minorTicks = []; | 
 |         const extent = this.getExtent(); | 
 |  | 
 |         for (let i = 1; i < ticks.length; i++) { | 
 |             const nextTick = ticks[i]; | 
 |             const prevTick = ticks[i - 1]; | 
 |             let count = 0; | 
 |             const minorTicksGroup = []; | 
 |             const interval = nextTick.value - prevTick.value; | 
 |             const minorInterval = interval / splitNumber; | 
 |  | 
 |             while (count < splitNumber - 1) { | 
 |                 const minorTick = roundNumber(prevTick.value + (count + 1) * minorInterval); | 
 |  | 
 |                 // For the first and last interval. The count may be less than splitNumber. | 
 |                 if (minorTick > extent[0] && minorTick < extent[1]) { | 
 |                     minorTicksGroup.push(minorTick); | 
 |                 } | 
 |                 count++; | 
 |             } | 
 |             minorTicks.push(minorTicksGroup); | 
 |         } | 
 |  | 
 |         return minorTicks; | 
 |     } | 
 |  | 
 |     /** | 
 |      * @param opt.precision If 'auto', use nice presision. | 
 |      * @param opt.pad returns 1.50 but not 1.5 if precision is 2. | 
 |      */ | 
 |     getLabel( | 
 |         data: ScaleTick, | 
 |         opt?: { | 
 |             precision?: 'auto' | number, | 
 |             pad?: boolean | 
 |         } | 
 |     ): string { | 
 |         if (data == null) { | 
 |             return ''; | 
 |         } | 
 |  | 
 |         let precision = opt && opt.precision; | 
 |  | 
 |         if (precision == null) { | 
 |             precision = numberUtil.getPrecision(data.value) || 0; | 
 |         } | 
 |         else if (precision === 'auto') { | 
 |             // Should be more precise then tick. | 
 |             precision = this._intervalPrecision; | 
 |         } | 
 |  | 
 |         // (1) If `precision` is set, 12.005 should be display as '12.00500'. | 
 |         // (2) Use roundNumber (toFixed) to avoid scientific notation like '3.5e-7'. | 
 |         const dataNum = roundNumber(data.value, precision as number, true); | 
 |  | 
 |         return formatUtil.addCommas(dataNum); | 
 |     } | 
 |  | 
 |     /** | 
 |      * @param splitNumber By default `5`. | 
 |      */ | 
 |     calcNiceTicks(splitNumber?: number, minInterval?: number, maxInterval?: number): void { | 
 |         splitNumber = splitNumber || 5; | 
 |         const extent = this._extent; | 
 |         let span = extent[1] - extent[0]; | 
 |         if (!isFinite(span)) { | 
 |             return; | 
 |         } | 
 |         // User may set axis min 0 and data are all negative | 
 |         // FIXME If it needs to reverse ? | 
 |         if (span < 0) { | 
 |             span = -span; | 
 |             extent.reverse(); | 
 |         } | 
 |  | 
 |         const result = helper.intervalScaleNiceTicks( | 
 |             extent, splitNumber, minInterval, maxInterval | 
 |         ); | 
 |  | 
 |         this._intervalPrecision = result.intervalPrecision; | 
 |         this._interval = result.interval; | 
 |         this._niceExtent = result.niceTickExtent; | 
 |     } | 
 |  | 
 |     calcNiceExtent(opt: { | 
 |         splitNumber: number, // By default 5. | 
 |         fixMin?: boolean, | 
 |         fixMax?: boolean, | 
 |         minInterval?: number, | 
 |         maxInterval?: number | 
 |     }): void { | 
 |         const extent = this._extent; | 
 |         // If extent start and end are same, expand them | 
 |         if (extent[0] === extent[1]) { | 
 |             if (extent[0] !== 0) { | 
 |                 // Expand extent | 
 |                 // Note that extents can be both negative. See #13154 | 
 |                 const expandSize = Math.abs(extent[0]); | 
 |                 // In the fowllowing case | 
 |                 //      Axis has been fixed max 100 | 
 |                 //      Plus data are all 100 and axis extent are [100, 100]. | 
 |                 // Extend to the both side will cause expanded max is larger than fixed max. | 
 |                 // So only expand to the smaller side. | 
 |                 if (!opt.fixMax) { | 
 |                     extent[1] += expandSize / 2; | 
 |                     extent[0] -= expandSize / 2; | 
 |                 } | 
 |                 else { | 
 |                     extent[0] -= expandSize / 2; | 
 |                 } | 
 |             } | 
 |             else { | 
 |                 extent[1] = 1; | 
 |             } | 
 |         } | 
 |         const span = extent[1] - extent[0]; | 
 |         // If there are no data and extent are [Infinity, -Infinity] | 
 |         if (!isFinite(span)) { | 
 |             extent[0] = 0; | 
 |             extent[1] = 1; | 
 |         } | 
 |  | 
 |         this.calcNiceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval); | 
 |         // let extent = this._extent; | 
 |         const interval = this._interval; | 
 |  | 
 |         if (!opt.fixMin) { | 
 |             extent[0] = roundNumber(Math.floor(extent[0] / interval) * interval); | 
 |         } | 
 |         if (!opt.fixMax) { | 
 |             extent[1] = roundNumber(Math.ceil(extent[1] / interval) * interval); | 
 |         } | 
 |     } | 
 |  | 
 |     setNiceExtent(min: number, max: number) { | 
 |         this._niceExtent = [min, max]; | 
 |     } | 
 | } | 
 |  | 
 | Scale.registerClass(IntervalScale); | 
 |  | 
 | export default IntervalScale; |