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

sushuang pushed a commit to branch fix/contain-label-name
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit f75a6031032d6f324362877f07acc28d1ea4b26e
Author: 100pah <[email protected]>
AuthorDate: Thu Apr 10 23:04:24 2025 +0800

    fix(grid layout): Change the impl and correct "contain axis name" and 
deprecated `grid.containLabel`. Support `grid.layoutContain.axisName` and 
`grid.layoutContain.axisLabel`.
---
 src/chart/pie/labelLayout.ts                      |   4 +-
 src/component/axis/AxisBuilder.ts                 |  33 +-
 src/component/axis/CartesianAxisView.ts           |  36 +-
 src/component/axis/ParallelAxisView.ts            |   4 +-
 src/component/axis/RadiusAxisView.ts              |   8 +-
 src/component/axis/SingleAxisView.ts              |   8 +-
 src/component/axisPointer/CartesianAxisPointer.ts |   4 +-
 src/component/radar/RadarView.ts                  |   8 +-
 src/coord/Axis.ts                                 |   3 +
 src/coord/axisCommonTypes.ts                      |  24 +-
 src/coord/axisHelper.ts                           | 179 +------
 src/coord/cartesian/AxisModel.ts                  |  13 -
 src/coord/cartesian/Grid.ts                       | 196 ++++---
 src/coord/cartesian/GridModel.ts                  |  12 +-
 src/coord/cartesian/cartesianAxisHelper.ts        |  35 +-
 src/label/labelLayoutHelper.ts                    |   2 +-
 src/scale/Scale.ts                                |   1 +
 test/axis-containLabel2.html                      | 591 +++++++++++++++++++++-
 18 files changed, 849 insertions(+), 312 deletions(-)

diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts
index 5052ca236..79dfb820a 100644
--- a/src/chart/pie/labelLayout.ts
+++ b/src/chart/pie/labelLayout.ts
@@ -327,7 +327,7 @@ function constrainTextWidth(
 
         const newRect = label.getBoundingRect();
         textRect.width = newRect.width;
-        const margin = (label.style.margin || 0) + 2.1;
+        const margin = ((label.style.margin as number) || 0) + 2.1;
         textRect.height = newRect.height + margin;
         textRect.y -= (textRect.height - oldHeight) / 2;
     }
