Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 75 additions & 3 deletions src/label/LabelManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,9 +444,11 @@ class LabelManager {
const height = api.getHeight();

const labelList: LabelLayoutWithGeometry[] = [];

each(this._labelList, inputItem => {
if (!inputItem.defaultAttr.ignore) {
labelList.push(newLabelLayoutWithGeometry({}, inputItem));
const labelLayout = newLabelLayoutWithGeometry({}, inputItem);
labelList.push(labelLayout);
}
});

Expand All @@ -457,8 +459,78 @@ class LabelManager {
return item.layoutOption.moveOverlap === 'shiftY';
});

shiftLayoutOnXY(labelsNeedsAdjustOnX, 0, 0, width);
shiftLayoutOnXY(labelsNeedsAdjustOnY, 1, 0, height);
// Group labels by grid (not by series) so that labels of multiple series
// in the same grid avoid overlapping each other.
const labelsByGridX: Dictionary<LabelLayoutWithGeometry[]> = {};
const labelsByGridY: Dictionary<LabelLayoutWithGeometry[]> = {};

function getGridName(inputItem: LabelLayoutWithGeometry): string {
const seriesMaster = inputItem?.seriesModel?.coordinateSystem?.master as any;
return seriesMaster?.name || 'grid_default';
}

each(labelsNeedsAdjustOnX, function (item) {
const seriesModel = item.seriesModel;
if (seriesModel) {
const key = getGridName(item);
if (!labelsByGridX[key]) {
labelsByGridX[key] = [];
}
labelsByGridX[key].push(item);
}
});

each(labelsNeedsAdjustOnY, function (item) {
const seriesModel = item.seriesModel;
if (seriesModel) {
const key = getGridName(item);
if (!labelsByGridY[key]) {
labelsByGridY[key] = [];
}
labelsByGridY[key].push(item);
}
});

// Adjust labels for each grid with its bounds
each(labelsByGridX, function (gridLabels) {
const seriesModel = gridLabels[0].seriesModel;
if (!seriesModel) {
return;
}
const coordSys = seriesModel.coordinateSystem;
let minBound = 0;
let maxBound = width;

if (coordSys && coordSys.master && (coordSys.master as any).getRect) {
const gridRect = (coordSys.master as any).getRect();
if (gridRect) {
minBound = gridRect.x;
maxBound = gridRect.x + gridRect.width;
}
}

shiftLayoutOnXY(gridLabels, 0, minBound, maxBound);
});

each(labelsByGridY, function (gridLabels) {
const seriesModel = gridLabels[0].seriesModel;
if (!seriesModel) {
return;
}
const coordSys = seriesModel.coordinateSystem;
let minBound = 0;
let maxBound = height;

if (coordSys && coordSys.master && (coordSys.master as any).getRect) {
const gridRect = (coordSys.master as any).getRect();
if (gridRect) {
minBound = gridRect.y;
maxBound = gridRect.y + gridRect.height;
}
}

shiftLayoutOnXY(gridLabels, 1, minBound, maxBound);
});

