You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2025/07/15 09:12:25 UTC
(echarts) 01/07: fix(labelLayout): clarify label layout modification logic.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch fix/label-layout-margin
in repository https://gitbox.apache.org/repos/asf/echarts.git
commit 9b8e3e3003b3c7a899f610ebcb6282e206a6b51c
Author: 100pah <su...@gmail.com>
AuthorDate: Sun Jul 6 18:06:47 2025 +0800
fix(labelLayout): clarify label layout modification logic.
---
src/chart/pie/labelLayout.ts | 4 +-
src/component/axis/AxisBuilder.ts | 4 +-
src/label/LabelManager.ts | 6 +--
src/label/labelLayoutHelper.ts | 106 +++++++++++++++++++++++---------------
src/util/graphic.ts | 4 ++
5 files changed, 75 insertions(+), 49 deletions(-)
diff --git a/src/chart/pie/labelLayout.ts b/src/chart/pie/labelLayout.ts
index 35edb358c..0c9507926 100644
--- a/src/chart/pie/labelLayout.ts
+++ b/src/chart/pie/labelLayout.ts
@@ -27,7 +27,7 @@ import ZRText from 'zrender/src/graphic/Text';
import BoundingRect, {RectLike} from 'zrender/src/core/BoundingRect';
import { each, isNumber } from 'zrender/src/core/util';
import { limitTurnAngle, limitSurfaceAngle } from '../../label/labelGuideHelper';
-import { shiftLayoutOnY } from '../../label/labelLayoutHelper';
+import { shiftLayoutOnXY } from '../../label/labelLayoutHelper';
const RADIAN = Math.PI / 180;
@@ -139,7 +139,7 @@ function adjustSingleSide(
}
}
- if (shiftLayoutOnY(list, viewTop, viewTop + viewHeight)) {
+ if (shiftLayoutOnXY(list, 1, viewTop, viewTop + viewHeight)) {
recalculateX(list);
}
}
diff --git a/src/component/axis/AxisBuilder.ts b/src/component/axis/AxisBuilder.ts
index f5fa72125..7e60b89a6 100644
--- a/src/component/axis/AxisBuilder.ts
+++ b/src/component/axis/AxisBuilder.ts
@@ -62,6 +62,7 @@ import {
rollbackToLabelLayoutInfoRaw,
LABEL_LAYOUT_INFO_KIND_COMPUTED,
createSingleLayoutInfoComputed,
+ applyTranslationOnLabelLayoutInfo,
} from '../../label/labelLayoutHelper';
import ExtensionAPI from '../../core/ExtensionAPI';
import { makeInner } from '../../util/model';
@@ -406,8 +407,7 @@ function moveIfOverlap(
bidirectional: false,
touchThreshold: 0.05,
})) {
- Point.add(movableLayoutInfo.label, movableLayoutInfo.label, mtv);
- ensureLabelLayoutInfoComputed(rollbackToLabelLayoutInfoRaw(movableLayoutInfo));
+ applyTranslationOnLabelLayoutInfo(movableLayoutInfo, mtv);
}
}
diff --git a/src/label/LabelManager.ts b/src/label/LabelManager.ts
index 2f262c4eb..b69d77e16 100644
--- a/src/label/LabelManager.ts
+++ b/src/label/LabelManager.ts
@@ -52,7 +52,7 @@ import { retrieve2, each, keys, isFunction, filter, indexOf } from 'zrender/src/
import { PathStyleProps } from 'zrender/src/graphic/Path';
import Model from '../model/Model';
import {
- hideOverlap, shiftLayoutOnX, shiftLayoutOnY,
+ hideOverlap, shiftLayoutOnXY,
createLabelLayoutList,
LabelLayoutInfoRaw,
LABEL_LAYOUT_INFO_KIND_RAW,
@@ -443,8 +443,8 @@ class LabelManager {
return item.layoutOption.moveOverlap === 'shiftY';
});
- shiftLayoutOnX(labelsNeedsAdjustOnX, 0, width);
- shiftLayoutOnY(labelsNeedsAdjustOnY, 0, height);
+ shiftLayoutOnXY(labelsNeedsAdjustOnX, 0, 0, width);
+ shiftLayoutOnXY(labelsNeedsAdjustOnY, 1, 0, height);
const labelsNeedsHideOverlap = filter(labelList, function (item) {
return item.layoutOption.hideOverlap;
diff --git a/src/label/labelLayoutHelper.ts b/src/label/labelLayoutHelper.ts
index 66d07d20d..b665a4815 100644
--- a/src/label/labelLayoutHelper.ts
+++ b/src/label/labelLayoutHelper.ts
@@ -20,7 +20,7 @@
import ZRText from 'zrender/src/graphic/Text';
import { LabelExtendedTextStyle, LabelLayoutOption, LabelMarginType, NullUndefined } from '../util/types';
import {
- BoundingRect, OrientedBoundingRect, Polyline, expandOrShrinkRect,
+ BoundingRect, OrientedBoundingRect, Polyline, WH, XY, expandOrShrinkRect,
isBoundingRectAxisAligned
} from '../util/graphic';
import type Element from 'zrender/src/Element';
@@ -157,7 +157,38 @@ function prepareLabelLayoutInfo(
}
/**
- * The reverse operation of `ensureLabelLayoutInfoComputedv`.
+ * This is a shortcut of
+ * ```js
+ * layoutInfo.label.x = newX;
+ * layoutInfo.label.y = newY;
+ * ensureLabelLayoutInfoComputed(rollbackToLabelLayoutInfoRaw(layoutInfo));
+ * ```
+ * and provide better performance in this common case.
+ */
+export function applyTranslationOnLabelLayoutInfo(
+ layoutInfo: LabelLayoutInfoAll,
+ offset: PointLike, // [offsetX, offsetY]
+): void {
+ layoutInfo.label.x += offset.x;
+ layoutInfo.label.y += offset.y;
+ if (layoutInfo.kind === LABEL_LAYOUT_INFO_KIND_COMPUTED) {
+ const transform = layoutInfo.transform;
+ if (transform) {
+ transform[4] += offset.x;
+ transform[5] += offset.y;
+ }
+ const globalRect = layoutInfo.rect;
+ globalRect.x += offset.x;
+ globalRect.y += offset.y;
+ const obb = layoutInfo.obb;
+ if (obb) {
+ obb.fromBoundingRect(layoutInfo.localRect, transform);
+ }
+ }
+}
+
+/**
+ * The reverse operation of `ensureLabelLayoutInfoComputed`.
*/
export function rollbackToLabelLayoutInfoRaw(
labelLayoutInfo: LabelLayoutInfoAll
@@ -177,7 +208,8 @@ export function rollbackToLabelLayoutInfoRaw(
* [CAUTION]
* - If the raw label is changed, must call
* `ensureLabelLayoutInfoComputed(rollbackToLabelLayoutInfoRaw(layoutInfo))`
- * to recreate the layout info.
+ * to recreate the layout info,
+ * or call `applyTranslationOnLabelLayoutInfo(layoutInfo)` is only translation is performed.
* - Null checking is needed for the result. @see prepareLabelLayoutInfo
*
* Usage:
@@ -245,18 +277,37 @@ export function applyTextMarginToLocalRect(
return localRect;
}
-function shiftLayout(
+/**
+ * Adjust labels on x/y direction to avoid overlap.
+ *
+ * PENDING: the current implementation is based on the global bounding rect rather than the local rect,
+ * which may be not preferable in some edge cases when the label has rotation, but works for most cases,
+ * since rotation is unnecessary when there is sufficient space, while squeezing is applied regardless
+ * of overlapping when there is no enough space.
+ *
+ * NOTICE:
+ * The input `list` and its content will be modified (sort, label.x/y, rect).
+ * The caller should sync the modifications to the other parts by `rollbackToLabelLayoutInfoRaw` if needed.
+ *
+ * @return adjusted
+ */
+export function shiftLayoutOnXY(
list: Pick<LabelLayoutInfoComputed, 'rect' | 'label'>[],
- xyDim: 'x' | 'y',
- sizeDim: 'width' | 'height',
- minBound: number,
- maxBound: number,
- balanceShift: boolean
-) {
+ xyDimIdx: 0 | 1, // 0 for x, 1 for y
+ minBound: number, // for x, leftBound; for y, topBound
+ maxBound: number, // for x, rightBound; for y, bottomBound
+ // If average the shifts on all labels and add them to 0
+ // TODO: Not sure if should enable it.
+ // Pros: The angle of lines will distribute more equally
+ // Cons: In some layout. It may not what user wanted. like in pie. the label of last sector is usually changed unexpectedly.
+ balanceShift?: boolean
+): boolean {
const len = list.length;
+ const xyDim = XY[xyDimIdx];
+ const sizeDim = WH[xyDimIdx];
if (len < 2) {
- return;
+ return false;
}
list.sort(function (a, b) {
@@ -267,7 +318,7 @@ function shiftLayout(
let delta;
let adjusted = false;
- const shifts = [];
+ // const shifts = [];
let totalShifts = 0;
for (let i = 0; i < len; i++) {
const item = list[i];
@@ -280,7 +331,7 @@ function shiftLayout(
adjusted = true;
}
const shift = Math.max(-delta, 0);
- shifts.push(shift);
+ // shifts.push(shift);
totalShifts += shift;
lastPos = rect[xyDim] + rect[sizeDim];
@@ -412,35 +463,6 @@ function shiftLayout(
return adjusted;
}
-/**
- * Adjust labels on x direction to avoid overlap.
- */
-export function shiftLayoutOnX(
- list: Pick<LabelLayoutInfoComputed, 'rect' | 'label'>[],
- leftBound: number,
- rightBound: number,
- // If average the shifts on all labels and add them to 0
- // TODO: Not sure if should enable it.
- // Pros: The angle of lines will distribute more equally
- // Cons: In some layout. It may not what user wanted. like in pie. the label of last sector is usually changed unexpectedly.
- balanceShift?: boolean
-): boolean {
- return shiftLayout(list, 'x', 'width', leftBound, rightBound, balanceShift);
-}
-
-/**
- * Adjust labels on y direction to avoid overlap.
- */
-export function shiftLayoutOnY(
- list: Pick<LabelLayoutInfoComputed, 'rect' | 'label'>[],
- topBound: number,
- bottomBound: number,
- // If average the shifts on all labels and add them to 0
- balanceShift?: boolean
-): boolean {
- return shiftLayout(list, 'y', 'height', topBound, bottomBound, balanceShift);
-}
-
/**
* [CAUTION]: the `label.ignore` in the input is not necessarily falsy.
* this method checks intersection regardless of current `ignore`,
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index 259d3a5d8..e863e713f 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -68,6 +68,7 @@ import {
isArray,
isNumber,
clone,
+ assert,
} from 'zrender/src/core/util';
import { getECData } from './innerStore';
import ComponentModel from '../model/Component';
@@ -607,6 +608,9 @@ export function expandOrShrinkRect<TRect extends RectLike>(
_tmpExpandRectDelta[0] = _tmpExpandRectDelta[1] = _tmpExpandRectDelta[2] = _tmpExpandRectDelta[3] = delta;
}
else {
+ if (__DEV__) {
+ assert(delta.length === 4);
+ }
_tmpExpandRectDelta[0] = delta[0];
_tmpExpandRectDelta[1] = delta[1];
_tmpExpandRectDelta[2] = delta[2];
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org