Skip to content

Commit

Permalink
line.pointLabel响应式下沉至组件配置项 (#1065)
Browse files Browse the repository at this point in the history
  • Loading branch information
paleface001 authored May 18, 2020
1 parent 7332d75 commit 3b20a94
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 18 deletions.
24 changes: 8 additions & 16 deletions __tests__/unit/plots/line/lineLabel-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('responsive line label', () => {
canvasDiv.id = 'canvas1';
document.body.appendChild(canvasDiv);

it.skip('单折线标签布局', () => {
it('单折线标签布局', () => {
const linePlot = new Line(canvasDiv, {
width: 500,
height: 500,
Expand All @@ -19,17 +19,9 @@ describe('responsive line label', () => {
yField: 'rate',
label: {
visible: true,
type: 'point',
adjustPosition: true,
},
xAxis: {
type: 'dateTime',
label: {
autoRotate: false,
},
},
tooltip: {
visible: false,
},
responsive: true,
});
linePlot.render();
});
Expand All @@ -45,14 +37,14 @@ describe('responsive line label', () => {
seriesField: 'type',
label: {
visible: true,
style: {
offset: 0,
},
type: 'point',
adjustPosition: true,
},
xAxis: {
visible: false,
type: 'dateTime',
autoRotateLabel: false,
label: {
autoRotate: true,
},
},
legend: {
position: 'right-center',
Expand Down
114 changes: 113 additions & 1 deletion src/components/label/point.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { get, map, isArray, last } from '@antv/util';
import { get, map, isArray, last, each } from '@antv/util';
import { Element, MappingDatum, _ORIGIN } from '../../dependents';
import BaseLabel, { registerLabelComponent } from '../../components/label/base';
import { TextStyle, Label } from '../../interface/config';
import { IShape, Geometry } from '../../dependents';
import { isBBoxIntersect } from '../../util/common';

/**
* 说明:
* 适用于展示面积图和折线图上数据点的label
* */

export default class PointLabel<L extends Label = Label> extends BaseLabel<L> {
protected getDefaultOptions() {
Expand Down Expand Up @@ -58,9 +65,114 @@ export default class PointLabel<L extends Label = Label> extends BaseLabel<L> {
return pos;
}

protected layoutLabels(geometry: Geometry, labels: IShape[]): void {
if (!this.options.adjustPosition) {
return;
}
let overlap = this.isOverlapped(labels);
// 规则1:先横向,优先显示横向上变化趋势大的label
if (overlap) {
const tolerance = this.getGlobalTolerance(labels);
each(labels, (label, index) => {
if (index > 1) {
this.labelResamplingByChange(label, labels, index, tolerance);
}
});
}
overlap = this.isOverlapped(labels);
// 规则2: 后纵向,优先保留纵向最高点label
if (overlap) {
each(labels, (label, index) => {
if (label.get('visible')) {
this.clearOverlapping(label, labels, index);
}
});
}
}

protected adjustLabel() {
return;
}

/** 根据变化进行抽样,保留变化较大的点,类似于点简化算法 */
private labelResamplingByChange(label: IShape, labels: IShape[], index: number, tolerance: number) {
const previous = this.findPrevious(index, labels);
const currentCenter = this.getCenter(label);
const previousCenter = this.getCenter(previous);
const distX = previousCenter.x - currentCenter.x;
const distY = previousCenter.y - currentCenter.y;
const dist = Math.sqrt(distX * distX + distY * distY);
if (dist < tolerance) {
label.set('visible', false);
}
}

private clearOverlapping(label: IShape, labels: IShape[], index: number) {
// 找到所有与当前点overlap的node
const overlapped = [];
for (let i = 0; i < labels.length; i++) {
const current = labels[i];
if (i !== index && current.get('visible')) {
const isOverlap = isBBoxIntersect(label.getBBox(), current.getBBox());
if (isOverlap) {
overlapped.push(current);
}
}
}
// 对overapped label进行处理
if (overlapped.length > 0) {
overlapped.push(label);
overlapped.sort((a, b) => {
return b.minY - a.minY;
});
// 隐藏除最高点以外的label
each(overlapped, (label: IShape, index: number) => {
if (index > 0) {
label.set('visible', false);
}
});
}
}

/** 检测一组label中是否存在重叠 **/
private isOverlapped(labels: IShape[]) {
for (let i = 0; i < labels.length; i++) {
if (labels[i].get('visible')) {
const labelABBox = labels[i].getBBox();
for (let j = 0; j < labels.length; j++) {
if (j !== i && labels[j].get('visible')) {
const labelBBBox = labels[j].getBBox();
const intersection = isBBoxIntersect(labelABBox, labelBBBox);
if (intersection) {
return true;
}
}
}
}
}
return false;
}

private getGlobalTolerance(labels: IShape[]) {
const labelsClone = labels.slice();
labelsClone.sort((a, b) => {
return b.getBBox().width - a.getBBox().width;
});
return Math.round(labelsClone[0].getBBox().width);
}

private findPrevious(index: number, labels: IShape[]) {
for (let i = index - 1; i > 0; i--) {
if (labels[i].get('visible')) {
return labels[i];
}
}
}

private getCenter(label: IShape) {
const { minX, maxX, minY, maxY } = label.getBBox();
return { x: minX + (maxX - minX) / 2, y: minY + (maxY - minY) / 2 };
}
}

registerLabelComponent('point', PointLabel);
13 changes: 12 additions & 1 deletion src/util/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { View, Axis, Legend, COMPONENT_TYPE } from '../dependents';
import { View, Axis, Legend, COMPONENT_TYPE, BBox } from '../dependents';

/**
* 判断text是否可用, title description
Expand Down Expand Up @@ -77,3 +77,14 @@ export function sortedLastIndex<T>(arr: T[], val: T): number {
}
return i;
}

/* 检测两个label包围盒是否重叠 */
export function isBBoxIntersect(bboxA: BBox, bboxB: BBox) {
if (bboxA.maxY < bboxB.minY || bboxB.maxY < bboxA.minY) {
return false;
}
if (bboxA.maxX < bboxB.minX || bboxB.maxX < bboxA.minX) {
return false;
}
return true;
}

0 comments on commit 3b20a94

Please sign in to comment.