Skip to content

Commit

Permalink
fix(funnel): 漏斗图转化率文本位置获取修改 (#3354)
Browse files Browse the repository at this point in the history
* fix(funnel): 漏斗图转化率文本位置获取修改

* fix(funnel): 对比漏斗图更改

Co-authored-by: ai-qing-hai <wb-xcf804241@antgroup.com>
  • Loading branch information
ai-qing-hai and ai-qing-hai committed Nov 4, 2022
1 parent bb26fd5 commit 4e28638
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 54 deletions.
9 changes: 6 additions & 3 deletions __tests__/unit/plots/funnel/compare-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@ describe('compare funnel', () => {
'2020Q1': PV_DATA_COMPARE.filter((item) => item.quarter === '2020Q1'),
'2020Q2': PV_DATA_COMPARE.filter((item) => item.quarter === '2020Q2'),
};
const max = {
'2020Q1': maxBy(origin['2020Q1'], 'pv').pv,
'2020Q2': maxBy(origin['2020Q2'], 'pv').pv,
};

const max = maxBy(PV_DATA_COMPARE, 'pv').pv;
const { data } = funnelView.getOptions();

const { data } = funnel.chart.getOptions();
data.forEach((item) => {
const originData = origin[item.quarter];
const originIndex = originData.findIndex((jtem) => jtem.pv === item.pv);
const percent = item.pv / max;
const percent = item.pv / max[item.quarter];
expect(item[FUNNEL_PERCENT]).toEqual(percent);
expect(item[FUNNEL_MAPPING_VALUE]).toEqual(0.5 * percent + 0.3);
expect(item[FUNNEL_CONVERSATION]).toEqual([get(originData, [originIndex - 1, 'pv']), item.pv]);
Expand Down
34 changes: 32 additions & 2 deletions src/plots/funnel/adaptor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isFunction, clone } from '@antv/util';
import { isFunction, clone, each } from '@antv/util';
import { Params } from '../../core/adaptor';
import { interaction, animation, theme, scale, annotation, tooltip } from '../../adaptor/common';
import { animation, theme, scale, annotation, tooltip } from '../../adaptor/common';
import { getLocale } from '../../core/locale';
import { Datum } from '../../types';
import { flow, deepAssign } from '../../utils';
Expand All @@ -11,6 +11,8 @@ import { compareFunnel } from './geometries/compare';
import { facetFunnel } from './geometries/facet';
import { dynamicHeightFunnel } from './geometries/dynamic-height';
import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from './constant';
import { interactionStart, FUNNEL_LEGEND_FILTER } from './interactions';
import type { Interaction } from './types';

/**
*
Expand Down Expand Up @@ -137,6 +139,34 @@ function legend(params: Params<FunnelOptions>): Params<FunnelOptions> {
return params;
}

/**
* Interaction 配置
* @param params
*/
export function interaction<O extends Pick<FunnelOptions, 'interactions'>>(params: Params<O>): Params<O> {
const { chart, options } = params;
// @ts-ignore
const { interactions, dynamicHeight } = options;

each(interactions, (i: Interaction) => {
if (i.enable === false) {
chart.removeInteraction(i.type);
} else {
chart.interaction(i.type, i.cfg || {});
}
});
// 动态高度 不进行交互操作
if (!dynamicHeight) {
chart.interaction(FUNNEL_LEGEND_FILTER, {
start: [{ ...interactionStart, arg: options }],
});
} else {
chart.removeInteraction(FUNNEL_LEGEND_FILTER);
}

return params;
}

/**
* 漏斗图适配器
* @param chart
Expand Down
15 changes: 10 additions & 5 deletions src/plots/funnel/geometries/basic.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Types } from '@antv/g2';
import { isArray } from '@antv/util';
import { isArray, get, map } from '@antv/util';
import { flow, findGeometry } from '../../../utils';
import { getTooltipMapping } from '../../../utils/tooltip';
import { Params } from '../../../core/adaptor';
Expand Down Expand Up @@ -80,10 +80,15 @@ function transpose(params: Params<FunnelOptions>): Params<FunnelOptions> {
* 转化率组件
* @param params
*/
function conversionTag(params: Params<FunnelOptions>): Params<FunnelOptions> {
const { options } = params;
export function conversionTag(params: Params<FunnelOptions>): Params<FunnelOptions> {
const { options, chart } = params;
const { maxSize } = options;

// 获取形状位置,再转化为需要的转化率位置
const dataArray = get(chart, ['geometries', '0', 'dataArray'], []);
const size = get(chart, ['options', 'data', 'length']);
const x = map(dataArray, (item) => get(item, ['0', 'nextPoints', '0', 'x']) * size - 0.5);

const getLineCoordinate = (
datum: Datum,
datumIndex: number,
Expand All @@ -93,8 +98,8 @@ function conversionTag(params: Params<FunnelOptions>): Params<FunnelOptions> {
const percent = maxSize - (maxSize - datum[FUNNEL_MAPPING_VALUE]) / 2;
return {
...initLineOption,
start: [datumIndex - 0.5, percent],
end: [datumIndex - 0.5, percent + 0.05],
start: [x[datumIndex - 1] || datumIndex - 0.5, percent],
end: [x[datumIndex - 1] || datumIndex - 0.5, percent + 0.05],
};
};

Expand Down
9 changes: 6 additions & 3 deletions src/plots/funnel/geometries/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { FUNNEL_PERCENT, FUNNEL_CONVERSATION, FUNNEL_MAPPING_VALUE } from '../co
import { Params } from '../../../core/adaptor';
import { FunnelOptions } from '../types';

export const CONVERSION_TAG_NAME = 'CONVERSION_TAG_NAME';

/**
* 漏斗图 transform
* @param geometry
Expand Down Expand Up @@ -47,16 +49,17 @@ export function conversionTagComponent(
) {
return function (params: Params<FunnelOptions>): Params<FunnelOptions> {
const { chart, options } = params;
const { conversionTag } = options;

const { data } = chart.getOptions();
// @ts-ignore
const { conversionTag, filteredData } = options;

const data = filteredData || chart.getOptions().data;
if (conversionTag) {
const { formatter } = conversionTag;
data.forEach((obj, index) => {
if (index <= 0 || Number.isNaN(obj[FUNNEL_MAPPING_VALUE])) return;
const lineOption = getLineCoordinate(obj, index, data, {
top: true,
name: CONVERSION_TAG_NAME,
text: {
content: isFunction(formatter) ? formatter(obj, data) : formatter,
offsetX: conversionTag.offsetX,
Expand Down
91 changes: 50 additions & 41 deletions src/plots/funnel/geometries/compare.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Types } from '@antv/g2';
import { isArray } from '@antv/util';
import { isArray, isNumber, get, map } from '@antv/util';
import { flow, deepAssign } from '../../../utils';
import { Params } from '../../../core/adaptor';
import { Datum, Data } from '../../../types/common';
Expand Down Expand Up @@ -114,52 +114,61 @@ function geometry(params: Params<FunnelOptions>): Params<FunnelOptions> {
return params;
}

export function compareConversionTag(params: Params<FunnelOptions>) {
// @ts-ignore
const { chart, index, options } = params;
const { conversionTag, isTransposed } = options;
(isNumber(index) ? [chart] : chart.views).forEach((view, viewIndex) => {
// 获取形状位置,再转化为需要的转化率位置
const dataArray = get(view, ['geometries', '0', 'dataArray'], []);
const size = get(view, ['options', 'data', 'length']);
const x = map(dataArray, (item) => get(item, ['0', 'nextPoints', '0', 'x']) * size - 0.5);

const getLineCoordinate = (
datum: Datum,
datumIndex: number,
data: Data,
initLineOption: Record<string, any>
): Types.LineOption => {
const ratio = (index || viewIndex) === 0 ? -1 : 1;
return deepAssign({}, initLineOption, {
start: [x[datumIndex - 1] || datumIndex - 0.5, datum[FUNNEL_MAPPING_VALUE]],
end: [x[datumIndex - 1] || datumIndex - 0.5, datum[FUNNEL_MAPPING_VALUE] + 0.05],
text: isTransposed
? {
style: {
textAlign: 'start',
},
}
: {
offsetX: conversionTag !== false ? ratio * conversionTag.offsetX : 0,
style: {
textAlign: (index || viewIndex) === 0 ? 'end' : 'start',
},
},
});
};

conversionTagComponent(getLineCoordinate)(
deepAssign(
{},
{
chart: view,
options,
}
)
);
});
}

/**
* 转化率组件
* @param params
*/
function conversionTag(params: Params<FunnelOptions>): Params<FunnelOptions> {
const { chart, options } = params;
const { conversionTag, isTransposed } = options;
const { chart } = params;
// @ts-ignore
chart.once('beforepaint', () => {
chart.views.forEach((view, viewIndex) => {
const getLineCoordinate = (
datum: Datum,
datumIndex: number,
data: Data,
initLineOption: Record<string, any>
): Types.LineOption => {
const ratio = viewIndex === 0 ? -1 : 1;
return deepAssign({}, initLineOption, {
start: [datumIndex - 0.5, datum[FUNNEL_MAPPING_VALUE]],
end: [datumIndex - 0.5, datum[FUNNEL_MAPPING_VALUE] + 0.05],
text: isTransposed
? {
style: {
textAlign: 'start',
},
}
: {
offsetX: conversionTag !== false ? ratio * conversionTag.offsetX : 0,
style: {
textAlign: viewIndex === 0 ? 'end' : 'start',
},
},
});
};

conversionTagComponent(getLineCoordinate)(
deepAssign(
{},
{
chart: view,
options,
}
)
);
});
});
chart.once('beforepaint', () => compareConversionTag(params));
return params;
}

Expand Down
1 change: 1 addition & 0 deletions src/plots/funnel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
FUNNEL_PERCENT,
FUNNEL_TOTAL_PERCENT,
} from './constant';
import './interactions';

export type { FunnelOptions };

Expand Down
57 changes: 57 additions & 0 deletions src/plots/funnel/interactions/funnel-conversion-tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { get, map, filter, each } from '@antv/util';
import { Action } from '@antv/g2';
import { conversionTag as basicConversionTag } from '../geometries/basic';
import { compareConversionTag } from '../geometries/compare';
import { transformData, CONVERSION_TAG_NAME } from '../geometries/common';
import { FunnelOptions } from '../types';

/**
* Funnel 转化率跟随 legend 变化事件
*/
export class ConversionTagAction extends Action {
private rendering = false;
public change(options: FunnelOptions) {
// 防止多次重复渲染
if (!this.rendering) {
const { seriesField, compareField } = options;
const conversionTag = compareField ? compareConversionTag : basicConversionTag;
const { view } = this.context;
// 兼容分面漏斗图
const views = seriesField || compareField ? view.views : [view];
map(views, (v, index) => {
// 防止影响其他 annotations 被去除
const annotationController = v.getController('annotation');

const annotations = filter(
get(annotationController, ['option'], []),
({ name }) => name !== CONVERSION_TAG_NAME
);

annotationController.clear(true);

each(annotations, (annotation) => {
if (typeof annotation === 'object') {
v.annotation()[annotation.type](annotation);
}
});

const data = get(v, ['filteredData'], v.getOptions().data);

conversionTag({
chart: v,
index,
options: {
...options,
// @ts-ignore
filteredData: transformData(data, data, options),
},
});

v.filterData(data);
this.rendering = true;
v.render(true);
});
}
this.rendering = false;
}
}
11 changes: 11 additions & 0 deletions src/plots/funnel/interactions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { registerAction, registerInteraction } from '@antv/g2';
import { ConversionTagAction } from './funnel-conversion-tag';

const FUNNEL_CONVERSION_TAG = 'funnel-conversion-tag';
export const FUNNEL_LEGEND_FILTER = 'funnel-afterrender';
export const interactionStart = { trigger: 'afterrender', action: `${FUNNEL_CONVERSION_TAG}:change` };

registerAction(FUNNEL_CONVERSION_TAG, ConversionTagAction);
registerInteraction(FUNNEL_LEGEND_FILTER, {
start: [interactionStart],
});
1 change: 1 addition & 0 deletions src/plots/funnel/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StyleAttr } from '../../types/attr';
import { TextStyle, Datum, Data, AnnotationPosition, Options } from '../../types/common';
export { Interaction } from '../../types';

export type ConversionPosition = {
start: AnnotationPosition;
Expand Down

0 comments on commit 4e28638

Please sign in to comment.