Skip to content

Commit f7b68f8

Browse files
visikyxinming
and
xinming
authored
Feat pie (#1290)
* chore: test-live 添加 watch 参数 * feat(types): 共同类型定义 * feat(common): 抽取共同 adaptor 构建器 [x] 支持 tooltip * feat(pie-plot): 新增 饼图 [x] angleField, colorField, legend, tooltip, pieStyle, etc * fix(pie-plot): fix cr suggestions Co-authored-by: xinming <[email protected]>
1 parent d89ecf6 commit f7b68f8

File tree

10 files changed

+313
-19
lines changed

10 files changed

+313
-19
lines changed
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { Pie } from '../../../../src';
2+
import { POSITIVE_NEGATIVE_DATA } from '../../../data/common';
3+
import { createDiv } from '../../../utils/dom';
4+
5+
describe('pie', () => {
6+
const data = POSITIVE_NEGATIVE_DATA.filter((o) => o.value > 0).map((d, idx) =>
7+
idx === 1 ? { ...d, type: 'item1' } : d
8+
);
9+
it('angleField: single color', () => {
10+
const pie = new Pie(createDiv(), {
11+
width: 400,
12+
height: 300,
13+
data,
14+
angleField: 'value',
15+
radius: 0.8,
16+
});
17+
18+
pie.render();
19+
20+
const geometry = pie.chart.geometries[0];
21+
const elements = geometry.elements;
22+
23+
expect(elements.length).toBe(data.length);
24+
expect(elements[0].getModel().color).toBe(elements[1].getModel().color);
25+
});
26+
27+
it('angleField with colorField: multiple colors', () => {
28+
const pie = new Pie(createDiv(), {
29+
width: 400,
30+
height: 300,
31+
data,
32+
angleField: 'value',
33+
colorField: 'type',
34+
color: ['blue', 'red', 'yellow', 'lightgreen', 'lightblue', 'pink'],
35+
radius: 0.8,
36+
});
37+
38+
pie.render();
39+
40+
const geometry = pie.chart.geometries[0];
41+
const elements = geometry.elements;
42+
// @ts-ignore
43+
expect(elements.length).toBe(data.length);
44+
// 绘图数据
45+
expect(elements[0].getModel().style?.fill || elements[0].getModel().color).toBe('blue');
46+
expect(elements[1].getModel().style?.fill || elements[1].getModel().color).toBe('red');
47+
});
48+
49+
it('no radius', () => {
50+
const pie = new Pie(createDiv(), {
51+
width: 400,
52+
height: 300,
53+
data,
54+
angleField: 'value',
55+
colorField: 'type',
56+
});
57+
58+
pie.render();
59+
60+
const coordinate = pie.chart.getCoordinate();
61+
const { radius } = coordinate;
62+
const polarRadius = coordinate.getRadius();
63+
expect(radius).toBeUndefined();
64+
expect(polarRadius).toBeGreaterThan(0);
65+
66+
const geometry = pie.chart.geometries[0];
67+
const elements = geometry.elements;
68+
});
69+
70+
it('innerRadius', () => {
71+
const pie = new Pie(createDiv(), {
72+
width: 400,
73+
height: 300,
74+
data,
75+
angleField: 'value',
76+
colorField: 'type',
77+
color: ['blue', 'red', 'yellow', 'lightgreen', 'lightblue', 'pink'],
78+
radius: 0.8,
79+
innerRadius: 0.5,
80+
});
81+
82+
pie.render();
83+
84+
const coordinate = pie.chart.getCoordinate();
85+
const { innerRadius, radius } = coordinate;
86+
expect(innerRadius).toBe((radius / 0.8) * 0.5);
87+
});
88+
89+
it('pieStyle: custom style of pie', () => {
90+
const pie = new Pie(createDiv(), {
91+
width: 400,
92+
height: 300,
93+
data,
94+
angleField: 'value',
95+
colorField: 'type',
96+
color: ['blue', 'red', 'yellow', 'lightgreen', 'lightblue', 'pink'],
97+
radius: 0.8,
98+
innerRadius: 0.5,
99+
pieStyle: {
100+
fill: 'red',
101+
lineWidth: 3,
102+
stroke: 'yellow',
103+
},
104+
});
105+
106+
pie.render();
107+
108+
const geometry = pie.chart.geometries[0];
109+
const elements = geometry.elements;
110+
expect(elements[0].getModel().style?.fill).toBe('red');
111+
expect(elements[1].getModel().style?.fill).toBe('red');
112+
expect(elements[1].getModel().style?.lineWidth).toBe(3);
113+
expect(elements[1].getModel().style?.stroke).toBe('yellow');
114+
});
115+
116+
it('pieStyle: with callback', () => {
117+
const pie = new Pie(createDiv(), {
118+
width: 400,
119+
height: 300,
120+
data,
121+
angleField: 'value',
122+
colorField: 'type',
123+
color: ['blue', 'red', 'yellow', 'lightgreen', 'lightblue', 'pink'],
124+
radius: 0.8,
125+
innerRadius: 0.5,
126+
pieStyle: (item) => ({
127+
fill: item === 'item1' ? 'blue' : 'red',
128+
lineWidth: 3,
129+
stroke: 'yellow',
130+
}),
131+
});
132+
133+
pie.render();
134+
135+
const geometry = pie.chart.geometries[0];
136+
const elements = geometry.elements;
137+
expect(elements[0].getModel().style?.fill).toBe('red');
138+
expect(elements[1].getModel().style?.fill).toBe('blue');
139+
expect(elements[2].getModel().style?.fill).toBe('red');
140+
});
141+
});

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"lint": "eslint --ext .ts ./src ./__tests__",
3535
"lint-staged": "lint-staged",
3636
"test": "jest",
37-
"test-live": "DEBUG_MODE=1 jest ./__tests__",
37+
"test-live": "DEBUG_MODE=1 jest --watch ./__tests__",
3838
"coverage": "jest --coverage",
3939
"ci": "run-s lint build coverage",
4040
"changelog": "conventional-changelog -i CHANGELOG.md -a -s",

src/common/adaptor.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @file 通用的一些 adaptor
3+
*/
4+
import { Params } from '../core/adaptor';
5+
import { Options } from '../types';
6+
7+
/**
8+
* 通用 tooltip 配置
9+
* @param params
10+
*/
11+
export function tooltip<O extends Options>(params: Params<O>): Params<O> {
12+
const { chart, options } = params;
13+
const { tooltip } = options;
14+
15+
if (tooltip) {
16+
chart.tooltip(tooltip);
17+
}
18+
19+
return params;
20+
}

src/core/adaptor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Chart } from '@antv/g2';
1+
import { Chart, Geometry } from '@antv/g2';
22
import { Options } from '../types';
33

