Skip to content

Commit 52e77c0

Browse files
author
xinming
committed
feat(pie-outer-label): 新增自定义饼图outer-label
1. 替代旧outer label 2. 添加了测试单例
1 parent 52e2dc0 commit 52e77c0

File tree

9 files changed

+737
-567
lines changed

9 files changed

+737
-567
lines changed

__tests__/unit/pie-label-spec.ts

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { Pie } from '../../src';
2+
import { Shape, BBox } from '@antv/g';
3+
import * as _ from '@antv/util';
4+
import { getElementLabels } from '@antv/g2';
5+
6+
const createDiv = (id: string, parent?: HTMLDivElement) => {
7+
const canvasDiv = document.createElement('div');
8+
canvasDiv.style.width = '400px';
9+
canvasDiv.style.height = '400px';
10+
canvasDiv.style.margin = '0 auto 10px';
11+
canvasDiv.style.border = '1px solid #eee';
12+
canvasDiv.id = id;
13+
parent ? parent.appendChild(canvasDiv) : document.body.appendChild(canvasDiv);
14+
return canvasDiv;
15+
};
16+
17+
const createRangeInput = (onClick: (val: number) => void, initialValue: number = 0, parentDOM?: HTMLDivElement) => {
18+
const spanGroup = document.createElement('span');
19+
const span = document.createElement('span');
20+
span.style.color = '#333';
21+
span.style.display = 'inline-block';
22+
span.style.fontSize = '14px';
23+
span.style.padding = '0 4px 0 8px';
24+
const rangeBtn = document.createElement('input');
25+
rangeBtn.type = 'range';
26+
rangeBtn.value = `${initialValue}`;
27+
span.innerHTML = `${initialValue}`;
28+
rangeBtn.onchange = (e) => {
29+
// @ts-ignore
30+
const value = e.target.value;
31+
onClick(value);
32+
span.innerHTML = value;
33+
};
34+
parentDOM ? parentDOM.appendChild(spanGroup) : document.body.appendChild(spanGroup);
35+
spanGroup.appendChild(span);
36+
spanGroup.appendChild(rangeBtn);
37+
};
38+
39+
describe('Pie plot with outer-label', () => {
40+
it('get outer-label', () => {
41+
expect(getElementLabels('outer')).toBeDefined();
42+
});
43+
44+
const group1 = document.createElement('div');
45+
document.body.appendChild(group1);
46+
// @ts-ignore
47+
group1.style = `margin: 10px auto;width: 420px;border: 1px solid #aaaaaa2b;padding: 16px;font-size: 14px;color:#333;`;
48+
const canvasDiv = createDiv('div1', group1);
49+
const pieConfig = {
50+
forceFit: true,
51+
padding: [16, 0, 0, 0],
52+
angleField: 'value',
53+
colorField: 'type',
54+
radius: 0.6,
55+
legend: {
56+
visible: false,
57+
},
58+
pieStyle: {
59+
lineWidth: 0,
60+
},
61+
};
62+
63+
const data = [];
64+
for (let i = 0; i < 16; i++) {
65+
data.push({ type: `分类 ${i + 1}`, value: 10 + Math.random() * 100 });
66+
}
67+
68+
// 记录 label 可视率
69+
const labelVisibleRecordDiv = document.createElement('span');
70+
group1.appendChild(labelVisibleRecordDiv);
71+
72+
const piePlot = new Pie(canvasDiv, {
73+
...pieConfig,
74+
data,
75+
label: {
76+
visible: true,
77+
type: 'outer',
78+
formatter: (text, item) => {
79+
return `${item._origin['type']} (${item._origin['value'].toFixed(2)})`;
80+
},
81+
},
82+
});
83+
piePlot.render();
84+
const plot = piePlot.getLayer().view;
85+
const element = plot.get('elements')[0];
86+
87+
// 半径 切换器
88+
createRangeInput(
89+
(value) => {
90+
piePlot.updateConfig({ radius: value / 100 });
91+
piePlot.render();
92+
const plot = piePlot.getLayer().view;
93+
const element = plot.get('elements')[0];
94+
calcVisibleRate(element.get('labels'), labelVisibleRecordDiv);
95+
},
96+
pieConfig.radius * 100,
97+
group1
98+
);
99+
100+
it('all labels visible', () => {
101+
const labelShapes: Shape[] = element.get('labels');
102+
if (data.length < 30) {
103+
labelShapes.forEach((label) => {
104+
expect(label.get('visible')).toBe(true);
105+
});
106+
}
107+
});
108+
109+
it('all labels outside pie', () => {
110+
const labelShapes: Shape[] = element.get('labels');
111+
const coord = plot.get('coord');
112+
labelShapes.forEach((label) => {
113+
const distX = Math.abs(coord.getCenter().x - label.attr('x'));
114+
const distY = Math.abs(coord.getCenter().y - label.attr('y'));
115+
const dist = Math.sqrt(distX * distX + distY * distY);
116+
expect(dist > coord.getRadius()).toBe(true);
117+
});
118+
});
119+
120+
it('all visible labels inside panel', () => {
121+
function inPanel(panel: BBox, label: BBox): boolean {
122+
return (
123+
label.minX >= panel.minX && label.maxX <= panel.maxX && label.minY >= panel.minY && label.maxY <= panel.maxY
124+
);
125+
}
126+
const panel = plot.get('panelRange');
127+
const labelShapes: Shape[] = _.filter(element.get('labels'), (l) => l.get('visible'));
128+
labelShapes.forEach((l) => {
129+
expect(inPanel(panel, l.getBBox())).toBe(true);
130+
});
131+
});
132+
133+
it('no label overlap', () => {
134+
function checkOverlap(a: BBox, b: BBox): boolean {
135+
const xOverlap = Math.max(0, Math.min(a.maxX, b.maxX) - Math.max(a.x, b.x));
136+
const yOverlap = Math.max(0, Math.min(a.maxY, b.maxY) - Math.max(a.y, b.y));
137+
if (xOverlap * yOverlap) return true;
138+
return false;
139+
}
140+
const labelShapes: Shape[] = _.filter(element.get('labels'), (l) => l.get('visible'));
141+
labelShapes.forEach((l, idx) => {
142+
for (let i = 0; i < labelShapes.length; i++) {
143+
if (i !== idx) {
144+
expect(checkOverlap(l.getBBox(), labelShapes[i].getBBox())).toBe(false);
145+
}
146+
}
147+
});
148+
});
149+
150+
function calcVisibleRate(labelShapes: Shape[], div: HTMLElement) {
151+
const visibleLabels: Shape[] = _.filter(labelShapes, (l) => l.get('visible'));
152+
153+
const visibleRate = visibleLabels.length / labelShapes.length;
154+
div.innerHTML = `label 可见率: ${visibleRate * 100}%`;
155+
return visibleRate;
156+
}
157+
158+
it('label 可见率 > 90%(data count < 90)', () => {
159+
const labelShapes: Shape[] = element.get('labels');
160+
const visibleRate = calcVisibleRate(labelShapes, labelVisibleRecordDiv);
161+
if (data.length < 90) {
162+
expect(visibleRate > 0.9).toBe(true);
163+
}
164+
});
165+
166+
afterAll(() => {
167+
// piePlot.destroy();
168+
});
169+
});

