/
githubmirror
/
echarts
Обзор
Документация
Войти
/
githubmirror
/
echarts
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/coord/axisBand.ts
209 строк
8 KB
100pah
fix regression: Fix category axis backward compatibility and axis stat travel.
08 апр 2026, 21:28
08 апр 2026, 21:28
8a17066
Код
Авторство
О чём код?
/* * 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 { assert, each } from 'zrender/src/core/util'; import { NullUndefined } from '../util/types'; import type Axis from './Axis'; import { isOrdinalScale } from '../scale/helper'; import { isNullableNumberFinite, mathAbs, mathMax } from '../util/number'; import { AxisStatKey, getAxisStat, getAxisStatBySeries, LINEAR_POSITIVE_MIN_GAP_SINGLE_VALID_VALUE, } from './axisStatistics'; import { getScaleLinearSpanForMapping } from '../scale/scaleMapper'; import type SeriesModel from '../model/Series'; // Arbitrary, leave some space to avoid overflowing when dataZoom moving. const FALLBACK_BAND_WIDTH_RATIO = 0.8; export type AxisBandWidthResult = { // bandWidth in pixel. // Never be null/undefined. // May be NaN if no meaningfull value. But it's unlikely to be NaN, since edge cases // are handled internally whenever possible. w: number; // bandWidth in data space. // Never be null/undefined. // May be NaN if no meaningfull value, typically when no valid series data item. w2: number; }; type CalculateBandWidthOpt = { // Only used on non-'category' axes. Calculate `bandWidth` based on statistics. // Require `requireAxisStatistics` to be called. fromStat?: { // Either `key` or `sers` is required. // - `key`: Calculate `bandWidth` based on a series collection defined by the `AxisStatKey`. key?: AxisStatKey; // - `sers`: Query all keys by the given series collection and union the corresponding bandWidth. // PENDING: If multiple axis statistics can be queried by `series`, currently we only support // to return a maximum `bandWidth`, which is suitable for cases like "axis pointer shadow". sers?: (SeriesModel | NullUndefined)[] | NullUndefined; }; // It also act as a fallback for NaN/null/undefined result. min?: number; }; /** * NOTICE: * - Require the axis pixel extent and the scale extent as inputs. But they * can be not precise for approximation. * - Can only be called after "data processing" stage. * * PENDING: * Currently `bandWidth` can not be specified by users explicitly. But if we * allow that in future, these issues must be considered: * - Can only allow specifying a band width in data scale rather than pixel. * - LogScale needs to be considered - band width can only be specified on linear * (but before break) scale, similar to `axis.interval`. * * A band is required on: * - series group band width in bar/boxplot/candlestick/...; * - tooltip axisPointer type "shadow"; * - etc. */ export function calcBandWidth( axis: Axis, opt?: CalculateBandWidthOpt | NullUndefined ): AxisBandWidthResult { opt = opt || {}; const out: AxisBandWidthResult = {w: NaN, w2: NaN}; const scale = axis.scale; const fromStat = opt.fromStat; const min = opt.min; // [BAND_WIDTH_USED_SCALE_LINEAR_SPAN] // - Band width should always respect to the currently specified extent, and `SCALE_EXTENT_KIND_MAPPING` // should be used if specified. // Otherwise, the result may incorrect, especially when data count is small. // For example, when "containShape" is calculating, no `SCALE_EXTENT_KIND_MAPPING` is set, so here only // `SCALE_EXTENT_KIND_EFFECTIVE` is returned, say, `[3, 5]`, based on which a `SCALE_EXTENT_KIND_MAPPING` // is calculated, say `[2.5, 5.5]` (expanded by `0.5`). Then when rendering, that `SCALE_EXTENT_KIND_MAPPING` // is returned here. // See AXIS_CONTAIN_SHAPE_COMMON_STRATEGY for more details. // - The span should be in the linear space (typically, the innermost space). // - We use the scale extent after being zoommed and `intervalScaleEnsureValidExtent`-ish applied and // "nice"/"align" applied, because: // - For OrdinalScale, fine; // - For numeric scale, `scaleLinearSpan` is normally not used for a consistent result when `dataZoom` // is applied, but used when none or single data item case. let scaleLinearSpan = getScaleLinearSpanForMapping(scale); if (!isNullableNumberFinite(scaleLinearSpan)) { // scale may be `[Infinity, -Infinity]`. scaleLinearSpan = NaN; } const axisExtent = axis.getExtent(); // Always use a new pxSpan because it may be changed in `grid` contain label calculation. const pxSpan = mathAbs(axisExtent[1] - axisExtent[0]); if (isOrdinalScale(scale)) { calcBandWidthForCategoryAxis(out, axis, scaleLinearSpan, pxSpan); } else if (fromStat) { calcBandWidthForNumericAxis(out, axis, scaleLinearSpan, pxSpan, fromStat); } else if (min == null) { if (__DEV__) { assert(false); } } if (min != null) { out.w = isNullableNumberFinite(out.w) ? mathMax(min, out.w) : min; } return out; } function calcBandWidthForCategoryAxis( out: AxisBandWidthResult, axis: Axis, scaleLinearSpan: number, pxSpan: number, ): void { const onBand = axis.onBand; let len = scaleLinearSpan + (onBand ? 1 : 0); // Fix #2728, avoid NaN when only one data. len === 0 && (len = 1); out.w = pxSpan / len; // NOTE: // - When `scaleLinearSpan === 0`, no need to expand extent. // - `onBand: true` (`boundaryGap: true`) does not need to support `containShape`, // thereby no `invRatio`. if (!onBand && scaleLinearSpan && pxSpan) { out.w2 = out.w * scaleLinearSpan / pxSpan; } } function calcBandWidthForNumericAxis( out: AxisBandWidthResult, axis: Axis, scaleLinearSpan: number, pxSpan: number, fromStat: CalculateBandWidthOpt['fromStat'], ): void { if (__DEV__) { assert(fromStat); } let onlySingular = false; let bandWidthInData = -Infinity; each( fromStat.key ? [getAxisStat(axis, fromStat.key)] : getAxisStatBySeries(axis, fromStat.sers || []), function (stat) { const liPosMinGap = stat.liPosMinGap; // NOTE: `liPosMinGap == null` may indicate that `requireAxisStatistics` // is not used by any series on this axis. We should not make `bandWidth` // for this case. if (liPosMinGap != null) { if (liPosMinGap > 0) { if (liPosMinGap > bandWidthInData) { bandWidthInData = liPosMinGap; } onlySingular = false; } else if (liPosMinGap === LINEAR_POSITIVE_MIN_GAP_SINGLE_VALID_VALUE) { onlySingular = true; } } } ); if (isNullableNumberFinite(scaleLinearSpan) && scaleLinearSpan > 0 && isNullableNumberFinite(bandWidthInData) ) { out.w = pxSpan / scaleLinearSpan * bandWidthInData; out.w2 = bandWidthInData; } else if (onlySingular) { // This is the special handing for single value case, where min gap can not // be calculated, but `w` and `w2` (for "containShape") are still needed. out.w = pxSpan * FALLBACK_BAND_WIDTH_RATIO; out.w2 = out.w * scaleLinearSpan / pxSpan; // Consider an axis has both candlestick and bar series, where candlestick has multiple data // but the bar series has no data. In this case, that bar series should be ignored; otherwise, // the axis will be significantly expanded by "containShape" but no bar shape displayed. } }