This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch PR/plainheart_fix/alignTicks-precision
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit d2cc085b26b130bfcacc1123debb80672c964db1
Author: 100pah <[email protected]>
AuthorDate: Tue Mar 10 02:00:01 2026 +0800

    tweak: Clarity the previous implements of axis statistics.
---
 src/chart/boxplot/boxplotLayout.ts         |  24 +-
 src/chart/candlestick/candlestickLayout.ts |  11 +-
 src/chart/helper/axisSnippets.ts           |  43 ++--
 src/chart/scatter/jitterLayout.ts          |   7 +-
 src/component/dataZoom/AxisProxy.ts        |   4 +-
 src/component/singleAxis/install.ts        |   4 +-
 src/coord/axisStatistics.ts                | 344 ++++++++++++++++++++---------
 src/coord/cartesian/Grid.ts                |  13 +-
 src/coord/cartesian/GridModel.ts           |   2 +-
 src/coord/parallel/Parallel.ts             |   8 +-
 src/coord/parallel/ParallelModel.ts        |   7 +-
 src/coord/parallel/parallelCreator.ts      |  13 +-
 src/coord/polar/PolarModel.ts              |   3 +-
 src/coord/polar/polarCreator.ts            |  19 +-
 src/coord/radar/Radar.ts                   |  21 +-
 src/coord/radar/RadarModel.ts              |  10 +-
 src/coord/scaleRawExtentInfo.ts            | 106 ++++-----
 src/coord/single/AxisModel.ts              |   6 +-
 src/coord/single/Single.ts                 |   8 +-
 src/coord/single/singleCreator.ts          |  12 +-
 src/core/echarts.ts                        |  10 +-
 src/layout/barCommon.ts                    |  23 +-
 src/layout/barGrid.ts                      |  29 ++-
 src/layout/barPolar.ts                     |  28 ++-
 src/model/Global.ts                        |   3 +
 src/scale/scaleMapper.ts                   |   4 +-
 src/util/jitter.ts                         |   6 +-
 27 files changed, 453 insertions(+), 315 deletions(-)

diff --git a/src/chart/boxplot/boxplotLayout.ts 
b/src/chart/boxplot/boxplotLayout.ts
index feda1efd5..283c18979 100644
--- a/src/chart/boxplot/boxplotLayout.ts
+++ b/src/chart/boxplot/boxplotLayout.ts
@@ -22,7 +22,7 @@ import {parsePercent} from '../../util/number';
 import type GlobalModel from '../../model/Global';
 import BoxplotSeriesModel, { SERIES_TYPE_BOXPLOT } from './BoxplotSeries';
 import {
-    eachCollectedAxis, eachCollectedSeries, getCollectedSeriesLength,
+    countSeriesOnAxisOnKey, eachAxisOnKey, eachSeriesOnAxisOnKey,
     requireAxisStatistics
 } from '../../coord/axisStatistics';
 import { makeCallOnlyOnce } from '../../util/model';
@@ -31,8 +31,9 @@ import Axis from '../../coord/Axis';
 import { registerAxisContainShapeHandler } from 
'../../coord/scaleRawExtentInfo';
 import { calcBandWidth } from '../../coord/axisBand';
 import {
-    makeAxisStatKey,
-    createSimpleAxisStatClient, createBandWidthBasedAxisContainShapeHandler
+    createBandWidthBasedAxisContainShapeHandler,
+    getMetricsNonOrdinalLinearPositiveMinGap,
+    makeAxisStatKey
 } from '../helper/axisSnippets';
 
 
