From 0882b32d386b5f25fc895f43f75f1ea76e67b8b5 Mon Sep 17 00:00:00 2001 From: Visiky <736929286@qq.com> Date: Tue, 15 Jun 2021 16:50:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor(funnel):=20=E6=94=AF=E6=8C=81=E5=B0=96?= =?UTF-8?q?=E5=BA=95=E6=BC=8F=E6=96=97=E5=9B=BE=E4=BB=A5=E5=8F=8A=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E8=AE=BE=E7=BD=AE=20(#2634)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(funnel): 补充漏斗图动态高度的文档说明 * refactor(funnel): 漏斗图抽取 constant 以及暴露常用的字段作为静态属性 * feat(funnel): 基础漏斗图支持shape设置金字塔以及支持 funnelStyle 设置 * docs: 添加尖底漏斗图 demo * feat(mix-plot): 多图层图表增加 funnel & 添加复合漏斗图 demo * test(funnel): 增加漏斗图单测 --- __tests__/unit/plots/funnel/index-spec.ts | 38 ++++++++++ __tests__/unit/plots/funnel/style-spec.ts | 52 +++++++++++++ docs/api/plots/funnel.en.md | 71 ++++++++++++++---- docs/api/plots/funnel.zh.md | 73 +++++++++++++++---- docs/common/plot-cfg.en.md | 2 +- docs/common/plot-cfg.zh.md | 2 +- examples/more-plots/funnel/demo/meta.json | 16 ++++ examples/more-plots/funnel/demo/pyramid.ts | 19 +++++ .../more-plots/funnel/demo/static-field.ts | 30 ++++++++ .../multi-view/demo/composite-funnel.ts | 54 ++++++++++++++ examples/plugin/multi-view/demo/meta.json | 8 ++ src/locales/en_US.ts | 2 +- src/plots/funnel/adaptor.ts | 49 +++++-------- src/plots/funnel/constant.ts | 35 +++++++++ src/plots/funnel/geometries/basic.ts | 5 +- src/plots/funnel/geometries/compare.ts | 9 +-- src/plots/funnel/geometries/dynamic-height.ts | 3 +- src/plots/funnel/index.ts | 19 ++++- src/plots/funnel/types.ts | 17 ++++- src/plots/mix/utils.ts | 8 ++ 20 files changed, 433 insertions(+), 79 deletions(-) create mode 100644 __tests__/unit/plots/funnel/index-spec.ts create mode 100644 __tests__/unit/plots/funnel/style-spec.ts create mode 100644 examples/more-plots/funnel/demo/pyramid.ts create mode 100644 examples/more-plots/funnel/demo/static-field.ts create mode 100644 examples/plugin/multi-view/demo/composite-funnel.ts diff --git a/__tests__/unit/plots/funnel/index-spec.ts b/__tests__/unit/plots/funnel/index-spec.ts new file mode 100644 index 0000000000..b715ad7889 --- /dev/null +++ b/__tests__/unit/plots/funnel/index-spec.ts @@ -0,0 +1,38 @@ +import { Funnel, FUNNEL_CONVERSATION_FIELD } from '../../../../src'; +import { DEFAULT_OPTIONS } from '../../../../src/plots/funnel/constant'; +import { createDiv } from '../../../utils/dom'; + +describe('funnel', () => { + const data = [ + { stage: '简历筛选', number: 253 }, + { stage: '初试人数', number: 151 }, + { stage: '复试人数', number: 113 }, + { stage: '录取人数', number: 87 }, + { stage: '入职人数', number: 59 }, + ]; + + const plot = new Funnel(createDiv(), { + data: data, + xField: 'stage', + yField: 'number', + legend: false, + }); + + plot.render(); + + it('defaultOptions', () => { + expect(plot.type).toBe('funnel'); + + expect(Funnel.getDefaultOptions()).toEqual(DEFAULT_OPTIONS); + // @ts-ignore + expect(plot.getDefaultOptions()).toEqual(DEFAULT_OPTIONS); + }); + + it('static properties', () => { + expect(Funnel.CONVERSATION_FIELD).toBeDefined(); + // 兼容旧的 + expect(Funnel.CONVERSATION_FIELD).toBe(FUNNEL_CONVERSATION_FIELD); + expect(Funnel.PERCENT_FIELD).toBeDefined(); + expect(Funnel.TOTAL_PERCENT_FIELD).toBeDefined(); + }); +}); diff --git a/__tests__/unit/plots/funnel/style-spec.ts b/__tests__/unit/plots/funnel/style-spec.ts new file mode 100644 index 0000000000..eaadc66ed6 --- /dev/null +++ b/__tests__/unit/plots/funnel/style-spec.ts @@ -0,0 +1,52 @@ +import { Funnel } from '../../../../src'; +import { PV_DATA_COMPARE } from '../../../data/conversion'; +import { createDiv } from '../../../utils/dom'; + +describe('funnel', () => { + const plot = new Funnel(createDiv(), { + data: PV_DATA_COMPARE, + autoFit: true, + xField: 'action', + yField: 'pv', + minSize: 0.3, + maxSize: 0.8, + funnelStyle: { + fill: 'red', + }, + }); + + plot.render(); + + it('default', () => { + expect(plot.type).toBe('funnel'); + + const geometry = plot.chart.geometries[0]; + const elements = geometry.elements; + expect(elements[0].shape.attr('fill')).toEqual('red'); + + plot.update({ funnelStyle: () => ({ fill: 'red', stroke: 'blue' }) }); + expect(plot.chart.geometries[0].elements[0].shape.attr('stroke')).toEqual('blue'); + }); + + it('对比漏斗图', () => { + plot.update({ compareField: 'quarter' }); + expect(plot.chart.views.length).toEqual(2); + + const geometry = plot.chart.views[0].geometries[0]; + const elements = geometry.elements; + expect(elements[0].shape.attr('fill')).toEqual('red'); + expect(elements[0].shape.attr('stroke')).toEqual('blue'); + expect(elements[0].shape.attr('lineWidth')).toEqual(1); + + plot.update({ funnelStyle: undefined }); + // 还原默认 + expect(plot.chart.views[0].geometries[0].elements[0].shape.attr('stroke')).toEqual('#fff'); + + plot.update({ funnelStyle: { stroke: 'yellow' } }); + expect(plot.chart.views[0].geometries[0].elements[0].shape.attr('stroke')).toEqual('yellow'); + + // function + plot.update({ funnelStyle: () => ({ stroke: 'blue' }) }); + expect(plot.chart.views[0].geometries[0].elements[0].shape.attr('stroke')).toEqual('blue'); + }); +}); diff --git a/docs/api/plots/funnel.en.md b/docs/api/plots/funnel.en.md index 941697ceb9..805fc30b58 100644 --- a/docs/api/plots/funnel.en.md +++ b/docs/api/plots/funnel.en.md @@ -23,30 +23,38 @@ Configure the data source. The data source is a collection of objects. For examp Field for comparing. - #### seriesField **optional** _string_ Field for spliting. +#### meta + +`markdown:docs/common/meta.en.md` + +### Graphic Style + #### isTransposed **optional** _boolean_ _default:_ `false` Whether the plot is transposed. -#### meta +#### shape -`markdown:docs/common/meta.en.md` +**optional** _string_ `'funnel' | 'pyramid'` -### Graphic Style +漏斗图形状。shape 设置为 'pyramid' 时,漏斗图展示为尖底样式(形如:金字塔)。目前只在基础漏斗图中适用。不适用场景: + +1. 在对比漏斗图(`compareField` 存在时)不适用 +2. 设置 dynamicHeight: 'true' 时不适用,此时需要设置 shape 为空。 #### dynamicHeight **optional** _boolean_ _default:_ `false` -Whether the height is dynamic. +Whether the height is dynamic. When set to `true`, the height of each elemnet in funnel plot is directly proportional to the corresponding value of yField. #### maxSize @@ -64,15 +72,13 @@ the min size of graphic,is between 0 and 1, default 0。 Tip: when set dynamicHeight to be true, this field is invalid -#### conversionTag +#### funnelStyle -**optional** _false | object_ +**optional** _object_ -Configure the conversion rate component. - -Defalut: `{offsetX: 10, offsetY: 0, formatter: (datum) => '转化率' + datum.$$percentage$$ * 100 + '%',}`。 +Graphic style of funnel. You can either pass in the 'shapeStyle' structure directly, or you can use callbacks to return different styles for different data. For the ShapeStyle data structure, see: -`markdown:docs/common/color.en.md` +`markdown:docs/common/shape-style.en.md` ### Plot Components @@ -84,6 +90,16 @@ Defalut: `{offsetX: 10, offsetY: 0, formatter: (datum) => '转化率' + datum.$$ `markdown:docs/common/label.en.md` +#### conversionTag + +**optional** _false | object_ + +Configure the conversion rate component. + +Defalut: `{offsetX: 10, offsetY: 0, formatter: (datum) => '转化率' + datum.$$percentage$$ * 100 + '%',}`。 + +`markdown:docs/common/color.en.md` + #### 图例 `markdown:docs/common/legend.en.md` @@ -105,21 +121,48 @@ Defalut: `{offsetX: 10, offsetY: 0, formatter: (datum) => '转化率' + datum.$$ `markdown:docs/common/theme.en.md` +### Static Properties + +Funnel plot provides static properties, makes it easy to use. + +#### Static variables -### Static Variables +| Field | Description | +| --- | --- | +| `CONVERSATION_FIELD` | The corresponding value of this field is an array, stores the current and previous values of the funnel, for example, [263, 151], from which the user can calculate the conversion rate | +| `PERCENT_FIELD` | The corresponding value of this field represents the `conversion percentage` between current value and previous value | +| `TOTAL_PERCENT_FIELD` | The corresponding value of this field represents the percentage of total conversion rate | -Funnel plot provides static variables, such as: +**Example:** + +```javascript +import { Funnel } from '@antv/g2plot'; +// usage +{ + conversionTag: { + formatter: (datum) => { + return `${(datum[Funnel.CONVERSATION_FIELD][1] / datum[Funnel.CONVERSATION_FIELD][0] * 100).toFixed(2)}%`; + }, + }, + label: { + formatter: (datum) => { + return `${(datum[Funnel.PERCENT_FIELD] * 100).toFixed(2)}%`; + } + } +} +``` #### FUNNEL_CONVERSATION_FIELD +> Attention: you can use `Funnel.CONVERSATION_FIELD` to replace `FUNNEL_CONVERSATION_FIELD` in the latest version. + FUNNEL_CONVERSATION_FIELD is an array, stores the current and previous values of the funnel, for example, [263, 151], from which the user can calculate the conversion rate, for example: ```javascript // 引用 import { FUNNEL_CONVERSATION_FIELD } from '@antv/g2plot'; - // 使用 { conversionTag: { diff --git a/docs/api/plots/funnel.zh.md b/docs/api/plots/funnel.zh.md index 42e5e3bca3..c8842ec86a 100644 --- a/docs/api/plots/funnel.zh.md +++ b/docs/api/plots/funnel.zh.md @@ -29,24 +29,32 @@ order: 9 分组字段。声明此字段时会自动渲染为分组漏斗图 +#### meta + +`markdown:docs/common/meta.zh.md` + +### 图形样式 + #### isTransposed **optional** _boolean_ _default:_ `false` 是否转置。 -#### meta +#### shape -`markdown:docs/common/meta.zh.md` +**optional** _string_ `'funnel' | 'pyramid'` -### 图形样式 +漏斗图形状。shape 设置为 'pyramid' 时,漏斗图展示为尖底样式(形如:金字塔)。目前只在基础漏斗图中适用。不适用场景: + +1. 在对比漏斗图(`compareField` 存在时)不适用 +2. 设置 dynamicHeight: 'true' 时不适用,此时需要设置 shape 为空。 #### dynamicHeight **optional** _boolean_ _default:_ `false` -是否映射为动态高度。 - +是否映射为动态高度。当设置为 'true' 时,漏斗图每个条目(图表元素)的高度和 y 轴字段对应数值成正比。 #### maxSize @@ -56,7 +64,6 @@ order: 9 注:因动态高度漏斗图将值映射为高度,因此声明 dynamicHeight: true 时,该字段无效 - #### minSize **optional** _number_ _default:_ `1` @@ -64,15 +71,14 @@ order: 9 图形最小宽度,为 [0, 1] 之间小数,默认为 0。 注:因动态高度漏斗图将值映射为高度,因此声明 dynamicHeight: true 时,该字段无效 -#### conversionTag -**optional** _false | object_ +#### funnelStyle -配置转化率组件。 +**可选** _object_ -默认配置:`{offsetX: 10, offsetY: 0, formatter: (datum) => '转化率' + datum.$$percentage$$ * 100 + '%',}`。 +漏斗图样式。可以直接传入 `ShapeStyle` 结构,也可以使用回调函数的方式,针对不同的数据,来返回不同的样式。对于 ShapeStyle 的数据结构,可以参考: -`markdown:docs/common/color.zh.md` +`markdown:docs/common/shape-style.zh.md` ### 图表组件 @@ -84,6 +90,16 @@ order: 9 `markdown:docs/common/label.zh.md` +#### conversionTag + +**optional** _false | object_ + +配置转化率组件。 + +默认配置:`{offsetX: 10, offsetY: 0, formatter: (datum) => '转化率' + datum.$$percentage$$ * 100 + '%',}`。 + +`markdown:docs/common/color.zh.md` + #### 图例 `markdown:docs/common/legend.zh.md` @@ -101,26 +117,53 @@ order: 9 `markdown:docs/common/chart-methods.zh.md` - ### 图表主题 `markdown:docs/common/theme.zh.md` +### 静态属性 -### 静态变量 +漏斗图提供静态属性,以方便用户在实际项目中的使用. -漏斗图提供静态变量,以方便用户在实际项目中的使用,包括 +#### 静态变量 + +| 字段 key 值 | 说明 | +| --- | --- | +| `CONVERSATION_FIELD` | 该字段对应数值为数组,存储漏斗当前和上一项值,例如 [263, 151], 用户可由此计算转化率 | +| `PERCENT_FIELD` | 该字段对应数值代表的是当前数值和上一项值的转化率百分比 | +| `TOTAL_PERCENT_FIELD` | 该字段对应数值代表的总转化率百分比 | + +**使用示例:** + +```javascript +// 引用 +import { Funnel } from '@antv/g2plot'; +// 使用 +{ + conversionTag: { + formatter: (datum) => { + return `${(datum[Funnel.CONVERSATION_FIELD][1] / datum[Funnel.CONVERSATION_FIELD][0] * 100).toFixed(2)}%`; + }, + }, + label: { + formatter: (datum) => { + return `${(datum[Funnel.PERCENT_FIELD] * 100).toFixed(2)}%`; + } + } +} +``` #### FUNNEL_CONVERSATION_FIELD +> 注意:在最新版本中,您可以直接使用 `Funnel.CONVERSATION_FIELD` 来替代 `FUNNEL_CONVERSATION_FIELD` + FUNNEL_CONVERSATION_FIELD 为数组,存储漏斗当前和上一项值,例如 [263, 151], 用户可由此计算转化率,例如: ```javascript // 引用 import { FUNNEL_CONVERSATION_FIELD } from '@antv/g2plot'; - // 使用 { conversionTag: { diff --git a/docs/common/plot-cfg.en.md b/docs/common/plot-cfg.en.md index 9546433aff..c9c9a5a5f5 100644 --- a/docs/common/plot-cfg.en.md +++ b/docs/common/plot-cfg.en.md @@ -6,7 +6,7 @@ plot 类型,通过传入指定 type 的 plot,可以在图层上渲染 G2Plot 目前开放的图表类型有以下类型: -- **基础图表**:`'line' | 'pie' | 'column' | 'bar' | 'area' | 'gauge' | 'scatter' | 'histogram'` +- **基础图表**:`'line' | 'pie' | 'column' | 'bar' | 'area' | 'gauge' | 'scatter' | 'histogram' | 'funnel` - **迷你图表**:`'tiny-line' | 'tiny-column' | 'tiny-area' | 'progress' | 'ring-progress'` #### IPlot.options diff --git a/docs/common/plot-cfg.zh.md b/docs/common/plot-cfg.zh.md index 97dc15fdce..0c01d824f5 100644 --- a/docs/common/plot-cfg.zh.md +++ b/docs/common/plot-cfg.zh.md @@ -6,7 +6,7 @@ plot 类型,通过传入指定 type 的 plot,可以在图层上渲染 G2Plot 目前开放的图表类型有以下类型: -- **基础图表**:`'line' | 'pie' | 'column' | 'bar' | 'area' | 'gauge' |'scatter' | 'histogram'` +- **基础图表**:`'line' | 'pie' | 'column' | 'bar' | 'area' | 'gauge' |'scatter' | 'histogram' | 'funnel'` - **迷你图表**:`'tiny-line' | 'tiny-column' | 'tiny-area' | 'progress' | 'ring-progress'` #### IPlot.options diff --git a/examples/more-plots/funnel/demo/meta.json b/examples/more-plots/funnel/demo/meta.json index 5cedc92036..3515ce0a9d 100644 --- a/examples/more-plots/funnel/demo/meta.json +++ b/examples/more-plots/funnel/demo/meta.json @@ -12,6 +12,22 @@ }, "screenshot": "https://gw.alicdn.com/tfs/TB158FxuAT2gK0jSZPcXXcKkpXa-646-500.png" }, + { + "filename": "static-field.ts", + "title": { + "zh": "漏斗图: 使用静态变量", + "en": "Funnel plot - static field" + }, + "screenshot": "https://gw.alicdn.com/tfs/TB158FxuAT2gK0jSZPcXXcKkpXa-646-500.png" + }, + { + "filename": "pyramid.ts", + "title": { + "zh": "尖底漏斗图", + "en": "Pyramid funnel plot" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/L88CCTUbpo/9127293d-767c-4a57-8852-e7c27fc4df8d.png" + }, { "filename": "dynamic-height.ts", "title": { diff --git a/examples/more-plots/funnel/demo/pyramid.ts b/examples/more-plots/funnel/demo/pyramid.ts new file mode 100644 index 0000000000..b421f22a57 --- /dev/null +++ b/examples/more-plots/funnel/demo/pyramid.ts @@ -0,0 +1,19 @@ +import { Funnel } from '@antv/g2plot'; + +const data = [ + { stage: '简历筛选', number: 253 }, + { stage: '初试人数', number: 151 }, + { stage: '复试人数', number: 113 }, + { stage: '录取人数', number: 87 }, + { stage: '入职人数', number: 59 }, +]; + +const funnelPlot = new Funnel('container', { + data: data, + xField: 'stage', + yField: 'number', + legend: false, + shape: 'pyramid', +}); + +funnelPlot.render(); diff --git a/examples/more-plots/funnel/demo/static-field.ts b/examples/more-plots/funnel/demo/static-field.ts new file mode 100644 index 0000000000..8ce09a65f0 --- /dev/null +++ b/examples/more-plots/funnel/demo/static-field.ts @@ -0,0 +1,30 @@ +import { Funnel } from '@antv/g2plot'; + +const data = [ + { stage: '简历筛选', number: 253 }, + { stage: '初试人数', number: 151 }, + { stage: '复试人数', number: 113 }, + { stage: '录取人数', number: 87 }, + { stage: '入职人数', number: 59 }, +]; + +const funnelPlot = new Funnel('container', { + data: data, + xField: 'stage', + yField: 'number', + legend: false, + label: { + formatter: (datum) => { + return `${(datum[Funnel.PERCENT_FIELD] * 100).toFixed(2)}%`; + }, + }, + conversionTag: { + formatter: (datum) => { + return `${((datum[Funnel.CONVERSATION_FIELD][1] / datum[Funnel.CONVERSATION_FIELD][0]) * 100).toFixed(2)}%`; + }, + }, + // 关闭 conversionTag 转化率 展示 + // conversionTag: false, +}); + +funnelPlot.render(); diff --git a/examples/plugin/multi-view/demo/composite-funnel.ts b/examples/plugin/multi-view/demo/composite-funnel.ts new file mode 100644 index 0000000000..9721eb824d --- /dev/null +++ b/examples/plugin/multi-view/demo/composite-funnel.ts @@ -0,0 +1,54 @@ +import { Mix } from '@antv/g2plot'; + +const expectData = [ + { value: 100, name: '展现' }, + { value: 80, name: '点击' }, + { value: 60, name: '访问' }, + { value: 40, name: '咨询' }, + { value: 30, name: '订单' }, +]; +const actualData = [ + { value: 80, name: '展现' }, + { value: 50, name: '点击' }, + { value: 30, name: '访问' }, + { value: 10, name: '咨询' }, + { value: 5, name: '订单' }, +]; + +const plot = new Mix('container', { + appendPadding: [8, 40, 8, 18], + syncViewPadding: true, + meta: { + value: { sync: true }, + }, + tooltip: { shared: true, showMarkers: false, showTitle: false }, + plots: [ + { + type: 'funnel', + options: { + data: expectData, + yField: 'value', + xField: 'name', + shape: 'pyramid', + conversionTag: false, + label: { position: 'right', style: { fill: 'rgba(0,0,0,0.65)' }, offsetX: 10 }, + funnelStyle: { fillOpacity: 0.85 }, + }, + }, + { + type: 'funnel', + options: { + data: actualData, + yField: 'value', + xField: 'name', + shape: 'pyramid', + conversionTag: false, + label: false, + funnelStyle: { lineWidth: 1, stroke: '#fff' }, + }, + }, + ], + interactions: [{ type: 'element-active' }], +}); + +plot.render(); diff --git a/examples/plugin/multi-view/demo/meta.json b/examples/plugin/multi-view/demo/meta.json index 3e138976c5..4a69c0a11e 100644 --- a/examples/plugin/multi-view/demo/meta.json +++ b/examples/plugin/multi-view/demo/meta.json @@ -20,6 +20,14 @@ }, "screenshot": "https://gw.alipayobjects.com/mdn/rms_f5c722/afts/img/A*19AdR6-BjRgAAAAAAAAAAABkARQnAQ" }, + { + "filename": "composite-funnel.ts", + "title": { + "zh": "复合漏斗图", + "en": "Composite funnel plot" + }, + "screenshot": "https://gw.alipayobjects.com/zos/antfincdn/SbR2Vn52G6/02155166-d38c-4f32-825e-720ec25804e0.png" + }, { "filename": "combo-plot.ts", "title": { diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 7c9e9e1f0c..205fc85c9b 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -17,7 +17,7 @@ export const EN_US_LOCALE: Locale = { }, /** conversionTag component */ conversionTag: { - label: 'Conversion rate', + label: 'Rate', }, legend: {}, tooltip: {}, diff --git a/src/plots/funnel/adaptor.ts b/src/plots/funnel/adaptor.ts index 00e36c3f81..0ee54b9d66 100644 --- a/src/plots/funnel/adaptor.ts +++ b/src/plots/funnel/adaptor.ts @@ -1,6 +1,8 @@ +import { isFunction } from '@antv/util'; import { Params } from '../../core/adaptor'; import { interaction, animation, theme, scale, annotation, tooltip } from '../../adaptor/common'; import { getLocale } from '../../core/locale'; +import { Datum } from '../../types'; import { flow, deepAssign } from '../../utils'; import { conversionTagFormatter } from '../../utils/conversion'; import { FunnelOptions } from './types'; @@ -8,7 +10,7 @@ import { basicFunnel } from './geometries/basic'; import { compareFunnel } from './geometries/compare'; import { facetFunnel } from './geometries/facet'; import { dynamicHeightFunnel } from './geometries/dynamic-height'; -import { FUNNEL_CONVERSATION, FUNNEL_MAPPING_VALUE, FUNNEL_PERCENT } from './constant'; +import { FUNNEL_CONVERSATION, FUNNEL_PERCENT } from './constant'; /** * @@ -25,51 +27,28 @@ import { FUNNEL_CONVERSATION, FUNNEL_MAPPING_VALUE, FUNNEL_PERCENT } from './con */ function defaultOptions(params: Params): Params { const { options } = params; - const { compareField, xField, yField, locale } = options; + const { compareField, xField, yField, locale, funnelStyle } = options; const i18n = getLocale(locale); const defaultOption = { - minSize: 0, - maxSize: 1, - meta: { - [FUNNEL_MAPPING_VALUE]: { - min: 0, - max: 1, - nice: false, - }, - }, label: compareField ? { fields: [xField, yField, compareField, FUNNEL_PERCENT, FUNNEL_CONVERSATION], - style: { - fill: '#fff', - fontSize: 12, - }, formatter: (datum) => `${datum[yField]}`, } : { fields: [xField, yField, FUNNEL_PERCENT, FUNNEL_CONVERSATION], offset: 0, position: 'middle', - style: { - fill: '#fff', - fontSize: 12, - }, formatter: (datum) => `${datum[xField]} ${datum[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: {}, // conversionTag 的计算和显示逻辑统一保持一致 formatter: (datum) => `${i18n.get(['conversionTag', 'label'])}: ${conversionTagFormatter( @@ -78,12 +57,20 @@ function defaultOptions(params: Params): Params { }, }; - return deepAssign( - { - options: defaultOption, - }, - params - ); + // 漏斗图样式 + let style; + if (compareField || funnelStyle) { + style = (datum: Datum) => { + return deepAssign( + {}, + // 对比漏斗图默认描边 + compareField && { lineWidth: 1, stroke: '#fff' }, + isFunction(funnelStyle) ? funnelStyle(datum) : funnelStyle + ); + }; + } + + return deepAssign({ options: defaultOption }, params, { options: { funnelStyle: style } }); } /** diff --git a/src/plots/funnel/constant.ts b/src/plots/funnel/constant.ts index 56cd9bd936..47afe4cb7d 100644 --- a/src/plots/funnel/constant.ts +++ b/src/plots/funnel/constant.ts @@ -10,3 +10,38 @@ export const FUNNEL_TOTAL_PERCENT = '$$totalPercentage$$'; // 漏斗多边型 x 坐标 export const PLOYGON_X = '$$x$$'; export const PLOYGON_Y = '$$y$$'; + +/** + * 漏斗图 默认配置项 + */ +export const DEFAULT_OPTIONS = { + appendPadding: [0, 80], + minSize: 0, + maxSize: 1, + meta: { + [FUNNEL_MAPPING_VALUE]: { + min: 0, + max: 1, + nice: false, + }, + }, + label: { + style: { + fill: '#fff', + fontSize: 12, + }, + }, + tooltip: { + showTitle: false, + showMarkers: false, + shared: false, + }, + conversionTag: { + offsetX: 10, + offsetY: 0, + style: { + fontSize: 12, + fill: 'rgba(0,0,0,0.45)', + }, + }, +}; diff --git a/src/plots/funnel/geometries/basic.ts b/src/plots/funnel/geometries/basic.ts index cb5ebffdb5..f677096702 100644 --- a/src/plots/funnel/geometries/basic.ts +++ b/src/plots/funnel/geometries/basic.ts @@ -33,7 +33,7 @@ function field(params: Params): Params { */ function geometry(params: Params): Params { const { chart, options } = params; - const { xField, yField, color, tooltip, label } = options; + const { xField, yField, color, tooltip, label, shape = 'funnel', funnelStyle } = options; const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField]); @@ -46,9 +46,10 @@ function geometry(params: Params): Params { colorField: xField, tooltipFields: isArray(fields) && fields.concat([FUNNEL_PERCENT, FUNNEL_CONVERSATION]), mapping: { - shape: 'funnel', + shape, tooltip: formatter, color, + style: funnelStyle, }, label, }, diff --git a/src/plots/funnel/geometries/compare.ts b/src/plots/funnel/geometries/compare.ts index 2ef423d812..2c619c283f 100644 --- a/src/plots/funnel/geometries/compare.ts +++ b/src/plots/funnel/geometries/compare.ts @@ -33,7 +33,8 @@ function field(params: Params): Params { */ function geometry(params: Params): Params { const { chart, options } = params; - const { data, xField, yField, color, compareField, isTransposed, tooltip, maxSize, minSize, label } = options; + const { data, xField, yField, color, compareField, isTransposed, tooltip, maxSize, minSize, label, funnelStyle } = + options; chart.facet('mirror', { fields: [compareField], @@ -83,13 +84,11 @@ function geometry(params: Params): Params { colorField: xField, tooltipFields: isArray(fields) && fields.concat([FUNNEL_PERCENT, FUNNEL_CONVERSATION]), mapping: { + // todo 暂时不提供 金字塔 shape,后续需要自定义下形状 shape: 'funnel', tooltip: formatter, color, - style: { - lineWidth: 1, - stroke: '#fff', - }, + style: funnelStyle, }, label: label === false ? false : deepAssign({}, defaultFacetLabel, label), }, diff --git a/src/plots/funnel/geometries/dynamic-height.ts b/src/plots/funnel/geometries/dynamic-height.ts index 89aedc872a..f97e14886c 100644 --- a/src/plots/funnel/geometries/dynamic-height.ts +++ b/src/plots/funnel/geometries/dynamic-height.ts @@ -85,7 +85,7 @@ function field(params: Params): Params { */ function geometry(params: Params): Params { const { chart, options } = params; - const { xField, yField, color, tooltip, label } = options; + const { xField, yField, color, tooltip, label, funnelStyle } = options; const { fields, formatter } = getTooltipMapping(tooltip, [xField, yField]); // 绘制漏斗图 @@ -101,6 +101,7 @@ function geometry(params: Params): Params { mapping: { tooltip: formatter, color, + style: funnelStyle, }, }, }); diff --git a/src/plots/funnel/index.ts b/src/plots/funnel/index.ts index 27a7d23e5c..66ff08ac5c 100644 --- a/src/plots/funnel/index.ts +++ b/src/plots/funnel/index.ts @@ -2,7 +2,12 @@ import { Plot } from '../../core/plot'; import { Adaptor } from '../../core/adaptor'; import { FunnelOptions } from './types'; import { adaptor } from './adaptor'; -import { FUNNEL_CONVERSATION as FUNNEL_CONVERSATION_FIELD } from './constant'; +import { + DEFAULT_OPTIONS, + FUNNEL_CONVERSATION as FUNNEL_CONVERSATION_FIELD, + FUNNEL_PERCENT, + FUNNEL_TOTAL_PERCENT, +} from './constant'; export type { FunnelOptions }; @@ -13,11 +18,17 @@ export class Funnel extends Plot { public type: string = 'funnel'; static getDefaultOptions(): Partial { - return { - appendPadding: [0, 80], - }; + return DEFAULT_OPTIONS; } + // 内部变量 + /** 漏斗 转化率 字段 */ + static CONVERSATION_FIELD = FUNNEL_CONVERSATION_FIELD; + /** 漏斗 百分比 字段 */ + static PERCENT_FIELD = FUNNEL_PERCENT; + /** 漏斗 总转换率百分比 字段 */ + static TOTAL_PERCENT_FIELD = FUNNEL_TOTAL_PERCENT; + /** * 获取 漏斗图 默认配置项 */ diff --git a/src/plots/funnel/types.ts b/src/plots/funnel/types.ts index 6d1ad2024c..da06be4774 100644 --- a/src/plots/funnel/types.ts +++ b/src/plots/funnel/types.ts @@ -1,3 +1,4 @@ +import { StyleAttr } from '../../types/attr'; import { TextStyle, Datum, Data, AnnotationPosition, Options } from '../../types/common'; export type ConversionPosition = { @@ -22,13 +23,21 @@ export interface FunnelOptions extends Options { readonly maxSize?: number; /** minSize: 最大宽度,0-1 之间 */ readonly minSize?: number; + + // 样式 + /** shape 形状: pyramid 金字塔, 只在基础漏斗图中适用. 在对比漏斗图以及设置 dynamicHeight: 'true' 时不适用 */ + readonly shape?: string; + /** 漏斗图样式 */ + readonly funnelStyle?: StyleAttr; + + // 组件 /** 转化率信息 */ readonly conversionTag?: | false | { - readonly offsetX: number; - readonly offsetY: number; - readonly style: TextStyle; - readonly formatter: string | ((datum?: Datum, data?: Data) => string); + readonly offsetX?: number; + readonly offsetY?: number; + readonly style?: TextStyle; + readonly formatter?: string | ((datum?: Datum, data?: Data) => string); }; } diff --git a/src/plots/mix/utils.ts b/src/plots/mix/utils.ts index 3fae645ee9..1a810b8930 100644 --- a/src/plots/mix/utils.ts +++ b/src/plots/mix/utils.ts @@ -14,6 +14,7 @@ import { adaptor as ringProgressAdaptor } from '../ring-progress/adaptor'; import { adaptor as progressAdaptor } from '../progress/adaptor'; import { adaptor as scatterAdaptor } from '../scatter/adaptor'; import { adaptor as histogramAdaptor } from '../histogram/adaptor'; +import { adaptor as funnelAdaptor } from '../funnel/adaptor'; import { Line, LineOptions } from '../line'; import { Pie, PieOptions } from '../pie'; import { Bar, BarOptions } from '../bar'; @@ -27,6 +28,7 @@ import { RingProgress, RingProgressOptions } from '../ring-progress'; import { Progress, ProgressOptions } from '../progress'; import { Scatter, ScatterOptions } from '../scatter'; import { Histogram, HistogramOptions } from '../histogram'; +import { Funnel, FunnelOptions } from '../funnel'; /** * 移除 options 中的 width、height 设置 @@ -94,6 +96,10 @@ export type IPlotTypes = | { readonly type: 'scatter'; readonly options: OmitSize; + } + | { + readonly type: 'funnel'; + readonly options: OmitSize; }; /** @@ -113,6 +119,7 @@ const PLOT_ADAPTORS = { progress: progressAdaptor, scatter: scatterAdaptor, histogram: histogramAdaptor, + funnel: funnelAdaptor, }; /** @@ -133,6 +140,7 @@ const PLOT_CONSTRUCTOR = { progress: Progress, scatter: Scatter, histogram: Histogram, + funnel: Funnel, }; /**