Skip to content

Commit 4e28638

Browse files
authored
fix(funnel): 漏斗图转化率文本位置获取修改 (#3354)
* fix(funnel): 漏斗图转化率文本位置获取修改 * fix(funnel): 对比漏斗图更改 Co-authored-by: ai-qing-hai <[email protected]>
1 parent bb26fd5 commit 4e28638

File tree

9 files changed

+174
-54
lines changed

9 files changed

+174
-54
lines changed

__tests__/unit/plots/funnel/compare-spec.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,17 @@ describe('compare funnel', () => {
6767
'2020Q1': PV_DATA_COMPARE.filter((item) => item.quarter === '2020Q1'),
6868
'2020Q2': PV_DATA_COMPARE.filter((item) => item.quarter === '2020Q2'),
6969
};
70+
const max = {
71+
'2020Q1': maxBy(origin['2020Q1'], 'pv').pv,
72+
'2020Q2': maxBy(origin['2020Q2'], 'pv').pv,
73+
};
7074

71-
const max = maxBy(PV_DATA_COMPARE, 'pv').pv;
75+
const { data } = funnelView.getOptions();
7276

73-
const { data } = funnel.chart.getOptions();
7477
data.forEach((item) => {
7578
const originData = origin[item.quarter];
7679
const originIndex = originData.findIndex((jtem) => jtem.pv === item.pv);
77-
const percent = item.pv / max;
80+
const percent = item.pv / max[item.quarter];
7881
expect(item[FUNNEL_PERCENT]).toEqual(percent);
7982
expect(item[FUNNEL_MAPPING_VALUE]).toEqual(0.5 * percent + 0.3);
8083
expect(item[FUNNEL_CONVERSATION]).toEqual([get(originData, [originIndex - 1, 'pv']), item.pv]);

src/plots/funnel/adaptor.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { isFunction, clone } from '@antv/util';
1+
import { isFunction, clone, each } from '@antv/util';
22
import { Params } from '../../core/adaptor';
3-
import { interaction, animation, theme, scale, annotation, tooltip } from '../../adaptor/common';
3+
import { animation, theme, scale, annotation, tooltip } from '../../adaptor/common';
44
import { getLocale } from '../../core/locale';
55
import { Datum } from '../../types';
66
import { flow, deepAssign } from '../../utils';
@@ -11,6 +11,8 @@ import { compareFunnel } from './geometries/compare';
1111
import { facetFunnel } from './geometries/facet';
1212
import { dynamicHeightFunnel } from './geometries/dynamic-height';
1313
import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from './constant';
14+
import { interactionStart, FUNNEL_LEGEND_FILTER } from './interactions';
15+
import type { Interaction } from './types';
1416

1517
/**
1618
*
@@ -137,6 +139,34 @@ function legend(params: Params<FunnelOptions>): Params<FunnelOptions> {
137139
return params;
138140
}
139141

142+
/**
143+
* Interaction 配置
144+
* @param params
145+
*/
146+
export function interaction<O extends Pick<FunnelOptions, 'interactions'>>(params: Params<O>): Params<O> {
147+
const { chart, options } = params;
148+
// @ts-ignore
149+
const { interactions, dynamicHeight } = options;
150+
151+
each(interactions, (i: Interaction) => {
152+
if (i.enable === false) {
153+
chart.removeInteraction(i.type);
154+
} else {
155+
chart.interaction(i.type, i.cfg || {});
156+
}
157+
});
158+
// 动态高度 不进行交互操作
159+
if (!dynamicHeight) {
160+
chart.interaction(FUNNEL_LEGEND_FILTER, {
161+
start: [{ ...interactionStart, arg: options }],
162+
});
163+
} else {
164+
chart.removeInteraction(FUNNEL_LEGEND_FILTER);
165+
}
166+
167+
return params;
168+
}
169+
140170
/**
141171
* 漏斗图适配器
142172
* @param chart

src/plots/funnel/geometries/basic.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Types } from '@antv/g2';
2-
import { isArray } from '@antv/util';
2+
import { isArray, get, map } from '@antv/util';
33
import { flow, findGeometry } from '../../../utils';
44
import { getTooltipMapping } from '../../../utils/tooltip';
55
import { Params } from '../../../core/adaptor';
@@ -80,10 +80,15 @@ function transpose(params: Params<FunnelOptions>): Params<FunnelOptions> {
8080
* 转化率组件
8181
* @param params
8282
*/
83-
function conversionTag(params: Params<FunnelOptions>): Params<FunnelOptions> {
84-
const { options } = params;
83+
export function conversionTag(params: Params<FunnelOptions>): Params<FunnelOptions> {
84+
const { options, chart } = params;
8585
const { maxSize } = options;
8686

87+
// 获取形状位置,再转化为需要的转化率位置
88+
const dataArray = get(chart, ['geometries', '0', 'dataArray'], []);
89+
const size = get(chart, ['options', 'data', 'length']);
90+
const x = map(dataArray, (item) => get(item, ['0', 'nextPoints', '0', 'x']) * size - 0.5);
91+
8792
const getLineCoordinate = (
8893
datum: Datum,
8994
datumIndex: number,
@@ -93,8 +98,8 @@ function conversionTag(params: Params<FunnelOptions>): Params<FunnelOptions> {
9398
const percent = maxSize - (maxSize - datum[FUNNEL_MAPPING_VALUE]) / 2;
9499
return {
95100
...initLineOption,
96-
start: [datumIndex - 0.5, percent],
97-
end: [datumIndex - 0.5, percent + 0.05],
101+
start: [x[datumIndex - 1] || datumIndex - 0.5, percent],
102+
end: [x[datumIndex - 1] || datumIndex - 0.5, percent + 0.05],
98103
};
99104
};
100105

src/plots/funnel/geometries/common.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { FUNNEL_PERCENT, FUNNEL_CONVERSATION, FUNNEL_MAPPING_VALUE } from '../co
55
import { Params } from '../../../core/adaptor';
66
import { FunnelOptions } from '../types';
77

8+
export const CONVERSION_TAG_NAME = 'CONVERSION_TAG_NAME';
9+
810
/**
911
* 漏斗图 transform
1012
* @param geometry
@@ -47,16 +49,17 @@ export function conversionTagComponent(
4749
) {
4850
return function (params: Params<FunnelOptions>): Params<FunnelOptions> {
4951
const { chart, options } = params;
50-
const { conversionTag } = options;
51-
52-
const { data } = chart.getOptions();
52+
// @ts-ignore
53+
const { conversionTag, filteredData } = options;
5354

55+
const data = filteredData || chart.getOptions().data;
5456
if (conversionTag) {
5557
const { formatter } = conversionTag;
5658
data.forEach((obj, index) => {
5759
if (index <= 0 || Number.isNaN(obj[FUNNEL_MAPPING_VALUE])) return;
5860
const lineOption = getLineCoordinate(obj, index, data, {
5961
top: true,
62+
name: CONVERSION_TAG_NAME,
6063
text: {
6164
content: isFunction(formatter) ? formatter(obj, data) : formatter,
6265
offsetX: conversionTag.offsetX,

src/plots/funnel/geometries/compare.ts

+50-41
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Types } from '@antv/g2';
2-
import { isArray } from '@antv/util';
2+
import { isArray, isNumber, get, map } from '@antv/util';
33
import { flow, deepAssign } from '../../../utils';
44
import { Params } from '../../../core/adaptor';
55
import { Datum, Data } from '../../../types/common';
@@ -114,52 +114,61 @@ function geometry(params: Params<FunnelOptions>): Params<FunnelOptions> {
114114
return params;
115115
}
116116

117+
export function compareConversionTag(params: Params<FunnelOptions>) {
118+
// @ts-ignore
119+
const { chart, index, options } = params;
120+
const { conversionTag, isTransposed } = options;
121+
(isNumber(index) ? [chart] : chart.views).forEach((view, viewIndex) => {
122+
// 获取形状位置,再转化为需要的转化率位置
123+
const dataArray = get(view, ['geometries', '0', 'dataArray'], []);
124+
const size = get(view, ['options', 'data', 'length']);
125+
const x = map(dataArray, (item) => get(item, ['0', 'nextPoints', '0', 'x']) * size - 0.5);
126+
127+
const getLineCoordinate = (
128+
datum: Datum,
129+
datumIndex: number,
130+
data: Data,
131+
initLineOption: Record<string, any>
132+
): Types.LineOption => {
133+
const ratio = (index || viewIndex) === 0 ? -1 : 1;
134+
return deepAssign({}, initLineOption, {
135+
start: [x[datumIndex - 1] || datumIndex - 0.5, datum[FUNNEL_MAPPING_VALUE]],
136+
end: [x[datumIndex - 1] || datumIndex - 0.5, datum[FUNNEL_MAPPING_VALUE] + 0.05],
137+
text: isTransposed
138+
? {
139+
style: {
140+
textAlign: 'start',
141+
},
142+
}
143+
: {
144+
offsetX: conversionTag !== false ? ratio * conversionTag.offsetX : 0,
145+
style: {
146+
textAlign: (index || viewIndex) === 0 ? 'end' : 'start',
147+
},
148+
},
149+
});
150+
};
151+
152+
conversionTagComponent(getLineCoordinate)(
153+
deepAssign(
154+
{},
155+
{
156+
chart: view,
157+
options,
158+
}
159+
)
160+
);
161+
});
162+
}
163+
117164
/**
118165
* 转化率组件
119166
* @param params
120167
*/
121168
function conversionTag(params: Params<FunnelOptions>): Params<FunnelOptions> {
122-
const { chart, options } = params;
123-
const { conversionTag, isTransposed } = options;
169+
const { chart } = params;
124170
// @ts-ignore
125-
chart.once('beforepaint', () => {
126-
chart.views.forEach((view, viewIndex) => {
127-
const getLineCoordinate = (
128-
datum: Datum,
129-
datumIndex: number,
130-
data: Data,
131-
initLineOption: Record<string, any>
132-
): Types.LineOption => {
133-
const ratio = viewIndex === 0 ? -1 : 1;
134-
return deepAssign({}, initLineOption, {
135-
start: [datumIndex - 0.5, datum[FUNNEL_MAPPING_VALUE]],
136-
end: [datumIndex - 0.5, datum[FUNNEL_MAPPING_VALUE] + 0.05],
137-
text: isTransposed
138-
? {
139-
style: {
140-
textAlign: 'start',
141-
},
142-
}
143-
: {
144-
offsetX: conversionTag !== false ? ratio * conversionTag.offsetX : 0,
145-
style: {
146-
textAlign: viewIndex === 0 ? 'end' : 'start',
147-
},
148-
},
149-
});
150-
};
151-
152-
conversionTagComponent(getLineCoordinate)(
153-
deepAssign(
154-
{},
155-
{
156-
chart: view,
157-
options,
158-
}
159-
)
160-
);
161-
});
162-
});
171+
chart.once('beforepaint', () => compareConversionTag(params));
163172
return params;
164173
}
165174

src/plots/funnel/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
FUNNEL_PERCENT,
1313
FUNNEL_TOTAL_PERCENT,
1414
} from './constant';
15+
import './interactions';
1516

1617
export type { FunnelOptions };
1718

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { get, map, filter, each } from '@antv/util';
2+
import { Action } from '@antv/g2';
3+
import { conversionTag as basicConversionTag } from '../geometries/basic';
4+
import { compareConversionTag } from '../geometries/compare';
5+
import { transformData, CONVERSION_TAG_NAME } from '../geometries/common';
6+
import { FunnelOptions } from '../types';
7+
8+
/**
9+
* Funnel 转化率跟随 legend 变化事件
10+
*/
11+
export class ConversionTagAction extends Action {
12+
private rendering = false;
13+
public change(options: FunnelOptions) {
14+
// 防止多次重复渲染
15+
if (!this.rendering) {
16+
const { seriesField, compareField } = options;
17+
const conversionTag = compareField ? compareConversionTag : basicConversionTag;
18+
const { view } = this.context;
19+
// 兼容分面漏斗图
20+
const views = seriesField || compareField ? view.views : [view];
21+
map(views, (v, index) => {
22+
// 防止影响其他 annotations 被去除
23+
const annotationController = v.getController('annotation');
24+
25+
const annotations = filter(
26+
get(annotationController, ['option'], []),
27+
({ name }) => name !== CONVERSION_TAG_NAME
28+
);
29+
30+
annotationController.clear(true);
31+
32+
each(annotations, (annotation) => {
33+
if (typeof annotation === 'object') {
34+
v.annotation()[annotation.type](annotation);
35+
}
36+
});
37+
38+
const data = get(v, ['filteredData'], v.getOptions().data);
39+
40+
conversionTag({
41+
chart: v,
42+
index,
43+
options: {
44+
...options,
45+
// @ts-ignore
46+
filteredData: transformData(data, data, options),
47+
},
48+
});
49+
50+
v.filterData(data);
51+
this.rendering = true;
52+
v.render(true);
53+
});
54+
}
55+
this.rendering = false;
56+
}
57+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { registerAction, registerInteraction } from '@antv/g2';
2+
import { ConversionTagAction } from './funnel-conversion-tag';
3+
4+
const FUNNEL_CONVERSION_TAG = 'funnel-conversion-tag';
5+
export const FUNNEL_LEGEND_FILTER = 'funnel-afterrender';
6+
export const interactionStart = { trigger: 'afterrender', action: `${FUNNEL_CONVERSION_TAG}:change` };
7+
8+
registerAction(FUNNEL_CONVERSION_TAG, ConversionTagAction);
9+
registerInteraction(FUNNEL_LEGEND_FILTER, {
10+
start: [interactionStart],
11+
});

src/plots/funnel/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { StyleAttr } from '../../types/attr';
22
import { TextStyle, Datum, Data, AnnotationPosition, Options } from '../../types/common';
3+
export { Interaction } from '../../types';
34

45
export type ConversionPosition = {
56
start: AnnotationPosition;

0 commit comments

Comments
 (0)