blob: bdd799056c1e72fa025a4edf66c8502c943811bc [file] [log] [blame]
/*
* 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, { ScaleGetTicksOpt } from './Scale';
import * as numberUtil from '../util/number';
// Use some method of IntervalScale
import IntervalScale from './Interval';
import {
DimensionLoose, DimensionName, ParsedAxisBreakList, AxisBreakOption,
ScaleTick
} from '../util/types';
import { getIntervalPrecision, logTransform } from './helper';
import SeriesData from '../data/SeriesData';
import { getScaleBreakHelper } from './break';
const fixRound = numberUtil.round;
const mathFloor = Math.floor;
const mathCeil = Math.ceil;
const mathPow = Math.pow;
const mathLog = Math.log;
class LogScale extends IntervalScale {
static type = 'log';
readonly type = 'log';
base = 10;
private _originalScale = new IntervalScale();
private _fixMin: boolean;
private _fixMax: boolean;
/**
* @param Whether expand the ticks to niced extent.
*/
getTicks(opt?: ScaleGetTicksOpt): ScaleTick[] {
opt = opt || {};
const extent = this._extent.slice() as [number, number];
const originalExtent = this._originalScale.getExtent();
const ticks = super.getTicks(opt);
const base = this.base;
const originalBreaks = this._originalScale._innerGetBreaks();
const scaleBreakHelper = getScaleBreakHelper();
return zrUtil.map(ticks, function (tick) {
const val = tick.value;
let roundingCriterion = null;
let powVal = mathPow(base, val);
// Fix #4158
if (val === extent[0] && this._fixMin) {
roundingCriterion = originalExtent[0];
}
else if (val === extent[1] && this._fixMax) {
roundingCriterion = originalExtent[1];
}
let vBreak;
if (scaleBreakHelper) {
const transformed = scaleBreakHelper.getTicksLogTransformBreak(
tick,
base,
originalBreaks,
fixRoundingError
);
vBreak = transformed.vBreak;
if (roundingCriterion == null) {
roundingCriterion = transformed.brkRoundingCriterion;
}
}
if (roundingCriterion != null) {
powVal = fixRoundingError(powVal, roundingCriterion);
}
return {
value: powVal,
break: vBreak,
};
}, this);
}
protected _getNonTransBreaks(): ParsedAxisBreakList {
return this._originalScale._innerGetBreaks();
}
setExtent(start: number, end: number): void {
this._originalScale.setExtent(start, end);
const loggedExtent = logTransform(this.base, [start, end]);
super.setExtent(loggedExtent[0], loggedExtent[1]);
}
/**
* @return {number} end
*/
getExtent() {
const base = this.base;
const extent = super.getExtent();
extent[0] = mathPow(base, extent[0]);
extent[1] = mathPow(base, extent[1]);
// Fix #4158
const originalExtent = this._originalScale.getExtent();
this._fixMin && (extent[0] = fixRoundingError(extent[0], originalExtent[0]));
this._fixMax && (extent[1] = fixRoundingError(extent[1], originalExtent[1]));
return extent;
}
unionExtentFromData(data: SeriesData, dim: DimensionName | DimensionLoose): void {
this._originalScale.unionExtentFromData(data, dim);
const loggedOther = logTransform(this.base, data.getApproximateExtent(dim), true);
this._innerUnionExtent(loggedOther);
}
/**
* 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.slice() as [number, number];
const span = this._getExtentSpanWithBreaks();
if (!isFinite(span) || 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 = [
fixRound(mathCeil(extent[0] / interval) * interval),
fixRound(mathFloor(extent[1] / interval) * interval)
] as [number, number];
this._interval = interval;
this._intervalPrecision = getIntervalPrecision(interval);
this._niceExtent = niceExtent;
}
calcNiceExtent(opt: {
splitNumber: number,
fixMin?: boolean,
fixMax?: boolean,
minInterval?: number,
maxInterval?: number
}): void {
super.calcNiceExtent(opt);
this._fixMin = opt.fixMin;
this._fixMax = opt.fixMax;
}
contain(val: number): boolean {
val = mathLog(val) / mathLog(this.base);
return super.contain(val);
}
normalize(val: number): number {
val = mathLog(val) / mathLog(this.base);
return super.normalize(val);
}
scale(val: number): number {
val = super.scale(val);
return mathPow(this.base, val);
}
setBreaksFromOption(
breakOptionList: AxisBreakOption[],
): void {
const scaleBreakHelper = getScaleBreakHelper();
if (!scaleBreakHelper) {
return;
}
const { parsedOriginal, parsedLogged } = scaleBreakHelper.logarithmicParseBreaksFromOption(
breakOptionList,
this.base,
zrUtil.bind(this.parse, this)
);
this._originalScale._innerSetBreak(parsedOriginal);
this._innerSetBreak(parsedLogged);
}
}
function fixRoundingError(val: number, originalVal: number): number {
return fixRound(val, numberUtil.getPrecision(originalVal));
}
Scale.registerClass(LogScale);
export default LogScale;