Skip to content

Commit 41b7cff

Browse files
authored
feat: multi layer 支持配置 annotations & label (#1913)
* refactor(docs): 饼图 & 雷达图 & 瀑布图走查 * feat(multi-layer): 实验室多图层,允许每个图层配置 annotations & 每个图层的每个geometry配置label * fix(geometry-adaptor/label): geometry label 通道与子弹图label通道的适配 * feat(geometry-adaptor/label): label 通道支持透传 fields 映射 * fix(geometry-adaptor): geometry adaptor 的 size 通道怪怪的 * feat(demo): 添加一个动态的多view组合 demo
1 parent b6b9d3d commit 41b7cff

File tree

9 files changed

+434
-5
lines changed

9 files changed

+434
-5
lines changed

__tests__/unit/plots/multi-view/index-spec.ts

+36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Lab } from '../../../../src';
22
import { createDiv } from '../../../utils/dom';
33
import { partySupport } from '../../../data/party-support';
4+
import { annotation } from '../../../../src/adaptor/common';
45

56
describe('multi-view', () => {
67
it('simple line', () => {
@@ -64,6 +65,41 @@ describe('multi-view', () => {
6465
expect(line.chart.views[0].getYScales()[0].type).toBe('log');
6566
expect(line.chart.views[0].getYScales()[0].tickCount).toBe(3);
6667

68+
// [components] label
69+
expect(line.chart.views[0].geometries[0].labelsContainer.getChildren().length).toBe(0);
70+
const options = line.options;
71+
// @ts-ignore
72+
options.views[0].geometries[0].label = { content: () => 'hello' };
73+
line.update(options);
74+
expect(line.chart.views[0].geometries[0].labelsContainer.getChildren().length).toBe(data.length);
75+
// @ts-ignore
76+
expect(line.chart.views[0].geometries[0].labelsContainer.getChildren()[0].getChildren()[0].attr('text')).toBe(
77+
'hello'
78+
);
79+
80+
// [components] annotations
81+
// @ts-ignore
82+
options.views[0].annotations = [{ type: 'text', content: 'G2Plot', position: ['50%', '50%'] }];
83+
line.update(options);
84+
expect(line.chart.getController('annotation').getComponents().length).toBe(1);
85+
86+
// multi-views
87+
options.views.push({
88+
data,
89+
geometries: [
90+
{
91+
type: 'interval',
92+
xField: 'date',
93+
yField: 'value',
94+
colorField: 'type',
95+
mapping: {},
96+
},
97+
],
98+
annotations: [{ type: 'text', content: 'view2', position: ['50%', '50%'] }],
99+
});
100+
line.update(options);
101+
expect(line.chart.getController('annotation').getComponents().length).toBe(2);
102+
67103
line.destroy();
68104
});
69105
});
+335
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
import { DataView } from '@antv/data-set';
2+
import { Lab } from '@antv/g2plot';
3+
4+
fetch('https://gw.alipayobjects.com/os/bmw-prod/738147f2-2cef-4591-8e0d-4fd5268c2e0a.json')
5+
.then((data) => data.json())
6+
.then((data) => {
7+
const COLORS = ['#ff93a7', '#ff9300', '#bb82f3', '#6349ec', '#0074ff'];
8+
const START_BTN = 'https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*rhl0QYDkV7EAAAAAAAAAAAAAARQnAQ';
9+
const STOP_BTN = 'https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*2F9fRr2RfEEAAAAAAAAAAAAAARQnAQ';
10+
let currentYear = 1901;
11+
/** 散点图数据(各国诺贝尔奖获奖者的年龄分布) */
12+
const getPointViewData = (year) => {
13+
return data.map((d) => (d.year <= year ? d : { ...d, age: null }));
14+
};
15+
/** 占比环图数据(各领域诺贝尔奖获奖分布) */
16+
const getIntervalViewData = (year) => {
17+
const ds = new DataView().source(data.map((d) => (d.year <= year ? d : { ...d, number: 0 })));
18+
const othersCnt = data.filter((d) => d.year > year).reduce((a, b) => a + b.number, 0);
19+
ds.transform({
20+
type: 'summary', // 别名 summary
21+
fields: ['number'], // 统计字段集
22+
operations: ['sum'], // 统计操作集
23+
as: ['counts'], // 存储字段集
24+
groupBy: ['type'], // 分组字段集
25+
});
26+
return [...ds.rows, { type: 'other', counts: othersCnt }];
27+
};
28+
const getDataGroupByField = (field) => {
29+
const transformData = [...data];
30+
for (let i = 1901; i < 2016; i++) {
31+
if (!data.find((d) => d.year === i)) {
32+
transformData.push({
33+
year: i,
34+
number: 0,
35+
name: '',
36+
nickName: '',
37+
age: 0,
38+
country: '',
39+
about: '',
40+
type: '',
41+
avatar: '',
42+
});
43+
}
44+
}
45+
return new DataView()
46+
.source(transformData)
47+
.transform({ type: 'group', groupBy: [field] })
48+
.rows.map((d) => ({ [field]: d[0][field] }));
49+
};
50+
51+
const labChart = new Lab.MultiView('container', {
52+
height: 500,
53+
padding: 'auto',
54+
theme: 'dark',
55+
appendPadding: [20, 0, 20, 0],
56+
legend: {
57+
type: { position: 'bottom' },
58+
},
59+
tooltip: {
60+
domStyles: {
61+
'g2-tooltip-list-item': {
62+
color: '#fff',
63+
},
64+
},
65+
},
66+
views: [
67+
{
68+
data: getIntervalViewData(currentYear),
69+
region: { start: { x: 0, y: 0.35 }, end: { x: 1, y: 0.65 } },
70+
coordinate: {
71+
type: 'theta',
72+
cfg: { innerRadius: 0.89, radius: 0.96 },
73+
},
74+
geometries: [
75+
{
76+
type: 'interval',
77+
xField: '1',
78+
yField: 'counts',
79+
colorField: 'type',
80+
mapping: {
81+
color: [...COLORS, '#D9D9D9'],
82+
},
83+
adjust: { type: 'stack' },
84+
},
85+
],
86+
annotations: [
87+
{
88+
type: 'html',
89+
position: ['50%', '50%'],
90+
style: {
91+
textAlign: 'center',
92+
fill: 'rgba(255,255,255,0.85)',
93+
},
94+
html: (container, view) => {
95+
const width = Math.min(view.getCoordinate().getWidth(), view.getCoordinate().getHeight()) * 0.2;
96+
return `<div id="btn" style="width=${width * 1.2}px;height=${
97+
width * 1.2
98+
}px;color: rgba(255,255,2550.85);transform: translate(-50%,-50%);cursor: pointer;">
99+
<img alt="image" width=${width} height=${width} src=${START_BTN}>
100+
</div>`;
101+
},
102+
},
103+
],
104+
},
105+
{
106+
data: getPointViewData(currentYear),
107+
region: { start: { x: 0, y: 0 }, end: { x: 1, y: 1 } },
108+
coordinate: {
109+
type: 'polar',
110+
cfg: {
111+
innerRadius: 0.45,
112+
radius: 0.64,
113+
},
114+
},
115+
axes: {
116+
country: {
117+
tickLine: null,
118+
label: null,
119+
},
120+
age: {
121+
min: 20,
122+
max: 100,
123+
tickInterval: 20,
124+
alias: '获奖\n年龄',
125+
label: {
126+
style: {
127+
fontSize: 10,
128+
},
129+
},
130+
grid: {
131+
line: {
132+
style: {
133+
lineWidth: 0.5,
134+
},
135+
},
136+
},
137+
},
138+
},
139+
geometries: [
140+
{
141+
type: 'point',
142+
xField: 'country',
143+
yField: 'age',
144+
colorField: 'type',
145+
mapping: {
146+
size: 3,
147+
shape: 'circle',
148+
style: {
149+
lineWidth: 0,
150+
},
151+
color: COLORS,
152+
},
153+
},
154+
],
155+
},
156+
{
157+
// 国家展示
158+
data: getDataGroupByField('country'),
159+
region: { start: { x: 0.18, y: 0.18 }, end: { x: 0.82, y: 0.82 } },
160+
coordinate: { type: 'polar', cfg: { innerRadius: 0.99, radius: 1 } },
161+
geometries: [
162+
{
163+
type: 'interval',
164+
xField: 'country',
165+
yField: '1',
166+
label: {
167+
labelEmit: true,
168+
fields: ['country'],
169+
style: {
170+
fontSize: 10,
171+
},
172+
},
173+
mapping: {
174+
color: 'transparent',
175+
},
176+
},
177+
],
178+
},
179+
{
180+
// 年度 label 展示
181+
data: getDataGroupByField('year'),
182+
region: {
183+
start: { x: 0.05, y: 0.05 },
184+
end: { x: 0.95, y: 0.95 },
185+
},
186+
axes: {
187+
year: {
188+
tickCount: 10,
189+
label: null,
190+
line: {
191+
style: {
192+
lineWidth: 0.5,
193+
},
194+
},
195+
},
196+
},
197+
coordinate: { type: 'polar', cfg: { innerRadius: 0.99, radius: 1 } },
198+
geometries: [
199+
{
200+
type: 'line',
201+
xField: 'year',
202+
yField: '0.9',
203+
label: {
204+
labelEmit: true,
205+
content: ({ year }) => {
206+
return year === '1901' || Number(year) % 10 === 0 ? year : '-';
207+
},
208+
},
209+
mapping: {
210+
color: 'transparent',
211+
},
212+
},
213+
],
214+
},
215+
{
216+
// 滑块
217+
data: getDataGroupByField('year'),
218+
region: {
219+
start: { x: 0.05, y: 0.05 },
220+
end: { x: 0.95, y: 0.95 },
221+
},
222+
coordinate: { type: 'polar', cfg: { innerRadius: 0.99, radius: 1 } },
223+
axes: {
224+
1: null,
225+
year: {
226+
// todo fix G2 tickCount 为 0,会死循环
227+
// tickCount: 0,
228+
label: null,
229+
},
230+
},
231+
geometries: [
232+
{
233+
type: 'interval',
234+
xField: 'year',
235+
yField: '1',
236+
label: {
237+
labelEmit: true,
238+
fields: ['year'],
239+
callback: (year) => {
240+
return {
241+
style: {
242+
fill: year === currentYear ? 'rgba(255,255,255,0.85)' : 'transparent',
243+
},
244+
content: () => `${currentYear}`,
245+
background: {
246+
padding: 2,
247+
// @ts-ignore
248+
radius: 2,
249+
style: {
250+
fill: year === currentYear ? 'pink' : 'transparent',
251+
},
252+
},
253+
};
254+
},
255+
},
256+
mapping: {
257+
color: 'transparent',
258+
},
259+
},
260+
],
261+
},
262+
],
263+
});
264+
265+
labChart.render();
266+
// @ts-ignore
267+
window.chart = labChart;
268+
const dymaticView = labChart.chart.views[4];
269+
dymaticView.on('element:click', (evt) => {
270+
const data = evt.data?.data;
271+
if (data) {
272+
if (typeof data?.year === 'number') {
273+
currentYear = data.year;
274+
rerender(currentYear);
275+
}
276+
}
277+
});
278+
279+
function rerender(y) {
280+
labChart.chart.views[0].changeData(getIntervalViewData(y));
281+
labChart.chart.views[1].changeData(getPointViewData(y));
282+
dymaticView.geometries[0].label('year', (year) => ({
283+
labelEmit: true,
284+
style: {
285+
fill: year === y ? 'rgba(255,255,255,0.85)' : 'transparent',
286+
},
287+
content: () => `${y}`,
288+
background: {
289+
padding: 2,
290+
// @ts-ignore
291+
radius: 2,
292+
style: {
293+
fill: year === y ? 'pink' : 'transparent',
294+
},
295+
},
296+
}));
297+
dymaticView.render(true);
298+
}
299+
300+
let interval;
301+
function start() {
302+
if (!interval) {
303+
if (document.querySelector('#btn').querySelector('img')) {
304+
document.querySelector('#btn').querySelector('img').setAttribute('src', START_BTN);
305+
}
306+
}
307+
interval = setInterval(() => {
308+
if (currentYear < 2016) {
309+
currentYear += 1;
310+
rerender(currentYear);
311+
} else {
312+
end();
313+
}
314+
}, 800);
315+
}
316+
function end() {
317+
clearInterval(interval);
318+
interval = null;
319+
if (document.querySelector('#btn').querySelector('img')) {
320+
document.querySelector('#btn').querySelector('img').setAttribute('src', STOP_BTN);
321+
}
322+
}
323+
324+
start();
325+
// 延迟绑定事件
326+
setTimeout(() => {
327+
document.querySelector('#btn').addEventListener('click', () => {
328+
if (!interval) {
329+
start();
330+
} else {
331+
end();
332+
}
333+
});
334+
}, 500);
335+
});

0 commit comments

Comments
 (0)