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 983e4ccbc2f6d6aac1ae70ea175a1012bd313e73 Author: 100pah <[email protected]> AuthorDate: Tue Jun 24 11:00:38 2025 +0800 feat(grid layout): (1) introduce outerBoundsMode and make outerBounds pure object. (2) fix name overlap cache bug. (3) make outerBounds merge as box layout merge. --- src/component/axis/AxisBuilder.ts | 15 ++++++------- src/coord/cartesian/Grid.ts | 42 +++++++++++++++++-------------------- src/coord/cartesian/GridModel.ts | 44 +++++++++++++++++++++++++++++++-------- src/label/labelLayoutHelper.ts | 30 +++++++++++++------------- src/util/layout.ts | 10 +++------ test/axis-layout-0.html | 18 +++++++--------- 6 files changed, 87 insertions(+), 72 deletions(-) diff --git a/src/component/axis/AxisBuilder.ts b/src/component/axis/AxisBuilder.ts index 40e0e4da9..23fc61a90 100644 --- a/src/component/axis/AxisBuilder.ts +++ b/src/component/axis/AxisBuilder.ts @@ -376,7 +376,7 @@ export const resolveAxisNameOverlapDefault: AxisBuilderSharedContext['resolveAxi function moveIfOverlap( basedLayoutInfo: LabelIntersectionCheckInfo, - movableLayoutInfo: LabelIntersectionCheckInfo & Pick<LabelLayoutInfoComputed, 'label'>, + movableLayoutInfo: LabelLayoutInfoComputed, moveDirVec: Point ): void { const mtv = new Point(); @@ -386,13 +386,14 @@ function moveIfOverlap( touchThreshold: 0.05, })) { Point.add(movableLayoutInfo.label, movableLayoutInfo.label, mtv); + ensureLabelLayoutInfoComputed(rollbackToLabelLayoutInfoRaw(movableLayoutInfo)); } } export function moveIfOverlapByLinearLabels( baseLayoutInfoList: (LabelIntersectionCheckInfo)[], baseDirVec: Point, - movableLayoutInfo: (LabelIntersectionCheckInfo & Pick<LabelLayoutInfoComputed, 'label'>), + movableLayoutInfo: LabelLayoutInfoComputed, moveDirVec: Point, ): void { // Detect and move from far to close. @@ -1040,11 +1041,11 @@ function fixMinMaxLabelShow( if (showMinMaxLabel === false) { ignoreEl(outmostLabelLayout.label); } - // If `optionHideOverlap === false`, do not hide anything. - // e.g., in category axis, hide some label is not reasonable. - // And currently the bounding rect of text might not accurate enough, - // might slightly bigger, which causes false positive. - else if (optionHideOverlap !== false) { + // PENDING: Originally we thougth `optionHideOverlap === false` means do not hide anything, + // since currently the bounding rect of text might not accurate enough and might slightly bigger, + // which causes false positive. But `optionHideOverlap: null/undfined` is falsy and likely + // be treated as false. + else { // In most fonts the glyph does not reach the boundary of the bouding rect. // This is needed to avoid too aggressive to hide two elements that meet at the edge // due to compact layout by the same bounding rect or OBB. diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts index f11e6bc8d..321b7a26e 100644 --- a/src/coord/cartesian/Grid.ts +++ b/src/coord/cartesian/Grid.ts @@ -38,7 +38,7 @@ import Axis2D from './Axis2D'; import {ParsedModelFinder, ParsedModelFinderKnown, SINGLE_REFERRING} from '../../util/model'; // Depends on GridModel, AxisModel, which performs preprocess. -import GridModel from './GridModel'; +import GridModel, { OUTER_BOUNDS_DEFAULT } from './GridModel'; import CartesianAxisModel from './AxisModel'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; @@ -222,10 +222,10 @@ class Grid implements CoordinateSystemMaster { let noPxChange: boolean; if (optionContainLabel) { if (legacyLayOutGridByContainLabel) { - console.time('legacyLayOutGridByContainLabel'); + // console.time('legacyLayOutGridByContainLabel'); legacyLayOutGridByContainLabel(this._axesList, gridRect); updateAllAxisExtentTransByGridRect(axesMap, gridRect); - console.timeEnd('legacyLayOutGridByContainLabel'); + // console.timeEnd('legacyLayOutGridByContainLabel'); } else { if (__DEV__) { @@ -240,13 +240,13 @@ class Grid implements CoordinateSystemMaster { else { const {outerBoundsRect, parsedOuterBoundsContain} = prepareOuterBounds(gridModel, gridRect, api); if (outerBoundsRect) { - console.time('layOutGridByOuterBounds'); + // console.time('layOutGridByOuterBounds'); noPxChange = layOutGridByOuterBounds(outerBoundsRect, parsedOuterBoundsContain, gridRect, axesMap); - console.timeEnd('layOutGridByOuterBounds'); + // console.timeEnd('layOutGridByOuterBounds'); } } - console.time('buildAxesView_determine'); + // console.time('buildAxesView_determine'); createOrUpdateAxesView( gridRect, axesMap, @@ -254,7 +254,7 @@ class Grid implements CoordinateSystemMaster { null, noPxChange ); - console.timeEnd('buildAxesView_determine'); + // console.timeEnd('buildAxesView_determine'); } // End of beforeDataProcessing each(this._coordsList, function (coord) { @@ -848,25 +848,21 @@ function prepareOuterBounds( outerBoundsRect: BoundingRect | NullUndefined parsedOuterBoundsContain: ParsedOuterBoundsContain } { - let optionOuterBounds = gridModel.get('outerBounds', true); let outerBoundsRect: BoundingRect | NullUndefined; - if (optionOuterBounds !== false) { - if (optionOuterBounds == null || optionOuterBounds === true || optionOuterBounds === 'auto') { - optionOuterBounds = {left: 5, right: 5, top: 5, bottom: 5}; - } - if (optionOuterBounds === 'same') { - outerBoundsRect = gridRect.clone(); - } - else if (isObject(optionOuterBounds)) { - const refContainer = {width: api.getWidth(), height: api.getHeight()}; - outerBoundsRect = getLayoutRect(optionOuterBounds, refContainer); - } - else { - if (__DEV__) { - error(`Invalid grid[${gridModel.componentIndex}].outerBounds.`); - } + const optionOuterBoundsMode = gridModel.get('outerBoundsMode', true); + if (optionOuterBoundsMode === 'same') { + outerBoundsRect = gridRect.clone(); + } + else if (optionOuterBoundsMode == null || optionOuterBoundsMode === 'auto') { + const refContainer = {width: api.getWidth(), height: api.getHeight()}; + outerBoundsRect = getLayoutRect(gridModel.get('outerBounds', true) || OUTER_BOUNDS_DEFAULT, refContainer); + } + else if (optionOuterBoundsMode !== 'none') { + if (__DEV__) { + error(`Invalid grid[${gridModel.componentIndex}].outerBoundsMode.`); } } + const optionOuterBoundsContain = gridModel.get('outerBoundsContain', true); let parsedOuterBoundsContain: ParsedOuterBoundsContain; if (optionOuterBoundsContain == null || optionOuterBoundsContain === 'auto') { diff --git a/src/coord/cartesian/GridModel.ts b/src/coord/cartesian/GridModel.ts index f76274507..541ffe29d 100644 --- a/src/coord/cartesian/GridModel.ts +++ b/src/coord/cartesian/GridModel.ts @@ -22,6 +22,10 @@ import ComponentModel from '../../model/Component'; import { ComponentOption, BoxLayoutOptionMixin, ZRColor, ShadowOptionMixin, NullUndefined } from '../../util/types'; import Grid from './Grid'; import { CoordinateSystemHostModel } from '../CoordinateSystem'; +import type GlobalModel from '../../model/Global'; +import { getLayoutParams, mergeLayoutParam } from '../../util/layout'; + +export const OUTER_BOUNDS_DEFAULT = {left: 5, right: 5, top: 5, bottom: 5}; export interface GridOption extends ComponentOption, BoxLayoutOptionMixin, ShadowOptionMixin { mainType?: 'grid'; @@ -41,18 +45,21 @@ export interface GridOption extends ComponentOption, BoxLayoutOptionMixin, Shado * But if axisLabel and/or axisName overflow the outerBounds, shrink the layout to avoid that overflow. * * Options: - * - `false`: outerBounds is infinity. + * - 'none': outerBounds is infinity. * - 'same': outerBounds is the same as the layout rect defined by `grid.left/right/top/bottom/width/height`. - * - {left, right, top, bottom, width, height}: Define a outerBounds rect, based on: - * - the canvas by default. - * - or the `dataToLayout` result if a `boxCoordinateSystem` is specified. - * - 'auto'/null/undefined/true: Default. be 'same' if `containLabel:true`, - * otherwises `{left: 5, right: 5, top: 5, bottom: 5}`. + * - 'auto'/null/undefined: Default. Use `outerBounds`, or 'same' if `containLabel:true`. * * Note: - * `grid.containLabel` is equivalent to `{outerBounds: 'same', outerBoundsContain: 'axisLabel'}`. + * `grid.containLabel` is equivalent to `{outerBoundsMode: 'same', outerBoundsContain: 'axisLabel'}`. + */ + outerBoundsMode?: 'auto' | NullUndefined | 'same' | 'none'; + /** + * {left, right, top, bottom, width, height}: Define a outerBounds rect, based on: + * - the canvas by default. + * - or the `dataToLayout` result if a `boxCoordinateSystem` is specified. + * By default {left: 5, right: 5, top: 5, bottom: 5}. */ - outerBounds?: boolean | 'same' | 'auto' | BoxLayoutOptionMixin | NullUndefined; + outerBounds?: BoxLayoutOptionMixin; /** * - 'all': Default. Contains the cartesian rect and axis labels and axis name. * - 'axisLabel': Contains the cartesian rect and axis labels. This effect differs slightly from the @@ -78,6 +85,24 @@ class GridModel extends ComponentModel<GridOption> implements CoordinateSystemHo coordinateSystem: Grid; + mergeDefaultAndTheme(option: GridOption, ecModel: GlobalModel): void { + const outerBoundsCp = getLayoutParams(option.outerBounds); + + super.mergeDefaultAndTheme.apply(this, arguments as any); + + if (outerBoundsCp && option.outerBounds) { + mergeLayoutParam(option.outerBounds, outerBoundsCp); + } + } + + mergeOption(newOption: GridOption, ecModel: GlobalModel) { + super.mergeOption.apply(this, arguments as any); + + if (this.option.outerBounds && newOption.outerBounds) { + mergeLayoutParam(this.option.outerBounds, newOption.outerBounds); + } + } + static defaultOption: GridOption = { show: false, // zlevel: 0, @@ -88,7 +113,8 @@ class GridModel extends ComponentModel<GridOption> implements CoordinateSystemHo bottom: 70, containLabel: false, - outerBounds: 'auto', + outerBoundsMode: 'auto', + outerBounds: OUTER_BOUNDS_DEFAULT, outerBoundsContain: 'all', // width: {totalWidth} - left - right, diff --git a/src/label/labelLayoutHelper.ts b/src/label/labelLayoutHelper.ts index 1893730bf..6c9001c7a 100644 --- a/src/label/labelLayoutHelper.ts +++ b/src/label/labelLayoutHelper.ts @@ -90,6 +90,21 @@ export interface LabelIntersectionCheckInfo { transform: number[] | NullUndefined } +export function createLabelLayoutList( + rawList: LabelLayoutInfoRaw[] +): LabelLayoutInfoComputed[] { + const resultList: LabelLayoutInfoComputed[] = []; + each(rawList, raw => { + // PENDING: necessary? + raw = extend({}, raw); + const layoutInfo = prepareLabelLayoutInfo(raw); + if (layoutInfo) { + resultList.push(layoutInfo); + } + }); + return resultList; +} + /** * If `defaultAttr.ignore: true`, return `NullUndefined`. * (the caller is reponsible for ensuring the label is always `ignore: true`.) @@ -155,21 +170,6 @@ export function rollbackToLabelLayoutInfoRaw( return raw; } -export function createLabelLayoutList( - rawList: LabelLayoutInfoRaw[] -): LabelLayoutInfoComputed[] { - const resultList: LabelLayoutInfoComputed[] = []; - each(rawList, raw => { - // PENDING: necessary? - raw = extend({}, raw); - const layoutInfo = prepareLabelLayoutInfo(raw); - if (layoutInfo) { - resultList.push(layoutInfo); - } - }); - return resultList; -} - /** * This method supports that the label layout info is not computed until needed, * for performance consideration. diff --git a/src/util/layout.ts b/src/util/layout.ts index dcf2f7762..698038b07 100644 --- a/src/util/layout.ts +++ b/src/util/layout.ts @@ -549,7 +549,7 @@ export function mergeLayoutParam<T extends BoxLayoutOptionMixin>( each(names, function (name: BoxLayoutKeys) { // Consider case: newOption.width is null, which is // set by user for removing width setting. - hasProp(newOption, name) && (newParams[name] = merged[name] = newOption[name]); + zrUtil.hasOwn(newOption, name) && (newParams[name] = merged[name] = newOption[name]); hasValue(newParams, name) && newValueCount++; hasValue(merged, name) && mergedValueCount++; }); @@ -582,7 +582,7 @@ export function mergeLayoutParam<T extends BoxLayoutOptionMixin>( // Chose another param from targetOption by priority. for (let i = 0; i < names.length; i++) { const name = names[i]; - if (!hasProp(newParams, name) && hasProp(targetOption, name)) { + if (!zrUtil.hasOwn(newParams, name) && zrUtil.hasOwn(targetOption, name)) { newParams[name] = targetOption[name]; break; } @@ -591,10 +591,6 @@ export function mergeLayoutParam<T extends BoxLayoutOptionMixin>( } } - function hasProp(obj: object, name: string): boolean { - return obj.hasOwnProperty(name); - } - function hasValue(obj: Dictionary<any>, name: string): boolean { return obj[name] != null && obj[name] !== 'auto'; } @@ -620,7 +616,7 @@ export function getLayoutParams(source: BoxLayoutOptionMixin): BoxLayoutOptionMi */ export function copyLayoutParams(target: BoxLayoutOptionMixin, source: BoxLayoutOptionMixin): BoxLayoutOptionMixin { source && target && each(LOCATION_PARAMS, function (name: BoxLayoutKeys) { - source.hasOwnProperty(name) && (target[name] = source[name]); + zrUtil.hasOwn(source, name) && (target[name] = source[name]); }); return target; } diff --git a/test/axis-layout-0.html b/test/axis-layout-0.html index b3051e783..ea320e699 100755 --- a/test/axis-layout-0.html +++ b/test/axis-layout-0.html @@ -58,7 +58,7 @@ under the License. let _useLazyUpdate; let _currNameTextMargin; let _currAxisLabelTextMargin; - let _currOuterBounds; + let _currOuterBoundsMode; let _currXAxisType; let _currYAxisType; let _currContainLabel; @@ -68,7 +68,7 @@ under the License. _useLazyUpdate = false; _currNameTextMargin = [3, 5, 3, 5]; _currAxisLabelTextMargin = [0, 3, 0, 3]; - _currOuterBounds = undefined; + _currOuterBoundsMode = undefined; _currXAxisType = 'value'; _currYAxisType = 'log'; _currContainLabel = false; @@ -146,7 +146,7 @@ under the License. } function updateIndicatorShapeOptions() { - let parsedOuterBounds = _currOuterBounds; + let parsedOuterBounds = _currOuterBoundsMode; if (parsedOuterBounds == null || parsedOuterBounds === true || parsedOuterBounds === 'auto') { if (_currContainLabel) { parsedOuterBounds = 'same'; @@ -564,13 +564,13 @@ under the License. }, { type: 'select', - text: 'grid.outerBounds:', - values: [undefined, {}, 'same', false, true, 'auto'], + text: 'grid.outerBoundsMode:', + values: [undefined, 'same', 'none', 'auto'], onchange() { - _currOuterBounds = this.value; + _currOuterBoundsMode = this.value; updateIndicatorShapeOptions(); chartSetOption(chart, { - grid: {outerBounds: _currOuterBounds}, + grid: {outerBoundsMode: _currOuterBoundsMode}, graphic: { elements: [ makeGridBoxIndicatorOption(), @@ -587,10 +587,6 @@ under the License. max: 500, value: _initValues.grid[prop], onchange() { - if (testHelper.getType(_currOuterBounds) !== 'object') { - console.error('should set grid.outerBounds to object first'); - return; - } const newVal = this.value; _gridOuterBoundsIndicatorShapeOptions[prop] = newVal; updateIndicatorShapeOptions(); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