__tests__/unit/pie-upgradeLabel-spec.ts

-122
This file was deleted.

__tests__/unit/utils-text-spec.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { getEllipsisText, measureTextWidth } from '../../src/plots/pie/component/label/utils/text';
2+
3+
const FONT = {
4+
fontSize: 10,
5+
fontFamily: 'serif',
6+
};
7+
8+
describe('measure-text', () => {
9+
it('measureTextWidth', () => {
10+
expect(measureTextWidth('蚂蚁')).toEqual(measureTextWidth('蚂') + measureTextWidth('蚁'));
11+
12+
expect(measureTextWidth('蚂蚁', FONT)).toBe(20);
13+
expect(measureTextWidth('东...', FONT)).toBeLessThan(measureTextWidth('东北', FONT));
14+
});
15+
16+
it('change font', () => {
17+
const newFont = { ...FONT, fontSize: 20 };
18+
expect(measureTextWidth('hello', newFont)).not.toEqual(measureTextWidth('hello', FONT));
19+
});
20+
21+
it('getEllipsisText', () => {
22+
expect(getEllipsisText('蚂蚁是一个什么公司了?', 54, FONT).endsWith('...')).toEqual(true);
23+
expect(getEllipsisText('蚂蚁是一个什么公司了?', 44, FONT)).toEqual('蚂蚁是...');
24+
expect(getEllipsisText('hello', 50, FONT)).toEqual('hello');
25+
26+
// measureTextWidth('东北') = 20
27+
expect(getEllipsisText('东北', 19, FONT)).toEqual('东...');
28+
expect(getEllipsisText('东北', 20, FONT)).toEqual('东北');
29+
30+
expect(getEllipsisText('东北东北东北东北东北东北东北东北东北东北', 199, FONT)).toBe(
31+
'东北东北东北东北东北东北东北东北东北东...'
32+
);
33+
expect(getEllipsisText('东北东北东北东北东北东北东北东北东北东北', 200, FONT)).toBe(
34+
'东北东北东北东北东北东北东北东北东北东北'
35+
);
36+
});
37+
38+
it('area getEllipsisText', () => {
39+
expect(getEllipsisText('city', 24, FONT)).toBe('city');
40+
});
41+
});

0 commit comments

Comments
 (0)