/
githubmirror
/
echarts
Обзор
Документация
Войти
/
githubmirror
/
echarts
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/scale/Log.ts
276 строк
9 KB
100pah
test: fix UT.
11 апр 2026, 16:50
11 апр 2026, 16:50
e548306
Код
Авторство
О чём код?
/* * 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;