Skip to content

Commit 3b20a94

Browse files
authored
line.pointLabel响应式下沉至组件配置项 (#1065)
1 parent 7332d75 commit 3b20a94

File tree

3 files changed

+133
-18
lines changed

3 files changed

+133
-18
lines changed

__tests__/unit/plots/line/lineLabel-spec.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe('responsive line label', () => {
99
canvasDiv.id = 'canvas1';
1010
document.body.appendChild(canvasDiv);
1111

12-
it.skip('单折线标签布局', () => {
12+
it('单折线标签布局', () => {
1313
const linePlot = new Line(canvasDiv, {
1414
width: 500,
1515
height: 500,
@@ -19,17 +19,9 @@ describe('responsive line label', () => {
1919
yField: 'rate',
2020
label: {
2121
visible: true,
22+
type: 'point',
23+
adjustPosition: true,
2224
},
23-
xAxis: {
24-
type: 'dateTime',
25-
label: {
26-
autoRotate: false,
27-
},
28-
},
29-
tooltip: {
30-
visible: false,
31-
},
32-
responsive: true,
3325
});
3426
linePlot.render();
3527
});
@@ -45,14 +37,14 @@ describe('responsive line label', () => {
4537
seriesField: 'type',
4638
label: {
4739
visible: true,
48-
style: {
49-
offset: 0,
50-
},
40+
type: 'point',
41+
adjustPosition: true,
5142
},
5243
xAxis: {
5344
visible: false,
54-
type: 'dateTime',
55-
autoRotateLabel: false,
45+
label: {
46+
autoRotate: true,
47+
},
5648
},
5749
legend: {
5850
position: 'right-center',

src/components/label/point.ts

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { get, map, isArray, last } from '@antv/util';
1+
import { get, map, isArray, last, each } from '@antv/util';
22
import { Element, MappingDatum, _ORIGIN } from '../../dependents';
33
import BaseLabel, { registerLabelComponent } from '../../components/label/base';
44
import { TextStyle, Label } from '../../interface/config';
5+
import { IShape, Geometry } from '../../dependents';
6+
import { isBBoxIntersect } from '../../util/common';
7+
8+
/**
9+
* 说明:
10+
* 适用于展示面积图和折线图上数据点的label
11+
* */
512

613
export default class PointLabel<L extends Label = Label> extends BaseLabel<L> {
714
protected getDefaultOptions() {
@@ -58,9 +65,114 @@ export default class PointLabel<L extends Label = Label> extends BaseLabel<L> {
5865
return pos;
5966
}
6067

68+
protected layoutLabels(geometry: Geometry, labels: IShape[]): void {
69+
if (!this.options.adjustPosition) {
70+
return;
71+
}
72+
let overlap = this.isOverlapped(labels);
73+
// 规则1:先横向,优先显示横向上变化趋势大的label
74+
if (overlap) {
75+
const tolerance = this.getGlobalTolerance(labels);
76+
each(labels, (label, index) => {
77+
if (index > 1) {
78+
this.labelResamplingByChange(label, labels, index, tolerance);
79+
}
80+
});
81+
}
82+
overlap = this.isOverlapped(labels);
83+
// 规则2: 后纵向,优先保留纵向最高点label
84+
if (overlap) {
85+
each(labels, (label, index) => {
86+
if (label.get('visible')) {
87+
this.clearOverlapping(label, labels, index);
88+
}
89+
});
90+
}
91+
}
92+
6193
protected adjustLabel() {
6294
return;
6395
}
96+
97+
/** 根据变化进行抽样,保留变化较大的点,类似于点简化算法 */
98+
private labelResamplingByChange(label: IShape, labels: IShape[], index: number, tolerance: number) {
99+
const previous = this.findPrevious(index, labels);
100+
const currentCenter = this.getCenter(label);
101+
const previousCenter = this.getCenter(previous);
102+
const distX = previousCenter.x - currentCenter.x;
103+
const distY = previousCenter.y - currentCenter.y;
104+
const dist = Math.sqrt(distX * distX + distY * distY);
105+
if (dist < tolerance) {
106+
label.set('visible', false);
107+
}
108+
}
109+
110+
private clearOverlapping(label: IShape, labels: IShape[], index: number) {
111+
// 找到所有与当前点overlap的node
112+
const overlapped = [];
113+
for (let i = 0; i < labels.length; i++) {
114+
const current = labels[i];
115+
if (i !== index && current.get('visible')) {
116+
const isOverlap = isBBoxIntersect(label.getBBox(), current.getBBox());
117+
if (isOverlap) {
118+
overlapped.push(current);
119+
}
120+
}
121+
}
122+
// 对overapped label进行处理
123+
if (overlapped.length > 0) {
124+
overlapped.push(label);
125+
overlapped.sort((a, b) => {
126+
return b.minY - a.minY;
127+
});
128+
// 隐藏除最高点以外的label
129+
each(overlapped, (label: IShape, index: number) => {
130+
if (index > 0) {
131+
label.set('visible', false);
132+
}
133+
});
134+
}
135+
}
136+
137+
/** 检测一组label中是否存在重叠 **/
138+
private isOverlapped(labels: IShape[]) {
139+
for (let i = 0; i < labels.length; i++) {
140+
if (labels[i].get('visible')) {
141+
const labelABBox = labels[i].getBBox();
142+
for (let j = 0; j < labels.length; j++) {
143+
if (j !== i && labels[j].get('visible')) {
144+
const labelBBBox = labels[j].getBBox();
145+
const intersection = isBBoxIntersect(labelABBox, labelBBBox);
146+
if (intersection) {
147+
return true;
148+
}
149+
}
150+
}
151+
}
152+
}
153+
return false;
154+
}
155+
156+
private getGlobalTolerance(labels: IShape[]) {
157+
const labelsClone = labels.slice();
158+
labelsClone.sort((a, b) => {
159+
return b.getBBox().width - a.getBBox().width;
160+
});
161+
return Math.round(labelsClone[0].getBBox().width);
162+
}
163+
164+
private findPrevious(index: number, labels: IShape[]) {
165+
for (let i = index - 1; i > 0; i--) {
166+
if (labels[i].get('visible')) {
167+
return labels[i];
168+
}
169+
}
170+
}
171+
172+
private getCenter(label: IShape) {
173+
const { minX, maxX, minY, maxY } = label.getBBox();
174+
return { x: minX + (maxX - minX) / 2, y: minY + (maxY - minY) / 2 };
175+
}
64176
}
65177

66178
registerLabelComponent('point', PointLabel);

src/util/common.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { View, Axis, Legend, COMPONENT_TYPE } from '../dependents';
1+
import { View, Axis, Legend, COMPONENT_TYPE, BBox } from '../dependents';
22

33
/**
44
* 判断text是否可用, title description
@@ -77,3 +77,14 @@ export function sortedLastIndex<T>(arr: T[], val: T): number {
7777
}
7878
return i;
7979
}
80+
81+
/* 检测两个label包围盒是否重叠 */
82+
export function isBBoxIntersect(bboxA: BBox, bboxB: BBox) {
83+
if (bboxA.maxY < bboxB.minY || bboxB.maxY < bboxA.minY) {
84+
return false;
85+
}
86+
if (bboxA.maxX < bboxB.minX || bboxB.maxX < bboxA.minX) {
87+
return false;
88+
}
89+
return true;
90+
}

0 commit comments

Comments
 (0)