/
githubmirror
/
echarts
Обзор
Документация
Войти
/
githubmirror
/
echarts
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/scale/Interval.ts
345 строк
13 KB
100pah
fix: (1) Fix time axis reach safeLimit when value scale is milisecond. (2) Fix regression - containShape should be word properly in single value case. And simply the code.
05 апр 2026, 21:12
05 апр 2026, 21:12
40b77b4
Код
Авторство
О чём код?
/* * 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 {round, mathRound, mathMin, getPrecision} from '../util/number'; import {addCommas} from '../util/format'; import Scale, { ScaleGetTicksOpt } from './Scale'; import { getIntervalPrecision, IntervalScaleGetLabelOpt } from './helper'; import {ScaleTick, ScaleDataValue, NullUndefined, AxisBreakOption} from '../util/types'; import { AxisBreakParsingResult, getBreaksUnsafe, getScaleBreakHelper, hasBreaks, simplyParseBreakOption } from './break'; import { assert, clone } from 'zrender/src/core/util'; import { getMinorTicks } from './minorTicks'; import { getScaleExtentForTickUnsafe, initBreakOrLinearMapper, ScaleMapperGeneric } from './scaleMapper'; import { warn } from '../util/log'; export type IntervalScaleConfig = { interval: IntervalScaleConfigParsed['interval']; intervalPrecision?: IntervalScaleConfigParsed['intervalPrecision'] | NullUndefined; intervalCount?: IntervalScaleConfigParsed['intervalCount'] | NullUndefined; niceExtent?: IntervalScaleConfigParsed['niceExtent'] | NullUndefined; }; type IntervalScaleConfigParsed = { /** * Step of ticks. */ interval: number; intervalPrecision: number; /** * `_intervalCount` effectively specifies the number of "nice segments". This is for special cases, * such as `alignTicks: true` and min max are fixed. In this case, `_interval` may be specified with * a "not-nice" value and needs to be rounded with `_intervalPrecision` for better appearance. Then * merely accumulating `_interval` may generate incorrect number of ticks due to cumulative errors. * So `_intervalCount` is required to specify the expected nice ticks number. * Should ensure `_intervalCount >= -1`, * where `-1` means no nice tick (e.g., `_extent: [5.2, 5.8], _interval: 1`), * and `0` means only one nice tick (e.g., `_extent: [5, 5.8], _interval: 1`). * @see setInterval */ intervalCount: number | NullUndefined; /** * Should ensure: * `_extent[0] <= _niceExtent[0] && _niceExtent[1] <= _extent[1]` * But NOTICE: * `_niceExtent[0] - _niceExtent[1] <= _interval`, rather than always `< 0`, * because `_niceExtent` is typically calculated by * `[ Math.ceil(_extent[0] / _interval) * _interval, Math.floor(_extent[1] / _interval) * _interval ]`. * e.g., `_extent: [5.2, 5.8]` with interval `1` will get `_niceExtent: [6, 5]`. * e.g., `_extent: [5, 5.8]` with interval `1` will get `_niceExtent: [5, 5]`. * e.g., `_extent: [5.7, 5.7]` with interval `1` will get `_niceExtent: [6, 5]`. * @see setInterval */ niceExtent: number[] | NullUndefined; }; type IntervalScaleSetting = { // Either `breakOption` or `parsedBreaks` can be specified. breakOption?: AxisBreakOption[] | NullUndefined; breakParsed?: AxisBreakParsingResult | NullUndefined; }; /** * @final NEVER inherit me! */ interface IntervalScale extends ScaleMapperGeneric<IntervalScale> {} class IntervalScale extends Scale<IntervalScale> { static type = 'interval'; type = 'interval' as const; private _cfg: IntervalScaleConfigParsed; constructor(setting?: IntervalScaleSetting) { super(); this.parse = IntervalScale.parse; setting = setting || {}; const breakParsed = simplyParseBreakOption(this, setting); const res = initBreakOrLinearMapper(this, breakParsed, null); // @ts-ignore this.brk = res.brk; this._cfg = { interval: 0, intervalPrecision: 2, intervalCount: undefined, niceExtent: undefined, }; } static parse(val: ScaleDataValue): number { // `Scale#parse` (and its overrids) are typically applied at the axis values input // in echarts option. e.g., `axis.min/max`, `dataZoom.min/max`, etc. // but `series.data` is not included, which uses `dataValueHelper.ts`#`parseDataValue`. // `Scale#parse` originally introduced in fb8c813215098b9d2458966229bb95c510883d5e // at 2016 for dataZoom start/end settings (See `parseAxisModelMinMax`). // // Historically `scale/Interval.ts` returns the input value directly. But numeric // values (such as a number-like string '123') effectively passed through here and // were involved in calculations, which was error-prone and inconsistent with the // declared TS return type. Previously such issues are fixed separately in different // places case by case (such as #2475). // // Now, we perform actual parse to ensure its `number` type here. The parsing rule // follows the series data parsing rule (`dataValueHelper.ts`#`parseDataValue`) // and maintains compatibility as much as possible (thus a more strict parsing // `number.ts`#`numericToNumber` is not used here.) // // FIXME: `ScaleDataValue` also need to be modified to include numeric string type, // since it effectively does. return (val == null || val === '') ? NaN // If string (like '-'), using '+' parse to NaN // If object, also parse to NaN : Number(val); } getConfig(): IntervalScaleConfigParsed { return clone(this._cfg); } setConfig(cfg: IntervalScaleConfig): void { const extent = getScaleExtentForTickUnsafe(this); if (__DEV__) { assert(cfg.interval != null); if (cfg.intervalCount != null) { assert( cfg.intervalCount >= -1 && cfg.intervalPrecision != null // Do not support intervalCount on axis break currently. && !hasBreaks(this) ); } if (cfg.niceExtent != null) { assert(isFinite(cfg.niceExtent[0]) && isFinite(cfg.niceExtent[1])); assert(extent[0] <= cfg.niceExtent[0] && cfg.niceExtent[1] <= extent[1]); assert(round(cfg.niceExtent[0] - cfg.niceExtent[1], getPrecision(cfg.interval)) <= cfg.interval); } } // Reset all. this._cfg = cfg = clone(cfg) as IntervalScaleConfigParsed; if (cfg.niceExtent == null) { // Dropped the auto calculated niceExtent and use user-set extent. // We assume users want to set both interval and extent to get a better result. cfg.niceExtent = extent.slice() as [number, number]; } if (cfg.intervalPrecision == null) { cfg.intervalPrecision = getIntervalPrecision(cfg.interval); } } /** * In ascending order. */ getTicks(opt?: ScaleGetTicksOpt): ScaleTick[] { opt = opt || {}; const cfg = this._cfg; const interval = cfg.interval; const extent = getScaleExtentForTickUnsafe(this); const niceExtent = cfg.niceExtent; const intervalPrecision = cfg.intervalPrecision; const scaleBreakHelper = getScaleBreakHelper(); const brk = this.brk; const brkAvailable = scaleBreakHelper && brk; const ticks = [] as ScaleTick[]; // If interval is 0, return []; if (!interval) { return ticks; } if (opt.breakTicks === 'only_break' && brkAvailable) { scaleBreakHelper.addBreaksToTicks(ticks, brk.breaks, extent); return ticks; } if (__DEV__) { assert(niceExtent != null); } // [CAVEAT]: If changing this logic, must sync it to `axisAlignTicks.ts`. // A fail-safe is required since `interval` can be user specified, or for the case // that using dataZoom toolbox and zoom repeatedly. const safeLimit = 3000; if (extent[0] < niceExtent[0]) { ticks.push({ value: opt.expandToNicedExtent ? round(niceExtent[0] - interval, intervalPrecision) : extent[0] }); } const estimateNiceMultiple = (tickVal: number, targetTick: number) => { return mathRound((targetTick - tickVal) / interval); }; const intervalCount = cfg.intervalCount; for ( let tick = niceExtent[0], niceTickIdx = 0; ; niceTickIdx++ ) { // Consider case `_extent: [5.2, 5.8], _niceExtent: [6, 5], interval: 1`, // `_intervalCount` makes sense iff `-1`. // Consider case `_extent: [5, 5.8], _niceExtent: [5, 5], interval: 1`, // `_intervalCount` makes sense iff `0`. if (intervalCount == null) { if (tick > niceExtent[1] || !isFinite(tick) || !isFinite(niceExtent[1])) { break; } } else { if (niceTickIdx > intervalCount) { // nice ticks number should be `intervalCount + 1` break; } // Consider cumulative error, especially caused by rounding, the last nice // `tick` may be less than or greater than `niceExtent[1]` slightly. tick = mathMin(tick, niceExtent[1]); if (niceTickIdx === intervalCount) { tick = niceExtent[1]; } } ticks.push({value: tick}); // Avoid rounding error tick = round(tick + interval, intervalPrecision); if (brk) { const moreMultiple = brk.calcNiceTickMultiple(tick, estimateNiceMultiple); if (moreMultiple >= 0) { tick = round(tick + moreMultiple * interval, intervalPrecision); } } if (ticks.length > 0 && tick === ticks[ticks.length - 1].value) { // Consider out of safe float point, e.g., // -3711126.9907707 + 2e-10 === -3711126.9907707 break; } if (ticks.length > safeLimit) { if (__DEV__) { warn('Exceed safe limit in IntervalScale["getTicks"].'); } return []; } } // Consider this case: the last item of ticks is smaller // than niceExtent[1] and niceExtent[1] === extent[1]. const lastNiceTick = ticks.length ? ticks[ticks.length - 1].value : niceExtent[1]; if (extent[1] > lastNiceTick) { ticks.push({ value: opt.expandToNicedExtent ? round(lastNiceTick + interval, intervalPrecision) : extent[1] }); } if (brkAvailable) { scaleBreakHelper.pruneTicksByBreak( opt.pruneByBreak, ticks, brk.breaks, item => item.value, cfg.interval, extent ); } if (brkAvailable && opt.breakTicks !== 'none') { scaleBreakHelper.addBreaksToTicks(ticks, brk.breaks, extent); } return ticks; } getMinorTicks(splitNumber: number): number[][] { return getMinorTicks( this, splitNumber, getBreaksUnsafe(this), this._cfg.interval ); } getLabel( tick: ScaleTick, opt?: IntervalScaleGetLabelOpt ): string { if (tick == null) { return ''; } let precision = opt && opt.precision; if (precision == null) { precision = getPrecision(tick.value) || 0; } else if (precision === 'auto') { // Should be more precise then tick. precision = this._cfg.intervalPrecision; } // (1) If `precision` is set, 12.005 should be display as '12.00500'. // (2) Use `round` (toFixed) to avoid scientific notation like '3.5e-7'. const dataNum = round(tick.value, precision as number, true); return addCommas(dataNum); } } Scale.registerClass(IntervalScale); export default IntervalScale;