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