Skip to content

Commit 9156861

Browse files
authored
feat(waterfall): 初始化瀑布图 (#1697)
* feat(waterfall): 初始化瀑布图 **checklist** - [x] draw - [x] types define - [x] custom register shape - [ ] tooltip - [ ] legend - [ ] testcases - [ ] others, such as: diff-label * refactor(waterfall-utils): 修改瀑布图处理数据方法 & 添加测例 * feat(waterfall-label): 完善 label, 提供 labelDataMode labelDataMode 指定标签展示绝对值还是相对差值 * feat(waterfall-demo): 完善基础 demo & 优化默认配置 * fix(waterfall-testcases): 修复测试用例 * fix(waterfalll-shape): 修复 shape 连接线异常 * feat(waterfall): 瀑布图各种补全 - [x] process data - [x] legend - [x] color, support config `risingFill` and `fallingFill` - [x] defaultOptions & demo * feat(waterfall-testcases): 增加测试用例 - [ ] base - [ ] label - [x] annotaiton - [x] axis - [x] style - [x] shape * feat(mapping): 添加 rawFields 参数, 增强回调参数数据 * fix: 修改 cr 建议 * feat(waterfall-docs): 瀑布图添加 api 文档, 使用文档暂缓 * refactor(waterfall): 修改 ci 建议,将数据处理抽取为一个方法 & 抽取通用变量到 constants 中 * refactor(waterfall): 修改 cr 建议
1 parent e7dea70 commit 9156861

26 files changed

+1315
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Waterfall } from '../../../../src';
2+
import { salesByArea } from '../../../data/sales';
3+
import { createDiv } from '../../../utils/dom';
4+
5+
describe('annotation', () => {
6+
const waterfall = new Waterfall(createDiv(), {
7+
width: 300,
8+
height: 400,
9+
data: salesByArea,
10+
xField: 'sales',
11+
yField: 'area',
12+
});
13+
14+
waterfall.render();
15+
16+
it('text annotation', () => {
17+
waterfall.update({
18+
...waterfall.options,
19+
annotations: [
20+
{
21+
type: 'text',
22+
position: ['median', 'median'],
23+
content: '辅助文本',
24+
},
25+
],
26+
});
27+
expect(waterfall.chart.getController('annotation').getComponents().length).toBe(1);
28+
expect(waterfall.chart.getController('annotation').getComponents()[0].component.get('content')).toBe('辅助文本');
29+
});
30+
31+
it('text annotation and line annotation', () => {
32+
waterfall.update({
33+
...waterfall.options,
34+
annotations: [
35+
{
36+
type: 'text',
37+
position: ['median', 'median'],
38+
content: '辅助文本',
39+
},
40+
{
41+
type: 'line',
42+
start: ['min', 'median'],
43+
end: ['max', 'median'],
44+
},
45+
],
46+
});
47+
expect(waterfall.chart.getController('annotation').getComponents().length).toBe(2);
48+
expect(waterfall.chart.getController('annotation').getComponents()[0].component.get('content')).toBe('辅助文本');
49+
expect(waterfall.chart.getController('annotation').getComponents()[1].component.get('type')).toBe('line');
50+
});
51+
52+
afterAll(() => {
53+
waterfall.destroy();
54+
});
55+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Waterfall } from '../../../../src';
2+
import { salesByArea } from '../../../data/sales';
3+
import { createDiv } from '../../../utils/dom';
4+
5+
describe('waterfall axis', () => {
6+
it('meta', () => {
7+
const formatter = (v) => `${Math.floor(v / 10000)}万`;
8+
const waterfall = new Waterfall(createDiv(), {
9+
width: 400,
10+
height: 300,
11+
data: salesByArea,
12+
xField: 'area',
13+
yField: 'sales',
14+
meta: {
15+
sales: {
16+
nice: true,
17+
formatter,
18+
},
19+
},
20+
});
21+
22+
waterfall.render();
23+
24+
const geometry = waterfall.chart.geometries[0];
25+
// @ts-ignore
26+
expect(geometry.scales.sales.nice).toBe(true);
27+
expect(geometry.scales.sales.formatter).toBe(formatter);
28+
29+
// 柱状图默认为 cat 类型
30+
// @ts-ignore
31+
expect(geometry.scales.area.type).toBe('cat');
32+
});
33+
34+
it('xAxis', () => {
35+
const waterfall = new Waterfall(createDiv(), {
36+
width: 400,
37+
height: 300,
38+
data: salesByArea,
39+
xField: 'area',
40+
yField: 'sales',
41+
xAxis: {
42+
label: {
43+
rotate: -Math.PI / 2,
44+
},
45+
},
46+
});
47+
48+
waterfall.render();
49+
const axisOptions = waterfall.chart.getOptions().axes;
50+
51+
// @ts-ignore
52+
expect(axisOptions.area.label.rotate).toBe(-Math.PI / 2);
53+
});
54+
55+
it('yAxis', () => {
56+
const waterfall = new Waterfall(createDiv(), {
57+
width: 400,
58+
height: 300,
59+
data: salesByArea,
60+
xField: 'area',
61+
yField: 'sales',
62+
yAxis: {
63+
minLimit: 10000,
64+
nice: true,
65+
},
66+
});
67+
68+
waterfall.render();
69+
70+
const geometry = waterfall.chart.geometries[0];
71+
const axisOptions = waterfall.chart.getOptions().axes;
72+
73+
// @ts-ignore
74+
expect(axisOptions.sales.minLimit).toBe(10000);
75+
expect(geometry.scales.sales.minLimit).toBe(10000);
76+
// @ts-ignore
77+
expect(geometry.scales.sales.nice).toBe(true);
78+
});
79+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Waterfall } from '../../../../src';
2+
import { createDiv } from '../../../utils/dom';
3+
4+
describe('waterfall plot', () => {
5+
const data = [
6+
{ type: '日用品', money: 300 },
7+
{ type: '伙食费', money: 900 },
8+
{ type: '交通费', money: -200 },
9+
{ type: '水电费', money: 300 },
10+
{ type: '房租', money: 1200 },
11+
{ type: '商场消费', money: 1000 },
12+
{ type: '应酬交际', money: 2000 },
13+
{ type: '总费用', money: 5900 },
14+
];
15+
16+
const waterfall = new Waterfall(createDiv(), {
17+
width: 400,
18+
height: 300,
19+
xField: 'type',
20+
yField: 'money',
21+
data: data,
22+
});
23+
24+
it('basic', () => {
25+
waterfall.render();
26+
const geometry = waterfall.chart.geometries[0];
27+
expect(geometry.elements.length).toBe(data.length + 1);
28+
});
29+
30+
it('total: false', () => {
31+
waterfall.update({ ...waterfall.options, total: false });
32+
expect(waterfall.chart.geometries[0].elements.length).toBe(data.length);
33+
});
34+
35+
it('total: label', () => {
36+
waterfall.update({ ...waterfall.options, total: { label: 'test' } });
37+
38+
const xScale = waterfall.chart.getScaleByField('type');
39+
expect(xScale.values.slice(-1)[0]).toBe('test');
40+
});
41+
42+
it('total: style', () => {
43+
waterfall.update({ ...waterfall.options, total: { style: { fill: 'red' } } });
44+
45+
expect(waterfall.chart.geometries[0].elements.length).toBe(data.length + 1);
46+
expect(waterfall.chart.geometries[0].elements[data.length].shape.get('children')[0].attr('fill')).toBe('red');
47+
});
48+
49+
it('leaderLine: false', () => {
50+
const shapes = waterfall.chart.geometries[0].getShapes();
51+
shapes.forEach((shape, idx) => {
52+
if (idx !== shapes.length - 1) {
53+
expect(shape.get('children').length).toBe(2);
54+
}
55+
});
56+
57+
waterfall.update({ ...waterfall.options, leaderLine: false });
58+
waterfall.chart.geometries[0].getShapes().forEach((shape) => {
59+
expect(shape.get('children').length).toBe(1);
60+
});
61+
});
62+
63+
it('leaderLine: style', () => {
64+
waterfall.update({ ...waterfall.options, leaderLine: { style: { stroke: 'red' } } });
65+
const shapes = waterfall.chart.geometries[0].getShapes();
66+
shapes.forEach((shape, idx) => {
67+
if (idx !== shapes.length - 1) {
68+
expect(shape.get('children')[1].attr('stroke')).toBe('red');
69+
}
70+
});
71+
});
72+
73+
it('color: risingFill & fallingFill', () => {
74+
waterfall.update({ ...waterfall.options, risingFill: 'pink', fallingFill: '#000' });
75+
expect(waterfall.chart.geometries[0].elements[0].shape.get('children')[0].attr('fill')).toBe('pink');
76+
expect(waterfall.chart.geometries[0].elements[2].shape.get('children')[0].attr('fill')).toBe('#000');
77+
});
78+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Waterfall } from '../../../../src';
2+
import { salesByArea } from '../../../data/sales';
3+
import { createDiv } from '../../../utils/dom';
4+
5+
describe('waterfall label', () => {
6+
it('position top', () => {
7+
const waterfall = new Waterfall(createDiv('position top'), {
8+
width: 400,
9+
height: 300,
10+
data: salesByArea,
11+
xField: 'area',
12+
yField: 'sales',
13+
meta: {
14+
sales: {
15+
nice: true,
16+
formatter: (v) => `${Math.floor(v / 10000)}万`,
17+
},
18+
},
19+
label: {
20+
position: 'top',
21+
},
22+
});
23+
24+
waterfall.render();
25+
26+
const geometry = waterfall.chart.geometries[0];
27+
const labelGroups = geometry.labelsContainer.getChildren();
28+
29+
// @ts-ignore
30+
expect(geometry.labelOption.cfg).toEqual({
31+
position: 'top',
32+
});
33+
expect(labelGroups).toHaveLength(salesByArea.length + 1);
34+
labelGroups.forEach((label, index) => {
35+
if (index !== salesByArea.length) {
36+
expect(label.get('children')[0].attr('text')).toBe(`${Math.floor(salesByArea[index].sales / 10000)}万`);
37+
}
38+
});
39+
});
40+
41+
it('position middle', () => {
42+
const waterfall = new Waterfall(createDiv('position middle'), {
43+
width: 400,
44+
height: 300,
45+
data: salesByArea,
46+
xField: 'area',
47+
yField: 'sales',
48+
meta: {
49+
sales: {
50+
nice: true,
51+
formatter: (v) => `${Math.floor(v / 10000)}万`,
52+
},
53+
},
54+
label: {
55+
position: 'middle',
56+
},
57+
});
58+
59+
waterfall.render();
60+
61+
const geometry = waterfall.chart.geometries[0];
62+
63+
// @ts-ignore
64+
expect(geometry.labelOption.cfg).toEqual({ position: 'middle' });
65+
});
66+
67+
it('position bottom', () => {
68+
const waterfall = new Waterfall(createDiv('position bottom'), {
69+
width: 400,
70+
height: 300,
71+
data: salesByArea,
72+
xField: 'area',
73+
yField: 'sales',
74+
meta: {
75+
sales: {
76+
nice: true,
77+
formatter: (v) => `${Math.floor(v / 10000)}万`,
78+
},
79+
},
80+
label: {
81+
position: 'bottom',
82+
},
83+
});
84+
85+
waterfall.render();
86+
87+
const geometry = waterfall.chart.geometries[0];
88+
89+
// @ts-ignore
90+
expect(geometry.labelOption.cfg).toEqual({ position: 'bottom' });
91+
});
92+
});

0 commit comments

Comments
 (0)