const labelsNeedsHideOverlap = filter(labelList, function (item) {
return item.layoutOption.hideOverlap;
Expand Down
83 changes: 67 additions & 16 deletions src/label/labelLayoutHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import ZRText from 'zrender/src/graphic/Text';
import { LabelLayoutOption, NullUndefined } from '../util/types';
import SeriesModel from '../model/Series';
import {
BoundingRect, OrientedBoundingRect, Polyline, WH, XY, ensureCopyRect, ensureCopyTransform, expandOrShrinkRect,
isBoundingRectAxisAligned
Expand All @@ -37,6 +38,8 @@ interface LabelLayoutBase {
labelLine?: Polyline | NullUndefined
layoutOption?: LabelLayoutOption | NullUndefined
priority: number
// Keep series model reference for later grouping without extra maps.
seriesModel?: SeriesModel
// @see `SavedLabelAttr` in `LabelManager.ts`
defaultAttr: {
ignore?: boolean
Expand Down Expand Up @@ -65,7 +68,7 @@ interface LabelLayoutBase {
suggestIgnore?: boolean;
}
const LABEL_LAYOUT_BASE_PROPS = [
'label', 'labelLine', 'layoutOption', 'priority', 'defaultAttr',
'label', 'labelLine', 'layoutOption', 'priority', 'seriesModel', 'defaultAttr',
'marginForce', 'minMarginForce', 'marginDefault', 'suggestIgnore'
] as const;

Expand Down Expand Up @@ -335,6 +338,8 @@ export function shiftLayoutOnXY(
const len = list.length;
const xyDim = XY[xyDimIdx];
const sizeDim = WH[xyDimIdx];
const otherDim = XY[xyDimIdx ? 0 : 1];
const otherSizeDim = WH[xyDimIdx ? 0 : 1];

if (len < 2) {
return false;
Expand All @@ -344,27 +349,58 @@ export function shiftLayoutOnXY(
return a.rect[xyDim] - b.rect[xyDim];
});

let lastPos = 0;
let delta;
let adjusted = false;

// const shifts = [];
// 仅在跨两个维度都重叠时才认为需要沿当前维度分散。
// 使用扫线维护同一维度重叠的活动集合,避免宽度(或高度)无交集时误偏移。
const active: {
end: number
otherStart: number
otherEnd: number
}[] = [];
let totalShifts = 0;
for (let i = 0; i < len; i++) {
const item = list[i];
const rect = item.rect;
delta = rect[xyDim] - lastPos;
const start = rect[xyDim];
const otherStart = rect[otherDim];
const otherEnd = otherStart + rect[otherSizeDim];

// 移除在当前维度上已无交集的活动项
for (let j = active.length - 1; j >= 0; j--) {
if (active[j].end <= start) {
active.splice(j, 1);
}
}

// 找到与当前矩形在另一维度也有交集的最大尾部位置
let maxOverlapEnd = start;
for (let j = 0; j < active.length; j++) {
const act = active[j];
const overlapOther = otherStart < act.otherEnd && otherEnd > act.otherStart;
if (overlapOther) {
if (act.end > maxOverlapEnd) {
maxOverlapEnd = act.end;
}
}
}

const delta = start - maxOverlapEnd;
if (delta < 0) {
// shiftForward(i, len, -delta);
rect[xyDim] -= delta;
item.label[xyDim] -= delta;
const shift = -delta;
rect[xyDim] += shift;
item.label[xyDim] += shift;
totalShifts += shift;
adjusted = true;
}
const shift = Math.max(-delta, 0);
// shifts.push(shift);
totalShifts += shift;

lastPos = rect[xyDim] + rect[sizeDim];
// 将(可能已移动的)当前矩形加入活动集
const newStart = rect[xyDim];
active.push({
end: newStart + rect[sizeDim],
otherStart,
otherEnd
});
}
if (totalShifts > 0 && balanceShift) {
// Shift back to make the distribution more equally.
Expand Down Expand Up @@ -434,10 +470,25 @@ export function shiftLayoutOnXY(
const gaps: number[] = [];
let totalGaps = 0;
for (let i = 1; i < len; i++) {
const prevItemRect = list[i - 1].rect;
const gap = Math.max(list[i].rect[xyDim] - prevItemRect[xyDim] - prevItemRect[sizeDim], 0);
gaps.push(gap);
totalGaps += gap;
const currRect = list[i].rect;
let minGap = Infinity;
for (let j = 0; j < i; j++) {
const prevRect = list[j].rect;
const overlapOther = currRect[otherDim] < prevRect[otherDim] + prevRect[otherSizeDim]
&& currRect[otherDim] + currRect[otherSizeDim] > prevRect[otherDim];
if (overlapOther) {
const gap = currRect[xyDim] - (prevRect[xyDim] + prevRect[sizeDim]);
if (gap < minGap) {
minGap = gap;
}
}
}
if (minGap === Infinity) {
minGap = 0;
}
minGap = Math.max(minGap, 0);
gaps.push(minGap);
totalGaps += minGap;
}
if (!totalGaps) {
return;
Expand Down
Loading