From 9d565e6762b52ac9e0b63b6807c544e26cfadde4 Mon Sep 17 00:00:00 2001 From: "aiyin.lzy" Date: Sun, 15 Nov 2020 00:16:11 +0800 Subject: [PATCH 1/7] fix: formatter --- src/plots/funnel/adaptor.ts | 50 +++++++++++++------------- src/plots/funnel/geometries/basic.ts | 34 ++++++++++++------ src/plots/funnel/geometries/compare.ts | 27 +++++++++++--- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/src/plots/funnel/adaptor.ts b/src/plots/funnel/adaptor.ts index b3b35e3fab..7ac22950cb 100644 --- a/src/plots/funnel/adaptor.ts +++ b/src/plots/funnel/adaptor.ts @@ -1,5 +1,5 @@ import { Params } from '../../core/adaptor'; -import { interaction, animation, theme, scale, annotation } from '../../adaptor/common'; +import { interaction, animation, theme, scale, annotation, tooltip as common } from '../../adaptor/common'; import { flow, deepAssign } from '../../utils'; import { FunnelOptions } from './types'; import { basicFunnel } from './geometries/basic'; @@ -20,22 +20,32 @@ import { dynamicHeightFunnel } from './geometries/dynamic-height'; */ function defaultOptions(params: Params): Params { const { options } = params; - const { compareField, label } = options; - let compareOptions = {}; - if (compareField) { - compareOptions = { + const { compareField, dynamicHeight, xField, yField } = options; + let additionalOption = {}; + if (dynamicHeight) { + additionalOption = { + tooltip: { + itemTpl: + '
  • ' + + '' + + `{${xField}}` + + `{${yField}}
  • `, + }, + }; + } else if (compareField) { + additionalOption = { appendPadding: [50, 50, 0, 50], - label: label && { + label: { callback: (xField, yField) => ({ content: `${yField}`, }), - ...label, }, }; } + return deepAssign({}, params, { options: { - ...compareOptions, + ...additionalOption, ...options, }, }); @@ -103,26 +113,18 @@ function legend(params: Params): Params { } /** - * tooltip 配置 + * tooltip * @param params */ -export function tooltip(params: Params): Params { +function tooltip(params: Params): Params { const { chart, options } = params; - const { tooltip, dynamicHeight, xField, yField } = options; - let tranformTooltip = tooltip; - if (tooltip && dynamicHeight) { - tranformTooltip = { - itemTpl: - '
  • ' + - '' + - `{${xField}}` + - `{${yField}}
  • `, - ...tooltip, - }; - } + const { legend } = options; - if (tranformTooltip !== undefined) { - chart.tooltip(tranformTooltip); + if (legend === false) { + chart.legend(false); + } else { + chart.legend(legend); + // TODO FIX: legend-click 时间和转化率组件之间的关联 } return params; diff --git a/src/plots/funnel/geometries/basic.ts b/src/plots/funnel/geometries/basic.ts index 6445114e47..a6f0905561 100644 --- a/src/plots/funnel/geometries/basic.ts +++ b/src/plots/funnel/geometries/basic.ts @@ -1,8 +1,10 @@ import { map } from '@antv/util'; import { LineOption } from '@antv/g2/lib/interface'; import { flow, findGeometry } from '../../../utils'; +import { getTooltipMapping } from '../../../utils/tooltip'; import { Params } from '../../../core/adaptor'; import { Datum, Data } from '../../../types/common'; +import { geometry as baseGeometry } from '../../../adaptor/geometries/base'; import { FunnelOptions } from '../types'; import { FUNNEL_PERCENT } from '../constant'; import { geometryLabel, conversionTagComponent } from './common'; @@ -36,14 +38,28 @@ function field(params: Params): Params { */ function geometry(params: Params): Params { const { chart, options } = params; - const { xField, yField, color } = options; + const { xField, yField, color, tooltip } = options; - chart - .interval() - .adjust('symmetric') - .position(`${xField}*${yField}*${FUNNEL_PERCENT}`) - .shape('funnel') - .color(xField, color); + const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT]); + + baseGeometry({ + chart, + options: { + type: 'interval', + xField: xField, + yField: yField, + colorField: xField, + tooltipFields: fields, + mapping: { + shape: 'funnel', + tooltip: formatter, + color, + }, + }, + }); + + const geo = findGeometry(params.chart, 'interval'); + geo.adjust('symmetric'); return params; } @@ -67,9 +83,7 @@ function transpose(params: Params): Params { * @param params */ function label(params: Params): Params { - const { chart } = params; - - geometryLabel(findGeometry(chart, 'interval'))(params); + geometryLabel(findGeometry(params.chart, 'interval'))(params); return params; } diff --git a/src/plots/funnel/geometries/compare.ts b/src/plots/funnel/geometries/compare.ts index 6fffedd4c3..16b1bc7486 100644 --- a/src/plots/funnel/geometries/compare.ts +++ b/src/plots/funnel/geometries/compare.ts @@ -3,6 +3,8 @@ import { LineOption } from '@antv/g2/lib/interface'; import { flow, findGeometry, deepAssign } from '../../../utils'; import { Params } from '../../../core/adaptor'; import { Datum, Data } from '../../../types/common'; +import { getTooltipMapping } from '../../../utils/tooltip'; +import { geometry as baseGeometry } from '../../../adaptor/geometries/base'; import { FunnelOptions } from '../types'; import { FUNNEL_PERCENT } from '../constant'; import { geometryLabel, conversionTagComponent } from './common'; @@ -46,7 +48,7 @@ function field(params: Params): Params { */ function geometry(params: Params): Params { const { chart, options } = params; - const { xField, yField, color, compareField, isTransposed } = options; + const { xField, yField, color, compareField, isTransposed, tooltip } = options; chart.facet('mirror', { fields: [compareField], @@ -61,9 +63,26 @@ function geometry(params: Params): Params { }); } // 绘制图形 - view.interval().position(`${xField}*${yField}*${FUNNEL_PERCENT}`).shape('funnel').color(xField, color).style({ - lineWidth: 1, - stroke: '#fff', + const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT]); + + baseGeometry({ + chart: view, + options: { + type: 'interval', + xField: xField, + yField: yField, + colorField: xField, + tooltipFields: fields, + mapping: { + shape: 'funnel', + tooltip: formatter, + color, + style: { + lineWidth: 1, + stroke: '#fff', + }, + }, + }, }); }, }); From 5e41207ec3bb77d7d805cc1c4935430fc8b4219c Mon Sep 17 00:00:00 2001 From: "aiyin.lzy" Date: Tue, 17 Nov 2020 16:07:12 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20base=20geometr?= =?UTF-8?q?y=20=E7=BB=98=E5=88=B6=E6=BC=8F=E6=96=97=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/funnel/basic/demo/basic.ts | 7 +++++ src/plots/funnel/adaptor.ts | 14 +++++---- src/plots/funnel/geometries/common.ts | 30 +++++++++++-------- src/plots/funnel/geometries/compare.ts | 5 +++- src/plots/funnel/geometries/dynamic-height.ts | 27 +++++++++++------ src/plots/funnel/index.ts | 11 +++++-- 6 files changed, 63 insertions(+), 31 deletions(-) diff --git a/examples/funnel/basic/demo/basic.ts b/examples/funnel/basic/demo/basic.ts index bba37b66e4..38a6f67af7 100644 --- a/examples/funnel/basic/demo/basic.ts +++ b/examples/funnel/basic/demo/basic.ts @@ -13,6 +13,13 @@ const funnelPlot = new Funnel('container', { xField: 'stage', yField: 'number', legend: false, + tooltip: { + formatter: (datum) => { + return { name: 'abc', value: '123' }; + }, + }, }); funnelPlot.render(); + +console.log(funnelPlot.chart); diff --git a/src/plots/funnel/adaptor.ts b/src/plots/funnel/adaptor.ts index 7ac22950cb..9627125641 100644 --- a/src/plots/funnel/adaptor.ts +++ b/src/plots/funnel/adaptor.ts @@ -25,11 +25,15 @@ function defaultOptions(params: Params): Params { if (dynamicHeight) { additionalOption = { tooltip: { - itemTpl: - '
  • ' + - '' + - `{${xField}}` + - `{${yField}}
  • `, + // itemTpl: + // '
  • ' + + // '' + + // `{${xField}}` + + // `{${yField}}
  • `, + formatter: (datum) => { + console.log(datum); + return { name: 'abc', value: '123' }; + }, }, }; } else if (compareField) { diff --git a/src/plots/funnel/geometries/common.ts b/src/plots/funnel/geometries/common.ts index 15a1da4dac..b67f46da91 100644 --- a/src/plots/funnel/geometries/common.ts +++ b/src/plots/funnel/geometries/common.ts @@ -48,21 +48,25 @@ export function conversionTagComponent( if (index <= 0) return; const lineCoordinateOption = getLineCoordinate(obj, index, data); - const lineOption = deepAssign({}, lineCoordinateOption, { - top: true, - text: { - content: isFunction(formatter) ? formatter(obj, data) : formatter, - offsetX: conversionTag.offsetX, - offsetY: conversionTag.offsetY, - position: 'end', - autoRotate: false, - style: { - ...conversionTag.style, - textAlign: 'start', - textBaseline: 'middle', + const lineOption = deepAssign( + {}, + { + top: true, + text: { + content: isFunction(formatter) ? formatter(obj, data) : formatter, + offsetX: conversionTag.offsetX, + offsetY: conversionTag.offsetY, + position: 'end', + autoRotate: false, + style: { + ...conversionTag.style, + textAlign: 'start', + textBaseline: 'middle', + }, }, }, - }); + lineCoordinateOption + ); chart.annotation().line(lineOption); }); } diff --git a/src/plots/funnel/geometries/compare.ts b/src/plots/funnel/geometries/compare.ts index 16b1bc7486..905a14370d 100644 --- a/src/plots/funnel/geometries/compare.ts +++ b/src/plots/funnel/geometries/compare.ts @@ -101,9 +101,11 @@ function label(params: Params): Params { chart.once('beforepaint', () => { chart.views.forEach((view, index) => { const geometry = findGeometry(view, 'interval'); + console.log(geometry); geometryLabel(geometry)( label ? deepAssign({}, params, { + chart: view, options: { label: { offset: 10, @@ -136,8 +138,9 @@ function conversionTag(params: Params): Params { return { start: [datumIndex - 0.5, data[0][yField] * datum[FUNNEL_PERCENT]], end: [datumIndex - 0.5, data[0][yField] * (datum[FUNNEL_PERCENT] + 0.05)], + // @ts-ignore text: { - content: undefined, + // content: undefined, offsetX: conversionTag !== false ? ratio * conversionTag.offsetX : 0, style: { textAlign: viewIndex === 0 ? 'end' : 'start', diff --git a/src/plots/funnel/geometries/dynamic-height.ts b/src/plots/funnel/geometries/dynamic-height.ts index aa98a35f06..e4cc2b1dd8 100644 --- a/src/plots/funnel/geometries/dynamic-height.ts +++ b/src/plots/funnel/geometries/dynamic-height.ts @@ -3,6 +3,8 @@ import { LineOption } from '@antv/g2/lib/interface'; import { flow, findGeometry } from '../../../utils'; import { Params } from '../../../core/adaptor'; import { FUNNEL_PERCENT, FUNNEL_TOTAL_PERCENT, PLOYGON_X, PLOYGON_Y } from '../constant'; +import { geometry as baseGeometry } from '../../../adaptor/geometries/base'; +import { getTooltipMapping } from '../../../utils/tooltip'; import { Datum } from '../../../types/common'; import { FunnelOptions } from '../types'; import { geometryLabel, conversionTagComponent } from './common'; @@ -80,17 +82,24 @@ function field(params: Params): Params { */ function geometry(params: Params): Params { const { chart, options } = params; - const { xField, yField, color } = options; + const { xField, yField, color, tooltip } = options; + const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT]); // 绘制漏斗图 - chart - .polygon() - .position(`${PLOYGON_X}*${PLOYGON_Y}`) - .color(xField, color) - .tooltip(`${xField}*${yField}`, (xFieldValue, yFieldValue) => ({ - [xField]: xFieldValue, - [yField]: yFieldValue, - })); + baseGeometry({ + chart, + options: { + type: 'polygon', + xField: PLOYGON_X, + yField: PLOYGON_Y, + colorField: xField, + tooltipFields: fields, + mapping: { + tooltip: formatter, + color, + }, + }, + }); return params; } diff --git a/src/plots/funnel/index.ts b/src/plots/funnel/index.ts index 083c0d66e2..db332dc17d 100644 --- a/src/plots/funnel/index.ts +++ b/src/plots/funnel/index.ts @@ -27,14 +27,19 @@ export class Funnel extends Plot { fill: '#fff', fontSize: 12, }, - callback: (xField, yField) => ({ - content: `${xField} ${yField}`, - }), + callback: (xField, yField, percent) => { + return { + content: `${xField} ${yField}`, + }; + }, }, tooltip: { showTitle: false, showMarkers: false, shared: false, + formatter: (datum) => { + return { name: 'abc', value: '123' }; + }, }, conversionTag: { offsetX: 10, From 6f3f424e4fc8518e9437823b654e65a2d77ae514 Mon Sep 17 00:00:00 2001 From: "aiyin.lzy" Date: Tue, 17 Nov 2020 19:25:17 +0800 Subject: [PATCH 3/7] fix: #1867 --- src/plots/funnel/adaptor.ts | 85 ++++++++----------- src/plots/funnel/geometries/basic.ts | 8 +- src/plots/funnel/geometries/common.ts | 35 ++++---- src/plots/funnel/geometries/compare.ts | 57 ++++++++----- src/plots/funnel/geometries/dynamic-height.ts | 10 ++- src/plots/funnel/index.ts | 36 +------- 6 files changed, 105 insertions(+), 126 deletions(-) diff --git a/src/plots/funnel/adaptor.ts b/src/plots/funnel/adaptor.ts index 9627125641..472426e321 100644 --- a/src/plots/funnel/adaptor.ts +++ b/src/plots/funnel/adaptor.ts @@ -1,5 +1,5 @@ import { Params } from '../../core/adaptor'; -import { interaction, animation, theme, scale, annotation, tooltip as common } from '../../adaptor/common'; +import { interaction, animation, theme, scale, annotation, tooltip } from '../../adaptor/common'; import { flow, deepAssign } from '../../utils'; import { FunnelOptions } from './types'; import { basicFunnel } from './geometries/basic'; @@ -20,39 +20,46 @@ import { dynamicHeightFunnel } from './geometries/dynamic-height'; */ function defaultOptions(params: Params): Params { const { options } = params; - const { compareField, dynamicHeight, xField, yField } = options; - let additionalOption = {}; - if (dynamicHeight) { - additionalOption = { - tooltip: { - // itemTpl: - // '
  • ' + - // '' + - // `{${xField}}` + - // `{${yField}}
  • `, - formatter: (datum) => { - console.log(datum); - return { name: 'abc', value: '123' }; - }, + const { compareField, xField, yField } = options; + const defaultOption = { + label: { + offset: 0, + position: 'middle', + style: { + fill: '#fff', + fontSize: 12, }, - }; - } else if (compareField) { - additionalOption = { - appendPadding: [50, 50, 0, 50], - label: { - callback: (xField, yField) => ({ - content: `${yField}`, - }), + callback: compareField + ? (xField, yField) => ({ + content: `${yField}`, + }) + : (xField, yField) => ({ + content: `${xField} ${yField}`, + }), + }, + tooltip: { + showTitle: false, + showMarkers: false, + shared: false, + title: xField, + formatter: (datum) => { + return { name: datum[xField], value: datum[yField] }; }, - }; - } + }, + conversionTag: { + offsetX: 10, + offsetY: 0, + style: {}, + formatter: (datum) => `转化率${(datum.$$percentage$$ * 100).toFixed(2)}%`, + }, + }; - return deepAssign({}, params, { - options: { - ...additionalOption, - ...options, + return deepAssign( + { + options: defaultOption, }, - }); + params + ); } /** @@ -116,24 +123,6 @@ function legend(params: Params): Params { return params; } -/** - * tooltip - * @param params - */ -function tooltip(params: Params): Params { - const { chart, options } = params; - const { legend } = options; - - if (legend === false) { - chart.legend(false); - } else { - chart.legend(legend); - // TODO FIX: legend-click 时间和转化率组件之间的关联 - } - - return params; -} - /** * 漏斗图适配器 * @param chart diff --git a/src/plots/funnel/geometries/basic.ts b/src/plots/funnel/geometries/basic.ts index a6f0905561..202c921ae2 100644 --- a/src/plots/funnel/geometries/basic.ts +++ b/src/plots/funnel/geometries/basic.ts @@ -96,9 +96,15 @@ function conversionTag(params: Params): Params { const { options } = params; const { yField } = options; - const getLineCoordinate = (datum: Datum, datumIndex: number, data: Data): LineOption => { + const getLineCoordinate = ( + datum: Datum, + datumIndex: number, + data: Data, + initLineOption: Record + ): LineOption => { const percent = 1 - (1 - datum[FUNNEL_PERCENT]) / 2; return { + ...initLineOption, start: [datumIndex - 0.5, data[0][yField] * percent], end: [datumIndex - 0.5, data[0][yField] * (percent + 0.05)], }; diff --git a/src/plots/funnel/geometries/common.ts b/src/plots/funnel/geometries/common.ts index b67f46da91..11a66826eb 100644 --- a/src/plots/funnel/geometries/common.ts +++ b/src/plots/funnel/geometries/common.ts @@ -34,7 +34,7 @@ export function geometryLabel(geometry: Geometry) { * @param getLineCoordinate 用于获取特定的 line 的位置及配置 */ export function conversionTagComponent( - getLineCoordinate: (datum: Datum, datumIndex: number, data: Data) => LineOption + getLineCoordinate: (datum: Datum, datumIndex: number, data: Data, initLineOption: Record) => LineOption ) { return function (params: Params): Params { const { chart, options } = params; @@ -46,27 +46,22 @@ export function conversionTagComponent( const { formatter } = conversionTag; data.forEach((obj, index) => { if (index <= 0) return; - const lineCoordinateOption = getLineCoordinate(obj, index, data); - - const lineOption = deepAssign( - {}, - { - top: true, - text: { - content: isFunction(formatter) ? formatter(obj, data) : formatter, - offsetX: conversionTag.offsetX, - offsetY: conversionTag.offsetY, - position: 'end', - autoRotate: false, - style: { - ...conversionTag.style, - textAlign: 'start', - textBaseline: 'middle', - }, + const lineOption = getLineCoordinate(obj, index, data, { + top: true, + text: { + content: isFunction(formatter) ? formatter(obj, data) : formatter, + offsetX: conversionTag.offsetX, + offsetY: conversionTag.offsetY, + position: 'end', + autoRotate: false, + style: { + textAlign: 'start', + textBaseline: 'middle', + ...conversionTag.style, }, }, - lineCoordinateOption - ); + }); + chart.annotation().line(lineOption); }); } diff --git a/src/plots/funnel/geometries/compare.ts b/src/plots/funnel/geometries/compare.ts index 905a14370d..c4e5cd729b 100644 --- a/src/plots/funnel/geometries/compare.ts +++ b/src/plots/funnel/geometries/compare.ts @@ -54,7 +54,7 @@ function geometry(params: Params): Params { fields: [compareField], // 漏斗图的转置规则与分面相反,默认是垂直布局 transpose: !isTransposed, - padding: 0, + padding: isTransposed ? 0 : [32, 0, 0, 0], eachView(view, facet) { if (!isTransposed) { view.coordinate({ @@ -96,24 +96,28 @@ function geometry(params: Params): Params { */ function label(params: Params): Params { const { chart, options } = params; - const { label } = options; + const { label, isTransposed } = options; chart.once('beforepaint', () => { chart.views.forEach((view, index) => { const geometry = findGeometry(view, 'interval'); - console.log(geometry); geometryLabel(geometry)( label ? deepAssign({}, params, { chart: view, options: { - label: { - offset: 10, - position: 'left', - style: { - textAlign: index === 0 ? 'end' : 'start', - }, - }, + label: isTransposed + ? { + offset: index === 0 ? 10 : -23, + position: index === 0 ? 'bottom' : 'top', + } + : { + offset: 10, + position: 'left', + style: { + textAlign: index === 0 ? 'end' : 'start', + }, + }, }, }) : params @@ -129,24 +133,33 @@ function label(params: Params): Params { */ function conversionTag(params: Params): Params { const { chart, options } = params; - const { yField, conversionTag } = options; + const { yField, conversionTag, isTransposed } = options; chart.once('beforepaint', () => { chart.views.forEach((view, viewIndex) => { - const getLineCoordinate = (datum: Datum, datumIndex: number, data: Data): LineOption => { + const getLineCoordinate = ( + datum: Datum, + datumIndex: number, + data: Data, + initLineOption: Record + ): LineOption => { const ratio = viewIndex === 0 ? -1 : 1; - return { + return deepAssign({}, initLineOption, { start: [datumIndex - 0.5, data[0][yField] * datum[FUNNEL_PERCENT]], end: [datumIndex - 0.5, data[0][yField] * (datum[FUNNEL_PERCENT] + 0.05)], - // @ts-ignore - text: { - // content: undefined, - offsetX: conversionTag !== false ? ratio * conversionTag.offsetX : 0, - style: { - textAlign: viewIndex === 0 ? 'end' : 'start', - }, - }, - }; + text: isTransposed + ? { + style: { + textAlign: 'start', + }, + } + : { + offsetX: conversionTag !== false ? ratio * conversionTag.offsetX : 0, + style: { + textAlign: viewIndex === 0 ? 'end' : 'start', + }, + }, + }); }; conversionTagComponent(getLineCoordinate)( diff --git a/src/plots/funnel/geometries/dynamic-height.ts b/src/plots/funnel/geometries/dynamic-height.ts index e4cc2b1dd8..61a6a7b032 100644 --- a/src/plots/funnel/geometries/dynamic-height.ts +++ b/src/plots/funnel/geometries/dynamic-height.ts @@ -5,7 +5,7 @@ import { Params } from '../../../core/adaptor'; import { FUNNEL_PERCENT, FUNNEL_TOTAL_PERCENT, PLOYGON_X, PLOYGON_Y } from '../constant'; import { geometry as baseGeometry } from '../../../adaptor/geometries/base'; import { getTooltipMapping } from '../../../utils/tooltip'; -import { Datum } from '../../../types/common'; +import { Datum, Data } from '../../../types/common'; import { FunnelOptions } from '../types'; import { geometryLabel, conversionTagComponent } from './common'; @@ -134,8 +134,14 @@ function label(params: Params): Params { * @param params */ function conversionTag(params: Params): Params { - const getLineCoordinate = (datum: Datum): LineOption => { + const getLineCoordinate = ( + datum: Datum, + datumIndex: number, + data: Data, + initLineOption: Record + ): LineOption => { return { + ...initLineOption, start: [datum[PLOYGON_X][1], datum[PLOYGON_Y][1]], end: [datum[PLOYGON_X][1] + 0.05, datum[PLOYGON_Y][1]], }; diff --git a/src/plots/funnel/index.ts b/src/plots/funnel/index.ts index db332dc17d..1a0e65a60f 100644 --- a/src/plots/funnel/index.ts +++ b/src/plots/funnel/index.ts @@ -14,40 +14,10 @@ export class Funnel extends Plot { * 获取 漏斗图 默认配置项 */ protected getDefaultOptions(): Partial { - return deepAssign({}, super.getDefaultOptions(), { - // annotation 无法自适应 chart,先用 appendPadding hack, 随后看看如何自适应 + // 由于不同漏斗图 defaultOption 有部分逻辑不同,此处仅处理 core.getDefaultOptions 覆盖范围,funnel 的 defaulOption 为不分散逻辑统一写到 adaptor 的 defaultOption 中 + return { appendPadding: [0, 50], - label: { - offset: 0, - position: 'middle', - // layout: { - // type: 'limit-in-shape', - // }, - style: { - fill: '#fff', - fontSize: 12, - }, - callback: (xField, yField, percent) => { - return { - content: `${xField} ${yField}`, - }; - }, - }, - tooltip: { - showTitle: false, - showMarkers: false, - shared: false, - formatter: (datum) => { - return { name: 'abc', value: '123' }; - }, - }, - conversionTag: { - offsetX: 10, - offsetY: 0, - style: {}, - formatter: (datum) => `转化率${(datum.$$percentage$$ * 100).toFixed(2)}%`, - }, - }); + }; } /** From deea0f1d188a33334870c85f51ed2c4cd55d9a9f Mon Sep 17 00:00:00 2001 From: "aiyin.lzy" Date: Tue, 17 Nov 2020 22:29:38 +0800 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=E5=8D=95=E6=B5=8B=20&=20=E8=BD=AC?= =?UTF-8?q?=E5=8C=96=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/unit/plots/funnel/basic-spec.ts | 77 ++++++++ __tests__/unit/plots/funnel/compare-spec.ts | 111 ++---------- .../unit/plots/funnel/conversion-tag-spec.ts | 90 ++++++++++ .../unit/plots/funnel/dynamic-height-spec.ts | 123 ++----------- __tests__/unit/plots/funnel/index-spec.ts | 165 ------------------ __tests__/unit/plots/funnel/label-spec.ts | 92 ++++++++++ __tests__/unit/plots/funnel/tooltip-spec.ts | 6 + src/plots/funnel/adaptor.ts | 11 +- src/plots/funnel/constant.ts | 4 +- src/plots/funnel/geometries/basic.ts | 7 +- src/plots/funnel/geometries/common.ts | 8 +- src/plots/funnel/geometries/compare.ts | 16 +- src/plots/funnel/geometries/dynamic-height.ts | 5 +- 13 files changed, 316 insertions(+), 399 deletions(-) create mode 100644 __tests__/unit/plots/funnel/basic-spec.ts create mode 100644 __tests__/unit/plots/funnel/conversion-tag-spec.ts delete mode 100644 __tests__/unit/plots/funnel/index-spec.ts create mode 100644 __tests__/unit/plots/funnel/label-spec.ts diff --git a/__tests__/unit/plots/funnel/basic-spec.ts b/__tests__/unit/plots/funnel/basic-spec.ts new file mode 100644 index 0000000000..2037fd1807 --- /dev/null +++ b/__tests__/unit/plots/funnel/basic-spec.ts @@ -0,0 +1,77 @@ +import { Funnel } from '../../../../src'; +import { PV_DATA } from '../../../data/conversion'; +import { createDiv } from '../../../utils/dom'; +import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from '../../../../src/plots/funnel/constant'; + +describe('basic funnel', () => { + let funnel; + + const funnelOption = { + width: 400, + height: 400, + data: PV_DATA, + xField: 'action', + yField: 'pv', + }; + + beforeAll(() => { + funnel = new Funnel(createDiv('basic funnel'), funnelOption); + funnel.render(); + }); + + afterAll(() => { + funnel.destroy(); + }); + + describe('geometry', () => { + test('geometry test', () => { + const geometry = funnel.chart.geometries[0]; + // 数据量 + expect(geometry.elements.length).toBe(PV_DATA.length); + + // geometry + expect(geometry.type).toBe('interval'); + // @ts-ignore + expect(geometry.adjustOption[0].type).toBe('symmetric'); + + // position + const positionFields = geometry.getAttribute('position').getFields(); + expect(positionFields).toHaveLength(2); + expect(positionFields[0]).toBe('action'); + expect(positionFields[1]).toBe('pv'); + + // shape + const shapeFields = geometry.getAttribute('shape').getFields(); + expect(shapeFields[0]).toBe('funnel'); + + // color + const colorFields = geometry.getAttribute('color').getFields(); + expect(colorFields[0]).toBe('action'); + + // transpose + const coordinate = funnel.chart.getCoordinate(); + expect(coordinate.isRect).toBe(true); + expect(coordinate.isTransposed).toBe(true); + + // 判断数据是否正确 + const { data } = funnel.chart.getOptions(); + data.forEach((item, index) => { + expect(item[FUNNEL_PERCENT]).toEqual(item.pv / data[0].pv); + expect(item[FUNNEL_CONVERSATION]).toEqual(index === 0 ? 1 : item.pv / data[index - 1].pv); + }); + }); + }); + + describe('transpose', () => { + test('transpose', () => { + funnel.update({ + ...funnelOption, + isTransposed: true, + }); + // transpose + const coordinate = funnel.chart.getCoordinate(); + expect(coordinate.isRect).toBe(true); + expect(coordinate.isTransposed).toBe(false); + }); + }); +}); diff --git a/__tests__/unit/plots/funnel/compare-spec.ts b/__tests__/unit/plots/funnel/compare-spec.ts index 10ef270e0f..74c8d4c090 100644 --- a/__tests__/unit/plots/funnel/compare-spec.ts +++ b/__tests__/unit/plots/funnel/compare-spec.ts @@ -1,7 +1,7 @@ import { Funnel } from '../../../../src'; import { PV_DATA_COMPARE } from '../../../data/conversion'; import { createDiv } from '../../../utils/dom'; -import { FUNNEL_PERCENT } from '../../../../src/plots/funnel/constant'; +import { FUNNEL_PERCENT, FUNNEL_CONVERSATION } from '../../../../src/plots/funnel/constant'; describe('compare funnel', () => { let funnel; @@ -44,115 +44,30 @@ describe('compare funnel', () => { // position const positionFields = geometry.getAttribute('position').getFields(); - expect(positionFields).toHaveLength(3); + expect(positionFields).toHaveLength(2); expect(positionFields[0]).toBe('action'); expect(positionFields[1]).toBe('pv'); - expect(positionFields[2]).toBe(FUNNEL_PERCENT); const shapeFields = geometry.getAttribute('shape').getFields(); expect(shapeFields[0]).toBe('funnel'); const colorFields = geometry.getAttribute('color').getFields(); expect(colorFields[0]).toBe('action'); - }); - }); - }); - describe('geometry label', () => { - test('geometry label default', () => { - funnel.chart.views.forEach((funnelView) => { - const geometry = funnelView.geometries[0]; - const { labelOption, labelsContainer } = geometry; - expect(labelOption.fields).toEqual(['action', 'pv', FUNNEL_PERCENT]); - expect(labelsContainer.cfg.children.length).toBe(5); - expect(labelOption.cfg.style.fill).toBe('#fff'); - expect(labelOption.cfg.style.fontSize).toBe(12); - }); - }); - - test('geometry label custom', () => { - funnel.update({ - ...funnelOption, - label: { - style: { - fill: '#f00', - fontSize: 14, - }, - callback: (xField, yField) => ({ - content: `${yField}`, - }), - }, - }); - - funnel.chart.views.forEach((funnelView) => { - expect(funnelView.geometries[0].labelOption.cfg.style.fill).toBe('#f00'); - expect(funnelView.geometries[0].labelOption.cfg.style.fontSize).toBe(14); - }); - }); - - test('geometry label close', () => { - // 关闭 label - funnel.update({ - ...funnelOption, - label: false, - }); - - funnel.chart.views.forEach((funnelView) => { - expect(funnelView.geometries[0].labelsContainer.cfg.children.length).toBe(0); - }); - }); - }); - - describe('conversionTag', () => { - // test('conversionTag default', () => { - // funnel.chart.views.forEach((funnelView) => { - // const { data } = funnelView.getOptions(); - // const annotation = funnelView.getController('annotation').getComponents(); - // expect(annotation.length).toEqual(5); - // expect(annotation[0].component.cfg.content).toBe(data[0].quarter); - // data.forEach((pvItem, index) => { - // if (index === 0) return; - // expect(annotation[index].component.get('text').content).toBe(`转化率${pvItem[FUNNEL_PERCENT] * 100}%`); - // }); - // }); - // }); - - test('conversionTag custom', () => { - // 自定义转化率组件 - funnel.update({ - ...funnelOption, - conversionTag: { - style: { - fill: '#f00', - fontSize: 18, - }, - formatter: (datum) => `${datum.$$percentage$$}转化`, - }, - }); - funnel.chart.views.forEach((funnelView) => { - const { data } = funnelView.getOptions(); - const customAnnotation = funnelView.getController('annotation').getComponents(); - expect(customAnnotation.length).toEqual(5); - data.forEach((pvItem, index) => { - if (index === 0) return; - const text = customAnnotation[index].component.get('text'); - expect(text.content).toBe(`${pvItem[FUNNEL_PERCENT]}转化`); - expect(text.style.fill).toBe('#f00'); - expect(text.style.fontSize).toBe(18); + const origin = { + '2020Q1': PV_DATA_COMPARE.filter((item) => item.quarter === '2020Q1'), + '2020Q2': PV_DATA_COMPARE.filter((item) => item.quarter === '2020Q2'), + }; + + const { data } = funnel.chart.getOptions(); + data.forEach((item, index) => { + const originData = origin[item.quarter]; + const originIndex = originData.findIndex((jtem) => jtem.pv === item.pv); + expect(item[FUNNEL_PERCENT]).toEqual(item.pv / originData[0].pv); + expect(item[FUNNEL_CONVERSATION]).toEqual(originIndex === 0 ? 1 : item.pv / originData[originIndex - 1].pv); }); }); }); - - test('conversionTag close', () => { - funnel.update({ - ...funnelOption, - conversionTag: false, - }); - - funnel.chart.views.forEach((funnelView) => { - expect(funnelView.getController('annotation').getComponents().length).toEqual(1); - }); - }); }); describe('transpose', () => { diff --git a/__tests__/unit/plots/funnel/conversion-tag-spec.ts b/__tests__/unit/plots/funnel/conversion-tag-spec.ts new file mode 100644 index 0000000000..2e35b4a923 --- /dev/null +++ b/__tests__/unit/plots/funnel/conversion-tag-spec.ts @@ -0,0 +1,90 @@ +import { Funnel } from '../../../../src'; +import { PV_DATA, PV_DATA_COMPARE } from '../../../data/conversion'; +import { createDiv } from '../../../utils/dom'; +import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from '../../../../src/plots/funnel/constant'; + +describe('conversition tag', () => { + test('conversition tag: basic & dynamicHeight', () => { + const funnelOption = { + width: 400, + height: 400, + data: PV_DATA, + xField: 'action', + yField: 'pv', + }; + const funnel = new Funnel(createDiv('conversition tag: basic & dynamicHeight'), funnelOption); + funnel.render(); + + const annotation = funnel.chart.getController('annotation').getComponents(); + expect(annotation.length).toEqual(4); + console.log(annotation); + PV_DATA.forEach((pvItem, index) => { + if (index === 0) return; + const content = annotation[index - 1].component.get('text').content; + console.log(content); + expect(content).toBe(`转化率${(pvItem[FUNNEL_CONVERSATION] * 100).toFixed(2)}%`); + }); + + // 自定义 label + funnel.update({ + ...funnelOption, + dynamicHeight: true, + conversionTag: { + offsetX: 50, + offsetY: 20, + style: { + fontSize: 18, + }, + formatter: (datum) => `${datum.$$percentage$$}占比`, + }, + }); + + const customAnnotation = funnel.chart.getController('annotation').getComponents(); + expect(customAnnotation.length).toEqual(4); + PV_DATA.forEach((pvItem, index) => { + if (index === 0) return; + const text = customAnnotation[index - 1].component.get('text'); + expect(text.content).toBe(`${pvItem[FUNNEL_PERCENT]}占比`); + expect(text.offsetX).toBe(50); + expect(text.offsetY).toBe(20); + expect(text.style.fontSize).toBe(18); + }); + + // 关闭转化率组件 + funnel.update({ + ...funnelOption, + conversionTag: false, + }); + expect(funnel.chart.getController('annotation').getComponents().length).toBe(0); + + funnel.destroy(); + }); + + test('conversation compare', () => { + // 自定义 label + const funnel = new Funnel(createDiv('conversation compare'), { + width: 400, + height: 400, + data: PV_DATA_COMPARE, + compareField: 'quarter', + xField: 'action', + yField: 'pv', + }); + funnel.render(); + + funnel.chart.views.forEach((funnelView) => { + const { data } = funnelView.getOptions(); + const annotation = funnelView.getController('annotation').getComponents(); + expect(annotation.length).toEqual(5); + expect(annotation[0].component.cfg.content).toBe(data[0].quarter); + data.forEach((pvItem, index) => { + if (index === 0) return; + expect(annotation[index].component.get('text').content).toBe( + `转化率${(pvItem[FUNNEL_CONVERSATION] * 100).toFixed(2)}%` + ); + }); + }); + + funnel.destroy(); + }); +}); diff --git a/__tests__/unit/plots/funnel/dynamic-height-spec.ts b/__tests__/unit/plots/funnel/dynamic-height-spec.ts index 23d068fb61..6af4b53e71 100644 --- a/__tests__/unit/plots/funnel/dynamic-height-spec.ts +++ b/__tests__/unit/plots/funnel/dynamic-height-spec.ts @@ -1,7 +1,13 @@ import { Funnel } from '../../../../src'; import { PV_DATA } from '../../../data/conversion'; import { createDiv } from '../../../utils/dom'; -import { FUNNEL_PERCENT, FUNNEL_TOTAL_PERCENT, PLOYGON_X, PLOYGON_Y } from '../../../../src/plots/funnel/constant'; +import { + FUNNEL_PERCENT, + FUNNEL_TOTAL_PERCENT, + FUNNEL_CONVERSATION, + PLOYGON_X, + PLOYGON_Y, +} from '../../../../src/plots/funnel/constant'; describe('dynamicHeight funnel', () => { let funnel; @@ -21,7 +27,7 @@ describe('dynamicHeight funnel', () => { }); afterAll(() => { - funnel.destroy(); + // funnel.destroy(); }); describe('geometry', () => { @@ -39,10 +45,12 @@ describe('dynamicHeight funnel', () => { expect(positionFields[0]).toBe(PLOYGON_X); expect(positionFields[1]).toBe(PLOYGON_Y); - // ratio + // 判断数据是否正确 const { data } = funnel.chart.getOptions(); - data.forEach((item) => { + data.forEach((item, index) => { expect(item[PLOYGON_Y][0] - item[PLOYGON_Y][2]).toEqual(item[FUNNEL_TOTAL_PERCENT]); + expect(item[FUNNEL_PERCENT]).toEqual(item.pv / data[0].pv); + expect(item[FUNNEL_CONVERSATION]).toEqual(index === 0 ? 1 : item.pv / data[index - 1].pv); }); // color @@ -50,111 +58,4 @@ describe('dynamicHeight funnel', () => { expect(colorFields[0]).toBe('action'); }); }); - - describe('geometry label', () => { - test('label default', () => { - const geometry = funnel.chart.geometries[0]; - const { labelOption, labelsContainer } = geometry; - - expect(labelOption.fields).toEqual(['action', 'pv', FUNNEL_PERCENT]); - expect(labelsContainer.cfg.children.length).toBe(5); - expect(labelOption.cfg.position).toBe('middle'); - expect(labelOption.cfg.style.fill).toBe('#fff'); - expect(labelOption.cfg.style.fontSize).toBe(12); - - PV_DATA.forEach((pvItem, index) => { - const tmp = labelsContainer.cfg.children[index].cfg.data; - expect(tmp.action).toBe(pvItem.action); - expect(tmp.pv).toBe(pvItem.pv); - expect(tmp[FUNNEL_PERCENT]).toBe(Math.round((pvItem.pv / PV_DATA[0].pv) * 100) / 100); - }); - }); - - test('label custom', () => { - // 自定义 label - funnel.update({ - ...funnelOption, - label: { - style: { - fill: '#f00', - fontSize: 14, - }, - callback: (xField, yField) => ({ - content: `${yField}`, - }), - }, - }); - - expect(funnel.chart.geometries[0].labelOption.cfg.style.fill).toBe('#f00'); - expect(funnel.chart.geometries[0].labelOption.cfg.style.fontSize).toBe(14); - }); - - test('label close', () => { - funnel.update({ - ...funnelOption, - label: false, - }); - expect(funnel.chart.geometries[0].labelsContainer.cfg.children.length).toBe(0); - }); - }); - - describe('conversion', () => { - // test('conversionTag default', () => { - // // 默认转化率组件 - // const annotation = funnel.chart.getController('annotation').getComponents(); - // expect(annotation.length).toEqual(4); - // PV_DATA.forEach((pvItem, index) => { - // if (index === 0) return; - // const content = annotation[index - 1].component.get('text').content; - // expect(content).toBe(`转化率${pvItem[FUNNEL_PERCENT] * 100}%`); - // }); - // }); - - test('conversionTag custom', () => { - // 自定义转化率组件 - funnel.update({ - ...funnelOption, - conversionTag: { - style: { - fill: '#f00', - fontSize: 18, - }, - formatter: (datum) => `${datum.$$percentage$$}转化`, - }, - }); - - const customAnnotation = funnel.chart.getController('annotation').getComponents(); - expect(customAnnotation.length).toEqual(4); - PV_DATA.forEach((pvItem, index) => { - if (index === 0) return; - const text = customAnnotation[index - 1].component.get('text'); - expect(text.content).toBe(`${pvItem[FUNNEL_PERCENT]}转化`); - expect(text.style.fill).toBe('#f00'); - expect(text.style.fontSize).toBe(18); - }); - }); - - test('conversionTag close', () => { - // 关闭转化率组件 - funnel.update({ - ...funnelOption, - conversionTag: false, - }); - expect(funnel.chart.getController('annotation').getComponents().length).toBe(0); - }); - }); - - describe('transpose', () => { - test('transpose', () => { - funnel.update({ - ...funnelOption, - isTransposed: true, - }); - - // transpose - const coordinate = funnel.chart.getCoordinate(); - expect(coordinate.isRect).toBe(true); - expect(coordinate.isReflectX).toBe(true); - }); - }); }); diff --git a/__tests__/unit/plots/funnel/index-spec.ts b/__tests__/unit/plots/funnel/index-spec.ts deleted file mode 100644 index b1d5a8d2af..0000000000 --- a/__tests__/unit/plots/funnel/index-spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { Funnel } from '../../../../src'; -import { PV_DATA } from '../../../data/conversion'; -import { createDiv } from '../../../utils/dom'; -import { FUNNEL_PERCENT } from '../../../../src/plots/funnel/constant'; - -describe('basic funnel', () => { - let funnel; - - const funnelOption = { - width: 400, - height: 400, - data: PV_DATA, - xField: 'action', - yField: 'pv', - }; - - beforeAll(() => { - funnel = new Funnel(createDiv('basic funnel'), funnelOption); - funnel.render(); - }); - - afterAll(() => { - funnel.destroy(); - }); - - describe('geometry', () => { - test('geometry test', () => { - const geometry = funnel.chart.geometries[0]; - // 数据量 - expect(geometry.elements.length).toBe(PV_DATA.length); - - // geometry - expect(geometry.type).toBe('interval'); - // @ts-ignore - expect(geometry.adjustOption[0].type).toBe('symmetric'); - - // position - const positionFields = geometry.getAttribute('position').getFields(); - expect(positionFields).toHaveLength(3); - expect(positionFields[0]).toBe('action'); - expect(positionFields[1]).toBe('pv'); - expect(positionFields[2]).toBe(FUNNEL_PERCENT); - - // shape - const shapeFields = geometry.getAttribute('shape').getFields(); - expect(shapeFields[0]).toBe('funnel'); - - // color - const colorFields = geometry.getAttribute('color').getFields(); - expect(colorFields[0]).toBe('action'); - - // transpose - const coordinate = funnel.chart.getCoordinate(); - expect(coordinate.isRect).toBe(true); - expect(coordinate.isTransposed).toBe(true); - }); - }); - - describe('geometry label', () => { - test('label default', () => { - const geometry = funnel.chart.geometries[0]; - const { labelOption, labelsContainer } = geometry; - - expect(labelOption.fields).toEqual(['action', 'pv', FUNNEL_PERCENT]); - expect(labelsContainer.cfg.children.length).toBe(5); - expect(labelOption.cfg.position).toBe('middle'); - expect(labelOption.cfg.style.fill).toBe('#fff'); - expect(labelOption.cfg.style.fontSize).toBe(12); - - PV_DATA.forEach((pvItem, index) => { - const tmp = labelsContainer.cfg.children[index].cfg.data; - expect(tmp.action).toBe(pvItem.action); - expect(tmp.pv).toBe(pvItem.pv); - expect(tmp[FUNNEL_PERCENT]).toBe(Math.round((pvItem.pv / PV_DATA[0].pv) * 100) / 100); - }); - }); - - test('label custom', () => { - // 自定义 label - funnel.update({ - ...funnelOption, - label: { - style: { - fill: '#f00', - fontSize: 14, - }, - callback: (xField, yField) => ({ - content: `${yField}`, - }), - }, - }); - - expect(funnel.chart.geometries[0].labelOption.cfg.style.fill).toBe('#f00'); - expect(funnel.chart.geometries[0].labelOption.cfg.style.fontSize).toBe(14); - }); - - test('label close', () => { - funnel.update({ - ...funnelOption, - label: false, - }); - expect(funnel.chart.geometries[0].labelsContainer.cfg.children.length).toBe(0); - }); - }); - - describe('conversion', () => { - // 会触发chart.update 的问题,等待chart.update更新后回复,conversionTag 使用下方的自定义转化率组件 - // test('conversionTag default', () => { - // const annotation = funnel.chart.getController('annotation').getComponents(); - // expect(annotation.length).toEqual(4); - // PV_DATA.forEach((pvItem, index) => { - // if (index === 0) return; - // const content = annotation[index - 1].component.get('text').content; - // expect(content).toBe(`转化率${pvItem[FUNNEL_PERCENT] * 100}%`); - // }); - // }); - - test('conversionTag custom', () => { - // 自定义转化率组件 - funnel.update({ - ...funnelOption, - conversionTag: { - style: { - fill: '#f00', - fontSize: 18, - }, - formatter: (datum) => `${datum.$$percentage$$}转化`, - }, - }); - - const customAnnotation = funnel.chart.getController('annotation').getComponents(); - expect(customAnnotation.length).toEqual(4); - PV_DATA.forEach((pvItem, index) => { - if (index === 0) return; - const text = customAnnotation[index - 1].component.get('text'); - expect(text.content).toBe(`${pvItem[FUNNEL_PERCENT]}转化`); - expect(text.style.fill).toBe('#f00'); - expect(text.style.fontSize).toBe(18); - }); - }); - - test('conversionTag close', () => { - // 关闭转化率组件 - funnel.update({ - ...funnelOption, - conversionTag: false, - }); - expect(funnel.chart.getController('annotation').getComponents().length).toBe(0); - }); - }); - - describe('transpose', () => { - test('transpose', () => { - funnel.update({ - ...funnelOption, - isTransposed: true, - }); - - // transpose - const coordinate = funnel.chart.getCoordinate(); - expect(coordinate.isRect).toBe(true); - expect(coordinate.isTransposed).toBe(false); - }); - }); -}); diff --git a/__tests__/unit/plots/funnel/label-spec.ts b/__tests__/unit/plots/funnel/label-spec.ts new file mode 100644 index 0000000000..b19e91ce8d --- /dev/null +++ b/__tests__/unit/plots/funnel/label-spec.ts @@ -0,0 +1,92 @@ +import { Funnel } from '../../../../src'; +import { PV_DATA, PV_DATA_COMPARE } from '../../../data/conversion'; +import { createDiv } from '../../../utils/dom'; +import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from '../../../../src/plots/funnel/constant'; + +describe('label', () => { + test('label basic & dynamicHeight', () => { + const funnelOption = { + width: 400, + height: 400, + data: PV_DATA, + xField: 'action', + yField: 'pv', + }; + const funnel = new Funnel(createDiv('label basic & dynamicHeight'), funnelOption); + funnel.render(); + const geometry = funnel.chart.geometries[0]; + const { labelOption, labelsContainer } = geometry; + const labelOptionCfg = labelOption && labelOption.cfg; + expect(labelOption).not.toBeFalsy(); + expect(labelOption && labelOption.fields).toEqual(['action', 'pv', FUNNEL_PERCENT, FUNNEL_CONVERSATION]); + expect(labelOptionCfg.position).toBe('middle'); + expect(labelsContainer.cfg.children.length).toBe(5); + expect(labelOptionCfg.style.fill).toBe('#fff'); + expect(labelOptionCfg.style.fontSize).toBe(12); + const { data } = funnel.chart.getOptions(); + labelsContainer.cfg.children.forEach((item, index) => { + expect(item.get('children')[0].attr('text')).toBe(`${data[index].action} ${data[index].pv}`); + }); + + // 自定义 label + funnel.update({ + ...funnelOption, + dynamicHeight: true, + label: { + style: { + fill: '#f00', + fontSize: 14, + }, + formatter: (datum) => { + return datum[FUNNEL_CONVERSATION].toFixed(2); + }, + }, + }); + + labelsContainer.cfg.children.forEach((item, index) => { + expect(item.get('children')[0].attr('text')).toBe(`${data[index][FUNNEL_CONVERSATION].toFixed(2)}`); + }); + // @ts-ignore + const customLabelCfg = funnel.chart.geometries[0].labelOption.cfg; + expect(customLabelCfg.style.fill).toBe('#f00'); + expect(customLabelCfg.style.fontSize).toBe(14); + + funnel.update({ + ...funnelOption, + label: false, + }); + expect(funnel.chart.geometries[0].labelsContainer.cfg.children.length).toBe(0); + + funnel.destroy(); + }); + + test('label compare', () => { + // 自定义 label + const funnel = new Funnel(createDiv('label compare'), { + width: 400, + height: 400, + data: PV_DATA_COMPARE, + compareField: 'quarter', + xField: 'action', + yField: 'pv', + }); + funnel.render(); + + funnel.chart.views.forEach((funnelView) => { + const geometry = funnelView.geometries[0]; + const { labelOption, labelsContainer } = geometry; + const { data } = funnelView.getOptions(); + const labelOptionCfg = labelOption && labelOption.cfg; + expect(labelOption && labelOption.fields).toEqual(['action', 'pv', FUNNEL_PERCENT, FUNNEL_CONVERSATION]); + expect(labelOptionCfg.position).toBe('left'); + expect(labelsContainer.cfg.children.length).toBe(5); + expect(labelOptionCfg.style.fill).toBe('#fff'); + expect(labelOptionCfg.style.fontSize).toBe(12); + labelsContainer.cfg.children.forEach((item, index) => { + expect(item.get('children')[0].attr('text')).toBe(`${data[index].pv}`); + }); + }); + + funnel.destroy(); + }); +}); diff --git a/__tests__/unit/plots/funnel/tooltip-spec.ts b/__tests__/unit/plots/funnel/tooltip-spec.ts index 4c10f44302..58c7083b9c 100644 --- a/__tests__/unit/plots/funnel/tooltip-spec.ts +++ b/__tests__/unit/plots/funnel/tooltip-spec.ts @@ -13,6 +13,12 @@ describe('funnel tooltip', () => { yField: 'pv', tooltip: { title: 'funnel', + formatter: () => { + return { + name: 'abc', + value: '123', + }; + }, }, }; diff --git a/src/plots/funnel/adaptor.ts b/src/plots/funnel/adaptor.ts index 472426e321..4616879b57 100644 --- a/src/plots/funnel/adaptor.ts +++ b/src/plots/funnel/adaptor.ts @@ -5,6 +5,7 @@ import { FunnelOptions } from './types'; import { basicFunnel } from './geometries/basic'; import { compareFunnel } from './geometries/compare'; import { dynamicHeightFunnel } from './geometries/dynamic-height'; +import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from './constant'; /** * @@ -29,13 +30,7 @@ function defaultOptions(params: Params): Params { fill: '#fff', fontSize: 12, }, - callback: compareField - ? (xField, yField) => ({ - content: `${yField}`, - }) - : (xField, yField) => ({ - content: `${xField} ${yField}`, - }), + formatter: compareField ? (datum) => `${datum[yField]}` : (datum) => `${datum[xField]} ${datum[yField]}`, }, tooltip: { showTitle: false, @@ -50,7 +45,7 @@ function defaultOptions(params: Params): Params { offsetX: 10, offsetY: 0, style: {}, - formatter: (datum) => `转化率${(datum.$$percentage$$ * 100).toFixed(2)}%`, + formatter: (datum) => `转化率${(datum[FUNNEL_CONVERSATION] * 100).toFixed(2)}%`, }, }; diff --git a/src/plots/funnel/constant.ts b/src/plots/funnel/constant.ts index 5cd8c501b2..192dd45186 100644 --- a/src/plots/funnel/constant.ts +++ b/src/plots/funnel/constant.ts @@ -1,5 +1,7 @@ -// 漏斗转化率: data[n][yField] / data[0][yField] +// 漏斗占比: data[n][yField] / data[0][yField] export const FUNNEL_PERCENT = '$$percentage$$'; +// 漏斗转化率: data[n][yField] / data[n-1][yField]; +export const FUNNEL_CONVERSATION = '$$conversion$$'; // 漏斗单项占总体和的百分比,用于动态漏斗图计算高度: // data[n][yField] / sum(data[0-n][yField]) export const FUNNEL_TOTAL_PERCENT = '$$total_percentage$$'; diff --git a/src/plots/funnel/geometries/basic.ts b/src/plots/funnel/geometries/basic.ts index 202c921ae2..fe3cdc4d6e 100644 --- a/src/plots/funnel/geometries/basic.ts +++ b/src/plots/funnel/geometries/basic.ts @@ -6,7 +6,7 @@ import { Params } from '../../../core/adaptor'; import { Datum, Data } from '../../../types/common'; import { geometry as baseGeometry } from '../../../adaptor/geometries/base'; import { FunnelOptions } from '../types'; -import { FUNNEL_PERCENT } from '../constant'; +import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from '../constant'; import { geometryLabel, conversionTagComponent } from './common'; /** @@ -19,9 +19,10 @@ function field(params: Params): Params { let formatData = []; // format 数据 if (data[0][yField]) { - formatData = map(data, (row) => { + formatData = map(data, (row, index) => { if (row[yField] !== undefined) { row[FUNNEL_PERCENT] = row[yField] / data[0][yField]; + row[FUNNEL_CONVERSATION] = index === 0 ? 1 : row[yField] / data[index - 1][yField]; } return row; }); @@ -40,7 +41,7 @@ function geometry(params: Params): Params { const { chart, options } = params; const { xField, yField, color, tooltip } = options; - const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT]); + const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT, FUNNEL_CONVERSATION]); baseGeometry({ chart, diff --git a/src/plots/funnel/geometries/common.ts b/src/plots/funnel/geometries/common.ts index 11a66826eb..4924d87831 100644 --- a/src/plots/funnel/geometries/common.ts +++ b/src/plots/funnel/geometries/common.ts @@ -2,8 +2,8 @@ import { Geometry } from '@antv/g2'; import { LineOption } from '@antv/g2/lib/interface'; import { isFunction } from '@antv/util'; import { Datum, Data } from '../../../types/common'; -import { deepAssign } from '../../../utils'; -import { FUNNEL_PERCENT } from '../constant'; +import { transformLabel } from '../../../utils'; +import { FUNNEL_PERCENT, FUNNEL_CONVERSATION } from '../constant'; import { Params } from '../../../core/adaptor'; import { FunnelOptions } from '../types'; @@ -20,9 +20,9 @@ export function geometryLabel(geometry: Geometry) { } else { const { callback, ...cfg } = label; geometry.label({ - fields: [xField, yField, FUNNEL_PERCENT], + fields: [xField, yField, FUNNEL_PERCENT, FUNNEL_CONVERSATION], callback, - cfg, + cfg: transformLabel(cfg), }); } return params; diff --git a/src/plots/funnel/geometries/compare.ts b/src/plots/funnel/geometries/compare.ts index c4e5cd729b..52a7386821 100644 --- a/src/plots/funnel/geometries/compare.ts +++ b/src/plots/funnel/geometries/compare.ts @@ -6,7 +6,7 @@ import { Datum, Data } from '../../../types/common'; import { getTooltipMapping } from '../../../utils/tooltip'; import { geometry as baseGeometry } from '../../../adaptor/geometries/base'; import { FunnelOptions } from '../types'; -import { FUNNEL_PERCENT } from '../constant'; +import { FUNNEL_PERCENT, FUNNEL_CONVERSATION } from '../constant'; import { geometryLabel, conversionTagComponent } from './common'; /** @@ -20,13 +20,15 @@ function field(params: Params): Params { let formatData = []; if (data[0][yField]) { // format 数据 - const firstRecord = {}; + const depRecord = {}; formatData = map(data, (row) => { if (row[yField] !== undefined && row[compareField]) { - if (!firstRecord[row[compareField]]) { - firstRecord[row[compareField]] = row[yField]; - } - row[FUNNEL_PERCENT] = row[yField] / firstRecord[row[compareField]]; + if (!depRecord[row[compareField]]) depRecord[row[compareField]] = row[yField]; + if (!depRecord[`last_${row[compareField]}`]) depRecord[`last_${row[compareField]}`] = row[yField]; + row[FUNNEL_PERCENT] = row[yField] / depRecord[row[compareField]]; + row[FUNNEL_CONVERSATION] = row[yField] / depRecord[`last_${row[compareField]}`]; + // 更新 lastVersion + depRecord[`last_${row[compareField]}`] = row[yField]; } return row; }); @@ -63,7 +65,7 @@ function geometry(params: Params): Params { }); } // 绘制图形 - const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT]); + const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT, FUNNEL_CONVERSATION]); baseGeometry({ chart: view, diff --git a/src/plots/funnel/geometries/dynamic-height.ts b/src/plots/funnel/geometries/dynamic-height.ts index 61a6a7b032..b19deacc58 100644 --- a/src/plots/funnel/geometries/dynamic-height.ts +++ b/src/plots/funnel/geometries/dynamic-height.ts @@ -2,7 +2,7 @@ import { map, reduce } from '@antv/util'; import { LineOption } from '@antv/g2/lib/interface'; import { flow, findGeometry } from '../../../utils'; import { Params } from '../../../core/adaptor'; -import { FUNNEL_PERCENT, FUNNEL_TOTAL_PERCENT, PLOYGON_X, PLOYGON_Y } from '../constant'; +import { FUNNEL_PERCENT, FUNNEL_CONVERSATION, FUNNEL_TOTAL_PERCENT, PLOYGON_X, PLOYGON_Y } from '../constant'; import { geometry as baseGeometry } from '../../../adaptor/geometries/base'; import { getTooltipMapping } from '../../../utils/tooltip'; import { Datum, Data } from '../../../types/common'; @@ -68,6 +68,7 @@ function field(params: Params): Params { row[PLOYGON_X] = x; row[PLOYGON_Y] = y; row[FUNNEL_PERCENT] = row[yField] / data[0][yField]; + row[FUNNEL_CONVERSATION] = index === 0 ? 1 : row[yField] / data[index - 1][yField]; return row; }); @@ -84,7 +85,7 @@ function geometry(params: Params): Params { const { chart, options } = params; const { xField, yField, color, tooltip } = options; - const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT]); + const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField, FUNNEL_PERCENT, FUNNEL_CONVERSATION]); // 绘制漏斗图 baseGeometry({ chart, From 81f64fc3ea2d454cb579b1fdac3afbf9de2c2509 Mon Sep 17 00:00:00 2001 From: "aiyin.lzy" Date: Tue, 17 Nov 2020 22:34:04 +0800 Subject: [PATCH 5/7] fix: remove unnessary code --- __tests__/unit/plots/funnel/conversion-tag-spec.ts | 2 -- __tests__/unit/plots/funnel/dynamic-height-spec.ts | 2 +- examples/funnel/basic/demo/basic-transpose.ts | 5 +++++ examples/funnel/basic/demo/basic.ts | 7 ------- src/plots/funnel/adaptor.ts | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/__tests__/unit/plots/funnel/conversion-tag-spec.ts b/__tests__/unit/plots/funnel/conversion-tag-spec.ts index 2e35b4a923..f82db80139 100644 --- a/__tests__/unit/plots/funnel/conversion-tag-spec.ts +++ b/__tests__/unit/plots/funnel/conversion-tag-spec.ts @@ -17,11 +17,9 @@ describe('conversition tag', () => { const annotation = funnel.chart.getController('annotation').getComponents(); expect(annotation.length).toEqual(4); - console.log(annotation); PV_DATA.forEach((pvItem, index) => { if (index === 0) return; const content = annotation[index - 1].component.get('text').content; - console.log(content); expect(content).toBe(`转化率${(pvItem[FUNNEL_CONVERSATION] * 100).toFixed(2)}%`); }); diff --git a/__tests__/unit/plots/funnel/dynamic-height-spec.ts b/__tests__/unit/plots/funnel/dynamic-height-spec.ts index 6af4b53e71..dd403c36c8 100644 --- a/__tests__/unit/plots/funnel/dynamic-height-spec.ts +++ b/__tests__/unit/plots/funnel/dynamic-height-spec.ts @@ -27,7 +27,7 @@ describe('dynamicHeight funnel', () => { }); afterAll(() => { - // funnel.destroy(); + funnel.destroy(); }); describe('geometry', () => { diff --git a/examples/funnel/basic/demo/basic-transpose.ts b/examples/funnel/basic/demo/basic-transpose.ts index d7284f2b90..f8b846ca93 100644 --- a/examples/funnel/basic/demo/basic-transpose.ts +++ b/examples/funnel/basic/demo/basic-transpose.ts @@ -13,6 +13,11 @@ const funnelPlot = new Funnel('container', { xField: 'stage', yField: 'number', isTransposed: true, + tooltip: { + formatter: (datum) => { + return { name: datum.stage, value: `${datum.number}个` }; + }, + }, }); funnelPlot.render(); diff --git a/examples/funnel/basic/demo/basic.ts b/examples/funnel/basic/demo/basic.ts index 38a6f67af7..bba37b66e4 100644 --- a/examples/funnel/basic/demo/basic.ts +++ b/examples/funnel/basic/demo/basic.ts @@ -13,13 +13,6 @@ const funnelPlot = new Funnel('container', { xField: 'stage', yField: 'number', legend: false, - tooltip: { - formatter: (datum) => { - return { name: 'abc', value: '123' }; - }, - }, }); funnelPlot.render(); - -console.log(funnelPlot.chart); diff --git a/src/plots/funnel/adaptor.ts b/src/plots/funnel/adaptor.ts index 4616879b57..bf8956df95 100644 --- a/src/plots/funnel/adaptor.ts +++ b/src/plots/funnel/adaptor.ts @@ -5,7 +5,7 @@ import { FunnelOptions } from './types'; import { basicFunnel } from './geometries/basic'; import { compareFunnel } from './geometries/compare'; import { dynamicHeightFunnel } from './geometries/dynamic-height'; -import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from './constant'; +import { FUNNEL_CONVERSATION } from './constant'; /** * From 6bb6e9a176df6b44dba2ad821ffdffc82ae0fe90 Mon Sep 17 00:00:00 2001 From: "aiyin.lzy" Date: Tue, 17 Nov 2020 22:50:05 +0800 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0=20demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/funnel/basic/demo/basic-transpose.ts | 12 ++++++++++++ examples/funnel/basic/demo/compare-transpose.ts | 9 +++++++++ examples/funnel/basic/demo/compare.ts | 9 --------- .../funnel/basic/demo/dynamic-height-transpose.ts | 1 + src/plots/funnel/index.ts | 3 +-- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/examples/funnel/basic/demo/basic-transpose.ts b/examples/funnel/basic/demo/basic-transpose.ts index f8b846ca93..203b3f05ab 100644 --- a/examples/funnel/basic/demo/basic-transpose.ts +++ b/examples/funnel/basic/demo/basic-transpose.ts @@ -13,6 +13,18 @@ const funnelPlot = new Funnel('container', { xField: 'stage', yField: 'number', isTransposed: true, + label: { + formatter: (datum) => { + // 提供占比$$percentage$$,转化率$$conversion$$两种格式 + return `${datum.stage}:${datum.number}`; + }, + }, + conversionTag: { + formatter: (datum) => { + // 提供占比$$percentage$$,转化率$$conversion$$两种格式 + return datum.$$conversion$$.toFixed(2); + }, + }, tooltip: { formatter: (datum) => { return { name: datum.stage, value: `${datum.number}个` }; diff --git a/examples/funnel/basic/demo/compare-transpose.ts b/examples/funnel/basic/demo/compare-transpose.ts index 20b90e16c5..ac7fccd196 100644 --- a/examples/funnel/basic/demo/compare-transpose.ts +++ b/examples/funnel/basic/demo/compare-transpose.ts @@ -19,5 +19,14 @@ const funnelPlot = new Funnel('container', { yField: 'number', compareField: 'company', isTransposed: true, + conversionTag: { + offsetX: 10, + style: { + fill: '#666', + fontSize: 12, + }, + formatter: (data) => `占比${(data.$$percentage$$ * 100).toFixed(2)}%`, + }, + legend: false, }); funnelPlot.render(); diff --git a/examples/funnel/basic/demo/compare.ts b/examples/funnel/basic/demo/compare.ts index b0cba7684a..1978ca5ffb 100644 --- a/examples/funnel/basic/demo/compare.ts +++ b/examples/funnel/basic/demo/compare.ts @@ -27,15 +27,6 @@ const funnelPlot = new Funnel('container', { formatter: (v) => `${v}次`, }, }, - conversionTag: { - offsetX: 10, - offsetY: 0, - style: { - fill: '#666', - fontSize: 12, - }, - formatter: (data) => `转化率${(data.$$percentage$$ * 100).toFixed(2)}%`, - }, legend: false, }); funnelPlot.render(); diff --git a/examples/funnel/basic/demo/dynamic-height-transpose.ts b/examples/funnel/basic/demo/dynamic-height-transpose.ts index bf860129c6..f97d4accf4 100644 --- a/examples/funnel/basic/demo/dynamic-height-transpose.ts +++ b/examples/funnel/basic/demo/dynamic-height-transpose.ts @@ -14,6 +14,7 @@ const funnelPlot = new Funnel('container', { yField: 'number', dynamicHeight: true, isTransposed: true, + legend: false, }); funnelPlot.render(); diff --git a/src/plots/funnel/index.ts b/src/plots/funnel/index.ts index 1a0e65a60f..6d39600cd8 100644 --- a/src/plots/funnel/index.ts +++ b/src/plots/funnel/index.ts @@ -1,5 +1,4 @@ import { Plot } from '../../core/plot'; -import { deepAssign } from '../../utils'; import { Adaptor } from '../../core/adaptor'; import { FunnelOptions } from './types'; import { adaptor } from './adaptor'; @@ -16,7 +15,7 @@ export class Funnel extends Plot { protected getDefaultOptions(): Partial { // 由于不同漏斗图 defaultOption 有部分逻辑不同,此处仅处理 core.getDefaultOptions 覆盖范围,funnel 的 defaulOption 为不分散逻辑统一写到 adaptor 的 defaultOption 中 return { - appendPadding: [0, 50], + appendPadding: [0, 80], }; } From 5e833dc57d89edbe4f74633020de6ec426125e38 Mon Sep 17 00:00:00 2001 From: hustcc Date: Wed, 18 Nov 2020 09:21:52 +0800 Subject: [PATCH 7/7] Update constant.ts --- src/plots/funnel/constant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/funnel/constant.ts b/src/plots/funnel/constant.ts index 192dd45186..0a1f84603c 100644 --- a/src/plots/funnel/constant.ts +++ b/src/plots/funnel/constant.ts @@ -4,7 +4,7 @@ export const FUNNEL_PERCENT = '$$percentage$$'; export const FUNNEL_CONVERSATION = '$$conversion$$'; // 漏斗单项占总体和的百分比,用于动态漏斗图计算高度: // data[n][yField] / sum(data[0-n][yField]) -export const FUNNEL_TOTAL_PERCENT = '$$total_percentage$$'; +export const FUNNEL_TOTAL_PERCENT = '$$totalPercentage$$'; // 漏斗多边型 x 坐标 export const PLOYGON_X = '$$x$$'; export const PLOYGON_Y = '$$y$$';