| /* | |
| * 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 Scale, { ScaleGetTicksOpt } from './Scale'; | |
| import IntervalScale from './Interval'; | |
| import { | |
| ScaleTick, | |
| NullUndefined, | |
| AxisBreakOption | |
| } from '../util/types'; | |
| import { | |
| logScalePowTick, | |
| IntervalScaleGetLabelOpt, | |
| logScaleLogTick, | |
| ValueTransformLookupOpt, | |
| } from './helper'; | |
| import { getBreaksUnsafe, getScaleBreakHelper, ParseAxisBreakOptionInwardTransformOut } from './break'; | |
| import { getMinorTicks } from './minorTicks'; | |
| import { | |
| DecoratedScaleMapperMethods, decorateScaleMapper, enableScaleMapperFreeze, SCALE_EXTENT_KIND_EFFECTIVE, | |
| SCALE_MAPPER_DEPTH_OUT_OF_BREAK, | |
| ScaleMapperTransformOutOpt | |
| } from './scaleMapper'; | |
| import { map } from 'zrender/src/core/util'; | |
| import { isValidBoundsForExtent } from '../util/model'; | |
| import { isNullableNumberFinite } from '../util/number'; | |
| type LogScaleSetting = { | |
| logBase: number | NullUndefined; | |
| breakOption: AxisBreakOption[] | NullUndefined; | |
| }; | |
| const LOOKUP_IDX_EXTENT_START = 0; | |
| const LOOKUP_IDX_EXTENT_END = 1; | |
| const LOOKUP_IDX_BREAK_START = 2; | |
| /** | |
| * @final NEVER inherit me! | |
| */ | |
| class LogScale extends Scale<LogScale> { | |
| static type = 'log'; | |
| readonly type = 'log' as const; | |
| readonly base: number; | |
| /** | |
| * `powStub` is used to save original values, i.e., values before logarithm | |
| * applied, such as raw extent and raw breaks. | |
| * NOTE: Logarithm transform is probably not inversible by rounding error, which | |
| * may cause min/max tick is displayed like `5.999999999999999`. The extent in | |
| * powStub is used to get the original precise extent for this issue. | |
| * | |
| * [CAVEAT] `powStub` and `intervalStub` should be modified synchronously. | |
| */ | |
| readonly powStub: IntervalScale; | |
| /** | |
| * `intervalStub` provides linear tick arrangement (logarithm applied). | |
| * @see {powStub} | |
| */ | |
| readonly intervalStub: IntervalScale; | |
| private _lookup: ValueTransformLookupOpt['lookup']; | |
| constructor(setting: LogScaleSetting) { | |
| super(); | |
| this.parse = IntervalScale.parse; | |
| this.base = setting.logBase || 10; | |
| const lookupFrom: number[] = []; | |
| const lookupTo: number[] = []; | |
| const lookup = this._lookup = {from: lookupFrom, to: lookupTo}; | |
| lookupFrom[LOOKUP_IDX_EXTENT_START] = | |
| lookupFrom[LOOKUP_IDX_EXTENT_END] = | |
| lookupTo[LOOKUP_IDX_EXTENT_START] = | |
| lookupTo[LOOKUP_IDX_EXTENT_END] = NaN; | |
| decorateScaleMapper(this, LogScale.mapperMethods); | |
| const scaleBreakHelper = getScaleBreakHelper(); | |
| const breakOption = setting.breakOption; | |
| const out: ParseAxisBreakOptionInwardTransformOut = {lookup}; | |
| if (scaleBreakHelper) { | |
| scaleBreakHelper.parseAxisBreakOptionInwardTransform( | |
| breakOption, this, {noNegative: true}, LOOKUP_IDX_BREAK_START, out | |
| ); | |
| } | |
| this.powStub = new IntervalScale({breakParsed: out.original}); | |
| this.intervalStub = new IntervalScale({breakParsed: out.transformed}); | |
| enableScaleMapperFreeze(this, this.intervalStub); | |
| } | |
| getTicks(opt?: ScaleGetTicksOpt): ScaleTick[] { | |
| const base = this.base; | |
| const powStub = this.powStub; | |
| const scaleBreakHelper = getScaleBreakHelper(); | |
| const intervalStub = this.intervalStub; | |
| const intervalExtent = intervalStub.getExtent(); | |
| const powExtent = powStub.getExtent(); | |
| const powOpt: ValueTransformLookupOpt = {lookup: {from: intervalExtent, to: powExtent}}; | |
| return map(intervalStub.getTicks(opt || {}), function (tick) { | |
| const val = tick.value; | |
| let powVal = logScalePowTick(val, base, powOpt); | |
| let vBreak; | |
| if (scaleBreakHelper) { | |
| const brkPowResult = scaleBreakHelper.getTicksBreakOutwardTransform( | |
| this, | |
| tick, | |
| getBreaksUnsafe(powStub), | |
| this._lookup, | |
| ); | |
| if (brkPowResult) { | |
| vBreak = brkPowResult.vBreak; | |
| powVal = brkPowResult.tickVal; | |
| } | |
| } | |
| return { | |
| value: powVal, | |
| break: vBreak, | |
| }; | |
| }, this); | |
| } | |
| getMinorTicks(splitNumber: number): number[][] { | |
| return getMinorTicks( | |
| this, | |
| splitNumber, | |
| getBreaksUnsafe(this.powStub), | |
| // NOTE: minor ticks are in the log scale value to visually hint users "logarithm". | |
| this.intervalStub.getConfig().interval | |
| ); | |
| } | |
| getLabel( | |
| data: ScaleTick, | |
| opt?: IntervalScaleGetLabelOpt | |
| ) { | |
| return this.intervalStub.getLabel(data, opt); | |
| } | |
| static mapperMethods: DecoratedScaleMapperMethods<LogScale> = { | |
| needTransform() { | |
| return true; | |
| }, | |
| normalize(val) { | |
| return this.intervalStub.normalize(logScaleLogTick(val, this.base)); | |
| }, | |
| scale(val) { | |
| // PENDING: Input `intervalStub.getExtent()` and `powStub.getExtent()` may | |
| // break monotonicity. Do not do it until real problems found. | |
| return logScalePowTick(this.intervalStub.scale(val), this.base, null); | |
| }, | |
| transformIn(val, opt) { | |
| val = logScaleLogTick(val, this.base); | |
| return (opt && opt.depth === SCALE_MAPPER_DEPTH_OUT_OF_BREAK) | |
| ? val | |
| : this.intervalStub.transformIn(val, opt); | |
| }, | |
| transformOut(val, opt) { | |
| const depth = opt ? opt.depth : null; | |
| tmpTransformOutOpt1.depth = depth; | |
| tmpTransformOutOpt2.lookup = this._lookup; | |
| return logScalePowTick( | |
| (depth === SCALE_MAPPER_DEPTH_OUT_OF_BREAK) | |
| ? val | |
| : this.intervalStub.transformOut(val, tmpTransformOutOpt1), | |
| this.base, | |
| tmpTransformOutOpt2 | |
| ); | |
| }, | |
| contain(val) { | |
| return this.powStub.contain(val); | |
| }, | |
| /** | |
| * NOTICE: The caller should ensure `start` and `end` are both non-negative. | |
| */ | |
| setExtent(start, end) { | |
| this.setExtent2(SCALE_EXTENT_KIND_EFFECTIVE, start, end); | |
| }, | |
| setExtent2(kind, start, end) { | |
| if (!isValidBoundsForExtent(start, end) | |
| || start <= 0 || end <= 0 | |
| ) { | |
| return; | |
| } | |
| let lookupTo = tmpNotUsedArr; | |
| let lookupFrom = tmpNotUsedArr; | |
| if (kind === SCALE_EXTENT_KIND_EFFECTIVE) { | |
| const lookup = this._lookup; | |
| lookupTo = lookup.to; | |
| lookupFrom = lookup.from; | |
| } | |
| this.powStub.setExtent2( | |
| kind, | |
| (lookupTo[LOOKUP_IDX_EXTENT_START] = start), | |
| (lookupTo[LOOKUP_IDX_EXTENT_END] = end) | |
| ); | |
| const base = this.base; | |
| this.intervalStub.setExtent2( | |
| kind, | |
| (lookupFrom[LOOKUP_IDX_EXTENT_START] = logScaleLogTick(start, base)), | |
| (lookupFrom[LOOKUP_IDX_EXTENT_END] = logScaleLogTick(end, base)) | |
| ); | |
| }, | |
| getFilter() { | |
| return {g: 0}; | |
| }, | |
| sanitize(value, dataExtent) { | |
| // Conservative - if dataExtent is invalid, do not sanitize. | |
| if (isValidBoundsForExtent(dataExtent[0], dataExtent[1]) | |
| && isNullableNumberFinite(value) | |
| && value <= 0 | |
| ) { | |
| // `DataStore` has ensured that `dataExtent` is valid for LogScale. | |
| value = dataExtent[0]; | |
| } | |
| return value; | |
| }, | |
| getDefaultStartValue() { | |
| return 1; | |
| }, | |
| getExtent() { | |
| return this.powStub.getExtent(); | |
| }, | |
| getExtentUnsafe(kind, depth) { | |
| return depth === null | |
| ? this.powStub.getExtentUnsafe(kind, null) | |
| : this.intervalStub.getExtentUnsafe(kind, depth); | |
| }, | |
| }; | |
| } | |
| Scale.registerClass(LogScale); | |
| const tmpTransformOutOpt1: ScaleMapperTransformOutOpt = {}; | |
| const tmpTransformOutOpt2: ValueTransformLookupOpt = {}; | |
| const tmpNotUsedArr: number[] = []; | |
| export default LogScale; |