From 05afca6e32eb8b72eb21fa5dc93c5abb54b10580 Mon Sep 17 00:00:00 2001
From: Kasmine <736929286@qq.com>
Date: Mon, 10 Aug 2020 14:41:40 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A5=BC=E3=80=81=E7=8E=AF?=
=?UTF-8?q?=E5=9B=BE=E6=96=87=E6=A1=A3=20=EF=BC=8C=E5=90=8C=E6=97=B6?=
=?UTF-8?q?=E8=A1=A5=E5=85=A8=E9=A5=BC=E5=9B=BE=E8=83=BD=E5=8A=9B=20(#1411?=
=?UTF-8?q?)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(v2/core): 增加 default cfg 配置
* feat(v2/legend-adaptor): 增加通用 legend adaptor, 修复 饼图 legend 关闭失效
* feat(v2/pie-inner-label): 增加 pie-inner label 类型 & 增 强 statistic
statistic 增加title 和 content boolean 类型,允许显式关闭
* test: 增加测试用例
* docs(v2/pie): 增加饼/环 demo & api 文档
- 移除 radius 默认配置
---
__tests__/unit/plots/pie/label-spec.ts | 7 +
__tests__/unit/plots/pie/legend-spec.ts | 31 ++++
__tests__/unit/plots/pie/utils-spec.ts | 12 +-
__tests__/utils/event.ts | 2 +-
examples/pie/basic/API.en.md | 186 ++++++++++++++++++++++++
examples/pie/basic/API.zh.md | 186 ++++++++++++++++++++++++
examples/pie/basic/demo/basic.ts | 30 ++++
examples/pie/basic/demo/custom-label.ts | 49 +++++++
examples/pie/basic/demo/meta.json | 48 ++++++
examples/pie/basic/demo/outer-label.ts | 25 ++++
examples/pie/basic/demo/pie-texture.ts | 36 +++++
examples/pie/basic/index.en.md | 4 +
examples/pie/basic/index.zh.md | 4 +
examples/pie/donut/API.en.md | 50 +++++++
examples/pie/donut/API.zh.md | 50 +++++++
examples/pie/donut/demo/basic.ts | 37 +++++
examples/pie/donut/demo/meta.json | 24 +++
examples/pie/donut/demo/statistics.ts | 34 +++++
examples/pie/donut/index.en.md | 4 +
examples/pie/donut/index.zh.md | 4 +
gatsby-browser.js | 1 +
gatsby-config.js | 8 +
src/adaptor/common.ts | 17 +++
src/core/plot.ts | 12 +-
src/plots/pie/adaptor.ts | 94 +++++++-----
src/plots/pie/index.ts | 13 ++
src/plots/pie/label/index.ts | 4 +
src/plots/pie/label/inner-label.ts | 37 +++++
src/plots/pie/types.ts | 3 +
src/plots/pie/utils.ts | 15 +-
30 files changed, 984 insertions(+), 43 deletions(-)
create mode 100644 __tests__/unit/plots/pie/legend-spec.ts
create mode 100644 examples/pie/basic/API.en.md
create mode 100644 examples/pie/basic/API.zh.md
create mode 100644 examples/pie/basic/demo/basic.ts
create mode 100644 examples/pie/basic/demo/custom-label.ts
create mode 100644 examples/pie/basic/demo/meta.json
create mode 100644 examples/pie/basic/demo/outer-label.ts
create mode 100644 examples/pie/basic/demo/pie-texture.ts
create mode 100644 examples/pie/basic/index.en.md
create mode 100644 examples/pie/basic/index.zh.md
create mode 100644 examples/pie/donut/API.en.md
create mode 100644 examples/pie/donut/API.zh.md
create mode 100644 examples/pie/donut/demo/basic.ts
create mode 100644 examples/pie/donut/demo/meta.json
create mode 100644 examples/pie/donut/demo/statistics.ts
create mode 100644 examples/pie/donut/index.en.md
create mode 100644 examples/pie/donut/index.zh.md
create mode 100644 src/plots/pie/label/index.ts
create mode 100644 src/plots/pie/label/inner-label.ts
diff --git a/__tests__/unit/plots/pie/label-spec.ts b/__tests__/unit/plots/pie/label-spec.ts
index 4033c3bc4d4..4803cc59b5e 100644
--- a/__tests__/unit/plots/pie/label-spec.ts
+++ b/__tests__/unit/plots/pie/label-spec.ts
@@ -1,4 +1,5 @@
import { IGroup } from '@antv/g-base';
+import { getGeometryLabel } from '@antv/g2';
import { Pie } from '../../../../src';
import { POSITIVE_NEGATIVE_DATA } from '../../../data/common';
import { createDiv } from '../../../utils/dom';
@@ -133,3 +134,9 @@ describe('support template string formatter', () => {
expect((labels[0] as IGroup).getChildren()[0].attr('text')).toBe('item1: 1(20.00%)');
// todo 补充图例点击后,百分比计算依然准确的 case
});
+
+describe('inner label', () => {
+ it('自定义注册饼图 inner label', () => {
+ expect(getGeometryLabel('pie-inner')).toBeDefined();
+ });
+});
diff --git a/__tests__/unit/plots/pie/legend-spec.ts b/__tests__/unit/plots/pie/legend-spec.ts
new file mode 100644
index 00000000000..ef27358c1cf
--- /dev/null
+++ b/__tests__/unit/plots/pie/legend-spec.ts
@@ -0,0 +1,31 @@
+import { Pie } from '../../../../src';
+import { createDiv } from '../../../utils/dom';
+
+describe('pie legend', () => {
+ const pie = new Pie(createDiv(), {
+ width: 400,
+ height: 400,
+ data: [
+ { type: '分类一', value: 10 },
+ { type: '分类二', value: 20 },
+ { type: '分类三', value: 15 },
+ { type: '其他', value: 23 },
+ ],
+ angleField: 'value',
+ colorField: 'type',
+ });
+
+ pie.render();
+
+ it('移除 legend', () => {
+ const legendController = pie.chart.getController('legend');
+ const legendComponent = legendController.getComponents()[0].component;
+ expect(legendComponent.get('items').length).toBe(4);
+
+ pie.update({
+ ...pie.options,
+ legend: false,
+ });
+ expect(legendComponent.get('items')).toBeUndefined();
+ });
+});
diff --git a/__tests__/unit/plots/pie/utils-spec.ts b/__tests__/unit/plots/pie/utils-spec.ts
index 7fcaa5bd3cc..5655a514d7c 100644
--- a/__tests__/unit/plots/pie/utils-spec.ts
+++ b/__tests__/unit/plots/pie/utils-spec.ts
@@ -1,6 +1,6 @@
import { Pie } from '../../../../src';
-import { getStatisticData, getTotalValue } from '../../../../src/plots/pie/utils';
import { createDiv } from '../../../utils/dom';
+import { getStatisticData, getTotalValue, parsePercentageToNumber } from '../../../../src/plots/pie/utils';
describe('utils of pie plot', () => {
const data = [
@@ -87,4 +87,14 @@ describe('utils of pie plot', () => {
value: '20',
});
});
+
+ it('将 字符串百分比 转换为 数值型百分比', () => {
+ // @ts-ignore 不合法的入参
+ expect(parsePercentageToNumber(null)).toBe(null);
+ // @ts-ignore 不合法的入参
+ expect(parsePercentageToNumber(100)).toBe(100);
+ expect(parsePercentageToNumber('0.35')).toBe(0.35);
+ expect(parsePercentageToNumber('34%')).toBe(0.34);
+ expect(parsePercentageToNumber('0%')).toBe(0);
+ });
});
diff --git a/__tests__/utils/event.ts b/__tests__/utils/event.ts
index 9f4dd477062..4f9e9cb35ae 100644
--- a/__tests__/utils/event.ts
+++ b/__tests__/utils/event.ts
@@ -1,4 +1,4 @@
-import { IElement } from '../../src/dependents';
+import { IElement } from '@antv/g2/lib/dependents';
// 触发 Canvas 上元素的鼠标事件
export const simulateMouseEvent = (element: IElement, event: string, cfg = {}) => {
diff --git a/examples/pie/basic/API.en.md b/examples/pie/basic/API.en.md
new file mode 100644
index 00000000000..8ebde744709
--- /dev/null
+++ b/examples/pie/basic/API.en.md
@@ -0,0 +1,186 @@
+---
+title: API
+---
+
+# 配置属性
+
+## 图表容器
+
+- 见 [通用配置](TODO)
+
+## 数据映射
+
+### data 📌
+
+**必选**, _array object_
+
+功能描述: 设置图表数据源
+
+默认配置: 无
+
+数据源为对象集合,例如:`[{ time: '1991',value: 20 }, { time: '1992',value: 20 }]`。
+
+### meta
+
+**可选**, _object_
+
+功能描述: 全局化配置图表数据元信息,以字段为单位进行配置。在 meta 上的配置将同时影响所有组件的文本信息。
+
+默认配置: 无
+
+| 细分配置项名称 | 类型 | 功能描述 |
+| -------------- | ---------- | ------------------------------------------- |
+| alias | _string_ | 字段的别名 |
+| formatter | _function_ | callback 方法,对该字段所有值进行格式化处理 |
+| values | _string[]_ | 枚举该字段下所有值 |
+| range | _number[]_ | 字段的数据映射区间,默认为[0,1] |
+
+```js
+const data = [
+ { country: 'Asia', year: '1750', value: 502,},
+ { country: 'Asia', year: '1800', value: 635,},
+ { country: 'Europe', year: '1750', value: 163,},
+ { country: 'Europe', year: '1800', value: 203,},
+];
+
+const piePlot = new Pie(document.getElementById('container'), {
+ data,
+ // highlight-start
+ meta: {
+ country: {
+ alias:'国家'
+ range: [0, 1],
+ },
+ value: {
+ alias: '数量',
+ formatter:(v)=>{return `${v}个`}
+ }
+ },
+ // highlight-end
+ angleField: 'value',
+ colorField: 'country',
+});
+piePlot.render();
+```
+
+### angleField 📌
+
+**必选**, _string_
+
+功能描述: 扇形切片大小(弧度)所对应的数据字段名。。
+
+默认配置: 无
+
+### colorField 📌
+
+**可选**, _string_
+
+功能描述: 扇形颜色映射对应的数据字段名。
+
+默认配置: 无
+
+## 图形样式
+
+### radius ✨
+
+**可选**, _number_
+
+功能描述: 饼图的半径,原点为画布中心。配置值域为 [0,1],0 代表饼图大小为 0,即不显示,1 代表饼图撑满绘图区域。
+
+### color
+
+**可选**, _string | string[] | Function_
+
+功能描述: 指定扇形颜色,即可以指定一系列色值,也可以通过回调函数的方法根据对应数值进行设置。
+
+默认配置:采用 theme 中的色板。
+
+用法示例:
+
+```js
+// 配合颜色映射,指定多值
+colorField:'type',
+color:['blue','yellow','green']
+//配合颜色映射,使用回调函数指定色值
+colorField:'type',
+color:(d)=>{
+ if(d==='a') return 'red';
+ return 'blue';
+}
+```
+
+### pieStyle ✨
+
+**可选**, _object_
+
+功能描述: 设置扇形样式。pieStyle 中的`fill`会覆盖 `color` 的配置。pieStyle 可以直接指定,也可以通过 callback 的方式,根据数据为每个扇形切片指定单独的样式。
+
+默认配置: 无
+
+| 细分配置 | 类型 | 功能描述 |
+| ------------- | ------ | ---------- |
+| fill | _string_ | 填充颜色 |
+| stroke | _string_ | 描边颜色 |
+| lineWidth | _number_ | 描边宽度 |
+| lineDash | _number_ | 虚线描边 |
+| opacity | _number_ | 整体透明度 |
+| fillOpacity | _number_ | 填充透明度 |
+| strokeOpacity | _number_ | 描边透明度 |
+
+## 图表组件
+
+
+
+### legend、tooltip、theme
+
+`legend` 、`tooltip`、`theme` 等通用组件请参考图表通用配置
+
+### label ✨
+
+功能描述: 标签文本
+
+[DEMO1](../../pie/basic#basic)
+[DEMO2](../../pie/basic#outer-label)
+
+| 细分配置 | 类型 | 功能描述 |
+| ------------- | ------ | ---------- |
+| type | `inner`, `outer` | 标签类型 |
+| content | _string_, _Fucntion_ | 标签内容,可通过回调的方式,也支持模板字符串配置:内置标签名(`{name}`)、百分比(`{percentage}`)、数值(`{value}`) 三种 |
+| style | _object, _Fucntion_ | 标签样式,可通过回调的方式 |
+| 其他 | any | 其他,请参考图表 label 通用配置 |
+
+
+## 事件
+
+[通用 events](../../general/events/API)
+
+# 图表方法
+
+## render() 📌
+
+**必选**
+
+渲染图表。
+
+## update()
+
+**可选**
+
+更新图表配置项。
+
+```js
+piePlot.update({
+ ...piePlot.options,
+ legend: false,
+});
+```
+
+## changeData()
+
+**可选**
+
+更新图表数据。`update()`方法会导致图形区域销毁并重建,如果只进行数据更新,而不涉及其他配置项更新,推荐使用本方法。
+
+```js
+piePlot.changeData(newData);
+```
diff --git a/examples/pie/basic/API.zh.md b/examples/pie/basic/API.zh.md
new file mode 100644
index 00000000000..8ebde744709
--- /dev/null
+++ b/examples/pie/basic/API.zh.md
@@ -0,0 +1,186 @@
+---
+title: API
+---
+
+# 配置属性
+
+## 图表容器
+
+- 见 [通用配置](TODO)
+
+## 数据映射
+
+### data 📌
+
+**必选**, _array object_
+
+功能描述: 设置图表数据源
+
+默认配置: 无
+
+数据源为对象集合,例如:`[{ time: '1991',value: 20 }, { time: '1992',value: 20 }]`。
+
+### meta
+
+**可选**, _object_
+
+功能描述: 全局化配置图表数据元信息,以字段为单位进行配置。在 meta 上的配置将同时影响所有组件的文本信息。
+
+默认配置: 无
+
+| 细分配置项名称 | 类型 | 功能描述 |
+| -------------- | ---------- | ------------------------------------------- |
+| alias | _string_ | 字段的别名 |
+| formatter | _function_ | callback 方法,对该字段所有值进行格式化处理 |
+| values | _string[]_ | 枚举该字段下所有值 |
+| range | _number[]_ | 字段的数据映射区间,默认为[0,1] |
+
+```js
+const data = [
+ { country: 'Asia', year: '1750', value: 502,},
+ { country: 'Asia', year: '1800', value: 635,},
+ { country: 'Europe', year: '1750', value: 163,},
+ { country: 'Europe', year: '1800', value: 203,},
+];
+
+const piePlot = new Pie(document.getElementById('container'), {
+ data,
+ // highlight-start
+ meta: {
+ country: {
+ alias:'国家'
+ range: [0, 1],
+ },
+ value: {
+ alias: '数量',
+ formatter:(v)=>{return `${v}个`}
+ }
+ },
+ // highlight-end
+ angleField: 'value',
+ colorField: 'country',
+});
+piePlot.render();
+```
+
+### angleField 📌
+
+**必选**, _string_
+
+功能描述: 扇形切片大小(弧度)所对应的数据字段名。。
+
+默认配置: 无
+
+### colorField 📌
+
+**可选**, _string_
+
+功能描述: 扇形颜色映射对应的数据字段名。
+
+默认配置: 无
+
+## 图形样式
+
+### radius ✨
+
+**可选**, _number_
+
+功能描述: 饼图的半径,原点为画布中心。配置值域为 [0,1],0 代表饼图大小为 0,即不显示,1 代表饼图撑满绘图区域。
+
+### color
+
+**可选**, _string | string[] | Function_
+
+功能描述: 指定扇形颜色,即可以指定一系列色值,也可以通过回调函数的方法根据对应数值进行设置。
+
+默认配置:采用 theme 中的色板。
+
+用法示例:
+
+```js
+// 配合颜色映射,指定多值
+colorField:'type',
+color:['blue','yellow','green']
+//配合颜色映射,使用回调函数指定色值
+colorField:'type',
+color:(d)=>{
+ if(d==='a') return 'red';
+ return 'blue';
+}
+```
+
+### pieStyle ✨
+
+**可选**, _object_
+
+功能描述: 设置扇形样式。pieStyle 中的`fill`会覆盖 `color` 的配置。pieStyle 可以直接指定,也可以通过 callback 的方式,根据数据为每个扇形切片指定单独的样式。
+
+默认配置: 无
+
+| 细分配置 | 类型 | 功能描述 |
+| ------------- | ------ | ---------- |
+| fill | _string_ | 填充颜色 |
+| stroke | _string_ | 描边颜色 |
+| lineWidth | _number_ | 描边宽度 |
+| lineDash | _number_ | 虚线描边 |
+| opacity | _number_ | 整体透明度 |
+| fillOpacity | _number_ | 填充透明度 |
+| strokeOpacity | _number_ | 描边透明度 |
+
+## 图表组件
+
+
+
+### legend、tooltip、theme
+
+`legend` 、`tooltip`、`theme` 等通用组件请参考图表通用配置
+
+### label ✨
+
+功能描述: 标签文本
+
+[DEMO1](../../pie/basic#basic)
+[DEMO2](../../pie/basic#outer-label)
+
+| 细分配置 | 类型 | 功能描述 |
+| ------------- | ------ | ---------- |
+| type | `inner`, `outer` | 标签类型 |
+| content | _string_, _Fucntion_ | 标签内容,可通过回调的方式,也支持模板字符串配置:内置标签名(`{name}`)、百分比(`{percentage}`)、数值(`{value}`) 三种 |
+| style | _object, _Fucntion_ | 标签样式,可通过回调的方式 |
+| 其他 | any | 其他,请参考图表 label 通用配置 |
+
+
+## 事件
+
+[通用 events](../../general/events/API)
+
+# 图表方法
+
+## render() 📌
+
+**必选**
+
+渲染图表。
+
+## update()
+
+**可选**
+
+更新图表配置项。
+
+```js
+piePlot.update({
+ ...piePlot.options,
+ legend: false,
+});
+```
+
+## changeData()
+
+**可选**
+
+更新图表数据。`update()`方法会导致图形区域销毁并重建,如果只进行数据更新,而不涉及其他配置项更新,推荐使用本方法。
+
+```js
+piePlot.changeData(newData);
+```
diff --git a/examples/pie/basic/demo/basic.ts b/examples/pie/basic/demo/basic.ts
new file mode 100644
index 00000000000..5907b5fb98f
--- /dev/null
+++ b/examples/pie/basic/demo/basic.ts
@@ -0,0 +1,30 @@
+import { Pie } from '@antv/g2plot';
+
+const data = [
+ { type: '分类一', value: 27 },
+ { type: '分类二', value: 25 },
+ { type: '分类三', value: 18 },
+ { type: '分类四', value: 15 },
+ { type: '分类五', value: 10 },
+ { type: '其他', value: 5 },
+];
+
+const piePlot = new Pie(document.getElementById('container'), {
+ width: 400,
+ height: 300,
+ appendPadding: 10,
+ data,
+ angleField: 'value',
+ colorField: 'type',
+ radius: 0.8,
+ label: {
+ type: 'inner',
+ content: '{name} {percentage}',
+ style: {
+ fill: '#fff',
+ fontSize: 14,
+ },
+ },
+});
+
+piePlot.render();
diff --git a/examples/pie/basic/demo/custom-label.ts b/examples/pie/basic/demo/custom-label.ts
new file mode 100644
index 00000000000..042212a7b55
--- /dev/null
+++ b/examples/pie/basic/demo/custom-label.ts
@@ -0,0 +1,49 @@
+import { Pie } from '@antv/g2plot';
+
+const data = [
+ { sex: '男', sold: 0.45 },
+ { sex: '女', sold: 0.55 },
+];
+
+const piePlot = new Pie(document.getElementById('container'), {
+ width: 400,
+ height: 300,
+ appendPadding: 10,
+ data,
+ angleField: 'sold',
+ colorField: 'sex',
+ radius: 0.8,
+ label: {
+ content: (obj) => {
+ const group = new (window as any).G.Group({});
+ group.addShape({
+ type: 'image',
+ attrs: {
+ x: 0,
+ y: 0,
+ width: 40,
+ height: 50,
+ img:
+ obj.sex === '男'
+ ? 'https://gw.alipayobjects.com/zos/rmsportal/oeCxrAewtedMBYOETCln.png'
+ : 'https://gw.alipayobjects.com/zos/rmsportal/mweUsJpBWucJRixSfWVP.png',
+ },
+ });
+
+ group.addShape({
+ type: 'text',
+ attrs: {
+ x: 20,
+ y: 54,
+ text: obj.sex,
+ textAlign: 'center',
+ textBaseline: 'top',
+ fill: obj.sex === '男' ? '#1890ff' : '#f04864',
+ },
+ });
+ return group;
+ },
+ },
+});
+
+piePlot.render();
diff --git a/examples/pie/basic/demo/meta.json b/examples/pie/basic/demo/meta.json
new file mode 100644
index 00000000000..f4c2953cfad
--- /dev/null
+++ b/examples/pie/basic/demo/meta.json
@@ -0,0 +1,48 @@
+{
+ "title": {
+ "zh": "中文分类",
+ "en": "Category"
+ },
+ "demos": [
+ {
+ "filename": "basic.ts",
+ "title": {
+ "zh": "饼图",
+ "en": "basic Pie chart"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*wmldRZZj9lIAAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "outer-label.ts",
+ "title": {
+ "zh": "饼图-外部图形标签",
+ "en": "Pie chart - outter label"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*ZztJQa4RLwoAAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "spider-label.ts",
+ "title": {
+ "zh": "饼图-图形标签蜘蛛布局",
+ "en": "Pie chart - spider-layout label"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*ahB1Qp7T-C8AAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "custom-label.ts",
+ "title": {
+ "en": "customize pie chart",
+ "zh": "个性化标签饼图"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_f5c722/afts/img/A*sqwJRIEzdQ0AAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "pie-texture.ts",
+ "title": {
+ "en": "pie chart fill with texture",
+ "zh": "饼图-带纹理"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_2274c3/afts/img/A*6DhLR77aZloAAAAAAAAAAABkARQnAQ"
+ }
+ ]
+}
diff --git a/examples/pie/basic/demo/outer-label.ts b/examples/pie/basic/demo/outer-label.ts
new file mode 100644
index 00000000000..a53450383ba
--- /dev/null
+++ b/examples/pie/basic/demo/outer-label.ts
@@ -0,0 +1,25 @@
+import { Pie } from '@antv/g2plot';
+
+const data = [
+ { type: '分类一', value: 27 },
+ { type: '分类二', value: 25 },
+ { type: '分类三', value: 18 },
+ { type: '分类四', value: 15 },
+ { type: '分类五', value: 10 },
+ { type: '其他', value: 5 },
+];
+
+const piePlot = new Pie(document.getElementById('container'), {
+ width: 400,
+ height: 300,
+ appendPadding: 10,
+ data,
+ angleField: 'value',
+ colorField: 'type',
+ radius: 0.8,
+ label: {
+ type: 'outer',
+ },
+});
+
+piePlot.render();
diff --git a/examples/pie/basic/demo/pie-texture.ts b/examples/pie/basic/demo/pie-texture.ts
new file mode 100644
index 00000000000..f40c5f2b1c9
--- /dev/null
+++ b/examples/pie/basic/demo/pie-texture.ts
@@ -0,0 +1,36 @@
+import { Pie } from '@antv/g2plot';
+
+const data = [
+ { sex: '男', sold: 0.45 },
+ { sex: '女', sold: 0.55 },
+];
+
+const piePlot = new Pie(document.getElementById('container'), {
+ width: 400,
+ height: 300,
+ appendPadding: 10,
+ data,
+ angleField: 'sold',
+ colorField: 'sex',
+ radius: 0.8,
+ legend: false,
+ label: {
+ type: 'inner',
+ style: {
+ fill: '#fff',
+ fontSize: 18,
+ },
+ },
+ pieStyle: (solid, sex) => {
+ if (sex === '男') {
+ return {
+ fill: 'p(a)https://gw.alipayobjects.com/zos/rmsportal/nASTPWDPJDMgkDRlAUmw.jpeg',
+ };
+ }
+ return {
+ fill: 'p(a)https://gw.alipayobjects.com/zos/rmsportal/ziMWHpHSTlTzURSzCarw.jpeg',
+ };
+ },
+});
+
+piePlot.render();
diff --git a/examples/pie/basic/index.en.md b/examples/pie/basic/index.en.md
new file mode 100644
index 00000000000..9eca165af1d
--- /dev/null
+++ b/examples/pie/basic/index.en.md
@@ -0,0 +1,4 @@
+---
+title: Pie
+order: 0
+---
diff --git a/examples/pie/basic/index.zh.md b/examples/pie/basic/index.zh.md
new file mode 100644
index 00000000000..393ef30d2fd
--- /dev/null
+++ b/examples/pie/basic/index.zh.md
@@ -0,0 +1,4 @@
+---
+title: 饼图
+order: 0
+---
diff --git a/examples/pie/donut/API.en.md b/examples/pie/donut/API.en.md
new file mode 100644
index 00000000000..7ac59f33bad
--- /dev/null
+++ b/examples/pie/donut/API.en.md
@@ -0,0 +1,50 @@
+---
+title: API
+---
+
+# 配置属性
+
+## 图表容器
+
+- 见 [通用配置](TODO)
+
+## 基础配置
+
+- 见 [饼图配置](TODO)
+
+## 特殊配置
+
+### innerRadius ✨
+
+**可选**, _number_
+
+功能描述: 环图的内半径,原点为画布中心,若不配置内半径,则直接为饼图
+
+### statistic ✨
+
+**可选**, _object_
+
+功能描述: 中心统计指标卡。当 `innerRadius` 配置不为 0 时,生效
+
+用法示例:
+
+```js
+{
+ statistic: {
+ title: {
+ formatter: () => 'Total',
+ },
+ },
+ // 同时 可添加 中心统计文本 交互
+ interactions: [{ name: 'pie-statistic-active' }]
+}
+```
+
+| 细分配置 | 类型 | 功能描述 |
+| ------------- | ------ | ---------- |
+| title | _boolean_, _object_ | 指标卡标题 |
+| formatter | _function_ | 通过回调的方式,设置指标卡标题 |
+| style | _object_ | 指标卡标题样式 |
+| content | _boolean_, _object_ | 指标卡内容 |
+| formatter | _function_ | 通过回调的方式,设置指标卡内容 |
+| style | _object_ | 指标卡内容样式 |
\ No newline at end of file
diff --git a/examples/pie/donut/API.zh.md b/examples/pie/donut/API.zh.md
new file mode 100644
index 00000000000..7ac59f33bad
--- /dev/null
+++ b/examples/pie/donut/API.zh.md
@@ -0,0 +1,50 @@
+---
+title: API
+---
+
+# 配置属性
+
+## 图表容器
+
+- 见 [通用配置](TODO)
+
+## 基础配置
+
+- 见 [饼图配置](TODO)
+
+## 特殊配置
+
+### innerRadius ✨
+
+**可选**, _number_
+
+功能描述: 环图的内半径,原点为画布中心,若不配置内半径,则直接为饼图
+
+### statistic ✨
+
+**可选**, _object_
+
+功能描述: 中心统计指标卡。当 `innerRadius` 配置不为 0 时,生效
+
+用法示例:
+
+```js
+{
+ statistic: {
+ title: {
+ formatter: () => 'Total',
+ },
+ },
+ // 同时 可添加 中心统计文本 交互
+ interactions: [{ name: 'pie-statistic-active' }]
+}
+```
+
+| 细分配置 | 类型 | 功能描述 |
+| ------------- | ------ | ---------- |
+| title | _boolean_, _object_ | 指标卡标题 |
+| formatter | _function_ | 通过回调的方式,设置指标卡标题 |
+| style | _object_ | 指标卡标题样式 |
+| content | _boolean_, _object_ | 指标卡内容 |
+| formatter | _function_ | 通过回调的方式,设置指标卡内容 |
+| style | _object_ | 指标卡内容样式 |
\ No newline at end of file
diff --git a/examples/pie/donut/demo/basic.ts b/examples/pie/donut/demo/basic.ts
new file mode 100644
index 00000000000..daa17185cc6
--- /dev/null
+++ b/examples/pie/donut/demo/basic.ts
@@ -0,0 +1,37 @@
+import { Pie } from '@antv/g2plot';
+
+const data = [
+ { type: '分类一', value: 27 },
+ { type: '分类二', value: 25 },
+ { type: '分类三', value: 18 },
+ { type: '分类四', value: 15 },
+ { type: '分类五', value: 10 },
+ { type: '其他', value: 5 },
+];
+
+const piePlot = new Pie(document.getElementById('container'), {
+ width: 400,
+ height: 300,
+ appendPadding: 10,
+ data,
+ angleField: 'value',
+ colorField: 'type',
+ radius: 0.8,
+ innerRadius: 0.6,
+ label: {
+ type: 'inner',
+ content: '{percentage}',
+ style: {
+ fill: '#fff',
+ fontSize: 14,
+ },
+ },
+ statistic: {
+ title: false,
+ content: {
+ formatter: () => 'AntV\nG2Plot',
+ },
+ },
+});
+
+piePlot.render();
diff --git a/examples/pie/donut/demo/meta.json b/examples/pie/donut/demo/meta.json
new file mode 100644
index 00000000000..3bbb0294530
--- /dev/null
+++ b/examples/pie/donut/demo/meta.json
@@ -0,0 +1,24 @@
+{
+ "title": {
+ "zh": "中文分类",
+ "en": "Category"
+ },
+ "demos": [
+ {
+ "filename": "basic.js",
+ "title": {
+ "zh": "环图",
+ "en": "basic Donut chart"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*wmldRZZj9lIAAAAAAAAAAABkARQnAQ"
+ },
+ {
+ "filename": "statistics.js",
+ "title": {
+ "zh": "环图 - 带统计指标卡",
+ "en": "Donut chart with statistics"
+ },
+ "screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*ZztJQa4RLwoAAAAAAAAAAABkARQnAQ"
+ }
+ ]
+}
diff --git a/examples/pie/donut/demo/statistics.ts b/examples/pie/donut/demo/statistics.ts
new file mode 100644
index 00000000000..75491427c98
--- /dev/null
+++ b/examples/pie/donut/demo/statistics.ts
@@ -0,0 +1,34 @@
+import { Pie } from '@antv/g2plot';
+
+const data = [
+ { type: '分类一', value: 27 },
+ { type: '分类二', value: 25 },
+ { type: '分类三', value: 18 },
+ { type: '分类四', value: 15 },
+ { type: '分类五', value: 10 },
+ { type: '其他', value: 5 },
+];
+
+const piePlot = new Pie(document.getElementById('container'), {
+ width: 400,
+ height: 300,
+ appendPadding: 10,
+ data,
+ angleField: 'value',
+ colorField: 'type',
+ radius: 0.8,
+ innerRadius: 0.64,
+ label: {
+ type: 'outer',
+ content: '{name} {percentage}',
+ },
+ statistic: {
+ title: {
+ formatter: () => 'Total',
+ },
+ },
+ // 添加 中心统计文本 交互
+ interactions: [{ name: 'pie-statistic-active' }],
+});
+
+piePlot.render();
diff --git a/examples/pie/donut/index.en.md b/examples/pie/donut/index.en.md
new file mode 100644
index 00000000000..eb6c115894b
--- /dev/null
+++ b/examples/pie/donut/index.en.md
@@ -0,0 +1,4 @@
+---
+title: Donut
+order: 1
+---
diff --git a/examples/pie/donut/index.zh.md b/examples/pie/donut/index.zh.md
new file mode 100644
index 00000000000..2b9cc813a78
--- /dev/null
+++ b/examples/pie/donut/index.zh.md
@@ -0,0 +1,4 @@
+---
+title: 环图
+order: 1
+---
diff --git a/gatsby-browser.js b/gatsby-browser.js
index 355cbd4d295..234e6111f51 100644
--- a/gatsby-browser.js
+++ b/gatsby-browser.js
@@ -1,3 +1,4 @@
window.g2plot = require('./src/index.ts');
window.dataSet = require('@antv/data-set');
window.util = require('@antv/util');
+window.G = require('@antv/g-canvas');
\ No newline at end of file
diff --git a/gatsby-config.js b/gatsby-config.js
index d1407ca4c4d..c85c0d74855 100644
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -47,6 +47,14 @@ module.exports = {
],
docs: [],
examples: [
+ {
+ slug: 'pie',
+ icon: 'pie',
+ title: {
+ zh: '饼图',
+ en: 'Pie Charts',
+ },
+ },
{
slug: 'scatter',
icon: 'point',
diff --git a/src/adaptor/common.ts b/src/adaptor/common.ts
index 319caf6f02f..217793778ba 100644
--- a/src/adaptor/common.ts
+++ b/src/adaptor/common.ts
@@ -7,6 +7,23 @@ import { Params } from '../core/adaptor';
import { Options } from '../types';
import { Interaction } from '../types/interaction';
+/**
+ * 通用 legend 配置, 适用于带 colorField 的图表
+ * @param params
+ */
+export function legend(params: Params): Params {
+ const { chart, options } = params;
+ const { legend, colorField } = options;
+
+ if (legend === false) {
+ chart.legend(false);
+ } else if (colorField) {
+ chart.legend(colorField, legend);
+ }
+
+ return params;
+}
+
/**
* 通用 tooltip 配置
* @param params
diff --git a/src/core/plot.ts b/src/core/plot.ts
index ced92d2271f..1fc4ce589d7 100644
--- a/src/core/plot.ts
+++ b/src/core/plot.ts
@@ -1,4 +1,5 @@
import { Chart } from '@antv/g2';
+import { deepMix } from '@antv/util';
import { bind } from 'size-sensor';
import { Adaptor } from './adaptor';
import { ChartOptions, Data } from '../types';
@@ -20,7 +21,8 @@ export abstract class Plot {
constructor(container: string | HTMLElement, options: O) {
this.container = typeof container === 'string' ? document.getElementById(container) : container;
- this.options = options;
+ const defaultOptions = this.getDefaultOptions();
+ this.options = deepMix({}, defaultOptions, options);
this.createG2();
}
@@ -44,6 +46,14 @@ export abstract class Plot {
});
}
+ /**
+ * 获取默认的 options 配置项
+ * 每个组件都可以复写
+ */
+ protected getDefaultOptions(): Partial {
+ return {};
+ }
+
/**
* 每个组件有自己的 schema adaptor
*/
diff --git a/src/plots/pie/adaptor.ts b/src/plots/pie/adaptor.ts
index 73d70e98b9d..e93c6904c5e 100644
--- a/src/plots/pie/adaptor.ts
+++ b/src/plots/pie/adaptor.ts
@@ -1,6 +1,6 @@
import { deepMix, each, every, filter, get, isFunction, isString, isNil } from '@antv/util';
import { Params } from '../../core/adaptor';
-import { tooltip, interaction, animation, theme } from '../../adaptor/common';
+import { legend, tooltip, interaction, animation, theme } from '../../adaptor/common';
import { flow, LEVEL, log, template } from '../../utils';
import { StatisticContentStyle, StatisticTitleStyle } from './constants';
import { PieOptions } from './types';
@@ -76,21 +76,6 @@ function coord(params: Params): Params {
return params;
}
-/**
- * legend 配置
- * @param params
- */
-function legend(params: Params): Params {
- const { chart, options } = params;
- const { legend, colorField } = options;
-
- if (legend && colorField) {
- chart.legend(colorField, legend);
- }
-
- return params;
-}
-
/**
* label 配置
* @param params
@@ -106,8 +91,10 @@ function label(params: Params): Params {
} else {
const { callback, ...cfg } = label;
const labelCfg = cfg;
- if (cfg.content) {
- const { content } = cfg;
+
+ // ① 提供模板字符串的 label content 配置
+ if (labelCfg.content) {
+ const { content } = labelCfg;
labelCfg.content = (data: object, dataum: any, index: number) => {
const name = data[colorField];
const value = data[angleField];
@@ -127,8 +114,24 @@ function label(params: Params): Params {
: content;
};
}
+
+ // ② 转换 label type 和 layout type
+ const LABEL_TYPE_MAP = {
+ inner: 'pie-inner',
+ outer: 'pie',
+ };
+ const LABEL_LAYOUT_TYPE_MAP = {
+ inner: '',
+ outer: 'pie-outer',
+ };
+ const labelType = LABEL_TYPE_MAP[labelCfg.type] || 'pie';
+ const labelLayoutType = LABEL_LAYOUT_TYPE_MAP[labelCfg.type] || 'pie-outer';
+ labelCfg.type = labelType;
+ labelCfg.layout = deepMix({}, labelCfg.layout, { type: labelLayoutType });
+
geometry.label({
- fields: [angleField, colorField],
+ // fix: could not create scale, when field is undefined(attributes 中的 fields 定义都会被用来创建 scale)
+ fields: colorField ? [angleField, colorField] : [angleField],
callback,
cfg: labelCfg,
});
@@ -176,26 +179,29 @@ function annotation(params: Params): Params {
if (innerRadius && statistic) {
const { title, content } = statistic;
- let titleLineHeight = get(title, 'style.lineHeight');
- if (!titleLineHeight) {
- titleLineHeight = get(title, 'style.fontSize', 20);
- }
-
- let valueLineHeight = get(content, 'style.lineHeight');
- if (!valueLineHeight) {
- valueLineHeight = get(content, 'style.fontSize', 20);
- }
-
+ let statisticTitle = {
+ type: 'text',
+ content: '',
+ };
+ let statisticContent = {
+ type: 'text',
+ content: '',
+ };
const filterData = chart.getData();
const angleScale = chart.getScaleByField(angleField);
const colorScale = chart.getScaleByField(colorField);
const statisticData = getStatisticData(filterData, angleScale, colorScale);
- const titleFormatter = get(title, 'formatter');
const contentFormatter = get(content, 'formatter');
- annotationOptions.push(
- {
+ if (title !== false) {
+ let titleLineHeight = get(title, 'style.lineHeight');
+ if (!titleLineHeight) {
+ titleLineHeight = get(title, 'style.fontSize', 20);
+ }
+ const titleFormatter = get(title, 'formatter');
+
+ statisticTitle = {
type: 'text',
position: ['50%', '50%'],
content: titleFormatter ? titleFormatter(statisticData, filterData) : statisticData.title,
@@ -204,14 +210,21 @@ function annotation(params: Params): Params {
{
// default config
style: StatisticTitleStyle,
- offsetY: -titleLineHeight,
+ offsetY: content === false ? 0 : -titleLineHeight,
// append-info
key: 'statistic',
},
title
),
- },
- {
+ };
+ }
+
+ if (content !== false) {
+ let valueLineHeight = get(content, 'style.lineHeight');
+ if (!valueLineHeight) {
+ valueLineHeight = get(content, 'style.fontSize', 20);
+ }
+ statisticContent = {
type: 'text',
position: ['50%', '50%'],
content: contentFormatter ? contentFormatter(statisticData, filterData) : statisticData.value,
@@ -220,14 +233,17 @@ function annotation(params: Params): Params {
{
// default config
style: StatisticContentStyle,
- offsetY: valueLineHeight,
+ // 居中
+ offsetY: title === false ? 0 : valueLineHeight,
// append-info
key: 'statistic',
},
content
),
- }
- );
+ };
+ }
+
+ annotationOptions.push(statisticTitle, statisticContent);
chart.render();
}
@@ -242,7 +258,7 @@ function annotation(params: Params): Params {
}
/**
- * 折线图适配器
+ * 饼图适配器
* @param chart
* @param options
*/
diff --git a/src/plots/pie/index.ts b/src/plots/pie/index.ts
index 49626e3893c..b994a6e4284 100644
--- a/src/plots/pie/index.ts
+++ b/src/plots/pie/index.ts
@@ -3,6 +3,7 @@ import { PieOptions } from './types';
import { adaptor } from './adaptor';
import { Adaptor } from '../../core/adaptor';
import './interaction';
+import './label';
export { PieOptions };
@@ -10,6 +11,18 @@ export class Pie extends Plot {
/** 图表类型 */
public type: string = 'pie';
+ /**
+ * 获取 饼图 默认配置项
+ */
+ protected getDefaultOptions(): Partial {
+ return {
+ tooltip: {
+ showTitle: false,
+ showMarkers: false,
+ },
+ };
+ }
+
/**
* 获取 饼图 的适配器
*/
diff --git a/src/plots/pie/label/index.ts b/src/plots/pie/label/index.ts
new file mode 100644
index 00000000000..ba63f435a79
--- /dev/null
+++ b/src/plots/pie/label/index.ts
@@ -0,0 +1,4 @@
+import { registerGeometryLabel } from '@antv/g2';
+import PieInnerLabel from './inner-label';
+
+registerGeometryLabel('pie-inner', PieInnerLabel);
diff --git a/src/plots/pie/label/inner-label.ts b/src/plots/pie/label/inner-label.ts
new file mode 100644
index 00000000000..dea8fdc99dd
--- /dev/null
+++ b/src/plots/pie/label/inner-label.ts
@@ -0,0 +1,37 @@
+import { getGeometryLabel } from '@antv/g2';
+import { deepMix, isNil, isString } from '@antv/util';
+import { parsePercentageToNumber } from '../utils';
+
+const PieLabel = getGeometryLabel('pie');
+
+export default class PieInnerLabel extends PieLabel {
+ public defaultLayout = 'pie-inner';
+
+ /**
+ * 获取 label 的默认配置
+ * - 饼图 inner-label 强制不显示 label 牵引线
+ */
+ protected getDefaultLabelCfg() {
+ const cfg = super.getDefaultLabelCfg();
+ return deepMix({}, cfg, { labelLine: false });
+ }
+
+ /**
+ * 获取标签 offset距离(默认 -30% )
+ * todo G2 offset 允许百分比设置后,移除 ts-ignore
+ */
+ // @ts-ignore
+ protected getDefaultOffset(offset: number | string) {
+ const coordinate = this.getCoordinate();
+ const radius = coordinate.getRadius();
+ let innerRadius = 0;
+ if (coordinate.innerRadius && coordinate.radius) {
+ innerRadius = radius * (coordinate.innerRadius / coordinate.radius);
+ }
+ let actualOffset = offset;
+ if (isString(actualOffset)) {
+ actualOffset = (radius - innerRadius) * parsePercentageToNumber(actualOffset);
+ }
+ return isNil(actualOffset) || actualOffset > 0 ? -(radius - innerRadius) * 0.3 : actualOffset;
+ }
+}
diff --git a/src/plots/pie/types.ts b/src/plots/pie/types.ts
index a7a5c309ee1..266f50d69d0 100644
--- a/src/plots/pie/types.ts
+++ b/src/plots/pie/types.ts
@@ -33,8 +33,11 @@ type Statistic = Readonly<{
export interface PieOptions extends Options {
/** 角度映射字段 */
readonly angleField: string;
+ /** 颜色映射字段 */
readonly colorField?: string;
+ /** 饼图半径 */
readonly radius?: number;
+ /** 饼图内半径 */
readonly innerRadius?: number;
/** 饼图图形样式 */
diff --git a/src/plots/pie/utils.ts b/src/plots/pie/utils.ts
index 4987568b6d2..3e439c13bbd 100644
--- a/src/plots/pie/utils.ts
+++ b/src/plots/pie/utils.ts
@@ -1,6 +1,6 @@
import { Scale } from '@antv/g2/lib/dependents';
import { Data, Datum } from '@antv/g2/lib/interface';
-import { each, isArray } from '@antv/util';
+import { each, isArray, isString } from '@antv/util';
import { StatisticData } from './types';
/**
@@ -42,3 +42,16 @@ export function getStatisticData(data: Data | Datum, angleScale?: Scale, colorSc
value: angleScale ? angleScale.getText(data[angleField]) : data[angleField],
};
}
+
+/**
+ * 将 字符串的百分比 转换为 数值百分比
+ */
+export function parsePercentageToNumber(percentage: string): number {
+ if (!isString(percentage)) {
+ return percentage;
+ }
+ if (percentage.endsWith('%')) {
+ return Number(percentage.slice(0, -1)) * 0.01;
+ }
+ return Number(percentage);
+}