|
1 | | -import { get, map, isArray, last } from '@antv/util'; |
| 1 | +import { get, map, isArray, last, each } from '@antv/util'; |
2 | 2 | import { Element, MappingDatum, _ORIGIN } from '../../dependents'; |
3 | 3 | import BaseLabel, { registerLabelComponent } from '../../components/label/base'; |
4 | 4 | import { TextStyle, Label } from '../../interface/config'; |
| 5 | +import { IShape, Geometry } from '../../dependents'; |
| 6 | +import { isBBoxIntersect } from '../../util/common'; |
| 7 | + |
| 8 | +/** |
| 9 | + * 说明: |
| 10 | + * 适用于展示面积图和折线图上数据点的label |
| 11 | + * */ |
5 | 12 |
|
6 | 13 | export default class PointLabel<L extends Label = Label> extends BaseLabel<L> { |
7 | 14 | protected getDefaultOptions() { |
@@ -58,9 +65,114 @@ export default class PointLabel<L extends Label = Label> extends BaseLabel<L> { |
58 | 65 | return pos; |
59 | 66 | } |
60 | 67 |
|
| 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 | + |
61 | 93 | protected adjustLabel() { |
62 | 94 | return; |
63 | 95 | } |
| 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 | + } |
64 | 176 | } |
65 | 177 |
|
66 | 178 | registerLabelComponent('point', PointLabel); |
0 commit comments