@@ -45,13 +46,13 @@ export interface BoxplotItemLayout {
 
 export function boxplotLayout(ecModel: GlobalModel) {
     const axisStatKey = makeAxisStatKey(SERIES_TYPE_BOXPLOT);
-    eachCollectedAxis(ecModel, axisStatKey, function (axis) {
-        const seriesCount = getCollectedSeriesLength(axis, axisStatKey);
+    eachAxisOnKey(ecModel, axisStatKey, function (axis) {
+        const seriesCount = countSeriesOnAxisOnKey(axis, axisStatKey);
         if (!seriesCount) {
             return;
         }
         const baseResult = calculateBase(axis, seriesCount);
-        eachCollectedSeries(axis, axisStatKey, function (seriesModel: 
BoxplotSeriesModel, idx) {
+        eachSeriesOnAxisOnKey(axis, axisStatKey, function (seriesModel: 
BoxplotSeriesModel, idx) {
             layoutSingleSeries(
                 seriesModel,
                 baseResult.boxOffsetList[idx],
@@ -77,7 +78,7 @@ function calculateBase(baseAxis: Axis, seriesCount: number): {
         {fromStat: {key: makeAxisStatKey(SERIES_TYPE_BOXPLOT)}, min: 1},
     ).w;
 
-    eachCollectedSeries(baseAxis, makeAxisStatKey(SERIES_TYPE_BOXPLOT), 
function (seriesModel: BoxplotSeriesModel) {
+    eachSeriesOnAxisOnKey(baseAxis, makeAxisStatKey(SERIES_TYPE_BOXPLOT), 
function (seriesModel: BoxplotSeriesModel) {
         let boxWidthBound = seriesModel.get('boxWidth');
         if (!isArray(boxWidthBound)) {
             boxWidthBound = [boxWidthBound, boxWidthBound];
@@ -93,7 +94,7 @@ function calculateBase(baseAxis: Axis, seriesCount: number): {
     const boxWidth = (availableWidth - boxGap * (seriesCount - 1)) / 
seriesCount;
     let base = boxWidth / 2 - availableWidth / 2;
 
-    eachCollectedSeries(baseAxis, makeAxisStatKey(SERIES_TYPE_BOXPLOT), 
function (seriesModel, idx) {
+    eachSeriesOnAxisOnKey(baseAxis, makeAxisStatKey(SERIES_TYPE_BOXPLOT), 
function (seriesModel, idx) {
         boxOffsetList.push(base);
         base += boxGap + boxWidth;
 
@@ -189,8 +190,11 @@ export function registerBoxplotAxisHandlers(registers: 
EChartsExtensionInstallRe
         const axisStatKey = makeAxisStatKey(SERIES_TYPE_BOXPLOT);
         requireAxisStatistics(
             registers,
-            axisStatKey,
-            createSimpleAxisStatClient(SERIES_TYPE_BOXPLOT)
+            {
+                key: axisStatKey,
+                seriesType: SERIES_TYPE_BOXPLOT,
+                getMetrics: getMetricsNonOrdinalLinearPositiveMinGap,
+            }
         );
         registerAxisContainShapeHandler(
             axisStatKey,
diff --git a/src/chart/candlestick/candlestickLayout.ts 
b/src/chart/candlestick/candlestickLayout.ts
index 5abd593ab..67fd4e710 100644
--- a/src/chart/candlestick/candlestickLayout.ts
+++ b/src/chart/candlestick/candlestickLayout.ts
@@ -34,7 +34,9 @@ import {
 import { EChartsExtensionInstallRegisters } from '../../extension';
 import { registerAxisContainShapeHandler } from 
'../../coord/scaleRawExtentInfo';
 import {
-    makeAxisStatKey, createSimpleAxisStatClient, 
createBandWidthBasedAxisContainShapeHandler
+    createBandWidthBasedAxisContainShapeHandler,
+    getMetricsNonOrdinalLinearPositiveMinGap,
+    makeAxisStatKey
 } from '../helper/axisSnippets';
 import { calcBandWidth } from '../../coord/axisBand';
 
@@ -280,8 +282,11 @@ export function registerCandlestickAxisHandlers(registers: 
EChartsExtensionInsta
         const axisStatKey = makeAxisStatKey(SERIES_TYPE_CANDLESTICK);
         requireAxisStatistics(
             registers,
-            axisStatKey,
-            createSimpleAxisStatClient(SERIES_TYPE_CANDLESTICK)
+            {
+                key: axisStatKey,
+                seriesType: SERIES_TYPE_CANDLESTICK,
+                getMetrics: getMetricsNonOrdinalLinearPositiveMinGap
+            }
         );
         registerAxisContainShapeHandler(
             axisStatKey,
diff --git a/src/chart/helper/axisSnippets.ts b/src/chart/helper/axisSnippets.ts
index d7b3ac142..66bcdf53b 100644
--- a/src/chart/helper/axisSnippets.ts
+++ b/src/chart/helper/axisSnippets.ts
@@ -17,33 +17,16 @@
 * under the License.
 */
 
+import type Axis from '../../coord/Axis';
 import { calcBandWidth } from '../../coord/axisBand';
-import { AxisStatisticsClient, AxisStatKey } from '../../coord/axisStatistics';
+import { AXIS_STAT_KEY_DELIMITER, AxisStatKey, AxisStatMetrics } from 
'../../coord/axisStatistics';
+import { CoordinateSystem } from '../../coord/CoordinateSystem';
 import { AxisContainShapeHandler } from '../../coord/scaleRawExtentInfo';
 import { isOrdinalScale } from '../../scale/helper';
 import { isNullableNumberFinite } from '../../util/number';
 import { ComponentSubType } from '../../util/types';
 
 
-export const getMetricsMinGapOnNonCategoryAxis: 
AxisStatisticsClient['getMetrics'] = function (axis) {
-    return {
-        liPosMinGap: !isOrdinalScale(axis.scale)
-    };
-};
-
-export function createSimpleAxisStatClient(
-    seriesType: ComponentSubType,
-): AxisStatisticsClient {
-    return {
-        collectAxisSeries(ecModel, saveAxisSeries) {
-            ecModel.eachSeriesByType(seriesType, function (seriesModel) {
-                saveAxisSeries(seriesModel.getBaseAxis(), seriesModel);
-            });
-        },
-        getMetrics: getMetricsMinGapOnNonCategoryAxis
-    };
-}
-
 /**
  * Require `requireAxisStatistics`.
  */
@@ -57,6 +40,24 @@ export function 
createBandWidthBasedAxisContainShapeHandler(axisStatKey: AxisSta
     };
 }
 
+
+/**
+ * A pre-built `makeAxisStatKey`.
+ * See `makeAxisStatKey2`. Use two functions rather than a optional parameter 
to impose checking.
+ */
 export function makeAxisStatKey(seriesType: ComponentSubType): AxisStatKey {
-    return seriesType as AxisStatKey;
+    return (seriesType + AXIS_STAT_KEY_DELIMITER) as AxisStatKey;
+}
+export function makeAxisStatKey2(seriesType: ComponentSubType, coordSysType: 
CoordinateSystem['type']): AxisStatKey {
+    return (seriesType + AXIS_STAT_KEY_DELIMITER + coordSysType) as 
AxisStatKey;
 }
+
+/**
+ * A pre-built `getMetrics`.
+ */
+export function getMetricsNonOrdinalLinearPositiveMinGap(axis: Axis): 
AxisStatMetrics {
+    return {
+        // non-category scale do not use `liPosMinGap` to calculate 
`bandWidth`.
+        liPosMinGap: !isOrdinalScale(axis.scale)
+    };
+};
diff --git a/src/chart/scatter/jitterLayout.ts 
b/src/chart/scatter/jitterLayout.ts
index ad2c2e9ba..1da8ce195 100644
--- a/src/chart/scatter/jitterLayout.ts
+++ b/src/chart/scatter/jitterLayout.ts
@@ -23,6 +23,8 @@ import type SingleAxis from '../../coord/single/SingleAxis';
 import type Axis2D from '../../coord/cartesian/Axis2D';
 import type { StageHandler } from '../../util/types';
 import createRenderPlanner from '../helper/createRenderPlanner';
+import { COORD_SYS_TYPE_CARTESIAN_2D } from '../../coord/cartesian/GridModel';
+import { COORD_SYS_TYPE_SINGLE_AXIS } from '../../coord/single/AxisModel';
 
 export default function jitterLayout(): StageHandler {
     return {
@@ -32,7 +34,10 @@ export default function jitterLayout(): StageHandler {
 
         reset(seriesModel: ScatterSeriesModel) {
             const coordSys = seriesModel.coordinateSystem;
-            if (!coordSys || (coordSys.type !== 'cartesian2d' && coordSys.type 
!== 'single')) {
+            if (!coordSys || (
+                coordSys.type !== COORD_SYS_TYPE_CARTESIAN_2D
+                && coordSys.type !== COORD_SYS_TYPE_SINGLE_AXIS
+            )) {
                 return;
             }
             const baseAxis = coordSys.getBaseAxis && coordSys.getBaseAxis() as 
Axis2D | SingleAxis;
diff --git a/src/component/dataZoom/AxisProxy.ts 
b/src/component/dataZoom/AxisProxy.ts
index 5b7d22a89..f84880d1c 100644
--- a/src/component/dataZoom/AxisProxy.ts
+++ b/src/component/dataZoom/AxisProxy.ts
@@ -33,7 +33,7 @@ import { getAxisMainType, isCoordSupported, 
DataZoomAxisDimension } from './help
 import { SINGLE_REFERRING } from '../../util/model';
 import { isOrdinalScale, isTimeScale } from '../../scale/helper';
 import {
-    AXIS_EXTENT_INFO_BUILD_FROM_DATA_ZOOM, scaleRawExtentInfoReallyCreate,
+    AXIS_EXTENT_INFO_BUILD_FROM_DATA_ZOOM, scaleRawExtentInfoCreate,
     ScaleRawExtentResultForZoom,
 } from '../../coord/scaleRawExtentInfo';
 import { discourageOnAxisZero } from '../../coord/axisHelper';
@@ -351,7 +351,7 @@ class AxisProxy {
         // Nevertheless, user can set min/max/scale on axes to make extent of 
axes
         // consistent.
         const axis = this.getAxisModel().axis;
-        scaleRawExtentInfoReallyCreate(this.ecModel, axis, 
AXIS_EXTENT_INFO_BUILD_FROM_DATA_ZOOM);
+        scaleRawExtentInfoCreate(this.ecModel, axis, 
AXIS_EXTENT_INFO_BUILD_FROM_DATA_ZOOM);
 
         discourageOnAxisZero(axis, {dz: true});
 
diff --git a/src/component/singleAxis/install.ts 
b/src/component/singleAxis/install.ts
index b2e6b8dc8..c949a1e53 100644
--- a/src/component/singleAxis/install.ts
+++ b/src/component/singleAxis/install.ts
@@ -21,7 +21,7 @@ import { EChartsExtensionInstallRegisters, use } from 
'../../extension';
 import ComponentView from '../../view/Component';
 import SingleAxisView from '../axis/SingleAxisView';
 import axisModelCreator from '../../coord/axisModelCreator';
-import SingleAxisModel from '../../coord/single/AxisModel';
+import SingleAxisModel, { COORD_SYS_TYPE_SINGLE_AXIS } from 
'../../coord/single/AxisModel';
 import singleCreator from '../../coord/single/singleCreator';
 import {install as installAxisPointer} from '../axisPointer/install';
 import AxisView from '../axis/AxisView';
@@ -43,7 +43,7 @@ export function install(registers: 
EChartsExtensionInstallRegisters) {
     registers.registerComponentView(SingleAxisView);
     registers.registerComponentModel(SingleAxisModel);
 
-    axisModelCreator(registers, 'single', SingleAxisModel, 
SingleAxisModel.defaultOption);
+    axisModelCreator(registers, COORD_SYS_TYPE_SINGLE_AXIS, SingleAxisModel, 
SingleAxisModel.defaultOption);
 
     registers.registerCoordinateSystem('single', singleCreator);
 }
\ No newline at end of file
diff --git a/src/coord/axisStatistics.ts b/src/coord/axisStatistics.ts
index ca2454619..77f864b8c 100644
--- a/src/coord/axisStatistics.ts
+++ b/src/coord/axisStatistics.ts
@@ -17,13 +17,13 @@
 * under the License.
 */
 
-import { assert, createHashMap, each, HashMap } from 'zrender/src/core/util';
+import { assert, createHashMap, HashMap, retrieve2 } from 
'zrender/src/core/util';
 import type GlobalModel from '../model/Global';
 import type SeriesModel from '../model/Series';
 import {
     initExtentForUnion, makeCallOnlyOnce, makeInner,
 } from '../util/model';
-import { DimensionIndex, NullUndefined } from '../util/types';
+import { ComponentSubType, DimensionIndex, NullUndefined } from 
'../util/types';
 import type Axis from './Axis';
 import { asc, isNullableNumberFinite } from '../util/number';
 import { parseSanitizationFilter, passesSanitizationFilter } from 
'../data/helper/dataValueHelper';
@@ -36,24 +36,41 @@ import {
     getCachePerECFullUpdate, getCachePerECPrepare, 
GlobalModelCachePerECFullUpdate,
     GlobalModelCachePerECPrepare
 } from '../util/cycleCache';
+import { CoordinateSystem } from './CoordinateSystem';
 
 
 const callOnlyOnce = makeCallOnlyOnce();
+// Ensure that it never appears in internal generated uid and pre-defined 
coordSysType.
+export const AXIS_STAT_KEY_DELIMITER = '|&';
 
 const ecModelCacheFullUpdateInner = makeInner<{
-    all: AxisStatAll;
+
+    // It stores all <axis, series> pairs, aggregated by axis, based on which 
axis scale extent is calculated.
+    // NOTICE: series that has been filtered out are included.
+    // It is unrelated to `AxisStatKeyedClient`.
+    axSer: HashMap<SeriesModel[], AxisBaseModel['uid']>;
+
+    // AxisStatKey based statistics records.
+    // Only `AxisStatKeyedClient` concerned <axis, series> pairs are 
collected, based on which
+    // statistics are calculated.
+    keyed: AxisStatKeyed;
+    // `keys` is only used to quick travel.
     keys: AxisStatKeys;
-    axisMapForCheck?: HashMap<1, ComponentModel['uid']>; // Only used in dev 
mode
-    seriesMapForCheck?: HashMap<1, ComponentModel['uid']>; // Only used in dev 
mode
+
+    // Only used in dev mode for duplication checking.
+    axSerPairCheck?: HashMap<1, string>;
+
 }, GlobalModelCachePerECFullUpdate>();
 
 type AxisStatKeys = HashMap<AxisStatKey[], ComponentModel['uid']>;
-type AxisStatAll = HashMap<AxisStatPerKey | NullUndefined, AxisStatKey>;
+type AxisStatKeyed = HashMap<AxisStatPerKey | NullUndefined, AxisStatKey>;
 type AxisStatPerKey = HashMap<AxisStatPerKeyPerAxis | NullUndefined, 
AxisBaseModel['uid']>;
 type AxisStatPerKeyPerAxis = {
     axis: Axis;
     // This is series use this axis as base axis and need to be laid out.
     // The order is determined by the client and must be respected.
+    // Never be null/undefined.
+    // series filtered out is included.
     sers: SeriesModel[];
     // For query. The array index is series index.
     serByIdx: SeriesModel[];
@@ -64,16 +81,14 @@ type AxisStatPerKeyPerAxis = {
     liPosMinGap?: number | NullUndefined;
 
     // metrics corresponds to this record.
-    metrics?: AxisStatisticsMetrics;
-    // ecPrepareCache corresponds to this record.
-    ecPrepare?: AxisStatECPrepareCachePerKeyPerAxis;
+    metrics?: AxisStatMetrics;
 };
 
 const ecModelCachePrepareInner = makeInner<{
-    all: AxisStatECPrepareCacheAll | NullUndefined;
+    keyed: AxisStatECPrepareCacheKeyed | NullUndefined;
 }, GlobalModelCachePerECPrepare>();
 
-type AxisStatECPrepareCacheAll = HashMap<AxisStatECPrepareCachePerKey | 
NullUndefined, AxisStatKey>;
+type AxisStatECPrepareCacheKeyed = HashMap<AxisStatECPrepareCachePerKey | 
NullUndefined, AxisStatKey>;
 type AxisStatECPrepareCachePerKey = 
HashMap<AxisStatECPrepareCachePerKeyPerAxis | NullUndefined, 
AxisBaseModel['uid']>;
 type AxisStatECPrepareCachePerKeyPerAxis =
     Pick<AxisStatPerKeyPerAxis, 'liPosMinGap'> & {
@@ -81,17 +96,20 @@ type AxisStatECPrepareCachePerKeyPerAxis =
         serUids?: HashMap<1, ComponentModel['uid']>
     };
 
-export type AxisStatisticsClient = {
-    /**
-     * NOTICE: It is called after series filtering.
-     */
-    collectAxisSeries: (
-        ecModel: GlobalModel,
-        saveAxisSeries: (axis: Axis | NullUndefined, series: SeriesModel) => 
void
-    ) => void;
-    getMetrics: (
-        axis: Axis,
-    ) => AxisStatisticsMetrics;
+export type AxisStatKeyedClient = {
+
+    // A key for retrieving result.
+    key: AxisStatKey;
+
+    // Only the specific `seriesType` is covered.
+    seriesType: ComponentSubType;
+    // `true` by default - the <axis, series> pair is collected only if 
series's base axis is that axis.
+    baseAxis?: boolean | NullUndefined;
+    // `NullUndefined` by default - all coordinate systems are covered.
+    coordSysType?: CoordinateSystem['type'] | NullUndefined;
+
+    // `NullUndefined` return indicates this axis should be omitted.
+    getMetrics: (axis: Axis) => AxisStatMetrics | NullUndefined;
 };
 
 /**
@@ -99,9 +117,9 @@ export type AxisStatisticsClient = {
  * designated by `AxisStatKey`. In most case `seriesType` is used as 
`AxisStatKey`.
  */
 export type AxisStatKey = string & {_: 'AxisStatKey'}; // Nominal to avoid 
misusing.
+type ClientQueryKey = string & {_: 'ClientQueryKey'}; // Nominal to avoid 
misusing; internal usage.
 
-type AxisStatisticsMetrics = {
-    // Currently only one metric is required.
+export type AxisStatMetrics = {
 
     // NOTICE:
     //  May be time-consuming in large data due to some metrics requiring 
travel and sort of
@@ -115,6 +133,8 @@ export type AxisStatisticsResult = Pick<
     'liPosMinGap'
 >;
 
+type AxisStatEachSeriesCb = (seriesModel: SeriesModel, travelIdx: number) => 
void;
+
 let validateInputAxis: ((axis: Axis) => void) | NullUndefined;
 if (__DEV__) {
     validateInputAxis = function (axis) {
@@ -127,8 +147,8 @@ function getAxisStatPerKeyPerAxis(
     axisStatKey: AxisStatKey
 ): AxisStatPerKeyPerAxis | NullUndefined {
     const axisModel = axis.model;
-    const all = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(axisModel.ecModel)).all;
-    const perKey = all && all.get(axisStatKey);
+    const keyed = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(axisModel.ecModel)).keyed;
+    const perKey = keyed && keyed.get(axisStatKey);
     return perKey && perKey.get(axisModel.uid);
 }
 
@@ -153,7 +173,7 @@ export function getAxisStatBySeries(
         validateInputAxis(axis);
     }
     const result: AxisStatisticsResult[] = [];
-    eachPerKeyPerAxis(axis.model.ecModel, function (perKeyPerAxis) {
+    eachKeyEachAxis(axis.model.ecModel, function (perKeyPerAxis) {
         for (let idx = 0; idx < seriesList.length; idx++) {
             if (seriesList[idx] && 
perKeyPerAxis.serByIdx[seriesList[idx].seriesIndex]) {
                 result.push(wrapStatResult(perKeyPerAxis));
@@ -163,7 +183,7 @@ export function getAxisStatBySeries(
     return result;
 }
 
-function eachPerKeyPerAxis(
+function eachKeyEachAxis(
     ecModel: GlobalModel,
     cb: (
         perKeyPerAxis: AxisStatPerKeyPerAxis,
@@ -171,8 +191,8 @@ function eachPerKeyPerAxis(
         axisModelUid: AxisBaseModel['uid']
     ) => void
 ): void {
-    const all = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(ecModel)).all;
-    all && all.each(function (perKey, axisStatKey) {
+    const keyed = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(ecModel)).keyed;
+    keyed && keyed.each(function (perKey, axisStatKey) {
         perKey.each(function (perKeyPerAxis, axisModelUid) {
             cb(perKeyPerAxis, axisStatKey, axisModelUid);
         });
@@ -185,19 +205,57 @@ function wrapStatResult(record: AxisStatPerKeyPerAxis | 
NullUndefined): AxisStat
     };
 }
 
-export function eachCollectedSeries(
+/**
+ * NOTE:
+ *  - series declaration order is respected.
+ *  - series filtered out are excluded.
+ */
+export function eachSeriesOnAxis(
+    axis: Axis,
+    cb: AxisStatEachSeriesCb
+): void {
+    if (__DEV__) {
+        validateInputAxis(axis);
+    }
+    const ecModel = axis.model.ecModel;
+    const seriesOnAxisMap = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(ecModel)).axSer;
+    seriesOnAxisMap && seriesOnAxisMap.each(function (seriesList) {
+        eachSeriesDeal(ecModel, seriesList, cb);
+    });
+}
+
+export function eachSeriesOnAxisOnKey(
     axis: Axis,
     axisStatKey: AxisStatKey,
     cb: (series: SeriesModel, idx: number) => void
 ): void {
     if (__DEV__) {
+        assert(axisStatKey != null);
         validateInputAxis(axis);
     }
     const perKeyPerAxis = getAxisStatPerKeyPerAxis(axis, axisStatKey);
-    perKeyPerAxis && each(perKeyPerAxis.sers, cb);
+    perKeyPerAxis && eachSeriesDeal(axis.model.ecModel, perKeyPerAxis.sers, 
cb);
+}
+
+function eachSeriesDeal(
+    ecModel: GlobalModel,
+    seriesList: SeriesModel[],
+    cb: AxisStatEachSeriesCb
+): void {
+    for (let i = 0; i < seriesList.length; i++) {
+        const seriesModel = seriesList[i];
+        // Legend-filtered series need to be ignored since series are 
registered before `legendFilter`.
+        if (!ecModel.isSeriesFiltered(seriesModel)) {
+            cb(seriesModel, i);
+        }
+    }
 }
 
-export function getCollectedSeriesLength(
+/**
+ * NOTE:
+ *  - series filtered out are excluded.
+ */
+export function countSeriesOnAxisOnKey(
     axis: Axis,
     axisStatKey: AxisStatKey,
 ): number {
@@ -206,10 +264,17 @@ export function getCollectedSeriesLength(
         validateInputAxis(axis);
     }
     const perKeyPerAxis = getAxisStatPerKeyPerAxis(axis, axisStatKey);
-    return perKeyPerAxis ? perKeyPerAxis.sers.length : 0;
+    if (!perKeyPerAxis || !perKeyPerAxis.sers.length) {
+        return 0;
+    }
+    let count = 0;
+    eachSeriesDeal(axis.model.ecModel, perKeyPerAxis.sers, function () {
+        count++;
+    });
+    return count;
 }
 
-export function eachCollectedAxis(
+export function eachAxisOnKey(
     ecModel: GlobalModel,
     axisStatKey: AxisStatKey,
     cb: (axis: Axis) => void
@@ -217,14 +282,17 @@ export function eachCollectedAxis(
     if (__DEV__) {
         assert(axisStatKey != null);
     }
-    const all = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(ecModel)).all;
-    const perKey = all && all.get(axisStatKey);
+    const keyed = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(ecModel)).keyed;
+    const perKey = keyed && keyed.get(axisStatKey);
     perKey && perKey.each(function (perKeyPerAxis) {
         cb(perKeyPerAxis.axis);
     });
 }
 
-export function eachAxisStatKey(
+/**
+ * NOTICE: Available after `CoordinateSystem['create']` (not included).
+ */
+export function eachKeyOnAxis(
     axis: Axis,
     cb: (axisStatKey: AxisStatKey) => void
 ): void {
@@ -239,75 +307,33 @@ export function eachAxisStatKey(
     });
 }
 
+/**
+ * NOTICE: this processor may be omitted - it is registered only if required.
+ */
 function performAxisStatisticsOnOverallReset(ecModel: GlobalModel): void {
-    const ecFullUpdateCache = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(ecModel));
-    const axisStatAll: AxisStatAll = ecFullUpdateCache.all = createHashMap();
-
     const ecPrepareCache = 
ecModelCachePrepareInner(getCachePerECPrepare(ecModel));
-    const ecPrepareCacheAll = ecPrepareCache.all || (ecPrepareCache.all = 
createHashMap());
-
-    axisStatisticsClients.each(function (client, axisStatKey) {
-        client.collectAxisSeries(ecModel, function saveAxisSeries(axis, 
series) {
-            if (!axis) {
-                return;
-            }
+    const ecPrepareCacheKeyed = ecPrepareCache.keyed || (ecPrepareCache.keyed 
= createHashMap());
 
-            if (__DEV__) {
-                validateInputAxis(axis);
-                // - An axis can be associated with multiple `axisStatKey`s. 
For example, if `axisStatKey`s are
-                //   "candlestick" and "bar", they can be associated with the 
same "xAxis".
-                // - Within an individual axis, it is a typically incorrect 
usage if a <series-axis> pair is
-                //   associated with multiple `perKeyPerAxis`, which may cause 
repeated calculation and
-                //   performance degradation, had hard to be found without the 
checking below. For example, If
-                //   `axisStatKey` are "grid-bar" (see `barGrid.ts`) and 
"polar-bar" (see `barPolar.ts`), and
-                //   a <xAxis-series> pair is wrongly associated with both 
"polar-bar" and "grid-bar", the
-                //   relevant statistics will be computed twice.
-                const axisMapForCheck = ecFullUpdateCache.axisMapForCheck
-                    || (ecFullUpdateCache.axisMapForCheck = createHashMap());
-                const seriesMapForCheck = ecFullUpdateCache.seriesMapForCheck
-                    || (ecFullUpdateCache.seriesMapForCheck = createHashMap());
-                assert(!axisMapForCheck.get(axis.model.uid) || 
!seriesMapForCheck.get(series.uid));
-                axisMapForCheck.set(axis.model.uid, 1);
-                seriesMapForCheck.set(series.uid, 1);
-            }
-
-            const perKey = axisStatAll.get(axisStatKey) || 
axisStatAll.set(axisStatKey, createHashMap());
-
-            const axisModelUid = axis.model.uid;
-            let perKeyPerAxis = perKey.get(axisModelUid);
-            if (!perKeyPerAxis) {
-                perKeyPerAxis = perKey.set(axisModelUid, {axis, sers: [], 
serByIdx: []});
-                perKeyPerAxis.metrics = client.getMetrics(axis) || {};
+    eachKeyEachAxis(ecModel, function (perKeyPerAxis, axisStatKey, 
axisModelUid) {
+        const ecPrepareCachePerKey = ecPrepareCacheKeyed.get(axisStatKey)
+            || ecPrepareCacheKeyed.set(axisStatKey, createHashMap());
+        const ecPreparePerKeyPerAxis = ecPrepareCachePerKey.get(axisModelUid)
+            || ecPrepareCachePerKey.set(axisModelUid, {});
 
-                const ecPrepareCachePerKey = ecPrepareCacheAll.get(axisStatKey)
-                    || ecPrepareCacheAll.set(axisStatKey, createHashMap());
-                perKeyPerAxis.ecPrepare = 
ecPrepareCachePerKey.get(axisModelUid)
-                    || ecPrepareCachePerKey.set(axisModelUid, {});
-            }
-            // NOTICE: series order should respect to the input order, since it
-            // matters in some cases (see `axisSnippets.ts` for more details).
-            perKeyPerAxis.sers.push(series);
-            perKeyPerAxis.serByIdx[series.seriesIndex] = series;
-        });
-    });
-
-    const axisStatKeys: AxisStatKeys = ecFullUpdateCache.keys = 
createHashMap();
-    eachPerKeyPerAxis(ecModel, function (perKeyPerAxis, axisStatKey, 
axisModelUid) {
-        (axisStatKeys.get(axisModelUid) || axisStatKeys.set(axisModelUid, []))
-            .push(axisStatKey);
-        performStatisticsForRecord(perKeyPerAxis);
+        performStatisticsForRecord(ecModel, perKeyPerAxis, 
ecPreparePerKeyPerAxis);
     });
 }
 
 function performStatisticsForRecord(
+    ecModel: GlobalModel,
     perKeyPerAxis: AxisStatPerKeyPerAxis,
+    ecPreparePerKeyPerAxis: AxisStatECPrepareCachePerKeyPerAxis
 ): void {
     if (!perKeyPerAxis.metrics.liPosMinGap) {
         return;
     }
 
     const newSerUids: AxisStatECPrepareCachePerKeyPerAxis['serUids'] = 
createHashMap();
-    const ecPreparePerKeyPerAxis = perKeyPerAxis.ecPrepare;
     const ecPrepareSerUids = ecPreparePerKeyPerAxis.serUids;
     const ecPrepareLiPosMinGap = ecPreparePerKeyPerAxis.liPosMinGap;
     let ecPrepareCacheMiss: boolean;
@@ -325,26 +351,25 @@ function performStatisticsForRecord(
     // timeAll[0] = Date.now(); // _EC_PERF_
 
     function eachSeries(
-        cb: (dimStoreIdx: DimensionIndex, seriesModel: SeriesModel, store: 
DataStore) => void
+        cb: (dimStoreIdx: DimensionIndex, seriesModel: SeriesModel, 
rawDataStore: DataStore) => void
     ) {
-        for (let i = 0; i < perKeyPerAxis.sers.length; i++) {
-            const seriesModel = perKeyPerAxis.sers[i];
-            const data = seriesModel.getData();
+        eachSeriesDeal(ecModel, perKeyPerAxis.sers, function (seriesModel) {
+            const rawData = seriesModel.getRawData();
             // NOTE: Currently there is no series that a "base axis" can map 
to multiple dimensions.
-            const dimStoreIdx = 
data.getDimensionIndex(data.mapDimension(axis.dim));
+            const dimStoreIdx = 
rawData.getDimensionIndex(rawData.mapDimension(axis.dim));
             if (dimStoreIdx >= 0) {
-                cb(dimStoreIdx, seriesModel, data.getStore());
+                cb(dimStoreIdx, seriesModel, rawData.getStore());
             }
-        }
+        });
     }
 
     let bufferCapacity = 0;
-    eachSeries(function (dimStoreIdx, seriesModel, store) {
+    eachSeries(function (dimStoreIdx, seriesModel, rawDataStore) {
         newSerUids.set(seriesModel.uid, 1);
         if (!ecPrepareSerUids || !ecPrepareSerUids.hasKey(seriesModel.uid)) {
             ecPrepareCacheMiss = true;
         }
-        bufferCapacity += store.count();
+        bufferCapacity += rawDataStore.count();
     });
 
     if (!ecPrepareSerUids || ecPrepareSerUids.keys().length !== 
newSerUids.keys().length) {
@@ -435,6 +460,101 @@ const tmpValueBuffer = tryEnsureTypedArray(
     50 // arbitrary. May be expanded if needed.
 );
 
+/**
+ * NOTICE:
+ *  - It must be called in `CoordinateSystem['create']`, before series 
filtering.
+ *  - It must be called in `seriesIndex` ascending order (series declaration 
order).
+ *    i.e., iterated by `ecModel.eachSeries`.
+ *  - Every <axis, series> pair can only call this method once.
+ *
+ * @see scaleRawExtentInfoCreate in `scaleRawExtentInfo.ts`
+ */
+export function associateSeriesWithAxis(
+    axis: Axis | NullUndefined,
+    seriesModel: SeriesModel,
+    coordSysType: CoordinateSystem['type']
+): void {
+    if (!axis) {
+        return;
+    }
+
+    const ecModel = seriesModel.ecModel;
+    const ecFullUpdateCache = 
ecModelCacheFullUpdateInner(getCachePerECFullUpdate(ecModel));
+    const axisModelUid = axis.model.uid;
+
+    if (__DEV__) {
+        validateInputAxis(axis);
+        // - An axis can be associated with multiple `axisStatKey`s. For 
example, if `axisStatKey`s are
+        //   "candlestick" and "bar", they can be associated with the same 
"xAxis".
+        // - Within an individual axis, it is a typically incorrect usage if a 
<axis, series> pair is
+        //   associated with multiple `perKeyPerAxis`, which may cause 
repeated calculation and
+        //   performance degradation, had hard to be found without the 
checking below. For example, If
+        //   `axisStatKey` are "grid-bar" (see `barGrid.ts`) and "polar-bar" 
(see `barPolar.ts`), and
+        //   a <xAxis-series> pair is wrongly associated with both "polar-bar" 
and "grid-bar", the
+        //   relevant statistics will be computed twice.
+        const axSerPairCheck = ecFullUpdateCache.axSerPairCheck
+            || (ecFullUpdateCache.axSerPairCheck = createHashMap());
+        const pairKey = 
`${axisModelUid}${AXIS_STAT_KEY_DELIMITER}${seriesModel.uid}`;
+        assert(!axSerPairCheck.get(pairKey));
+        axSerPairCheck.set(pairKey, 1);
+    }
+
+    const seriesOnAxisMap = ecFullUpdateCache.axSer || 
(ecFullUpdateCache.axSer = createHashMap());
+    const seriesListPerAxis = seriesOnAxisMap.get(axisModelUid) || 
(seriesOnAxisMap.set(axisModelUid, []));
+    if (__DEV__) {
+        const lastSeries = seriesListPerAxis[seriesListPerAxis.length - 1];
+        if (lastSeries) {
+            // Series order should respect to the input order, since it 
matters in some cases
+            // (e.g., see `barGrid.ts` and `barPolar.ts` - ec option 
declaration order matters).
+            assert(lastSeries.seriesIndex < seriesModel.seriesIndex);
+        }
+    }
+    seriesListPerAxis.push(seriesModel);
+
+    const seriesType = seriesModel.subType;
+    const isBaseAxis = seriesModel.getBaseAxis() === axis;
+
+    const client = clientsByQueryKey.get(makeClientQueryKey(seriesType, 
isBaseAxis, coordSysType))
+        || clientsByQueryKey.get(makeClientQueryKey(seriesType, isBaseAxis, 
null));
+    if (!client) {
+        return;
+    }
+
+    const keyed: AxisStatKeyed = ecFullUpdateCache.keyed || 
(ecFullUpdateCache.keyed = createHashMap());
+    const keys: AxisStatKeys = ecFullUpdateCache.keys || 
(ecFullUpdateCache.keys = createHashMap());
+
+    const axisStatKey = client.key;
+    const perKey = keyed.get(axisStatKey) || keyed.set(axisStatKey, 
createHashMap());
+    let perKeyPerAxis = perKey.get(axisModelUid);
+    if (!perKeyPerAxis) {
+        perKeyPerAxis = perKey.set(axisModelUid, {axis, sers: [], serByIdx: 
[]});
+        // They should only be executed for each <key, axis> pair once:
+        perKeyPerAxis.metrics = client.getMetrics(axis);
+        (keys.get(axisModelUid) || keys.set(axisModelUid, []))
+            .push(axisStatKey);
+    }
+
+    // series order should respect to the input order.
+    perKeyPerAxis.sers.push(seriesModel);
+    perKeyPerAxis.serByIdx[seriesModel.seriesIndex] = seriesModel;
+}
+
+/**
+ * NOTE: Currently, the scenario is simple enough to look up clients by hash 
map.
+ * Otherwise, a caller-provided `filter` may be an alternative if more complex 
requirements arise.
+ */
+function makeClientQueryKey(
+    seriesType: ComponentSubType,
+    isBaseAxis: boolean | NullUndefined,
+    coordSysType: CoordinateSystem['type'] | NullUndefined
+): ClientQueryKey {
+    return (
+        seriesType
+        + AXIS_STAT_KEY_DELIMITER + retrieve2(isBaseAxis, true)
+        + AXIS_STAT_KEY_DELIMITER + (coordSysType || '')
+    ) as ClientQueryKey;
+}
+
 /**
  * NOTICE: Can only be called in "install" stage.
  *
@@ -442,20 +562,32 @@ const tmpValueBuffer = tryEnsureTypedArray(
  */
 export function requireAxisStatistics(
     registers: EChartsExtensionInstallRegisters,
-    axisStatKey: AxisStatKey,
-    client: AxisStatisticsClient
+    client: AxisStatKeyedClient
 ): void {
+    const queryKey = makeClientQueryKey(client.seriesType, client.baseAxis, 
client.coordSysType);
+
     if (__DEV__) {
-        assert(!axisStatisticsClients.get(axisStatKey));
+        assert(client.seriesType
+            && client.key
+            && !clientsCheckStatKey.get(client.key)
+            && !clientsByQueryKey.get(queryKey)
+        ); // More checking is performed in `axSerPairCheck`.
+        clientsCheckStatKey.set(client.key, 1);
     }
 
-    axisStatisticsClients.set(axisStatKey, client);
+    clientsByQueryKey.set(queryKey, client);
 
     callOnlyOnce(registers, function () {
         
registers.registerProcessor(registers.PRIORITY.PROCESSOR.AXIS_STATISTICS, {
+            // Theoretically, `appendData` requires to re-calculate them.
+            dirtyOnOverallProgress: true,
             overallReset: performAxisStatisticsOnOverallReset
         });
     });
 }
 
-const axisStatisticsClients: HashMap<AxisStatisticsClient, AxisStatKey> = 
createHashMap();
+let clientsCheckStatKey: HashMap<1, AxisStatKey>;
+if (__DEV__) {
+    clientsCheckStatKey = createHashMap();
+}
+const clientsByQueryKey: HashMap<AxisStatKeyedClient, ClientQueryKey> = 
createHashMap();
diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts
index 1a2a2c131..915e9f041 100644
--- a/src/coord/cartesian/Grid.ts
+++ b/src/coord/cartesian/Grid.ts
@@ -43,7 +43,7 @@ import Axis2D from './Axis2D';
 import {ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING} from 
'../../util/model';
 
 // Depends on GridModel, AxisModel, which performs preprocess.
-import GridModel, { GridOption, OUTER_BOUNDS_CLAMP_DEFAULT, 
OUTER_BOUNDS_DEFAULT } from './GridModel';
+import GridModel, { COORD_SYS_TYPE_CARTESIAN_2D, GridOption, 
OUTER_BOUNDS_CLAMP_DEFAULT, OUTER_BOUNDS_DEFAULT } from './GridModel';
 import CartesianAxisModel from './AxisModel';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
@@ -77,9 +77,10 @@ import { createDimNameMap } from 
'../../data/helper/SeriesDataSchema';
 import type Axis from '../Axis';
 import {
     AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE,
-    scaleRawExtentInfoEnableBoxCoordSysUsage, scaleRawExtentInfoReallyCreate, 
scaleRawExtentInfoRequireCreate
+    scaleRawExtentInfoEnableBoxCoordSysUsage, scaleRawExtentInfoCreate
 } from '../scaleRawExtentInfo';
 import { hasBreaks } from '../../scale/break';
+import { associateSeriesWithAxis } from '../axisStatistics';
 
 
 type Cartesian2DDimensionName = 'x' | 'y';
@@ -134,7 +135,7 @@ class Grid implements CoordinateSystemMaster {
         const axesMap = this._axesMap;
 
         each(this._axesList, function (axis) {
-            scaleRawExtentInfoReallyCreate(ecModel, axis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
+            scaleRawExtentInfoCreate(ecModel, axis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
             const scale = axis.scale;
             if (isOrdinalScale(scale)) {
                 scale.setSortInfo(axis.model.get('categorySortInfo'));
@@ -559,7 +560,7 @@ class Grid implements CoordinateSystemMaster {
 
             injectCoordSysByOption({
                 targetModel: seriesModel,
-                coordSysType: 'cartesian2d',
+                coordSysType: COORD_SYS_TYPE_CARTESIAN_2D,
                 coordSysProvider: coordSysProvider
             });
 
@@ -594,8 +595,8 @@ class Grid implements CoordinateSystemMaster {
                 );
             }
             if (xAxis && yAxis) {
-                scaleRawExtentInfoRequireCreate(xAxis, seriesModel);
-                scaleRawExtentInfoRequireCreate(yAxis, seriesModel);
+                associateSeriesWithAxis(xAxis, seriesModel, 
COORD_SYS_TYPE_CARTESIAN_2D);
+                associateSeriesWithAxis(yAxis, seriesModel, 
COORD_SYS_TYPE_CARTESIAN_2D);
             }
 
         }, this);
diff --git a/src/coord/cartesian/GridModel.ts b/src/coord/cartesian/GridModel.ts
index edef4d51a..654919df5 100644
--- a/src/coord/cartesian/GridModel.ts
+++ b/src/coord/cartesian/GridModel.ts
@@ -23,7 +23,7 @@ import {
     ComponentOption, BoxLayoutOptionMixin, ZRColor, ShadowOptionMixin, 
NullUndefined,
     ComponentOnCalendarOptionMixin, ComponentOnMatrixOptionMixin
 } from '../../util/types';
-import Grid from './Grid';
+import type Grid from './Grid';
 import { CoordinateSystemHostModel } from '../CoordinateSystem';
 import type GlobalModel from '../../model/Global';
 import { getLayoutParams, mergeLayoutParam } from '../../util/layout';
diff --git a/src/coord/parallel/Parallel.ts b/src/coord/parallel/Parallel.ts
index 53852a770..be3c3e71e 100644
--- a/src/coord/parallel/Parallel.ts
+++ b/src/coord/parallel/Parallel.ts
@@ -31,7 +31,7 @@ import ParallelAxis from './ParallelAxis';
 import * as graphic from '../../util/graphic';
 import {mathCeil, mathFloor, mathMax, mathMin, mathPI, round} from 
'../../util/number';
 import sliderMove from '../../component/helper/sliderMove';
-import ParallelModel, { ParallelLayoutDirection } from './ParallelModel';
+import ParallelModel, { COORD_SYS_TYPE_PARALLEL, ParallelLayoutDirection } 
from './ParallelModel';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import { Dictionary, DimensionName, ScaleDataValue } from '../../util/types';
@@ -42,7 +42,7 @@ import { AxisBaseModel } from '../AxisBaseModel';
 import { CategoryAxisBaseOption } from '../axisCommonTypes';
 import { scaleCalcNice } from '../axisNiceTicks';
 import {
-    AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE, 
scaleRawExtentInfoReallyCreate
+    AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE, scaleRawExtentInfoCreate
 } from '../scaleRawExtentInfo';
 
 
@@ -77,7 +77,7 @@ type SlidedAxisExpandBehavior = 'none' | 'slide' | 'jump';
 
 class Parallel implements CoordinateSystemMaster, CoordinateSystem {
 
-    readonly type = 'parallel';
+    readonly type = COORD_SYS_TYPE_PARALLEL;
 
     /**
      * key: dimension
@@ -149,7 +149,7 @@ class Parallel implements CoordinateSystemMaster, 
CoordinateSystem {
     update(ecModel: GlobalModel, api: ExtensionAPI): void {
         each(this.dimensions, function (dim) {
             const axis = this._axesMap.get(dim);
-            scaleRawExtentInfoReallyCreate(ecModel, axis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
+            scaleRawExtentInfoCreate(ecModel, axis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
             scaleCalcNice(axis);
         }, this);
     }
diff --git a/src/coord/parallel/ParallelModel.ts 
b/src/coord/parallel/ParallelModel.ts
index 4ca648bb3..080e9d9fb 100644
--- a/src/coord/parallel/ParallelModel.ts
+++ b/src/coord/parallel/ParallelModel.ts
@@ -20,7 +20,7 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import ComponentModel from '../../model/Component';
-import Parallel from './Parallel';
+import type Parallel from './Parallel';
 import {
     DimensionName, ComponentOption, BoxLayoutOptionMixin, 
ComponentOnCalendarOptionMixin,
     ComponentOnMatrixOptionMixin
@@ -31,6 +31,9 @@ import ParallelSeriesModel from 
'../../chart/parallel/ParallelSeries';
 import SeriesModel from '../../model/Series';
 
 
+export const COORD_SYS_TYPE_PARALLEL = 'parallel';
+export const COMPONENT_TYPE_PARALLEL = COORD_SYS_TYPE_PARALLEL;
+
 export type ParallelLayoutDirection = 'horizontal' | 'vertical';
 
 export interface ParallelCoordinateSystemOption extends
@@ -65,7 +68,7 @@ export interface ParallelCoordinateSystemOption extends
 
 class ParallelModel extends ComponentModel<ParallelCoordinateSystemOption> {
 
-    static type = 'parallel';
+    static type = COMPONENT_TYPE_PARALLEL;
     readonly type = ParallelModel.type;
 
     static dependencies = ['parallelAxis'];
diff --git a/src/coord/parallel/parallelCreator.ts 
b/src/coord/parallel/parallelCreator.ts
index 1f6ff7392..a9eaac5e8 100644
--- a/src/coord/parallel/parallelCreator.ts
+++ b/src/coord/parallel/parallelCreator.ts
@@ -25,17 +25,18 @@
 import Parallel from './Parallel';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
-import ParallelModel from './ParallelModel';
+import ParallelModel, { COMPONENT_TYPE_PARALLEL, COORD_SYS_TYPE_PARALLEL } 
from './ParallelModel';
 import { CoordinateSystemMaster } from '../CoordinateSystem';
 import ParallelSeriesModel from '../../chart/parallel/ParallelSeries';
 import { SINGLE_REFERRING } from '../../util/model';
 import { each } from 'zrender/src/core/util';
-import {scaleRawExtentInfoRequireCreate} from '../scaleRawExtentInfo';
+import { associateSeriesWithAxis } from '../axisStatistics';
+
 
 function createParallelCoordSys(ecModel: GlobalModel, api: ExtensionAPI): 
CoordinateSystemMaster[] {
     const coordSysList: CoordinateSystemMaster[] = [];
 
-    ecModel.eachComponent('parallel', function (parallelModel: ParallelModel, 
idx: number) {
+    ecModel.eachComponent(COMPONENT_TYPE_PARALLEL, function (parallelModel: 
ParallelModel, idx: number) {
         const coordSys = new Parallel(parallelModel, ecModel, api);
 
         coordSys.name = 'parallel_' + idx;
@@ -49,14 +50,14 @@ function createParallelCoordSys(ecModel: GlobalModel, api: 
ExtensionAPI): Coordi
 
     // Inject the coordinateSystems into seriesModel
     ecModel.eachSeries(function (seriesModel) {
-        if ((seriesModel as ParallelSeriesModel).get('coordinateSystem') === 
'parallel') {
+        if ((seriesModel as ParallelSeriesModel).get('coordinateSystem') === 
COORD_SYS_TYPE_PARALLEL) {
             const parallelModel = seriesModel.getReferringComponents(
-                'parallel', SINGLE_REFERRING
+                COMPONENT_TYPE_PARALLEL, SINGLE_REFERRING
             ).models[0] as ParallelModel;
             const parallel = seriesModel.coordinateSystem = 
parallelModel.coordinateSystem;
             if (parallel) {
                 each(parallel.dimensions, function (dim) {
-                    scaleRawExtentInfoRequireCreate(parallel.getAxis(dim), 
seriesModel);
+                    associateSeriesWithAxis(parallel.getAxis(dim), 
seriesModel, COORD_SYS_TYPE_PARALLEL);
                 });
             }
         }
diff --git a/src/coord/polar/PolarModel.ts b/src/coord/polar/PolarModel.ts
index da158d377..96a6fb174 100644
--- a/src/coord/polar/PolarModel.ts
+++ b/src/coord/polar/PolarModel.ts
@@ -22,7 +22,7 @@ import {
     ComponentOnMatrixOptionMixin
 } from '../../util/types';
 import ComponentModel from '../../model/Component';
-import Polar from './Polar';
+import type Polar from './Polar';
 import { AngleAxisModel, RadiusAxisModel } from './AxisModel';
 
 export interface PolarOption extends
@@ -33,6 +33,7 @@ export interface PolarOption extends
 }
 
 export const COORD_SYS_TYPE_POLAR = 'polar';
+export const COMPONENT_TYPE_POLAR = COORD_SYS_TYPE_POLAR;
 
 class PolarModel extends ComponentModel<PolarOption> {
     static type = COORD_SYS_TYPE_POLAR;
diff --git a/src/coord/polar/polarCreator.ts b/src/coord/polar/polarCreator.ts
index 152d08506..f1f3a45e2 100644
--- a/src/coord/polar/polarCreator.ts
+++ b/src/coord/polar/polarCreator.ts
@@ -27,7 +27,7 @@ import {
     determineAxisType,
 } from '../../coord/axisHelper';
 
-import PolarModel from './PolarModel';
+import PolarModel, { COMPONENT_TYPE_POLAR, COORD_SYS_TYPE_POLAR } from 
'./PolarModel';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import GlobalModel from '../../model/Global';
 import OrdinalScale from '../../scale/Ordinal';
@@ -42,8 +42,9 @@ import { CategoryAxisBaseOption } from '../axisCommonTypes';
 import { createBoxLayoutReference } from '../../util/layout';
 import { scaleCalcNice } from '../axisNiceTicks';
 import {
-    AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE, 
scaleRawExtentInfoReallyCreate, scaleRawExtentInfoRequireCreate
+    AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE, scaleRawExtentInfoCreate
 } from '../scaleRawExtentInfo';
+import { associateSeriesWithAxis } from '../axisStatistics';
 
 /**
  * Resize method bound to the polar
@@ -85,8 +86,8 @@ function updatePolarScale(this: Polar, ecModel: GlobalModel, 
api: ExtensionAPI)
     const angleAxis = polar.getAngleAxis();
     const radiusAxis = polar.getRadiusAxis();
 
-    scaleRawExtentInfoReallyCreate(ecModel, angleAxis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
-    scaleRawExtentInfoReallyCreate(ecModel, radiusAxis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
+    scaleRawExtentInfoCreate(ecModel, angleAxis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
+    scaleRawExtentInfoCreate(ecModel, radiusAxis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
 
     scaleCalcNice(angleAxis);
     scaleCalcNice(radiusAxis);
@@ -131,7 +132,7 @@ const polarCreator = {
 
     create: function (ecModel: GlobalModel, api: ExtensionAPI) {
         const polarList: Polar[] = [];
-        ecModel.eachComponent('polar', function (polarModel: PolarModel, idx: 
number) {
+        ecModel.eachComponent(COMPONENT_TYPE_POLAR, function (polarModel: 
PolarModel, idx: number) {
             const polar = new Polar(idx + '');
             // Inject resize and update method
             polar.update = updatePolarScale;
@@ -157,9 +158,9 @@ const polarCreator = {
             polarIndex?: number
             polarId?: string
         }>) {
-            if (seriesModel.get('coordinateSystem') === 'polar') {
+            if (seriesModel.get('coordinateSystem') === COORD_SYS_TYPE_POLAR) {
                 const polarModel = seriesModel.getReferringComponents(
-                    'polar', SINGLE_REFERRING
+                    COMPONENT_TYPE_POLAR, SINGLE_REFERRING
                 ).models[0] as PolarModel;
 
                 if (__DEV__) {
@@ -175,8 +176,8 @@ const polarCreator = {
                 }
                 const polar = seriesModel.coordinateSystem = 
polarModel.coordinateSystem;
                 if (polar) {
-                    scaleRawExtentInfoRequireCreate(polar.getRadiusAxis(), 
seriesModel);
-                    scaleRawExtentInfoRequireCreate(polar.getAngleAxis(), 
seriesModel);
+                    associateSeriesWithAxis(polar.getRadiusAxis(), 
seriesModel, COORD_SYS_TYPE_POLAR);
+                    associateSeriesWithAxis(polar.getAngleAxis(), seriesModel, 
COORD_SYS_TYPE_POLAR);
                 }
             }
         });
diff --git a/src/coord/radar/Radar.ts b/src/coord/radar/Radar.ts
index 242b5d0b0..0710772b3 100644
--- a/src/coord/radar/Radar.ts
+++ b/src/coord/radar/Radar.ts
@@ -23,7 +23,9 @@ import IndicatorAxis from './IndicatorAxis';
 import IntervalScale from '../../scale/Interval';
 import * as numberUtil from '../../util/number';
 import { CoordinateSystemMaster, CoordinateSystem } from '../CoordinateSystem';
-import RadarModel from './RadarModel';
+import RadarModel, {
+    COMPONENT_TYPE_RADAR, COORD_SYS_TYPE_RADAR, RADAR_DEFAULT_SPLIT_NUMBER, 
SERIES_TYPE_RADAR
+} from './RadarModel';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import { ScaleDataValue } from '../../util/types';
@@ -32,16 +34,15 @@ import { map, each, isString, isNumber } from 
'zrender/src/core/util';
 import { scaleCalcAlign } from '../axisAlignTicks';
 import { createBoxLayoutReference } from '../../util/layout';
 import {
-    AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE, 
scaleRawExtentInfoReallyCreate, scaleRawExtentInfoRequireCreate
+    AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE, scaleRawExtentInfoCreate
 } from '../scaleRawExtentInfo';
 import { ensureValidSplitNumber } from '../../scale/helper';
+import { associateSeriesWithAxis } from '../axisStatistics';
 
 
-export const RADAR_DEFAULT_SPLIT_NUMBER = 5;
-
 class Radar implements CoordinateSystem, CoordinateSystemMaster {
 
-    readonly type: 'radar';
+    readonly type = COORD_SYS_TYPE_RADAR;
     /**
      *
      * Radar dimensions
@@ -168,7 +169,7 @@ class Radar implements CoordinateSystem, 
CoordinateSystemMaster {
         dummyScale.setConfig({interval: 1});
         // Force all the axis fixing the maxSplitNumber.
         each(indicatorAxes, function (indicatorAxis) {
-            scaleRawExtentInfoReallyCreate(ecModel, indicatorAxis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
+            scaleRawExtentInfoCreate(ecModel, indicatorAxis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
             scaleCalcAlign(indicatorAxis, dummyScale);
         });
     }
@@ -192,19 +193,19 @@ class Radar implements CoordinateSystem, 
CoordinateSystemMaster {
 
     static create(ecModel: GlobalModel, api: ExtensionAPI) {
         const radarList: Radar[] = [];
-        ecModel.eachComponent('radar', function (radarModel: RadarModel) {
+        ecModel.eachComponent(COMPONENT_TYPE_RADAR, function (radarModel: 
RadarModel) {
             const radar = new Radar(radarModel, ecModel, api);
             radarList.push(radar);
             radarModel.coordinateSystem = radar;
         });
-        ecModel.eachSeriesByType('radar', function (radarSeries) {
-            if (radarSeries.get('coordinateSystem') === 'radar') {
+        ecModel.eachSeriesByType(SERIES_TYPE_RADAR, function (radarSeries) {
+            if (radarSeries.get('coordinateSystem') === COORD_SYS_TYPE_RADAR) {
                 // Inject coordinate system
                 // @ts-ignore
                 const radar = radarSeries.coordinateSystem = 
radarList[radarSeries.get('radarIndex') || 0];
                 if (radar) {
                     each(radar.getIndicatorAxes(), function (indicatorAxis) {
-                        scaleRawExtentInfoRequireCreate(indicatorAxis, 
radarSeries);
+                        associateSeriesWithAxis(indicatorAxis, radarSeries, 
COORD_SYS_TYPE_RADAR);
                     });
                 }
             }
diff --git a/src/coord/radar/RadarModel.ts b/src/coord/radar/RadarModel.ts
index 7fb1fbea0..de7254f4e 100644
--- a/src/coord/radar/RadarModel.ts
+++ b/src/coord/radar/RadarModel.ts
@@ -32,13 +32,19 @@ import {
 } from '../../util/types';
 import { AxisBaseOption, CategoryAxisBaseOption, ValueAxisBaseOption } from 
'../axisCommonTypes';
 import { AxisBaseModel } from '../AxisBaseModel';
-import Radar, { RADAR_DEFAULT_SPLIT_NUMBER } from './Radar';
+import type Radar from './Radar';
 import {CoordinateSystemHostModel} from '../../coord/CoordinateSystem';
 import tokens from '../../visual/tokens';
 import { getUID } from '../../util/component';
 
 const valueAxisDefault = axisDefault.value;
 
+export const COORD_SYS_TYPE_RADAR = 'radar';
+export const COMPONENT_TYPE_RADAR = COORD_SYS_TYPE_RADAR;
+export const SERIES_TYPE_RADAR = COORD_SYS_TYPE_RADAR;
+
+export const RADAR_DEFAULT_SPLIT_NUMBER = 5;
+
 function defaultsShow(opt: object, show: boolean) {
     return zrUtil.defaults({
         show: show
@@ -104,7 +110,7 @@ export type InnerIndicatorAxisOption = AxisBaseOption & {
 };
 
 class RadarModel extends ComponentModel<RadarOption> implements 
CoordinateSystemHostModel {
-    static readonly type = 'radar';
+    static readonly type = COMPONENT_TYPE_RADAR;
     readonly type = RadarModel.type;
 
     coordinateSystem: Radar;
diff --git a/src/coord/scaleRawExtentInfo.ts b/src/coord/scaleRawExtentInfo.ts
index f378b5da5..607e3639e 100644
--- a/src/coord/scaleRawExtentInfo.ts
+++ b/src/coord/scaleRawExtentInfo.ts
@@ -27,9 +27,8 @@ import {
     NumericAxisBaseOptionCommon,
     NumericAxisBoundaryGapOptionItemValue,
 } from './axisCommonTypes';
-import { ComponentSubType, DimensionIndex, DimensionName, NullUndefined, 
ScaleDataValue } from '../util/types';
+import { DimensionIndex, DimensionName, NullUndefined, ScaleDataValue } from 
'../util/types';
 import { isIntervalScale, isLogScale, isOrdinalScale, isTimeScale } from 
'../scale/helper';
-import type SeriesModel from '../model/Series';
 import {
     makeInner, initExtentForUnion, unionExtentFromNumber, 
isValidNumberForExtent,
     extentHasValue,
@@ -46,7 +45,7 @@ import { error } from '../util/log';
 import type Axis from './Axis';
 import { mathMax, mathMin } from '../util/number';
 import { SCALE_EXTENT_KIND_MAPPING } from '../scale/scaleMapper';
-import { AxisStatKey, eachAxisStatKey } from './axisStatistics';
+import { AxisStatKey, eachKeyOnAxis, eachSeriesOnAxis } from 
'./axisStatistics';
 
 
 /**
@@ -58,8 +57,6 @@ import { AxisStatKey, eachAxisStatKey } from 
'./axisStatistics';
  */
 const scaleInner = makeInner<{
     extent: number[];
-    // series on this axis to union data extent.
-    seriesList: SeriesModel[];
     dimIdxInCoord: number;
 }, Scale>();
 
@@ -502,31 +499,58 @@ function parseBoundaryGapOptionItem(
     ) || 0;
 }
 
+/**
+ * NOTE: `associateSeriesWithAxis` is not necessarily called, e.g., when
+ * an axis is not used by any series.
+ */
+function ensureScaleStore(axisLike: {scale: Scale}) {
+    const store = scaleInner(axisLike.scale);
+    if (!store.extent) {
+        store.extent = initExtentForUnion();
+    }
+    return store;
+}
+
+/**
+ * This supports union extent on case like: pie (or other similar series)
+ * lays out on cartesian2d.
+ * @see scaleRawExtentInfoCreate
+ */
+export function scaleRawExtentInfoEnableBoxCoordSysUsage(
+    axisLike: {
+        scale: Scale;
+        dim: DimensionName;
+    },
+    coordSysDimIdxMap: HashMap<DimensionIndex, DimensionName> | NullUndefined
+): void {
+    ensureScaleStore(axisLike).dimIdxInCoord = 
coordSysDimIdxMap.get(axisLike.dim);
+}
+
 /**
  * @usage
  *  class SomeCoordSys {
  *      static create() {
  *          ecModel.eachSeries(function (seriesModel) {
- *              scaleRawExtentInfoRequireCreate(axis1, seriesModel, ...);
- *              scaleRawExtentInfoRequireCreate(axis2, seriesModel, ...);
+ *              associateSeriesWithAxis(axis1, seriesModel, ...);
+ *              associateSeriesWithAxis(axis2, seriesModel, ...);
  *              // ...
  *          });
  *      }
  *      update() {
- *          scaleRawExtentInfoReallyCreate(axis1);
- *          scaleRawExtentInfoReallyCreate(axis2);
+ *          scaleRawExtentInfoCreate(axis1);
+ *          scaleRawExtentInfoCreate(axis2);
  *      }
  *  }
  *  class AxisProxy {
  *      reset() {
- *          scaleRawExtentInfoReallyCreate(axis1);
+ *          scaleRawExtentInfoCreate(axis1);
  *      }
  *  }
  *
  * NOTICE:
- *  - `scaleRawExtentInfoRequireCreate` should be typically called in:
+ *  - `associateSeriesWithAxis`(in `axisStatistics.ts`) should be called in:
  *      - Coord sys create method.
- *  - `scaleRawExtentInfoReallyCreate` should be typically called in:
+ *  - `scaleRawExtentInfoCreate` should be typically called in:
  *      - `dataZoom` processor. It require processing like:
  *          1. Filter series data by dataZoom1;
  *          2. Union the filtered data and init the extent of the orthogonal 
axes, which is the 100% of dataZoom2;
@@ -536,49 +560,9 @@ function parseBoundaryGapOptionItem(
  *          NOTE: If `dataZoom` exists can cover this series, this data and 
its extent
  *          has been dataZoom-filtered. Therefore this handling should not 
before dataZoom.
  *  - The callback of `min`/`max` in ec option should NOT be called multiple 
times,
- *      therefore, we initialize `ScaleRawExtentInfo` uniformly in 
`scaleRawExtentInfoReallyCreate`.
+ *      therefore, we initialize `ScaleRawExtentInfo` uniformly in 
`scaleRawExtentInfoCreate`.
  */
-export function scaleRawExtentInfoRequireCreate(
-    axisLike: {
-        scale: Scale;
-    },
-    seriesModel: SeriesModel
-): void {
-    ensureScaleStore(axisLike).seriesList.push(seriesModel);
-}
-
-/**
- * NOTE: `scaleRawExtentInfoRequireCreate` is not necessarily called, e.g., 
when
- * an axis is not used by any series.
- */
-function ensureScaleStore(axisLike: {scale: Scale}) {
-    const store = scaleInner(axisLike.scale);
-    if (!store.extent) {
-        store.extent = initExtentForUnion();
-        store.seriesList = [];
-    }
-    return store;
-}
-
-/**
- * This supports union extent on case like: pie (or other similar series)
- * lays out on cartesian2d.
- * @see scaleRawExtentInfoRequireCreate
- */
-export function scaleRawExtentInfoEnableBoxCoordSysUsage(
-    axisLike: {
-        scale: Scale;
-        dim: DimensionName;
-    },
-    coordSysDimIdxMap: HashMap<DimensionIndex, DimensionName> | NullUndefined
-): void {
-    ensureScaleStore(axisLike).dimIdxInCoord = 
coordSysDimIdxMap.get(axisLike.dim);
-}
-
-/**
- * @see scaleRawExtentInfoRequireCreate
- */
-export function scaleRawExtentInfoReallyCreate(
+export function scaleRawExtentInfoCreate(
     ecModel: GlobalModel,
     axis: Axis,
     from: AxisExtentInfoBuildFrom
@@ -602,12 +586,12 @@ export function scaleRawExtentInfoReallyCreate(
         return;
     }
 
-    scaleRawExtentInfoReallyCreateDeal(scale, axis, axisDim, model, ecModel, 
from);
+    scaleRawExtentInfoCreateDeal(scale, axis, axisDim, model, ecModel, from);
 
     calcContainShape(scale, axis, ecModel, scale.rawExtentInfo);
 }
 
-function scaleRawExtentInfoReallyCreateDeal(
+function scaleRawExtentInfoCreateDeal(
     scale: Scale,
     axis: Axis,
     axisDim: DimensionName,
@@ -618,11 +602,7 @@ function scaleRawExtentInfoReallyCreateDeal(
     const scaleStore = ensureScaleStore(axis);
     const extent = scaleStore.extent;
 
-    each(scaleStore.seriesList, function (seriesModel) {
-        // Legend-filtered series need to be ignored since series are 
registered before `legendFilter`.
-        if (ecModel.isSeriesFiltered(seriesModel)) {
-            return;
-        }
+    eachSeriesOnAxis(axis, function (seriesModel) {
         if (seriesModel.boxCoordinateSystem) {
             // This supports union extent on case like: pie (or other similar 
series)
             // lays out on cartesian2d.
@@ -658,7 +638,7 @@ function scaleRawExtentInfoReallyCreateDeal(
     const rawExtentInfo = new ScaleRawExtentInfo(scale, model, extent);
     injectScaleRawExtentInfo(scale, rawExtentInfo, from);
 
-    scaleStore.seriesList = scaleStore.extent = null; // Clean up
+    scaleStore.extent = null; // Clean up
 }
 
 /**
@@ -791,7 +771,7 @@ function calcContainShape(
     // `NullUndefined` indicates that `linearSupplement` is not introduced.
     let linearSupplement: number[] | NullUndefined;
 
-    eachAxisStatKey(axis, function (axisStatKey) {
+    eachKeyOnAxis(axis, function (axisStatKey) {
         const handler = axisContainShapeHandlerMap.get(axisStatKey);
         if (handler) {
             const singleLinearSupplement = handler(axis, scale, ecModel);
diff --git a/src/coord/single/AxisModel.ts b/src/coord/single/AxisModel.ts
index a65e149c8..581edf1bb 100644
--- a/src/coord/single/AxisModel.ts
+++ b/src/coord/single/AxisModel.ts
@@ -29,6 +29,10 @@ import {
 import { AxisBaseModel } from '../AxisBaseModel';
 import { mixin } from 'zrender/src/core/util';
 
+
+export const COORD_SYS_TYPE_SINGLE_AXIS = 'singleAxis';
+export const COMPONENT_TYPE_SINGLE_AXIS = COORD_SYS_TYPE_SINGLE_AXIS;
+
 export type SingleAxisPosition = 'top' | 'bottom' | 'left' | 'right';
 
 export type SingleAxisOption = AxisBaseOption & BoxLayoutOptionMixin & {
@@ -39,7 +43,7 @@ export type SingleAxisOption = AxisBaseOption & 
BoxLayoutOptionMixin & {
 
 class SingleAxisModel extends ComponentModel<SingleAxisOption>
     implements AxisBaseModel<SingleAxisOption> {
-    static type = 'singleAxis';
+    static type = COMPONENT_TYPE_SINGLE_AXIS;
     type = SingleAxisModel.type;
 
     static readonly layoutMode = 'box';
diff --git a/src/coord/single/Single.ts b/src/coord/single/Single.ts
index d69004e8f..7c2e804e4 100644
--- a/src/coord/single/Single.ts
+++ b/src/coord/single/Single.ts
@@ -28,14 +28,14 @@ import { CoordinateSystem, CoordinateSystemMaster } from 
'../CoordinateSystem';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import BoundingRect from 'zrender/src/core/BoundingRect';
-import SingleAxisModel from './AxisModel';
+import SingleAxisModel, { COORD_SYS_TYPE_SINGLE_AXIS } from './AxisModel';
 import { ParsedModelFinder, ParsedModelFinderKnown } from '../../util/model';
 import { ScaleDataValue } from '../../util/types';
 import { AxisBaseModel } from '../AxisBaseModel';
 import { CategoryAxisBaseOption } from '../axisCommonTypes';
 import { scaleCalcNice } from '../axisNiceTicks';
 import {
-    AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE, 
scaleRawExtentInfoReallyCreate
+    AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE, scaleRawExtentInfoCreate
 } from '../scaleRawExtentInfo';
 
 export const singleDimensions = ['single'];
@@ -44,7 +44,7 @@ export const singleDimensions = ['single'];
  */
 class Single implements CoordinateSystem, CoordinateSystemMaster {
 
-    readonly type = 'single';
+    readonly type = COORD_SYS_TYPE_SINGLE_AXIS;
 
     readonly dimension = 'single';
     /**
@@ -101,7 +101,7 @@ class Single implements CoordinateSystem, 
CoordinateSystemMaster {
      */
     update(ecModel: GlobalModel, api: ExtensionAPI) {
         const axis = this._axis;
-        scaleRawExtentInfoReallyCreate(ecModel, axis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
+        scaleRawExtentInfoCreate(ecModel, axis, 
AXIS_EXTENT_INFO_BUILD_FROM_COORD_SYS_UPDATE);
         scaleCalcNice(axis);
     }
 
diff --git a/src/coord/single/singleCreator.ts 
b/src/coord/single/singleCreator.ts
index 88c41cd13..e1821cb20 100644
--- a/src/coord/single/singleCreator.ts
+++ b/src/coord/single/singleCreator.ts
@@ -24,11 +24,11 @@
 import Single, { singleDimensions } from './Single';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
-import SingleAxisModel from './AxisModel';
+import SingleAxisModel, { COMPONENT_TYPE_SINGLE_AXIS, 
COORD_SYS_TYPE_SINGLE_AXIS } from './AxisModel';
 import SeriesModel from '../../model/Series';
 import { SeriesOption } from '../../util/types';
 import { SINGLE_REFERRING } from '../../util/model';
-import { scaleRawExtentInfoRequireCreate } from '../scaleRawExtentInfo';
+import { associateSeriesWithAxis } from '../axisStatistics';
 
 /**
  * Create single coordinate system and inject it into seriesModel.
@@ -36,7 +36,7 @@ import { scaleRawExtentInfoRequireCreate } from 
'../scaleRawExtentInfo';
 function create(ecModel: GlobalModel, api: ExtensionAPI) {
     const singles: Single[] = [];
 
-    ecModel.eachComponent('singleAxis', function (axisModel: SingleAxisModel, 
idx: number) {
+    ecModel.eachComponent(COMPONENT_TYPE_SINGLE_AXIS, function (axisModel: 
SingleAxisModel, idx: number) {
 
         const single = new Single(axisModel, ecModel, api);
         single.name = 'single_' + idx;
@@ -50,13 +50,13 @@ function create(ecModel: GlobalModel, api: ExtensionAPI) {
         singleAxisIndex?: number
         singleAxisId?: string
     }>) {
-        if (seriesModel.get('coordinateSystem') === 'singleAxis') {
+        if (seriesModel.get('coordinateSystem') === 
COORD_SYS_TYPE_SINGLE_AXIS) {
             const singleAxisModel = seriesModel.getReferringComponents(
-                'singleAxis', SINGLE_REFERRING
+                COMPONENT_TYPE_SINGLE_AXIS, SINGLE_REFERRING
             ).models[0] as SingleAxisModel;
             const single = seriesModel.coordinateSystem = singleAxisModel && 
singleAxisModel.coordinateSystem;
             if (single) {
-                scaleRawExtentInfoRequireCreate(single.getAxis(), seriesModel);
+                associateSeriesWithAxis(single.getAxis(), seriesModel, 
COORD_SYS_TYPE_SINGLE_AXIS);
             }
         }
     });
diff --git a/src/core/echarts.ts b/src/core/echarts.ts
index a68bddad0..b2a887293 100644
--- a/src/core/echarts.ts
+++ b/src/core/echarts.ts
@@ -154,17 +154,18 @@ export const dependencies = {
 const TEST_FRAME_REMAIN_TIME = 1;
 
 const PRIORITY_PROCESSOR_SERIES_FILTER = 800;
-// Axis statistics require filtered series.
-const PRIORITY_PROCESSOR_AXIS_STATISTICS = 810;
 // In the current impl, "data stack" will modifies the original "series data 
extent". Some data
 // processors rely on the stack result dimension to calculate extents. So data 
stack
 // should be in front of other data processors.
 const PRIORITY_PROCESSOR_DATASTACK = 900;
+// AXIS_STATISTICS should be after SERIES_FILTER, as it may change the 
statistics result (like min gap).
+// AXIS_STATISTICS should be before filter (dataZoom), as dataZoom require the 
result in "containShape" calculation.
+const PRIORITY_PROCESSOR_AXIS_STATISTICS = 920;
 // `PRIORITY_PROCESSOR_FILTER` is typically used by `dataZoom` (see 
`AxisProxy`), which relies
 // on the initialized "axis extent".
 const PRIORITY_PROCESSOR_FILTER = 1000;
 const PRIORITY_PROCESSOR_DEFAULT = 2000;
-const PRIORITY_PROCESSOR_STATISTIC = 5000;
+const PRIORITY_PROCESSOR_STATISTICS = 5000;
 // NOTICE: Data processors above block the stream (especially time-consuming 
processors like data filters).
 
 const PRIORITY_VISUAL_LAYOUT = 1000;
@@ -188,7 +189,8 @@ export const PRIORITY = {
         SERIES_FILTER: PRIORITY_PROCESSOR_SERIES_FILTER,
         AXIS_STATISTICS: PRIORITY_PROCESSOR_AXIS_STATISTICS,
         FILTER: PRIORITY_PROCESSOR_FILTER,
-        STATISTIC: PRIORITY_PROCESSOR_STATISTIC
+        STATISTIC: PRIORITY_PROCESSOR_STATISTICS, // naming - backward 
compatibility.
+        STATISTICS: PRIORITY_PROCESSOR_STATISTICS,
     },
     VISUAL: {
         LAYOUT: PRIORITY_VISUAL_LAYOUT,
diff --git a/src/layout/barCommon.ts b/src/layout/barCommon.ts
index 3c9d7699c..0f8dd1b8c 100644
--- a/src/layout/barCommon.ts
+++ b/src/layout/barCommon.ts
@@ -17,7 +17,7 @@
 * under the License.
 */
 
-import { getMetricsMinGapOnNonCategoryAxis } from 
'../chart/helper/axisSnippets';
+import { getMetricsNonOrdinalLinearPositiveMinGap } from 
'../chart/helper/axisSnippets';
 import type Axis from '../coord/Axis';
 import { AxisStatKey, requireAxisStatistics } from '../coord/axisStatistics';
 import { EChartsExtensionInstallRegisters } from '../extension';
@@ -28,28 +28,19 @@ export type BaseBarSeriesSubType = 'bar' | 'pictorialBar';
 
 export const BAR_SERIES_TYPE = 'bar';
 
-export function registerAxisStatisticsForBaseBar(
+export function requireAxisStatisticsForBaseBar(
     registers: EChartsExtensionInstallRegisters,
     axisStatKey: AxisStatKey,
     seriesType: BaseBarSeriesSubType,
     coordSysType: 'cartesian2d' | 'polar'
-) {
+): void {
     requireAxisStatistics(
         registers,
-        axisStatKey,
         {
-            collectAxisSeries(ecModel, saveAxisSeries) {
-                // NOTICE: The order of series matters - must be respected to 
the declaration on ec option,
-                // because for historical reason, in `barGrid.ts`, the last 
series holds the effective ec option.
-                // (See `calcBarWidthAndOffset` in `barGrid.ts`).
-                ecModel.eachSeriesByType(seriesType, function (seriesModel) {
-                    const coordSys = seriesModel.coordinateSystem;
-                    if (coordSys && coordSys.type === coordSysType) {
-                        saveAxisSeries(seriesModel.getBaseAxis(), seriesModel);
-                    }
-                });
-            },
-            getMetrics: getMetricsMinGapOnNonCategoryAxis,
+            key: axisStatKey,
+            seriesType,
+            coordSysType,
+            getMetrics: getMetricsNonOrdinalLinearPositiveMinGap
         }
     );
 }
diff --git a/src/layout/barGrid.ts b/src/layout/barGrid.ts
index a1fa83b4a..dd1368aa2 100644
--- a/src/layout/barGrid.ts
+++ b/src/layout/barGrid.ts
@@ -43,15 +43,15 @@ import {
 } from '../coord/scaleRawExtentInfo';
 import { EChartsExtensionInstallRegisters } from '../extension';
 import {
-    AxisStatKey,
-    eachCollectedAxis,
-    eachCollectedSeries, getCollectedSeriesLength
+    eachAxisOnKey,
+    eachSeriesOnAxisOnKey, countSeriesOnAxisOnKey,
 } from '../coord/axisStatistics';
 import {
     AxisBandWidthResult, calcBandWidth
 } from '../coord/axisBand';
-import { BaseBarSeriesSubType, getStartValue, registerAxisStatisticsForBaseBar 
} from './barCommon';
+import { BaseBarSeriesSubType, getStartValue, requireAxisStatisticsForBaseBar 
} from './barCommon';
 import { COORD_SYS_TYPE_CARTESIAN_2D } from '../coord/cartesian/GridModel';
+import { makeAxisStatKey2 } from '../chart/helper/axisSnippets';
 
 
 const callOnlyOnce = makeCallOnlyOnce();
@@ -171,14 +171,15 @@ function createLayoutInfoListOnAxis(
     seriesType: BaseBarSeriesSubType
 ): BarGridLayoutAxisInfo {
 
+    const axisStatKey = makeAxisStatKey2(seriesType, 
COORD_SYS_TYPE_CARTESIAN_2D);
     const seriesInfoOnAxis: BarGridLayoutAxisSeriesInfo[] = [];
     const bandWidthResult = calcBandWidth(
         baseAxis,
-        {fromStat: {key: makeAxisStatKey(seriesType)}, min: 1}
+        {fromStat: {key: axisStatKey}, min: 1}
     );
     const bandWidth = bandWidthResult.w;
 
-    eachCollectedSeries(baseAxis, makeAxisStatKey(seriesType), function 
(seriesModel: BaseBarSeriesModel) {
+    eachSeriesOnAxisOnKey(baseAxis, axisStatKey, function (seriesModel: 
BaseBarSeriesModel) {
         seriesInfoOnAxis.push({
             barWidth: parsePercent(seriesModel.get('barWidth'), bandWidth),
             barMaxWidth: parsePercent(seriesModel.get('barMaxWidth'), 
bandWidth),
@@ -353,14 +354,14 @@ function calcBarWidthAndOffset(
 }
 
 export function layout(seriesType: BaseBarSeriesSubType, ecModel: 
GlobalModel): void {
-    const axisStatKey = makeAxisStatKey(seriesType);
-    eachCollectedAxis(ecModel, axisStatKey, function (axis: Axis2D) {
+    const axisStatKey = makeAxisStatKey2(seriesType, 
COORD_SYS_TYPE_CARTESIAN_2D);
+    eachAxisOnKey(ecModel, axisStatKey, function (axis: Axis2D) {
         if (__DEV__) {
             assert(axis instanceof Axis2D);
         }
         const columnLayout = makeColumnLayoutOnAxisReal(axis, seriesType);
 
-        eachCollectedSeries(axis, axisStatKey, function (seriesModel) {
+        eachSeriesOnAxisOnKey(axis, axisStatKey, function (seriesModel) {
             const columnLayoutInfo = 
columnLayout.columnMap[getSeriesStackId(seriesModel)];
             seriesModel.getData().setLayout({
                 bandWidth: columnLayoutInfo.bandWidth,
@@ -520,7 +521,7 @@ function barGridCreateAxisContainShapeHandler(seriesType: 
BaseBarSeriesSubType):
         // If bars are placed on 'time', 'value', 'log' axis, handle bars 
overflow here.
         // See #6728, #4862, `test/bar-overflow-time-plot.html`
         if (axis && axis instanceof Axis2D && !isOrdinalScale(scale)) {
-            if (!getCollectedSeriesLength(axis, makeAxisStatKey(seriesType))) {
+            if (!countSeriesOnAxisOnKey(axis, makeAxisStatKey2(seriesType, 
COORD_SYS_TYPE_CARTESIAN_2D))) {
                 return; // Quick path - in most cases there is no bar on 
non-ordinal axis.
             }
             const columnLayout = makeColumnLayoutOnAxisReal(axis, seriesType);
@@ -571,16 +572,12 @@ function calcShapeOverflowSupplement(
     }
 }
 
-function makeAxisStatKey(seriesType: BaseBarSeriesSubType): AxisStatKey {
-    return `barGrid-${seriesType}` as AxisStatKey;
-}
-
 export function registerBarGridAxisHandlers(registers: 
EChartsExtensionInstallRegisters) {
     callOnlyOnce(registers, function () {
 
         function register(seriesType: BaseBarSeriesSubType): void {
-            const axisStatKey = makeAxisStatKey(seriesType);
-            registerAxisStatisticsForBaseBar(
+            const axisStatKey = makeAxisStatKey2(seriesType, 
COORD_SYS_TYPE_CARTESIAN_2D);
+            requireAxisStatisticsForBaseBar(
                 registers,
                 axisStatKey,
                 seriesType,
diff --git a/src/layout/barPolar.ts b/src/layout/barPolar.ts
index 357e24c6f..2cdf2eae1 100644
--- a/src/layout/barPolar.ts
+++ b/src/layout/barPolar.ts
@@ -27,12 +27,12 @@ import GlobalModel from '../model/Global';
 import ExtensionAPI from '../core/ExtensionAPI';
 import { Dictionary } from '../util/types';
 import { calcBandWidth } from '../coord/axisBand';
-import { createBandWidthBasedAxisContainShapeHandler } from 
'../chart/helper/axisSnippets';
+import { createBandWidthBasedAxisContainShapeHandler, makeAxisStatKey2 } from 
'../chart/helper/axisSnippets';
 import { makeCallOnlyOnce } from '../util/model';
 import { EChartsExtensionInstallRegisters } from '../extension';
 import { registerAxisContainShapeHandler } from '../coord/scaleRawExtentInfo';
-import { getStartValue, registerAxisStatisticsForBaseBar } from './barCommon';
-import { AxisStatKey, eachCollectedAxis, eachCollectedSeries } from 
'../coord/axisStatistics';
+import { getStartValue, requireAxisStatisticsForBaseBar } from './barCommon';
+import { eachAxisOnKey, eachSeriesOnAxisOnKey } from '../coord/axisStatistics';
 import { COORD_SYS_TYPE_POLAR } from '../coord/polar/PolarModel';
 import type Axis from '../coord/Axis';
 import { assert, each } from 'zrender/src/core/util';
@@ -64,9 +64,9 @@ function getSeriesStackId(seriesModel: BarSeriesModel) {
 }
 
 export function barLayoutPolar(seriesType: 'bar', ecModel: GlobalModel, api: 
ExtensionAPI) {
-    const axisStatKey = makeAxisStatKey(seriesType);
+    const axisStatKey = makeAxisStatKey2(seriesType, COORD_SYS_TYPE_POLAR);
 
-    eachCollectedAxis(ecModel, axisStatKey, function (axis: PolarAxis) {
+    eachAxisOnKey(ecModel, axisStatKey, function (axis: PolarAxis) {
         if (__DEV__) {
             assert((axis instanceof AngleAxis) || axis instanceof RadiusAxis);
         }
@@ -74,7 +74,7 @@ export function barLayoutPolar(seriesType: 'bar', ecModel: 
GlobalModel, api: Ext
         const barWidthAndOffset = calcRadialBar(axis, seriesType);
 
         const lastStackCoords: LastStackCoords = {};
-        eachCollectedSeries(axis, axisStatKey, function (seriesModel: 
BarSeriesModel) {
+        eachSeriesOnAxisOnKey(axis, axisStatKey, function (seriesModel: 
BarSeriesModel) {
             layoutPerAxisPerSeries(axis, seriesModel, barWidthAndOffset, 
lastStackCoords);
         });
     });
@@ -93,7 +93,7 @@ function layoutPerAxisPerSeries(
     const columnWidth = columnLayoutInfo.width;
     const polar = seriesModel.coordinateSystem as Polar;
     if (__DEV__) {
-        assert(polar.type === 'polar');
+        assert(polar.type === COORD_SYS_TYPE_POLAR);
     }
     const valueAxis = polar.getOtherAxis(baseAxis);
 
@@ -209,9 +209,11 @@ function layoutPerAxisPerSeries(
  * Calculate bar width and offset for radial bar charts
  */
 function calcRadialBar(axis: Axis, seriesType: 'bar'): BarWidthAndOffsetOnAxis 
{
+    const axisStatKey = makeAxisStatKey2(seriesType, COORD_SYS_TYPE_POLAR);
+
     const bandWidth = calcBandWidth(
         axis,
-        {fromStat: {key: makeAxisStatKey(seriesType)}, min: 1}
+        {fromStat: {key: axisStatKey}, min: 1}
     ).w;
 
     let remainedWidth: number = bandWidth;
@@ -220,7 +222,7 @@ function calcRadialBar(axis: Axis, seriesType: 'bar'): 
BarWidthAndOffsetOnAxis {
     let gapOption: string | number = '30%';
     const stacks: Dictionary<StackInfo> = {};
 
-    eachCollectedSeries(axis, makeAxisStatKey(seriesType), function 
(seriesModel: BarSeriesModel, idx) {
+    eachSeriesOnAxisOnKey(axis, axisStatKey, function (seriesModel: 
BarSeriesModel) {
         const stackId = getSeriesStackId(seriesModel);
 
         if (!stacks[stackId]) {
@@ -308,17 +310,13 @@ function calcRadialBar(axis: Axis, seriesType: 'bar'): 
BarWidthAndOffsetOnAxis {
     return result;
 }
 
-function makeAxisStatKey(seriesType: 'bar'): AxisStatKey {
-    return `barPolar-${seriesType}` as AxisStatKey;
-}
-
 export function registerBarPolarAxisHandlers(
     registers: EChartsExtensionInstallRegisters,
     seriesType: 'bar' // Currently only 'bar' is supported.
 ): void {
     callOnlyOnce(registers, function () {
-        const axisStatKey = makeAxisStatKey(seriesType);
-        registerAxisStatisticsForBaseBar(
+        const axisStatKey = makeAxisStatKey2(seriesType, COORD_SYS_TYPE_POLAR);
+        requireAxisStatisticsForBaseBar(
             registers,
             axisStatKey,
             seriesType,
diff --git a/src/model/Global.ts b/src/model/Global.ts
index 33b92ba3f..1b5c741c2 100644
--- a/src/model/Global.ts
+++ b/src/model/Global.ts
@@ -849,6 +849,9 @@ echarts.use([${seriesImportName}]);`);
         return each(this.getSeriesByType(subType), cb, context);
     }
 
+    /**
+     * It means "filtered out".
+     */
     isSeriesFiltered(seriesModel: SeriesModel): boolean {
         assertSeriesInitialized(this);
         return this._seriesIndicesMap.get(seriesModel.componentIndex) == null;
diff --git a/src/scale/scaleMapper.ts b/src/scale/scaleMapper.ts
index 845594430..da8b02bee 100644
--- a/src/scale/scaleMapper.ts
+++ b/src/scale/scaleMapper.ts
@@ -232,8 +232,8 @@ export interface ScaleMapperGeneric<This> {
      *
      * [The steps of extent construction in EC_MAIN_CYCLE]:
      *  - step#1. At `CoordinateSystem#create` stage, requirements of 
collecting series data extents are
-     *            committed to `scaleRawExtentInfoRequireCreate`, and `Scale` 
instances are created.
-     *  - step#2. Call `scaleRawExtentInfoReallyCreate` to really collect 
series data extent and create
+     *            committed to `associateSeriesWithAxis`, and `Scale` 
instances are created.
+     *  - step#2. Call `scaleRawExtentInfoCreate` to really collect series 
data extent and create
      *            `ScaleRawExtentInfo` instances to manage extent related 
configurations
      *                - at "data processing" stage for dataZoom controlled 
axes, if any, or
      *                - at "CoordinateSystem#update" stage for all other axes.
diff --git a/src/util/jitter.ts b/src/util/jitter.ts
index 36bbda5c0..89460bdaa 100644
--- a/src/util/jitter.ts
+++ b/src/util/jitter.ts
@@ -21,6 +21,8 @@ import type Axis from '../coord/Axis';
 import { calcBandWidth } from '../coord/axisBand';
 import type { AxisBaseModel } from '../coord/AxisBaseModel';
 import Axis2D from '../coord/cartesian/Axis2D';
+import { COORD_SYS_TYPE_CARTESIAN_2D } from '../coord/cartesian/GridModel';
+import { COORD_SYS_TYPE_SINGLE_AXIS } from '../coord/single/AxisModel';
 import type SingleAxis from '../coord/single/SingleAxis';
 import type SeriesModel from '../model/Series';
 import { isOrdinalScale } from '../scale/helper';
@@ -31,8 +33,8 @@ export function needFixJitter(seriesModel: SeriesModel, axis: 
Axis): boolean {
     const coordType = coordinateSystem && coordinateSystem.type;
     const baseAxis = coordinateSystem && coordinateSystem.getBaseAxis && 
coordinateSystem.getBaseAxis();
     const scaleType = baseAxis && baseAxis.scale && baseAxis.scale.type;
-    const seriesValid = coordType === 'cartesian2d' && scaleType === 'ordinal'
-        || coordType === 'single';
+    const seriesValid = coordType === COORD_SYS_TYPE_CARTESIAN_2D && scaleType 
=== 'ordinal'
+        || coordType === COORD_SYS_TYPE_SINGLE_AXIS;
 
     const axisValid = (axis.model as AxisBaseModel).get('jitter') > 0;
     return seriesValid && axisValid;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to