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]
