/* * 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;