@@ -501,7 +501,7 @@ export default function pieLabelLayout(
             const textRect = label.getBoundingRect().clone();
             textRect.applyTransform(label.getComputedTransform());
             // Text has a default 1px stroke. Exclude this.
-            const margin = (label.style.margin || 0) + 2.1;
+            const margin = ((label.style.margin as number) || 0) + 2.1;
             textRect.y -= margin / 2;
             textRect.height += margin;
 
diff --git a/src/component/axis/AxisBuilder.ts 
b/src/component/axis/AxisBuilder.ts
index 736a87c8f..846bc62f6 100644
--- a/src/component/axis/AxisBuilder.ts
+++ b/src/component/axis/AxisBuilder.ts
@@ -31,12 +31,12 @@ import {applyTransform as v2ApplyTransform} from 
'zrender/src/core/vector';
 import {isNameLocationCenter, shouldShowAllLabels} from 
'../../coord/axisHelper';
 import { AxisBaseModel } from '../../coord/AxisBaseModel';
 import { ZRTextVerticalAlign, ZRTextAlign, ECElement, ColorString } from 
'../../util/types';
-import { AxisBaseOption } from '../../coord/axisCommonTypes';
+import { AxisBaseOption, AxisBaseOptionCommon } from 
'../../coord/axisCommonTypes';
 import type Element from 'zrender/src/Element';
 import { PathStyleProps } from 'zrender/src/graphic/Path';
 import OrdinalScale from '../../scale/Ordinal';
 import { prepareLayoutList, hideOverlap } from '../../label/labelLayoutHelper';
-import CartesianAxisModel from '../../coord/cartesian/AxisModel';
+
 
 const PI = Math.PI;
 
@@ -60,6 +60,7 @@ type AxisLabelText = graphic.Text & {
     __truncatedText: string
 } & ECElement;
 
+
 export interface AxisBuilderCfg {
     position?: number[]
     rotation?: number
@@ -104,6 +105,8 @@ interface TickCoord {
 }
 
 /**
+ * A builder for a straight-line axis.
+ *
  * A final axis is translated and rotated from a "standard axis".
  * So opt.position and opt.rotation is required.
  *
@@ -135,6 +138,9 @@ class AxisBuilder {
 
     private _transformGroup: graphic.Group;
 
+    /**
+     * [CAUTION]: axisModel.axis.extent/scale must be ready to use.
+     */
     constructor(axisModel: AxisBaseModel, opt?: AxisBuilderCfg) {
 
         this.opt = opt;
@@ -170,12 +176,13 @@ class AxisBuilder {
         this._transformGroup = transformGroup;
     }
 
-    hasBuilder(name: keyof typeof builders) {
-        return !!builders[name];
-    }
-
-    add(name: keyof typeof builders) {
-        builders[name](this.opt, this.axisModel, this.group, 
this._transformGroup);
+    build(axisPartNameMap: AxisBuilderAxisPartMap) {
+        // axisName layout depends on axisTickLabel layout result to resolve 
overlap.
+        each(['axisLine', 'axisTickLabel', 'axisName'] as const, partName => {
+            if (axisPartNameMap[partName]) {
+                builders[partName](this.opt, this.axisModel, this.group, 
this._transformGroup);
+            }
+        });
     }
 
     getGroup() {
@@ -241,7 +248,10 @@ interface AxisElementsBuilder {
     ):void
 }
 
-const builders: Record<'axisLine' | 'axisTickLabel' | 'axisName', 
AxisElementsBuilder> = {
+export type AxisBuilderAxisPartName = 'axisLine' | 'axisTickLabel' | 
'axisName';
+export type AxisBuilderAxisPartMap = {[axisPartName in 
AxisBuilderAxisPartName]?: boolean};
+
+const builders: Record<AxisBuilderAxisPartName, AxisElementsBuilder> = {
 
     axisLine(opt, axisModel, group, transformGroup) {
 
@@ -377,8 +387,7 @@ const builders: Record<'axisLine' | 'axisTickLabel' | 
'axisName', AxisElementsBu
         const nameLocation = axisModel.get('nameLocation');
         const nameDirection = opt.nameDirection;
         const textStyleModel = axisModel.getModel('nameTextStyle');
-        const axisToNameGapStartGap = axisModel instanceof CartesianAxisModel 
? axisModel.axisToNameGapStartGap : 0;
-        const gap = (axisModel.get('nameGap') || 0) + axisToNameGapStartGap;
+        const gap = (axisModel.get('nameGap') || 0);
 
         const extent = axisModel.axis.getExtent();
         const gapSignal = extent[0] > extent[1] ? -1 : 1;
@@ -480,7 +489,7 @@ const builders: Record<'axisLine' | 'axisTickLabel' | 
'axisName', AxisElementsBu
 };
 
 function endTextLayout(
-    rotation: number, textPosition: 'start' | 'middle' | 'end', textRotate: 
number, extent: number[]
+    rotation: number, textPosition: AxisBaseOptionCommon['nameLocation'], 
textRotate: number, extent: number[]
 ) {
     const rotationDiff = remRadian(textRotate - rotation);
     let textAlign: ZRTextAlign;
diff --git a/src/component/axis/CartesianAxisView.ts 
b/src/component/axis/CartesianAxisView.ts
index abc3e8cff..5a8088713 100644
--- a/src/component/axis/CartesianAxisView.ts
+++ b/src/component/axis/CartesianAxisView.ts
@@ -19,7 +19,6 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import * as graphic from '../../util/graphic';
-import AxisBuilder, {AxisBuilderCfg} from './AxisBuilder';
 import AxisView from './AxisView';
 import * as cartesianAxisHelper from 
'../../coord/cartesian/cartesianAxisHelper';
 import {rectCoordAxisBuildSplitArea, rectCoordAxisHandleRemove} from 
'./axisSplitHelper';
@@ -28,11 +27,12 @@ import ExtensionAPI from '../../core/ExtensionAPI';
 import CartesianAxisModel from '../../coord/cartesian/AxisModel';
 import GridModel from '../../coord/cartesian/GridModel';
 import { Payload } from '../../util/types';
-import { isIntervalOrLogScale } from '../../scale/helper';
 
-const axisBuilderAttrs = [
-    'axisLine', 'axisTickLabel', 'axisName'
-] as const;
+const axisBuilderAttrs = {
+    axisLine: true,
+    axisTickLabel: true,
+    axisName: true,
+} as const;
 const selfBuilderAttrs = [
     'splitArea', 'splitLine', 'minorSplitLine'
 ] as const;
@@ -64,25 +64,13 @@ class CartesianAxisView extends AxisView {
 
         const gridModel = axisModel.getCoordSysModel();
 
-        const layout = cartesianAxisHelper.layout(gridModel, axisModel);
-
-        const axisBuilder = new AxisBuilder(axisModel, zrUtil.extend({
-            handleAutoShown(elementType) {
-                const cartesians = gridModel.coordinateSystem.getCartesians();
-                for (let i = 0; i < cartesians.length; i++) {
-                    if 
(isIntervalOrLogScale(cartesians[i].getOtherAxis(axisModel.axis).scale)) {
-                        // Still show axis tick or axisLine if other axis is 
value / log
-                        return true;
-                    }
-                }
-                // Not show axisTick or axisLine if other axis is category / 
time
-                return false;
-            }
-        } as AxisBuilderCfg, layout));
-
-        zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
-
-        this._axisGroup.add(axisBuilder.getGroup());
+        const grid = gridModel.coordinateSystem;
+        
this._axisGroup.add(cartesianAxisHelper.buildCartesianAxisViewCommonPart(
+            axisBuilderAttrs,
+            grid.getRect(),
+            grid.getCartesians(),
+            axisModel
+        ));
 
         zrUtil.each(selfBuilderAttrs, function (name) {
             if (axisModel.get([name, 'show'])) {
diff --git a/src/component/axis/ParallelAxisView.ts 
b/src/component/axis/ParallelAxisView.ts
index 4f6af6946..d4fae3c27 100644
--- a/src/component/axis/ParallelAxisView.ts
+++ b/src/component/axis/ParallelAxisView.ts
@@ -34,7 +34,7 @@ import ParallelModel from 
'../../coord/parallel/ParallelModel';
 import { ParallelAxisLayoutInfo } from '../../coord/parallel/Parallel';
 
 
-const elementList = ['axisLine', 'axisTickLabel', 'axisName'];
+const axisBuilderAxisPartMap = {axisLine: true, axisTickLabel: true, axisName: 
true};
 
 class ParallelAxisView extends ComponentView {
 
@@ -94,7 +94,7 @@ class ParallelAxisView extends ComponentView {
 
         const axisBuilder = new AxisBuilder(axisModel, builderOpt);
 
-        zrUtil.each(elementList, axisBuilder.add, axisBuilder);
+        axisBuilder.build(axisBuilderAxisPartMap);
 
         this._axisGroup.add(axisBuilder.getGroup());
 
diff --git a/src/component/axis/RadiusAxisView.ts 
b/src/component/axis/RadiusAxisView.ts
index b5e8f8c59..404804a00 100644
--- a/src/component/axis/RadiusAxisView.ts
+++ b/src/component/axis/RadiusAxisView.ts
@@ -26,9 +26,9 @@ import Polar from '../../coord/polar/Polar';
 import RadiusAxis from '../../coord/polar/RadiusAxis';
 import GlobalModel from '../../model/Global';
 
-const axisBuilderAttrs = [
-    'axisLine', 'axisTickLabel', 'axisName'
-] as const;
+const axisBuilderAttrs = {
+    axisLine: true, axisTickLabel: true, axisName: true
+} as const;
 const selfBuilderAttrs = [
     'splitLine', 'splitArea', 'minorSplitLine'
 ] as const;
@@ -64,7 +64,7 @@ class RadiusAxisView extends AxisView {
 
         const layout = layoutAxis(polar, radiusAxisModel, axisAngle);
         const axisBuilder = new AxisBuilder(radiusAxisModel, layout);
-        zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
+        axisBuilder.build(axisBuilderAttrs);
         newAxisGroup.add(axisBuilder.getGroup());
 
         graphic.groupTransition(oldAxisGroup, newAxisGroup, radiusAxisModel);
diff --git a/src/component/axis/SingleAxisView.ts 
b/src/component/axis/SingleAxisView.ts
index 600b55a0f..8a1ce700b 100644
--- a/src/component/axis/SingleAxisView.ts
+++ b/src/component/axis/SingleAxisView.ts
@@ -28,9 +28,9 @@ import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import { Payload } from '../../util/types';
 
-const axisBuilderAttrs = [
-    'axisLine', 'axisTickLabel', 'axisName'
-] as const;
+const axisBuilderAttrs = {
+    axisLine: true, axisTickLabel: true, axisName: true
+ } as const;
 
 const selfBuilderAttrs = ['splitArea', 'splitLine'] as const;
 
@@ -56,7 +56,7 @@ class SingleAxisView extends AxisView {
 
         const axisBuilder = new AxisBuilder(axisModel, layout);
 
-        zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
+        axisBuilder.build(axisBuilderAttrs);
 
         group.add(this._axisGroup);
         group.add(axisBuilder.getGroup());
diff --git a/src/component/axisPointer/CartesianAxisPointer.ts 
b/src/component/axisPointer/CartesianAxisPointer.ts
index a0ff122b0..286a77dd8 100644
--- a/src/component/axisPointer/CartesianAxisPointer.ts
+++ b/src/component/axisPointer/CartesianAxisPointer.ts
@@ -59,7 +59,7 @@ class CartesianAxisPointer extends BaseAxisPointer {
             elOption.pointer = pointerOption;
         }
 
-        const layoutInfo = cartesianAxisHelper.layout(grid.model, axisModel);
+        const layoutInfo = cartesianAxisHelper.layout(grid.getRect(), 
axisModel);
         viewHelper.buildCartesianSingleLabelElOption(
             // @ts-ignore
             value, elOption, layoutInfo, axisModel, axisPointerModel, api
@@ -74,7 +74,7 @@ class CartesianAxisPointer extends BaseAxisPointer {
         axisModel: CartesianAxisModel,
         axisPointerModel: AxisPointerModel
     ) {
-        const layoutInfo = 
cartesianAxisHelper.layout(axisModel.axis.grid.model, axisModel, {
+        const layoutInfo = 
cartesianAxisHelper.layout(axisModel.axis.grid.getRect(), axisModel, {
             labelInside: false
         });
         // @ts-ignore
diff --git a/src/component/radar/RadarView.ts b/src/component/radar/RadarView.ts
index 1b3c11f6d..8857b31ba 100644
--- a/src/component/radar/RadarView.ts
+++ b/src/component/radar/RadarView.ts
@@ -26,9 +26,9 @@ import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import { ZRColor } from '../../util/types';
 
-const axisBuilderAttrs = [
-    'axisLine', 'axisTickLabel', 'axisName'
-] as const;
+const axisBuilderAttrs = {
+    axisLine: true, axisTickLabel: true, axisName: true
+} as const;
 
 class RadarView extends ComponentView {
 
@@ -62,7 +62,7 @@ class RadarView extends ComponentView {
         });
 
         zrUtil.each(axisBuilders, function (axisBuilder) {
-            zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
+            axisBuilder.build(axisBuilderAttrs);
             this.group.add(axisBuilder.getGroup());
         }, this);
     }
diff --git a/src/coord/Axis.ts b/src/coord/Axis.ts
index bdaca2873..91082e288 100644
--- a/src/coord/Axis.ts
+++ b/src/coord/Axis.ts
@@ -59,6 +59,9 @@ class Axis {
     // Axis scale
     scale: Scale;
 
+    // Make sure that `extent[0] > extent[1]` if and only if `inverse: true`.
+    // The unit is pixel, but not necessarily the global pixel,
+    //  probably need to transform (usually rotate) to global pixel.
     private _extent: [number, number];
 
     // Injected outside
diff --git a/src/coord/axisCommonTypes.ts b/src/coord/axisCommonTypes.ts
index fe4e4fc6f..2cb1c4fe6 100644
--- a/src/coord/axisCommonTypes.ts
+++ b/src/coord/axisCommonTypes.ts
@@ -37,7 +37,13 @@ export interface AxisBaseOptionCommon extends 
ComponentOption,
     inverse?: boolean;
     // Axis name displayed.
     name?: string;
-    nameLocation?: 'start' | 'middle' | 'end';
+    /**
+     * - 'start': place name based on axis.extent[0].
+     * - 'end': place name based on axis.extent[1].
+     * - 'middle': place name based on the center of the axis.
+     * - 'center' has been deprecated, ='middle'.
+     */
+    nameLocation?: 'start' | 'middle' | 'center' | 'end';
     // By degree.
     nameRotate?: number;
     nameTruncate?: {
@@ -46,8 +52,22 @@ export interface AxisBaseOptionCommon extends 
ComponentOption,
         placeholder?: string;
     };
     nameTextStyle?: AxisNameTextStyleOption;
-    // The gap between axisName and axisLine.
+    /**
+     * This is the offset of axis name from:
+     * - If `nameMoveOverlap: false`: offset from axisLine.
+     * - If `nameMoveOverlap: true`: offset from axisLine+axisLabels.
+     *
+     * PENDING: should it named as "nameOffset" or support `[offsetX, 
offsetY]`?
+     */
     nameGap?: number;
+    /**
+     * Whether to auto move axis name to avoid overlap with axis labels.
+     * The procedure of axis name layout:
+     * 1. Firstly apply `nameRotate`, `nameTruncate`, `nameLocation`.
+     * 2. If `nameMoveOverlap: true`, move the name util it does not overlap 
with axis lables.
+     * 3. Then apply `nameGap`.
+     */
+    nameMoveOverlap?: boolean;
 
     silent?: boolean;
     triggerEvent?: boolean;
diff --git a/src/coord/axisHelper.ts b/src/coord/axisHelper.ts
index 124a9f1c3..8d5b26df4 100644
--- a/src/coord/axisHelper.ts
+++ b/src/coord/axisHelper.ts
@@ -26,7 +26,6 @@ import {
     makeColumnLayout,
     retrieveColumnLayout
 } from '../layout/barGrid';
-import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect';
 
 import TimeScale from '../scale/Time';
 import Model from '../model/Model';
@@ -35,17 +34,18 @@ import LogScale from '../scale/Log';
 import Axis from './Axis';
 import {
     AxisBaseOption,
+    AxisBaseOptionCommon,
     CategoryAxisBaseOption,
     LogAxisBaseOption,
     TimeAxisLabelFormatterOption,
     ValueAxisBaseOption
 } from './axisCommonTypes';
-import CartesianAxisModel, { CartesianAxisPosition, 
inverseCartesianAxisPositionMap } from './cartesian/AxisModel';
+import CartesianAxisModel from './cartesian/AxisModel';
 import SeriesData from '../data/SeriesData';
 import { getStackedDimension } from '../data/helper/dataStackHelper';
 import { Dictionary, DimensionName, ScaleTick, TimeScaleTick } from 
'../util/types';
 import { ensureScaleRawExtentInfo } from './scaleRawExtentInfo';
-import Axis2D from './cartesian/Axis2D';
+import BoundingRect from 'zrender/src/core/BoundingRect';
 
 
 type BarWidthAndOffset = ReturnType<typeof makeColumnLayout>;
@@ -292,10 +292,10 @@ export function getAxisRawValue(axis: Axis, tick: 
ScaleTick): number | string {
 }
 
 /**
- * @param axis
+ * @deprecated
  * @return Be null/undefined if no labels.
  */
-export function estimateLabelUnionRect(axis: Axis) {
+export function legacyEstimateLabelUnionRect(axis: Axis) {
     const axisModel = axis.model;
     const scale = axis.scale;
 
@@ -321,7 +321,7 @@ export function estimateLabelUnionRect(axis: Axis) {
 
     let rect;
     let step = 1;
-    // Simple optimization for large amount of labels
+    // Simple optimization for large amount of category labels
     if (tickCount > 40) {
         step = Math.ceil(tickCount / 40);
     }
@@ -339,38 +339,21 @@ export function estimateLabelUnionRect(axis: Axis) {
     }
 
     return rect;
-}
 
-/**
- * @param axis
- * @return Be null/undefined if no name.
- */
-export function computeNameBoundingRect(axis: Axis2D): BoundingRect {
-    const axisModel = axis.model;
-    if (!axisModel.get('name')) {
-        return;
+    function rotateTextRect(textRect: BoundingRect, rotate: number) {
+        const rotateRadians = rotate * Math.PI / 180;
+        const beforeWidth = textRect.width;
+        const beforeHeight = textRect.height;
+        const afterWidth = beforeWidth * Math.abs(Math.cos(rotateRadians))
+            + Math.abs(beforeHeight * Math.sin(rotateRadians));
+        const afterHeight = beforeWidth * Math.abs(Math.sin(rotateRadians))
+            + Math.abs(beforeHeight * Math.cos(rotateRadians));
+        const rotatedRect = new BoundingRect(textRect.x, textRect.y, 
afterWidth, afterHeight);
+
+        return rotatedRect;
     }
-    const axisLabelModel = axisModel.getModel('nameTextStyle');
-    const unRotatedNameBoundingRect = 
axisLabelModel.getTextRect(axisModel.get('name'));
-    const defaultRotation = axis.isHorizontal() || 
!isNameLocationCenter(axisModel.get('nameLocation')) ? 0 : -90;
-    const rotatedNameBoundingRect = rotateTextRect(
-        unRotatedNameBoundingRect, axisModel.get('nameRotate') ?? 
defaultRotation
-    );
-    return rotatedNameBoundingRect;
 }
 
-function rotateTextRect(textRect: RectLike, rotate: number) {
-    const rotateRadians = rotate * Math.PI / 180;
-    const beforeWidth = textRect.width;
-    const beforeHeight = textRect.height;
-    const afterWidth = beforeWidth * Math.abs(Math.cos(rotateRadians))
-        + Math.abs(beforeHeight * Math.sin(rotateRadians));
-    const afterHeight = beforeWidth * Math.abs(Math.sin(rotateRadians))
-        + Math.abs(beforeHeight * Math.cos(rotateRadians));
-    const rotatedRect = new BoundingRect(textRect.x, textRect.y, afterWidth, 
afterHeight);
-
-    return rotatedRect;
-}
 
 /**
  * @param model axisLabelModel or axisTickModel
@@ -419,132 +402,6 @@ export function unionAxisExtentFromData(dataExtent: 
number[], data: SeriesData,
     }
 }
 
-export function isNameLocationCenter(nameLocation: string) {
+export function isNameLocationCenter(nameLocation: 
AxisBaseOptionCommon['nameLocation']) {
     return nameLocation === 'middle' || nameLocation === 'center';
 }
-
-function isNameLocationStart(nameLocation: string) {
-    return nameLocation === 'start';
-}
-
-function isNameLocationEnd(nameLocation: string) {
-    return nameLocation === 'end';
-}
-
-
-export type CartesianAxisPositionMargins = {[K in CartesianAxisPosition]: 
number};
-
-export type ReservedSpace = {
-    labels: CartesianAxisPositionMargins,
-    name: CartesianAxisPositionMargins,
-    nameGap: CartesianAxisPositionMargins,
-    namePositionCurrAxis: CartesianAxisPosition
-};
-
-/*
- * Compute the reserved space (determined by axis labels and axis names) in 
each direction
- */
-export function computeReservedSpace(
-    axis: Axis2D, labelUnionRect: BoundingRect, nameBoundingRect: BoundingRect
-): ReservedSpace {
-    const reservedSpace: ReservedSpace = {
-        labels: {left: 0, top: 0, right: 0, bottom: 0},
-        nameGap: {left: 0, top: 0, right: 0, bottom: 0},
-        name: {left: 0, top: 0, right: 0, bottom: 0},
-        namePositionCurrAxis: null
-    };
-
-    const boundingRectDim = axis.isHorizontal() ? 'height' : 'width';
-
-    if (labelUnionRect) {
-        const margin = axis.model.get(['axisLabel', 'margin']);
-        reservedSpace.labels[axis.position] = labelUnionRect[boundingRectDim] 
+ margin;
-    }
-
-    if (nameBoundingRect) {
-        let nameLocation = axis.model.get('nameLocation');
-        const onZeroOfAxis = axis.getAxesOnZeroOf()?.[0];
-        let namePositionOrthogonalAxis: CartesianAxisPosition = axis.position;
-        if (onZeroOfAxis && ['start', 'end'].includes(nameLocation)) {
-            const defaultZero = onZeroOfAxis.isHorizontal() ? 'left' : 
'bottom';
-            namePositionOrthogonalAxis = onZeroOfAxis.inverse
-                ? inverseCartesianAxisPositionMap[defaultZero]
-                : defaultZero;
-        }
-
-        const nameGap = axis.model.get('nameGap');
-        const nameRotate = axis.model.get('nameRotate');
-
-        if (axis.inverse) {
-            if (nameLocation === 'start') {
-                nameLocation = 'end';
-            }
-            else if (nameLocation === 'end') {
-                nameLocation = 'start';
-            }
-        }
-
-        const nameBoundingRectSize = nameBoundingRect[boundingRectDim];
-
-        if (isNameLocationCenter(nameLocation)) {
-            reservedSpace.namePositionCurrAxis = axis.position;
-            reservedSpace.nameGap[axis.position] = nameGap;
-            reservedSpace.name[axis.position] = nameBoundingRectSize;
-        }
-        else {
-            const inverseBoundingRectDim = boundingRectDim === 'height' ? 
'width' : 'height';
-            const nameBoundingRectSizeInverseDim = 
nameBoundingRect?.[inverseBoundingRectDim] || 0;
-
-            const rotationInRadians = nameRotate * (Math.PI / 180);
-            const sin = Math.sin(rotationInRadians);
-            const cos = Math.cos(rotationInRadians);
-
-            const nameRotationIsFirstOrThirdQuadrant = sin > 0 && cos > 0 || 
sin < 0 && cos < 0;
-            const nameRotationIsSecondOrFourthQuadrant = sin > 0 && cos < 0 || 
sin < 0 && cos > 0;
-            const nameRotationIsMultipleOf180degrees = sin === 0 || cos === 1 
|| cos === -1;
-            const nameRotationIsMultipleOf90degrees =
-                nameRotationIsMultipleOf180degrees || sin === 1 || sin === -1 
|| cos === 0;
-
-            const nameLocationIsStart = isNameLocationStart(nameLocation);
-            const nameLocationIsEnd = isNameLocationEnd(nameLocation);
-
-            const reservedSpacePosition = axis.isHorizontal()
-                ? (nameLocationIsStart ? 'left' : 'right')
-                : (nameLocationIsStart ? 'bottom' : 'top');
-
-            reservedSpace.namePositionCurrAxis = reservedSpacePosition;
-            reservedSpace.nameGap[reservedSpacePosition] = nameGap;
-            reservedSpace.name[reservedSpacePosition] = 
nameBoundingRectSizeInverseDim;
-
-            const reservedLabelSpace = 
reservedSpace.labels[namePositionOrthogonalAxis];
-            const reservedNameSpace = nameBoundingRectSize - 
reservedLabelSpace;
-
-            const orthogonalAxisPositionIsTop = namePositionOrthogonalAxis === 
'top';
-            const orthogonalAxisPositionIsBottom = namePositionOrthogonalAxis 
=== 'bottom';
-            const orthogonalAxisPositionIsLeft = namePositionOrthogonalAxis 
=== 'left';
-            const orthogonalAxisPositionIsRight = namePositionOrthogonalAxis 
=== 'right';
-
-            if (axis.isHorizontal() && nameRotationIsMultipleOf90degrees
-                || !axis.isHorizontal() && nameRotationIsMultipleOf180degrees) 
{
-                    reservedSpace.name[namePositionOrthogonalAxis] = 
nameBoundingRectSize / 2 - reservedLabelSpace;
-            }
-            else if (
-                axis.isHorizontal() && (
-                    nameLocationIsStart && orthogonalAxisPositionIsTop && 
nameRotationIsSecondOrFourthQuadrant
-                        || nameLocationIsStart && 
orthogonalAxisPositionIsBottom && nameRotationIsFirstOrThirdQuadrant
-                        || nameLocationIsEnd && orthogonalAxisPositionIsTop && 
nameRotationIsFirstOrThirdQuadrant
-                        || nameLocationIsEnd && orthogonalAxisPositionIsBottom 
&& nameRotationIsSecondOrFourthQuadrant
-                )
-                || !axis.isHorizontal() && (
-                    nameLocationIsStart && orthogonalAxisPositionIsLeft && 
nameRotationIsFirstOrThirdQuadrant
-                        || nameLocationIsStart && 
orthogonalAxisPositionIsRight && nameRotationIsSecondOrFourthQuadrant
-                        || nameLocationIsEnd && orthogonalAxisPositionIsLeft 
&& nameRotationIsSecondOrFourthQuadrant
-                        || nameLocationIsEnd && orthogonalAxisPositionIsRight 
&& nameRotationIsFirstOrThirdQuadrant
-                )
-            ) {
-                reservedSpace.name[namePositionOrthogonalAxis] = 
reservedNameSpace;
-            }
-        }
-    }
-    return reservedSpace;
-}
\ No newline at end of file
diff --git a/src/coord/cartesian/AxisModel.ts b/src/coord/cartesian/AxisModel.ts
index d501e4fed..1b0d35a13 100644
--- a/src/coord/cartesian/AxisModel.ts
+++ b/src/coord/cartesian/AxisModel.ts
@@ -30,13 +30,6 @@ import { SINGLE_REFERRING } from '../../util/model';
 
 export type CartesianAxisPosition = 'top' | 'bottom' | 'left' | 'right';
 
-export const inverseCartesianAxisPositionMap = {
-    left: 'right',
-    right: 'left',
-    top: 'bottom',
-    bottom: 'top'
-} as const;
-
 export type CartesianAxisOption = AxisBaseOption & {
     gridIndex?: number;
     gridId?: string;
@@ -60,12 +53,6 @@ export class CartesianAxisModel extends 
ComponentModel<CartesianAxisOption>
 
     axis: Axis2D;
 
-    /**
-     * The gap between the axis and the name gap.
-     * Injected outside.
-     */
-    axisToNameGapStartGap: number = 0;
-
     getCoordSysModel(): GridModel {
         return this.getReferringComponents('grid', SINGLE_REFERRING).models[0] 
as GridModel;
     }
diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts
index 2c10825aa..18daa1822 100644
--- a/src/coord/cartesian/Grid.ts
+++ b/src/coord/cartesian/Grid.ts
@@ -23,26 +23,22 @@
  * TODO Default cartesian
  */
 
-import {isObject, each, indexOf, retrieve3, keys, map} from 
'zrender/src/core/util';
+import {isObject, each, indexOf, retrieve3, keys} from 'zrender/src/core/util';
 import {getLayoutRect, LayoutRect} from '../../util/layout';
 import {
     createScaleByModel,
     ifAxisCrossZero,
     niceScaleExtent,
-    estimateLabelUnionRect,
+    legacyEstimateLabelUnionRect,
     getDataDimensionsOnAxis,
-    computeNameBoundingRect,
-    computeReservedSpace,
-    ReservedSpace,
-    CartesianAxisPositionMargins
 } from '../../coord/axisHelper';
 import Cartesian2D, {cartesian2DDimensions} from './Cartesian2D';
 import Axis2D from './Axis2D';
 import {ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING} from 
'../../util/model';
 
 // Depends on GridModel, AxisModel, which performs preprocess.
-import GridModel from './GridModel';
-import CartesianAxisModel, { CartesianAxisPosition } from './AxisModel';
+import GridModel, { GridOption } from './GridModel';
+import CartesianAxisModel from './AxisModel';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import { Dictionary } from 'zrender/src/core/types';
@@ -50,14 +46,14 @@ import {CoordinateSystemMaster} from '../CoordinateSystem';
 import { ScaleDataValue } from '../../util/types';
 import SeriesData from '../../data/SeriesData';
 import OrdinalScale from '../../scale/Ordinal';
-import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper';
+import { isCartesian2DSeries, findAxisModels, buildCartesianAxisViewCommonPart 
} from './cartesianAxisHelper';
 import { CategoryAxisBaseOption, NumericAxisBaseOptionCommon } from 
'../axisCommonTypes';
 import { AxisBaseModel } from '../AxisBaseModel';
 import { isIntervalOrLogScale } from '../../scale/helper';
 import { alignScaleTicks } from '../axisAlignTicks';
 import IntervalScale from '../../scale/Interval';
 import LogScale from '../../scale/Log';
-import { BoundingRect } from 'zrender';
+import { BoundingRect } from '../../util/graphic';
 
 
 type Cartesian2DDimensionName = 'x' | 'y';
@@ -68,6 +64,10 @@ type AxesMap = {
     y: Axis2D[]
 };
 
+const WH = ['width', 'height'] as const;
+const XY = ['x', 'y'] as const;
+
+
 class Grid implements CoordinateSystemMaster {
 
     // FIXME:TS where used (different from registered type 'cartesian2d')?
@@ -170,63 +170,46 @@ class Grid implements CoordinateSystemMaster {
     }
 
     /**
-     * Resize the grid
+     * Resize the grid.
+     *
+     * [NOTE]
+     * If both "containLabel,containName" and "dataSampling" exist, circular 
dependency occurs in logic.
+     * The final compromised sequence is:
+     *  1. Calculate "axis.extent" (pixel extent) based on only "grid layout 
options". Not accurate if
+     *      "containLabel,containName" is required, but it is a compromise to 
avoid circular dependency.
+     *  2. Perform "series data processing" (where "dataSampling" requires 
"axis.extent").
+     *  3. Calculate "scale.extent" (data extent) based on "processed series 
data".
+     *  4. Modify "axis.extent" for "containLabel,containName":
+     *      4.1. Calculate "axis labels" based on "scale.extent".
+     *      4.2. Modify "axis.extent" by the bounding rects of "axis labels 
and names".
      */
-    resize(gridModel: GridModel, api: ExtensionAPI, ignoreContainLabel?: 
boolean): void {
+    resize(gridModel: GridModel, api: ExtensionAPI, beforeDataProcessing?: 
boolean): void {
 
         const boxLayoutParams = gridModel.getBoxLayoutParams();
-        const isContainLabel = !ignoreContainLabel && 
gridModel.get('containLabel');
 
         const gridRect = getLayoutRect(
-            boxLayoutParams, {
+            boxLayoutParams,
+            {
                 width: api.getWidth(),
                 height: api.getHeight()
             });
 
         this._rect = gridRect;
+        const axesMap = this._axesMap;
 
-        const axesList = this._axesList;
-
-        adjustAxes();
-
-        // Minus label, name, and nameGap size
-        if (isContainLabel) {
-            const reservedSpacePerAxis: ReservedSpace[] = [];
-            each(axesList, function (axis) {
-                const nameBoundingRect = computeNameBoundingRect(axis);
-
-                let labelUnionRect: BoundingRect;
-                if (!axis.model.get(['axisLabel', 'inside'])) {
-                    labelUnionRect = estimateLabelUnionRect(axis);
-                }
-
-                reservedSpacePerAxis.push(computeReservedSpace(axis, 
labelUnionRect, nameBoundingRect));
-            });
-
-            const maxLabelSpace: CartesianAxisPositionMargins = { left: 0, 
top: 0, right: 0, bottom: 0};
-            const maxNameAndNameGapSpace: CartesianAxisPositionMargins = { 
left: 0, top: 0, right: 0, bottom: 0};
-            const cartesianAxisPositions: CartesianAxisPosition[] = ['left', 
'top', 'right', 'bottom'];
-
-            each(cartesianAxisPositions, (position) => {
-                maxLabelSpace[position] = 
Math.max(...map(reservedSpacePerAxis, ({ labels }) => labels[position]));
-                maxNameAndNameGapSpace[position] =
-                    Math.max(...map(reservedSpacePerAxis, ({ name, nameGap }) 
=> name[position] + nameGap[position]));
-            });
-
-            axesList.forEach((axis, axisIndex) => {
-                axis.model.axisToNameGapStartGap =
-                    
maxLabelSpace[reservedSpacePerAxis[axisIndex].namePositionCurrAxis];
-            });
-
-            const maxReservedSpaceLeft = maxLabelSpace.left + 
maxNameAndNameGapSpace.left;
-            const maxReservedSpaceTop = maxLabelSpace.top + 
maxNameAndNameGapSpace.top;
-
-            gridRect.x += maxReservedSpaceLeft;
-            gridRect.y += maxReservedSpaceTop;
-            gridRect.width -= maxReservedSpaceLeft + maxLabelSpace.right + 
maxNameAndNameGapSpace.right;
-            gridRect.height -= maxReservedSpaceTop + maxLabelSpace.bottom + 
maxNameAndNameGapSpace.bottom;
+        const optionContainLabel = gridModel.get('containLabel'); // No `true` 
for backward compat.
+        const optionLayoutContain = gridModel.get('layoutContain', true) || {};
 
-            adjustAxes();
+        if (!beforeDataProcessing
+            && (optionLayoutContain.axisLabel || optionLayoutContain.axisName)
+        ) {
+            layOutGridByContained(optionLayoutContain, gridRect, 
this._coordsList, this._axesMap);
+        }
+        else if (!beforeDataProcessing && optionContainLabel) {
+            legacyLayOutGridByContained(axesMap, this._axesList, gridRect);
+        }
+        else {
+            updateAxisLayoutAllByGridRect(axesMap, gridRect);
         }
 
         each(this._coordsList, function (coord) {
@@ -234,16 +217,6 @@ class Grid implements CoordinateSystemMaster {
             // If all the axes scales are time or value.
             coord.calcAffineTransform();
         });
-
-        function adjustAxes() {
-            each(axesList, function (axis) {
-                const isHorizontal = axis.isHorizontal();
-                const extent = isHorizontal ? [0, gridRect.width] : [0, 
gridRect.height];
-                const idx = axis.inverse ? 1 : 0;
-                axis.setExtent(extent[idx], extent[1 - idx]);
-                updateAxisTransform(axis, isHorizontal ? gridRect.x : 
gridRect.y);
-            });
-        }
     }
 
     getAxis(dim: Cartesian2DDimensionName, axisIndex?: number): Axis2D {
@@ -684,4 +657,97 @@ function updateAxisTransform(axis: Axis2D, coordBase: 
number) {
         };
 }
 
+function updateAxisLayoutAllByGridRect(axesMap: AxesMap, gridRect: LayoutRect) 
{
+    each(axesMap.x, axis => updateAxisLayoutByGridRect(axis, gridRect.x, 
gridRect.width));
+    each(axesMap.y, axis => updateAxisLayoutByGridRect(axis, gridRect.y, 
gridRect.height));
+}
+
+function updateAxisLayoutByGridRect(axis: Axis2D, gridXY: number, gridWH: 
number): void {
+    const extent = [0, gridWH];
+    const idx = axis.inverse ? 1 : 0;
+    axis.setExtent(extent[idx], extent[1 - idx]);
+    updateAxisTransform(axis, gridXY);
+}
+
+/**
+ * The input gridRect and axes will be modified.
+ */
+function legacyLayOutGridByContained(axesMap: AxesMap, axesList: Axis2D[], 
gridRect: LayoutRect): void {
+    updateAxisLayoutAllByGridRect(axesMap, gridRect);
+    each(axesList, function (axis) {
+        if (!axis.model.get(['axisLabel', 'inside'])) {
+            const labelUnionRect = legacyEstimateLabelUnionRect(axis);
+            if (labelUnionRect) {
+                const dim: 'height' | 'width' = axis.isHorizontal() ? 'height' 
: 'width';
+                const margin = axis.model.get(['axisLabel', 'margin']);
+                gridRect[dim] -= labelUnionRect[dim] + margin;
+                if (axis.position === 'top') {
+                    gridRect.y += labelUnionRect.height + margin;
+                }
+                else if (axis.position === 'left') {
+                    gridRect.x += labelUnionRect.width + margin;
+                }
+            }
+        }
+    });
+    updateAxisLayoutAllByGridRect(axesMap, gridRect);
+}
+
+/**
+ * The input gridRect and axes will be modified.
+ */
+function layOutGridByContained(
+    optionLayoutContain: GridOption['layoutContain'],
+    gridRect: LayoutRect,
+    cartesians: Cartesian2D[],
+    axesMap: AxesMap,
+): void {
+    const axisBuilderAxisPartMap = {
+        axisLine: true,
+        axisTickLabel: !!optionLayoutContain.axisLabel,
+        axisName: !!optionLayoutContain.axisName,
+    };
+
+    updateAxisLayoutAllByGridRect(axesMap, gridRect);
+
+    const layoutRect = BoundingRect.create(gridRect);
+    // The bounding rect of the created `axisGroup` might be sensitve to 
variations in
+    // `axis.extent` due to strategies like hideOverlap/moveOverlap. To make 
it more
+    // consistent to the final actual layout, `gridRect` is modified 
immediately one dimension
+    // is calculated, and the latter calculation is based on the updated 
`gridRect`. And yAxis
+    // is calculated first, as empirically, the yAxis lables is less sensitive 
to variations
+    // in "axis.extent".
+    each([1, 0] as const, xyIdx => {
+        // - Considered axis may be blank or no labels and the returned rect 
size is 0.
+        // - The final rect must not be greater than the original input 
`gridRect`. That is, event if
+        //  labels/ticks/lines/names are all hide or inside, other parts not 
addressed here, such as
+        //  splitLine and tooltip trigger area, still need to be displayed 
within the gridRect.
+        const unionRect = BoundingRect.create(gridRect);
+        each(axesMap[XY[xyIdx]], axis => {
+            const axisGroup = buildCartesianAxisViewCommonPart(
+                axisBuilderAxisPartMap, gridRect, cartesians, axis.model
+            );
+            unionRect.union(axisGroup.getBoundingRect());
+        });
+        trimGridRect(unionRect, 1 - xyIdx);
+        trimGridRect(unionRect, xyIdx);
+
+        function trimGridRect(unionRect: BoundingRect, xyIdx: number): void {
+            const wh = WH[xyIdx];
+            const xy = XY[xyIdx];
+            let minNew = gridRect[xy]
+                + Math.max(0, layoutRect[xy] - unionRect[xy]);
+            let maxNew = (gridRect[xy] + gridRect[wh])
+                - Math.max(0, (unionRect[xy] + unionRect[wh]) - 
(layoutRect[xy] + layoutRect[wh]));
+            if (minNew > maxNew) {
+                minNew = maxNew = (minNew + maxNew) / 2;
+            }
+            gridRect[xy] = minNew;
+            gridRect[wh] = maxNew - minNew;
+
+            each(axesMap[xy], axis => updateAxisLayoutByGridRect(axis, 
gridRect[xy], gridRect[wh]));
+        }
+    });
+}
+
 export default Grid;
diff --git a/src/coord/cartesian/GridModel.ts b/src/coord/cartesian/GridModel.ts
index 5e2f43860..c95ff1898 100644
--- a/src/coord/cartesian/GridModel.ts
+++ b/src/coord/cartesian/GridModel.ts
@@ -28,8 +28,18 @@ export interface GridOption extends ComponentOption, 
BoxLayoutOptionMixin, Shado
 
     show?: boolean;
 
-    // Whether grid size contain label.
+    /**
+     * @deprecated Use `layoutContain` instead.
+     * Whether grid size contains axis labels. This approach estimates the 
size by sample labels.
+     * It works for most case but it does not strictly contain all labels in 
some cases.
+     */
     containLabel?: boolean;
+    layoutContain?: {
+        // Whether grid size contains axis labels.
+        axisLabel?: boolean;
+        // Whether grid size contains axis names.
+        axisName?: boolean;
+    };
 
     backgroundColor?: ZRColor;
     borderWidth?: number;
diff --git a/src/coord/cartesian/cartesianAxisHelper.ts 
b/src/coord/cartesian/cartesianAxisHelper.ts
index 6f977bf2a..18ad36f2b 100644
--- a/src/coord/cartesian/cartesianAxisHelper.ts
+++ b/src/coord/cartesian/cartesianAxisHelper.ts
@@ -19,10 +19,14 @@
 
 
 import * as zrUtil from 'zrender/src/core/util';
-import GridModel from './GridModel';
 import CartesianAxisModel from './AxisModel';
 import SeriesModel from '../../model/Series';
 import { SINGLE_REFERRING } from '../../util/model';
+import { LayoutRect } from '../../util/layout';
+import Group from 'zrender/src/graphic/Group';
+import AxisBuilder, { AxisBuilderAxisPartMap, AxisBuilderCfg } from 
'../../component/axis/AxisBuilder';
+import { isIntervalOrLogScale } from '../../scale/helper';
+import type Cartesian2D from './Cartesian2D';
 
 interface CartesianAxisLayout {
     position: [number, number];
@@ -40,10 +44,9 @@ interface CartesianAxisLayout {
  * (Can be called before coordinate system update stage).
  */
 export function layout(
-    gridModel: GridModel, axisModel: CartesianAxisModel, opt?: {labelInside?: 
boolean}
+    rect: LayoutRect, axisModel: CartesianAxisModel, opt?: {labelInside?: 
boolean}
 ): CartesianAxisLayout {
     opt = opt || {};
-    const grid = gridModel.coordinateSystem;
     const axis = axisModel.axis;
     const layout = {} as CartesianAxisLayout;
     const otherAxisOnZeroOf = axis.getAxesOnZeroOf()[0];
@@ -52,7 +55,6 @@ export function layout(
     const axisPosition: 'onZero' | typeof axis.position = otherAxisOnZeroOf ? 
'onZero' : rawAxisPosition;
     const axisDim = axis.dim;
 
-    const rect = grid.getRect();
     const rectBound = [rect.x, rect.x + rect.width, rect.y, rect.y + 
rect.height];
     const idx = {left: 0, right: 1, top: 0, bottom: 1, onZero: 2};
     const axisOffset = axisModel.get('offset') || 0;
@@ -132,3 +134,28 @@ export function findAxisModels(seriesModel: SeriesModel): {
     return axisModelMap;
 }
 
+export function buildCartesianAxisViewCommonPart(
+    axisBuilderAxisPartMap: AxisBuilderAxisPartMap,
+    gridRect: LayoutRect,
+    cartesians: Cartesian2D[],
+    axisModel: CartesianAxisModel
+): Group {
+    const layoutResult = layout(gridRect, axisModel);
+
+    const axisBuilder = new AxisBuilder(axisModel, zrUtil.extend({
+        handleAutoShown(elementType) {
+            for (let i = 0; i < cartesians.length; i++) {
+                if 
(isIntervalOrLogScale(cartesians[i].getOtherAxis(axisModel.axis).scale)) {
+                    // Still show axis tick or axisLine if other axis is value 
/ log
+                    return true;
+                }
+            }
+            // Not show axisTick or axisLine if other axis is category / time
+            return false;
+        }
+    } as AxisBuilderCfg, layoutResult));
+
+    axisBuilder.build(axisBuilderAxisPartMap);
+
+    return axisBuilder.group;
+}
diff --git a/src/label/labelLayoutHelper.ts b/src/label/labelLayoutHelper.ts
index 2f42db5c7..67b29b5b4 100644
--- a/src/label/labelLayoutHelper.ts
+++ b/src/label/labelLayoutHelper.ts
@@ -64,7 +64,7 @@ export function prepareLayoutList(input: 
LabelLayoutListPrepareInput[]): LabelLa
         const localRect = label.getBoundingRect();
         const isAxisAligned = !transform || (transform[1] < 1e-5 && 
transform[2] < 1e-5);
 
-        const minMargin = label.style.margin || 0;
+        const minMargin = (label.style.margin as number) || 0;
         const globalRect = localRect.clone();
         globalRect.applyTransform(transform);
         globalRect.x -= minMargin / 2;
diff --git a/src/scale/Scale.ts b/src/scale/Scale.ts
index 143f4c9b7..3d5671ed6 100644
--- a/src/scale/Scale.ts
+++ b/src/scale/Scale.ts
@@ -37,6 +37,7 @@ abstract class Scale<SETTING extends Dictionary<unknown> = 
Dictionary<unknown>>
 
     private _setting: SETTING;
 
+    // Make sure that extent[0] always <= extent[1].
     protected _extent: [number, number];
 
     private _isBlank: boolean;
diff --git a/test/axis-containLabel2.html b/test/axis-containLabel2.html
index 7a6a6308b..569d158ba 100755
--- a/test/axis-containLabel2.html
+++ b/test/axis-containLabel2.html
@@ -39,20 +39,22 @@ under the License.
         </style>
 
 
-        <div id="main0" style="width: 800px;height:600px;"></div>
+        <div id="main_compat_padding"></div>
 
+        <div id="main_all"></div>
 
-        <script>
 
-            var chart;
-            var myChart;
-            var option;
+        <script>
 
             require([
-                'echarts'/*, 'map/js/china' */
+                'echarts'
             ], function (echarts) {
+                const _yAxisLabelPaddingValues = [
+                    [0, 10],
+                    0
+                ];
 
-                option = {
+                var option = {
                     backgroundColor: '#ddd',
                     xAxis: [{
                     }],
@@ -60,8 +62,7 @@ under the License.
                         type: 'category',
                         axisLabel: {
                             color: '#333',
-                            padding: [0, 10],
-                            // padding: [0, 0, 10, 10],
+                            padding: _yAxisLabelPaddingValues[0],
                             rich: {
                                 spStyle: {
                                     fontWeight: 'bold'
@@ -87,16 +88,584 @@ under the License.
                     }]
                 };
 
-                chart = myChart = testHelper.create(echarts, 'main0', {
+                var chart = testHelper.create(echarts, 'main_compat_padding', {
                     title: [
+                        'Backward compatibility:',
                         'Line chart should be render normally when ',
                         '**grid.containLabel: true** and **axisLabel.padding: 
[0, 20]** (**in shorthand form**)',
                         'text bounding rect should be calculated based on 
normalized style props'
                     ],
-                    option: option
+                    option: option,
+                    width: 700,
+                    height: 400,
+                    buttons: [{
+                        type: 'select',
+                        text: 'yAxis.axisLabel.padding',
+                        values: _yAxisLabelPaddingValues,
+                        onchange() {
+                            chart.setOption({
+                                yAxis: {
+                                    axisLabel: {
+                                        padding: this.value
+                                    }
+                                }
+                            });
+                        }
+                    }]
                 });
             });
 
         </script>
+
+
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                const _values = {
+                    allAxis: {
+                        nameTextStyle: {
+                            fontSize: [undefined, 30]
+                        },
+                        axisLabel: {
+                            padding: [undefined, [0, 10], 0],
+                            margin: [undefined, 60, -60],
+                            fontSize: [undefined, 35],
+                        },
+                        nameLocation: ['end', 'start', 'center'],
+                    },
+                    xAxis: {
+                        position: ['bottom', 'top'],
+                        nameRotate: 0,
+                        axisLabel: {
+                        },
+                    },
+                    yAxis: {
+                        position: ['left', 'right'],
+                        nameRotate: 0,
+                        axisLabel: {
+                        },
+                    },
+                    grid: {
+                        left: 0,
+                        right: 0,
+                        top: 0,
+                        bottom: 0,
+                    }
+                };
+
+                function makeAxisName({xy, idx, richTag}) {
+                    const txt = `${xy}Axis_${idx} long name`;
+                    return richTag
+                        ? `{name_big_1|${txt}}`
+                        : txt;
+                }
+
+                function createOption({xyAxisCount}) {
+                    const axisCountArr = (new Array(xyAxisCount)).fill(1);
+                    const nameRichStyle = {
+                        name_big_1: {
+                            borderWidth: 15,
+                            borderColor: 'rgba(150,0,0,0.5)',
+                            backgroundColor: 'rgba(0,150,0,0.5)',
+                            padding: 10,
+                            color: '#000',
+                            fontSize: 30,
+                        }
+                    };
+                    const labelRichStyle = {
+                        label_big_1: {
+                            borderWidth: 15,
+                            borderColor: 'rgba(0,0,150,0.5)',
+                            backgroundColor: 'rgba(0,150,0,0.5)',
+                            padding: 10,
+                            color: '#000',
+                            fontSize: 20,
+                        }
+                    };
+
+                    return {
+                        backgroundColor: 'rgba(0,0,0,0.1)',
+                        xAxis: axisCountArr.map((_, idx) => ({
+                            id: idx,
+                            name: makeAxisName({xy: 'x', idx}),
+                            nameTextStyle: {
+                                fontSize: 
_values.allAxis.nameTextStyle.fontSize[0],
+                                rich: nameRichStyle,
+                            },
+                            nameLocation: _values.allAxis.nameLocation[0],
+                            axisLabel: {
+                                padding: _values.allAxis.axisLabel.padding[0],
+                                fontSize: 
_values.allAxis.axisLabel.fontSize[0],
+                                textStyle: {
+                                    rich: labelRichStyle
+                                }
+                            },
+                            axisLine: {
+                                show: true,
+                            },
+                            axisLine: {
+                                onZero: false,
+                            },
+                            position: _values.xAxis.position,
+                        })),
+                        yAxis: axisCountArr.map((_, idx) => ({
+                            id: idx,
+                            name: makeAxisName({xy: 'x', idx}),
+                            type: 'log',
+                            // type: 'category',
+                            nameTextStyle: {
+                                fontSize: 
_values.allAxis.nameTextStyle.fontSize[0],
+                                rich: nameRichStyle,
+                            },
+                            nameLocation: _values.allAxis.nameLocation[0],
+                            axisLabel: {
+                                padding: _values.allAxis.axisLabel.padding[0],
+                                fontSize: 
_values.allAxis.axisLabel.fontSize[0],
+                                fontWeight: 'bold',
+                                textStyle: {
+                                    rich: labelRichStyle
+                                }
+                            },
+                            axisLine: {
+                                onZero: false,
+                            },
+                            position: _values.yAxis.position,
+                        })),
+                        grid: [{
+                            left: _values.grid.left,
+                            right: _values.grid.right,
+                            top: _values.grid.top,
+                            bottom: _values.grid.bottom,
+                            containLabel: undefined,
+                            layoutContain: {
+                                axisLabel: true,
+                                axisName: true,
+                            },
+                            show: true,
+                            backgroundColor: 'rgba(150,0,0,0.2)',
+                        }],
+                        series: axisCountArr.map((_, idx) => ({
+                            xAxisIndex: idx,
+                            yAxisIndex: idx,
+                            name: '指标1',
+                            data: [
+                                [1000000, 123232],
+                                [3333333, 32323233232],
+                                [5555555, 31133232233]
+                            ],
+                            type: 'line'
+                        }))
+                    };
+                }
+
+                let _controllingAxisIndex = 0;
+                let _controlledAxisIndexState = [
+                    null,
+                    null,
+                ];
+                let _controllingAxisIndices = [0, 1, 2];
+
+                const chart = testHelper.create(echarts, 'main_all', {
+                    title: [
+                        'layout contain all tests',
+                    ],
+                    option: createOption({xyAxisCount: 1}),
+                    width: 750,
+                    height: 350,
+                    inputsStyle: 'compact',
+                    inputs: [
+                        ...(['top', 'right', 'bottom', 'left'].map(prop => ({
+                            type: 'range',
+                            text: `grid.${prop}`,
+                            value: _values.grid[prop],
+                            onchange() {
+                                chart.setOption({grid: {[prop]: this.value}});
+                            }
+                        }))),
+                        {type: 'br'},
+                        {
+                            type: 'select',
+                            text: 'containLabel:',
+                            values: [undefined, true, false],
+                            onchange() {
+                                chart.setOption({
+                                    grid: {containLabel: this.value}
+                                });
+                            }
+                        },
+                        {
+                            type: 'select',
+                            text: 'layoutContain.axisLabel:',
+                            values: [true, false, undefined],
+                            onchange() {
+                                chart.setOption({
+                                    grid: {layoutContain: {axisLabel: 
this.value}}
+                                });
+                            }
+                        },
+                        {
+                            type: 'select',
+                            text: 'layoutContain.axisName:',
+                            values: [true, false, undefined],
+                            onchange() {
+                                chart.setOption({
+                                    grid: {layoutContain: {axisName: 
this.value}}
+                                });
+                            }
+                        },
+                        {
+                            type: 'br',
+                        },
+                        {
+                            type: 'select',
+                            text: 'xyAxis count',
+                            values: [1, 2, 3],
+                            onchange() {
+                                const option = createOption({
+                                    xyAxisCount: this.value,
+                                });
+                                chart.setOption(option, true);
+                            }
+                        },
+                        {
+                            type: 'select',
+                            text: 'inputs below control xy axis index:',
+                            values: _controllingAxisIndices,
+                            onchange() {
+                                saveState(_controllingAxisIndex);
+                                _controllingAxisIndex = this.value;
+                                restoreState(_controllingAxisIndex);
+                            },
+                        },
+                        {
+                            type: 'br',
+                        },
+                        ...(['xAxis', 'yAxis'].map(prop => ({
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: `${prop} .rotate`,
+                            options: [
+                                {value: undefined},
+                                {input: {
+                                    type: 'range',
+                                    value: 0,
+                                    min: -100,
+                                    max: 100,
+                                }}
+                            ],
+                            onchange() {
+                                chart.setOption({[prop]: {
+                                    axisLabel: {rotate: this.value},
+                                    id: _controllingAxisIndex,
+                                },});
+                            }
+                        }))),
+                        {type: 'br'},
+                        ...(['xAxis', 'yAxis'].map(prop => ({
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: `${prop}.nameRotate`,
+                            options: [
+                                {value: undefined},
+                                {input: {
+                                    type: 'range',
+                                    value: 0,
+                                    min: -100,
+                                    max: 100,
+                                }},
+                            ],
+                            onchange() {
+                                chart.setOption({
+                                    [prop]: {
+                                        nameRotate: this.value,
+                                        id: _controllingAxisIndex,
+                                    }
+                                });
+                            }
+                        }))),
+                        {type: 'br'},
+                        ...(['xAxis', 'yAxis'].map(prop => ({
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: `${prop}.nameGap`,
+                            options: [
+                                {value: undefined},
+                                {input: {
+                                    type: 'range',
+                                    value: 15,
+                                    min: -100,
+                                    max: 100,
+                                }}
+                            ],
+                            onchange() {
+                                chart.setOption({[prop]: {
+                                    nameGap: this.value,
+                                    id: _controllingAxisIndex,
+                                }});
+                            }
+                        }))),
+                        {type: 'br'},
+                        ...(['xAxis', 'yAxis'].map(prop => ({
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: `${prop}.offset`,
+                            options: [
+                                {value: undefined},
+                                {input: {
+                                    type: 'range',
+                                    value: 0,
+                                    min: -100,
+                                    max: 100,
+                                }}
+                            ],
+                            onchange() {
+                                chart.setOption({[prop]: {
+                                    offset: this.value,
+                                    id: _controllingAxisIndex,
+                                }});
+                            }
+                        }))),
+                        {type: 'br'},
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'axisLabel.padding',
+                            values: _values.allAxis.axisLabel.padding,
+                            onchange() {
+                                chart.setOption({
+                                    xAxis: {
+                                        axisLabel: {padding: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        axisLabel: {padding: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'axisLabel.margin',
+                            values: _values.allAxis.axisLabel.margin,
+                            onchange() {
+                                chart.setOption({
+                                    xAxis: {
+                                        axisLabel: {margin: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        axisLabel: {margin: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: `axisLabel.fontSize`,
+                            values: _values.allAxis.axisLabel.fontSize,
+                            onchange() {
+                                chart.setOption({
+                                    xAxis: {
+                                        axisLabel: {fontSize: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        axisLabel: {fontSize: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'xAxis.position',
+                            values: _values.xAxis.position,
+                            onchange() {
+                                chart.setOption({xAxis: {
+                                    position: this.value,
+                                    id: _controllingAxisIndex,
+                                }});
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'yAxis.position',
+                            values: _values.yAxis.position,
+                            onchange() {
+                                chart.setOption({yAxis: {
+                                    position: this.value,
+                                    id: _controllingAxisIndex,
+                                }});
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'axis.name fontSize',
+                            values: _values.allAxis.nameTextStyle.fontSize,
+                            onchange() {
+                                chart.setOption({
+                                    xAxis: {
+                                        nameTextStyle: {fontSize: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        nameTextStyle: {fontSize: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'nameLocation',
+                            values: _values.allAxis.nameLocation,
+                            onchange() {
+                                chart.setOption({
+                                    xAxis: {
+                                        nameLocation: this.value,
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        nameLocation: this.value,
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'nameTrancate:',
+                            values: [undefined, 20],
+                            onchange() {
+                                chart.setOption({
+                                    xAxis: {
+                                        nameTruncate: {maxWidth: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        nameTruncate: {maxWidth: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'inverse:',
+                            values: [false, true],
+                            onchange() {
+                                chart.setOption({
+                                    xAxis: {
+                                        inverse: this.value,
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        inverse: this.value,
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'axisLable.inside:',
+                            values: [false, true],
+                            onchange() {
+                                chart.setOption({
+                                    xAxis: {
+                                        axisLabel: {inside: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        axisLabel: {inside: this.value},
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'name rich text:',
+                            values: [false, true],
+                            onchange() {
+                                const richTag = this.value ? 'name_big_1' : 
undefined;
+                                chart.setOption({
+                                    xAxis: {
+                                        name: makeAxisName(
+                                            {xy: 'x', idx: 
_controllingAxisIndex, richTag}
+                                        ),
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        name: makeAxisName(
+                                            {xy: 'y', idx: 
_controllingAxisIndex, richTag}
+                                        ),
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                        {
+                            stateGroup: 'controlling_axis',
+                            type: 'select',
+                            text: 'label rich text:',
+                            values: [false, true],
+                            onchange() {
+                                const formatter = this.value
+                                    ? `{label_big_1|{value}}`
+                                    : null;
+                                chart.setOption({
+                                    xAxis: {
+                                        axisLabel: {formatter},
+                                        id: _controllingAxisIndex,
+                                    },
+                                    yAxis: {
+                                        axisLabel: {formatter},
+                                        id: _controllingAxisIndex,
+                                    },
+                                });
+                            }
+                        },
+                    ]
+                });
+
+                _controllingAxisIndices.forEach(idx => saveState(idx));
+
+                function saveState(controllingAxisIndex) {
+                    if (chart) {
+                        _controlledAxisIndexState[controllingAxisIndex] =
+                            chart.__testHelper.getState('controlling_axis');
+                    }
+                }
+                function restoreState(controllingAxisIndex) {
+                    if (chart) {
+                        const savedState = 
_controlledAxisIndexState[controllingAxisIndex];
+                        if (savedState) {
+                            chart.__testHelper.setState(savedState, 
'controlling_axis');
+                        }
+                    }
+                }
+            });
+
+        </script>
+
+
     </body>
 </html>
\ No newline at end of file


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

Reply via email to