44
/**

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export * from './types';
55

66
// 折线图及类型定义
77
export { Line, LineOptions } from './plots/line';
8+
// 饼图及类型定义
9+
export { Pie, PieOptions } from './plots/pie';
810

911
//散点图及类型定义
1012
export { Scatter, ScatterOptions } from './plots/scatter';

src/plots/line/adaptor.ts

+1-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { deepMix } from '@antv/util';
22
import { Params } from '../../core/adaptor';
3+
import { tooltip } from '../../common/adaptor';
34
import { flow, pick } from '../../utils';
45
import { LineOptions } from './types';
56

@@ -81,21 +82,6 @@ function legend(params: Params<LineOptions>): Params<LineOptions> {
8182
return params;
8283
}
8384

84-
/**
85-
* tooltip 配置
86-
* @param params
87-
*/
88-
function tooltip(params: Params<LineOptions>): Params<LineOptions> {
89-
const { chart, options } = params;
90-
const { tooltip } = options;
91-
92-
if (tooltip) {
93-
chart.tooltip(tooltip);
94-
}
95-
96-
return params;
97-
}
98-
9985
/**
10086
* 折线图适配器
10187
* @param chart

src/plots/pie/adaptor.ts

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { deepMix, isFunction } from '@antv/util';
2+
import { Params } from '../../core/adaptor';
3+
import { tooltip } from '../../common/adaptor';
4+
import { flow } from '../../utils';
5+
import { PieOptions } from './types';
6+
7+
/**
8+
* 字段
9+
* @param params
10+
*/
11+
function field(params: Params<PieOptions>): Params<PieOptions> {
12+
const { chart, options } = params;
13+
const { data, angleField, colorField, color } = options;
14+
15+
// TODO 饼图数据非法处理
16+
chart.data(data);
17+
const geometry = chart.interval().position(`1*${angleField}`).adjust({ type: 'stack' });
18+
19+
if (colorField) {
20+
geometry.color(colorField, color);
21+
}
22+
23+
return params;
24+
}
25+
26+
/**
27+
* meta 配置
28+
* @param params
29+
*/
30+
function meta(params: Params<PieOptions>): Params<PieOptions> {
31+
const { chart, options } = params;
32+
const { meta, colorField } = options;
33+
34+
// meta 直接是 scale 的信息
35+
const scales = deepMix({}, meta);
36+
chart.scale(scales, {
37+
[colorField]: { type: 'cat' },
38+
});
39+
40+
return params;
41+
}
42+
43+
/**
44+
* coord 配置
45+
* @param params
46+
*/
47+
function coord(params: Params<PieOptions>): Params<PieOptions> {
48+
const { chart, options } = params;
49+
const { radius, innerRadius } = options;
50+
51+
chart.coordinate({
52+
type: 'theta',
53+
cfg: {
54+
radius,
55+
innerRadius,
56+
},
57+
});
58+
59+
return params;
60+
}
61+
62+
/**
63+
* legend 配置
64+
* @param params
65+
*/
66+
function legend(params: Params<PieOptions>): Params<PieOptions> {
67+
const { chart, options } = params;
68+
const { legend, colorField } = options;
69+
70+
if (legend && colorField) {
71+
chart.legend(colorField, legend);
72+
}
73+
74+
return params;
75+
}
76+
77+
/**
78+
* style 配置
79+
* @param params
80+
*/
81+
function style(params: Params<PieOptions>): Params<PieOptions> {
82+
const { chart, options } = params;
83+
const { pieStyle, angleField, colorField } = options;
84+
85+
const geometry = chart.geometries[0];
86+
if (pieStyle && geometry) {
87+
if (isFunction(pieStyle)) {
88+
// 为了兼容,colorField 放第一位
89+
geometry.style(colorField ? `${colorField}*${angleField}` : angleField, pieStyle);
90+
} else {
91+
geometry.style(pieStyle);
92+
}
93+
}
94+
95+
return params;
96+
}
97+
98+
/**
99+
* 折线图适配器
100+
* @param chart
101+
* @param options
102+
*/
103+
export function adaptor(params: Params<PieOptions>) {
104+
// flow 的方式处理所有的配置到 G2 API
105+
flow(field, meta, coord, legend, tooltip, style)(params);
106+
}

