| /* | |
| * 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 zrUtil from 'zrender/src/core/util'; | |
| import Scale from './Scale'; | |
| import * as numberUtil from '../util/number'; | |
| import * as scaleHelper from './helper'; | |
| // Use some method of IntervalScale | |
| import IntervalScale from './Interval'; | |
| import SeriesData from '../data/SeriesData'; | |
| import { DimensionName, ScaleTick } from '../util/types'; | |
| const scaleProto = Scale.prototype; | |
| // FIXME:TS refactor: not good to call it directly with `this`? | |
| const intervalScaleProto = IntervalScale.prototype; | |
| const roundingErrorFix = numberUtil.round; | |
| const mathFloor = Math.floor; | |
| const mathCeil = Math.ceil; | |
| const mathPow = Math.pow; | |
| const mathLog = Math.log; | |
| class LogScale extends Scale { | |
| static type = 'log'; | |
| readonly type = 'log'; | |
| base = 10; | |
| private _originalScale: IntervalScale = new IntervalScale(); | |
| private _fixMin: boolean; | |
| private _fixMax: boolean; | |
| // FIXME:TS actually used by `IntervalScale` | |
| private _interval: number = 0; | |
| // FIXME:TS actually used by `IntervalScale` | |
| private _niceExtent: [number, number]; | |
| /** | |
| * @param Whether expand the ticks to niced extent. | |
| */ | |
| getTicks(expandToNicedExtent?: boolean): ScaleTick[] { | |
| const originalScale = this._originalScale; | |
| const extent = this._extent; | |
| const originalExtent = originalScale.getExtent(); | |
| const ticks = intervalScaleProto.getTicks.call(this, expandToNicedExtent); | |
| return zrUtil.map(ticks, function (tick) { | |
| const val = tick.value; | |
| let powVal = numberUtil.round(mathPow(this.base, val)); | |
| // Fix #4158 | |
| powVal = (val === extent[0] && this._fixMin) | |
| ? fixRoundingError(powVal, originalExtent[0]) | |
| : powVal; | |
| powVal = (val === extent[1] && this._fixMax) | |
| ? fixRoundingError(powVal, originalExtent[1]) | |
| : powVal; | |
| return { | |
| value: powVal | |
| }; | |
| }, this); | |
| } | |
| setExtent(start: number, end: number): void { | |
| const base = mathLog(this.base); | |
| // log(-Infinity) is NaN, so safe guard here | |
| start = mathLog(Math.max(0, start)) / base; | |
| end = mathLog(Math.max(0, end)) / base; | |
| intervalScaleProto.setExtent.call(this, start, end); | |
| } | |
| /** | |
| * @return {number} end | |
| */ | |
| getExtent() { | |
| const base = this.base; | |
| const extent = scaleProto.getExtent.call(this); | |
| extent[0] = mathPow(base, extent[0]); | |
| extent[1] = mathPow(base, extent[1]); | |
| // Fix #4158 | |
| const originalScale = this._originalScale; | |
| const originalExtent = originalScale.getExtent(); | |
| this._fixMin && (extent[0] = fixRoundingError(extent[0], originalExtent[0])); | |
| this._fixMax && (extent[1] = fixRoundingError(extent[1], originalExtent[1])); | |
| return extent; | |
| } | |
| unionExtent(extent: [number, number]): void { | |
| this._originalScale.unionExtent(extent); | |
| const base = this.base; | |
| extent[0] = mathLog(extent[0]) / mathLog(base); | |
| extent[1] = mathLog(extent[1]) / mathLog(base); | |
| scaleProto.unionExtent.call(this, extent); | |
| } | |
| unionExtentFromData(data: SeriesData, dim: DimensionName): void { | |
| // TODO | |
| // filter value that <= 0 | |
| this.unionExtent(data.getApproximateExtent(dim)); | |
| } | |
| /** | |
| * Update interval and extent of intervals for nice ticks | |
| * @param approxTickNum default 10 Given approx tick number | |
| */ | |
| calcNiceTicks(approxTickNum: number): void { | |
| approxTickNum = approxTickNum || 10; | |
| const extent = this._extent; | |
| const span = extent[1] - extent[0]; | |
| if (span === Infinity || span <= 0) { | |
| return; | |
| } | |
| let interval = numberUtil.quantity(span); | |
| const err = approxTickNum / span * interval; | |
| // Filter ticks to get closer to the desired count. | |
| if (err <= 0.5) { | |
| interval *= 10; | |
| } | |
| // Interval should be integer | |
| while (!isNaN(interval) && Math.abs(interval) < 1 && Math.abs(interval) > 0) { | |
| interval *= 10; | |
| } | |
| const niceExtent = [ | |
| numberUtil.round(mathCeil(extent[0] / interval) * interval), | |
| numberUtil.round(mathFloor(extent[1] / interval) * interval) | |
| ] as [number, number]; | |
| this._interval = interval; | |
| this._niceExtent = niceExtent; | |
| } | |
| calcNiceExtent(opt: { | |
| splitNumber: number, // By default 5. | |
| fixMin?: boolean, | |
| fixMax?: boolean, | |
| minInterval?: number, | |
| maxInterval?: number | |
| }): void { | |
| intervalScaleProto.calcNiceExtent.call(this, opt); | |
| this._fixMin = opt.fixMin; | |
| this._fixMax = opt.fixMax; | |
| } | |
| parse(val: any): number { | |
| return val; | |
| } | |
| contain(val: number): boolean { | |
| val = mathLog(val) / mathLog(this.base); | |
| return scaleHelper.contain(val, this._extent); | |
| } | |
| normalize(val: number): number { | |
| val = mathLog(val) / mathLog(this.base); | |
| return scaleHelper.normalize(val, this._extent); | |
| } | |
| scale(val: number): number { | |
| val = scaleHelper.scale(val, this._extent); | |
| return mathPow(this.base, val); | |
| } | |
| getMinorTicks: IntervalScale['getMinorTicks']; | |
| getLabel: IntervalScale['getLabel']; | |
| } | |
| const proto = LogScale.prototype; | |
| proto.getMinorTicks = intervalScaleProto.getMinorTicks; | |
| proto.getLabel = intervalScaleProto.getLabel; | |
| function fixRoundingError(val: number, originalVal: number): number { | |
| return roundingErrorFix(val, numberUtil.getPrecision(originalVal)); | |
| } | |
| Scale.registerClass(LogScale); | |
| export default LogScale; |