Skip to content

Commit 5678aa4

Browse files
authored
example(compare funnel): 添加了对比漏斗图案例 (#2859)
1 parent e8cca00 commit 5678aa4

File tree

3 files changed

+288
-3
lines changed

3 files changed

+288
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
import { G2, P } from '@antv/g2plot';
2+
import { deepMix } from '@antv/util';
3+
4+
const currDate = ['2020-08-01'];
5+
const compareDate = ['2020-07-06'];
6+
const name = ['本期', '对比期'];
7+
const tooltipItemsName = ['本期留存', '本期流失', '对比期留存', '对比期流失'];
8+
9+
const columnColor = [
10+
{
11+
transformed: '#3b89ff',
12+
'non-transformed': '#e1eeff',
13+
increased: '#e1eeff',
14+
},
15+
{
16+
transformed: '#4ccaa1',
17+
'non-transformed': '#defbf1',
18+
increased: '#defbf1',
19+
},
20+
];
21+
const rawData = [
22+
{ date: '2020-08-01', index: '投放点击用户数', type: '本期', value: 46893 },
23+
{ date: '2020-08-01', index: '会场曝光用户数', type: '本期', value: 37896 },
24+
{ date: '2020-08-01', index: '会场点击用户数', type: '本期', value: 34896 },
25+
{ date: '2020-08-01', index: '权益领取用户数', type: '本期', value: 28896 },
26+
{ date: '2020-08-01', index: '引导IUV', type: '本期', value: 14896 },
27+
{ date: '2020-08-01', index: '引导成交用户数', type: '本期', value: 4755 },
28+
{ date: '2020-07-06', index: '投放点击用户数', type: '对比期', value: 46893 },
29+
{ date: '2020-07-06', index: '会场曝光用户数', type: '对比期', value: 37896 },
30+
{ date: '2020-07-06', index: '会场点击用户数', type: '对比期', value: 34896 },
31+
{ date: '2020-07-06', index: '权益领取用户数', type: '对比期', value: 28896 },
32+
{ date: '2020-07-06', index: '引导IUV', type: '对比期', value: 36896 },
33+
{ date: '2020-07-06', index: '引导成交用户数', type: '对比期', value: 34896 },
34+
];
35+
36+
function processData(rawData) {
37+
const res = [];
38+
[rawData.filter(({ type }) => type === '本期'), rawData.filter(({ type }) => type === '对比期')].forEach((data) => {
39+
const len = data.length - 1;
40+
for (let idx = 0; idx < data.length; idx += 1) {
41+
const prevVal = data[idx === 0 ? 0 : idx - 1].value;
42+
const nextVal = data[idx === len ? len : idx + 1].value;
43+
const { date, index, value, type } = data[idx];
44+
res.push({
45+
index,
46+
value,
47+
type,
48+
date,
49+
flag: 'transformed',
50+
});
51+
const incFlag = value < nextVal;
52+
res.push({
53+
index,
54+
type,
55+
date,
56+
value: Math.max(prevVal - value, 0),
57+
flag: incFlag ? 'increased' : 'non-transformed',
58+
rate: incFlag ? (nextVal - value) / value : nextVal / value,
59+
});
60+
}
61+
});
62+
return res;
63+
}
64+
65+
function getRectPath(points) {
66+
const path = [];
67+
for (let i = 0; i < points.length; i++) {
68+
const point = points[i];
69+
if (point) {
70+
const action = i === 0 ? 'M' : 'L';
71+
path.push([action, point.x, point.y]);
72+
}
73+
}
74+
75+
const first = points[0];
76+
path.push(['L', first.x, first.y]);
77+
path.push(['z']);
78+
return path;
79+
}
80+
81+
function getFillAttrs(cfg) {
82+
const defaultAttrs = {
83+
lineWidth: 0,
84+
fill: '#1890FF',
85+
fillOpacity: 0.85,
86+
};
87+
88+
return {
89+
...defaultAttrs,
90+
...cfg.style,
91+
fill: cfg.color,
92+
stroke: cfg.color,
93+
fillOpacity: cfg.opacity,
94+
};
95+
}
96+
97+
// 自定义 Shape
98+
G2.registerShape('interval', 'contrast-funnel', {
99+
draw(cfg, container) {
100+
const attrs = getFillAttrs(cfg);
101+
let rectPath = getRectPath(cfg.points);
102+
rectPath = this.parsePath(rectPath);
103+
104+
const group = container.addGroup();
105+
group.addShape('path', {
106+
attrs: {
107+
...attrs,
108+
path: rectPath,
109+
},
110+
});
111+
const { flag } = cfg.data;
112+
if (cfg.nextPoints && flag !== 'transformed') {
113+
let linkPath = [
114+
['M', cfg.points[2].x, cfg.points[3].y],
115+
['L', cfg.nextPoints[0].x, cfg.nextPoints[0].y],
116+
];
117+
118+
if (cfg.nextPoints[0].y === 0) {
119+
linkPath[1] = ['L', cfg.nextPoints[1].x, cfg.nextPoints[1].y];
120+
}
121+
linkPath = this.parsePath(linkPath);
122+
123+
const [[, x1, y1], [, x2, y2]] = linkPath;
124+
group.addShape('path', {
125+
attrs: {
126+
path: linkPath,
127+
stroke: '#c5d0d9',
128+
},
129+
});
130+
const text = group.addShape('text', {
131+
attrs: {
132+
x: (x1 + x2) / 2,
133+
y: (y1 + y2) / 2,
134+
text: `${{ 'non-transformed': '▼转化率', increased: '▲增长率' }[flag]}${(cfg.data.rate * 100).toFixed(0)}%`,
135+
fill: { 'non-transformed': '#009f86', increased: '#ff4737' }[flag],
136+
textAlign: 'center',
137+
textBaseline: 'middle',
138+
fontSize: 14,
139+
},
140+
zIndex: 2,
141+
});
142+
const { x, y, width, height } = text.getBBox();
143+
group.addShape('rect', {
144+
attrs: {
145+
x,
146+
y,
147+
width,
148+
height,
149+
fill: 'white',
150+
},
151+
zIndex: 1,
152+
});
153+
text.toFront();
154+
}
155+
return group;
156+
},
157+
});
158+
159+
const defaultOptions = {};
160+
161+
function adaptor(params) {
162+
const { chart, options } = params;
163+
const { data, theme } = options;
164+
165+
chart.data(data);
166+
chart.legend(false);
167+
chart.theme(deepMix({}, theme || G2.getTheme(theme)));
168+
chart.scale('value', { nice: true });
169+
170+
chart.facet('mirror', {
171+
fields: ['type'],
172+
spacing: ['12%', 0],
173+
transpose: true,
174+
showTitle: false,
175+
eachView: (view, facet) => {
176+
const idx = facet.columnIndex;
177+
// 关闭所有 axis
178+
view.axis(false);
179+
view.legend({
180+
custom: true,
181+
position: ['top-right', 'top-left'][idx],
182+
items: [
183+
{
184+
name: name[idx],
185+
marker: {
186+
symbol: 'hyphen',
187+
style: {
188+
stroke: columnColor.map((c) => c.transformed)[idx],
189+
},
190+
},
191+
},
192+
],
193+
});
194+
view
195+
.coordinate()
196+
.transpose()
197+
.scale(...(idx === 0 ? [-1, -1] : [1, -1]));
198+
view
199+
.interval()
200+
.adjust('stack')
201+
.position('index*value')
202+
.color('index*flag', (index, flag) => columnColor[idx][flag])
203+
.label('value*flag', (value, flag) => {
204+
if (flag !== 'transformed') return { content: '' };
205+
return {
206+
position: 'left',
207+
content: value.toLocaleString(),
208+
style: {
209+
textAlign: ['end', 'start'][idx],
210+
fill: '#fff',
211+
shadowColor: '#212121',
212+
shadowBlur: 5,
213+
},
214+
};
215+
})
216+
.shape('contrast-funnel');
217+
},
218+
});
219+
220+
chart.tooltip({
221+
shared: true,
222+
title: (item) => {
223+
return `${item}`;
224+
},
225+
customItems: ([currSurplus, currLoss, compareSurplus, compareLoss]) => {
226+
const [currColor, completeColor] = columnColor;
227+
const [n1, n2, n3, n4] = tooltipItemsName;
228+
return [
229+
{
230+
marker: true,
231+
color: currColor.transformed,
232+
name: `${n1}(${currSurplus.data.date})`,
233+
value: Number(currSurplus.value).toLocaleString(),
234+
},
235+
{
236+
marker: true,
237+
color: currColor['non-transformed'],
238+
name: `${n2}(${currLoss.data.date})`,
239+
value: Number(currLoss.value).toLocaleString(),
240+
},
241+
{
242+
marker: true,
243+
color: completeColor.transformed,
244+
name: `${n3}(${compareSurplus.data.date})`,
245+
value: Number(compareSurplus.value).toLocaleString(),
246+
},
247+
{
248+
marker: true,
249+
color: completeColor['non-transformed'],
250+
name: `${n4}(${compareLoss.data.date})`,
251+
value: Number(compareLoss.value).toLocaleString(),
252+
},
253+
];
254+
},
255+
});
256+
257+
rawData
258+
.filter(({ type }) => type === '本期')
259+
.forEach(({ index }, idx) => {
260+
chart.annotation().text({
261+
content: index,
262+
style: { textAlign: 'center', textBaseline: 'middle' },
263+
position: () => {
264+
const { y: cY, height: cHeight } = chart.coordinateBBox;
265+
const { y: vY, height: vHeight } = chart.views[0].coordinateBBox;
266+
const yScale = chart.views[0].getScaleByField('index');
267+
return ['50%', `${((vY - cY + vHeight * yScale.scale(index)) / cHeight) * 100}%`];
268+
},
269+
});
270+
});
271+
272+
chart.removeInteraction('legend-filter'); // 移除图例过滤交互
273+
}
274+
275+
const funnel = new P('container', {}, adaptor, { data: processData(rawData) });
276+
277+
funnel.render();

examples/case/statistical-scenario/demo/meta.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
"en": "Category"
55
},
66
"demos": [
7+
{
8+
"filename": "compare-funnel.ts",
9+
"title": {
10+
"zh": "对比漏斗图",
11+
"en": "Compare Funnel Diagram"
12+
},
13+
"new": true,
14+
"screenshot": "https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*gS12TpN1MN8AAAAAAAAAAAAAARQnAQ"
15+
},
716
{
817
"filename": "trend.jsx",
918
"title": {
@@ -36,7 +45,6 @@
3645
"en": "Statistical Charts-Pie"
3746
},
3847
"screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*OOnhQI4Sme4AAAAAAAAAAAAAARQnAQ"
39-
4048
},
4149
{
4250
"filename": "trend-funnel.ts",

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
},
5757
"dependencies": {
5858
"@antv/event-emitter": "^0.1.2",
59-
"@antv/g2": "^4.1.23",
59+
"@antv/g2": "^4.1.26",
6060
"d3-hierarchy": "^2.0.0",
6161
"d3-regression": "^1.3.5",
6262
"fmin": "^0.0.2",
@@ -159,4 +159,4 @@
159159
"@babel/standalone": "7.12.6",
160160
"d3-array": "2.12.1"
161161
}
162-
}
162+
}

0 commit comments

Comments
 (0)