src/plots/pie/index.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Plot } from '../../core/plot';
2+
import { PieOptions } from './types';
3+
import { adaptor } from './adaptor';
4+
import { Adaptor } from '../../core/adaptor';
5+
6+
export { PieOptions };
7+
8+
export class Pie extends Plot<PieOptions> {
9+
/** 图表类型 */
10+
public type: string = 'pie';
11+
12+
/**
13+
* 获取 饼图 的适配器
14+
*/
15+
protected getSchemaAdaptor(): Adaptor<PieOptions> {
16+
return adaptor;
17+
}
18+
}

src/plots/pie/types.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Options } from '../../types';
2+
import { ShapeStyle } from '../../types/style';
3+
4+
export interface PieOptions extends Options {
5+
/** 角度映射字段 */
6+
readonly angleField: string;
7+
readonly colorField?: string;
8+
readonly radius?: number;
9+
readonly innerRadius?: number;
10+
11+
/** 饼图图形样式 */
12+
readonly pieStyle?: ShapeStyle | ((...args: string[]) => ShapeStyle);
13+
}

src/types/style.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
1-
/** G shape style 配置 */
2-
export type ShapeStyle = {};
1+
/** G shape style 配置, 按道理应该从 G 中引入 */
2+
export type ShapeStyle = Readonly<{
3+
fill?: string;
4+
stroke?: string;
5+
lineWidth?: number;
6+
lineDash?: number[];
7+
opacity?: number;
8+
fillOpacity?: number;
9+
strokeOpacity?: number;
10+
}>;

0 commit comments

Comments
 (0)