Skip to content

Commit 3ff9ecc

Browse files
yp0413150120沈建斌
and
沈建斌
authored
Feat box plot change data (#2283)
* feat: 重新箱线图changeData方法 && 修复多View情况下y轴度量不一致 * feat: 添加箱线图changeData单测 * feat: 添加箱线图原始数据转换方法单测 * fix: 完善箱线图关于异常值的单测 * fix: 修复lint问题 * fix: 修改子View不直接修改data * fix: 完善utils方法类型定义 * fix: 完善箱线图transformData类型定义及注释 Co-authored-by: 沈建斌 <[email protected]>
1 parent 9e62cb9 commit 3ff9ecc

File tree

7 files changed

+192
-17
lines changed

7 files changed

+192
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Box } from '../../../../src';
2+
import { OUTLIERS_VIEW_ID } from '../../../../src/plots/box/constant';
3+
import { boxData, outliersData } from '../../../data/box';
4+
import { createDiv } from '../../../utils/dom';
5+
6+
describe('box change data', () => {
7+
it('base box change data: normal', () => {
8+
const box = new Box(createDiv(), {
9+
width: 400,
10+
height: 500,
11+
data: boxData,
12+
xField: 'x',
13+
yField: ['low', 'q1', 'median', 'q3', 'high'],
14+
});
15+
16+
box.render();
17+
18+
expect(box.chart.geometries[0].elements.length).toEqual(boxData.length);
19+
expect(box.options.data).toEqual(boxData);
20+
21+
const newData = [...boxData, { x: 'new node', low: 11, q1: 66, median: 88, q3: 113, high: 116 }];
22+
23+
box.changeData(newData);
24+
expect(box.chart.geometries[0].elements.length).toEqual(newData.length);
25+
expect(box.options.data).toEqual(newData);
26+
27+
box.destroy();
28+
});
29+
30+
it('base box change data: from empty to have data', () => {
31+
const box = new Box(createDiv(), {
32+
width: 400,
33+
height: 500,
34+
data: [],
35+
xField: 'x',
36+
yField: ['low', 'q1', 'median', 'q3', 'high'],
37+
});
38+
39+
box.render();
40+
41+
expect(box.chart.geometries[0].elements.length).toEqual(0);
42+
43+
box.changeData(boxData);
44+
expect(box.chart.geometries[0].elements.length).toEqual(boxData.length);
45+
expect(box.options.data).toEqual(boxData);
46+
47+
box.destroy();
48+
});
49+
50+
it('outliers box change data: normal', () => {
51+
const box = new Box(createDiv(), {
52+
width: 400,
53+
height: 500,
54+
data: outliersData,
55+
xField: 'x',
56+
yField: ['low', 'q1', 'median', 'q3', 'high'],
57+
outliersField: 'outliers',
58+
});
59+
60+
box.render();
61+
const outliersView = box.chart.views.find((v) => v.id === OUTLIERS_VIEW_ID);
62+
63+
expect(box.chart.geometries[0].elements.length).toEqual(outliersData.length);
64+
expect(outliersView.geometries[0].elements.length).toEqual(outliersData.length);
65+
66+
const newData = [
67+
...outliersData,
68+
{ x: '职业 I', low: 68000, q1: 73000, median: 88000, q3: 96000, high: 120000, outliers: [58000, 55000] },
69+
];
70+
71+
box.changeData(newData);
72+
expect(box.chart.geometries[0].elements.length).toEqual(newData.length);
73+
expect(outliersView.geometries[0].elements.length).toEqual(newData.length);
74+
box.destroy();
75+
});
76+
});

__tests__/unit/plots/box/outliers-spec.ts

+14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Box } from '../../../../src';
2+
import { OUTLIERS_VIEW_ID, BOX_SYNC_NAME, BOX_RANGE } from '../../../../src/plots/box/constant';
23
import { outliersData } from '../../../data/box';
34
import { createDiv } from '../../../utils/dom';
45

@@ -17,11 +18,24 @@ describe('box outliers', () => {
1718

1819
const view = box.chart.views[0];
1920
const geometry = view.geometries[0];
21+
const outliersScale = view.getScaleByField('outliers');
22+
const yFieldScale = box.chart.getScaleByField(BOX_RANGE);
2023

24+
// id
25+
expect(view.id).toBe(OUTLIERS_VIEW_ID);
2126
// 类型
2227
expect(geometry.type).toBe('point');
2328
// 图形元素个数
2429
expect(geometry.elements.length).toBe(outliersData.length);
30+
// 同步y轴度量 axis sync
31+
// @ts-ignore
32+
expect(outliersScale.sync).toEqual(BOX_SYNC_NAME);
33+
// @ts-ignore
34+
expect(outliersScale.nice).toBeTruthy();
35+
// @ts-ignore
36+
expect(yFieldScale.sync).toEqual(BOX_SYNC_NAME);
37+
// @ts-ignore
38+
expect(yFieldScale.nice).toBeTruthy();
2539

2640
box.destroy();
2741
});
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { transformData } from '../../../../src/plots/box/utils';
2+
import { BOX_RANGE } from '../../../../src/plots/box/constant';
3+
import { boxData } from '../../../data/box';
4+
5+
describe('transformData', () => {
6+
it('normal data: typeof yField is array', () => {
7+
const data = transformData(boxData, ['low', 'q1', 'median', 'q3', 'high']);
8+
expect(data).toEqual([
9+
{ x: 'Oceania', low: 1, q1: 9, median: 16, q3: 22, high: 24, [BOX_RANGE]: [1, 9, 16, 22, 24] },
10+
{ x: 'East Europe', low: 1, q1: 5, median: 8, q3: 12, high: 16, [BOX_RANGE]: [1, 5, 8, 12, 16] },
11+
{ x: 'Australia', low: 1, q1: 8, median: 12, q3: 19, high: 26, [BOX_RANGE]: [1, 8, 12, 19, 26] },
12+
{ x: 'South America', low: 2, q1: 8, median: 12, q3: 21, high: 28, [BOX_RANGE]: [2, 8, 12, 21, 28] },
13+
{ x: 'North Africa', low: 1, q1: 8, median: 14, q3: 18, high: 24, [BOX_RANGE]: [1, 8, 14, 18, 24] },
14+
{ x: 'North America', low: 3, q1: 10, median: 17, q3: 28, high: 30, [BOX_RANGE]: [3, 10, 17, 28, 30] },
15+
{ x: 'West Europe', low: 1, q1: 7, median: 10, q3: 17, high: 22, [BOX_RANGE]: [1, 7, 10, 17, 22] },
16+
{ x: 'West Africa', low: 1, q1: 6, median: 8, q3: 13, high: 16, [BOX_RANGE]: [1, 6, 8, 13, 16] },
17+
]);
18+
});
19+
20+
it('normal data: typeof yField is not array', () => {
21+
const data = transformData(boxData, 'low');
22+
expect(data).toEqual(boxData);
23+
});
24+
25+
it('invalid data: typeof yField is array', () => {
26+
const data = [
27+
{ x: 'Oceania', low: 1, q1: 9, median: 16, q3: undefined, high: 24 },
28+
{ x: 'East Europe', low: 1, q1: undefined, median: 8, q3: 12, high: 16 },
29+
{ x: 'Australia', low: 1, q1: 8, median: 12, q3: 19, high: 26 },
30+
{ x: 'South America', low: 2, q1: 8, median: null, q3: 21, high: null },
31+
];
32+
33+
const newData = transformData(data, ['low', 'q1', 'median', 'q3', 'high']);
34+
expect(newData).toEqual([
35+
{ x: 'Oceania', low: 1, q1: 9, median: 16, q3: undefined, high: 24, [BOX_RANGE]: [1, 9, 16, undefined, 24] },
36+
{ x: 'East Europe', low: 1, q1: undefined, median: 8, q3: 12, high: 16, [BOX_RANGE]: [1, undefined, 8, 12, 16] },
37+
{ x: 'Australia', low: 1, q1: 8, median: 12, q3: 19, high: 26, [BOX_RANGE]: [1, 8, 12, 19, 26] },
38+
{ x: 'South America', low: 2, q1: 8, median: null, q3: 21, high: null, [BOX_RANGE]: [2, 8, null, 21, null] },
39+
]);
40+
});
41+
42+
it('invalid data: typeof yField is not array', () => {
43+
const data = [
44+
{ x: 'Oceania', low: 1, q1: 9, median: 16, q3: undefined, high: 24 },
45+
{ x: 'East Europe', low: 1, q1: undefined, median: 8, q3: 12, high: 16 },
46+
{ x: 'Australia', low: 1, q1: 8, median: 12, q3: 19, high: 26 },
47+
{ x: 'South America', low: 2, q1: 8, median: null, q3: 21, high: null },
48+
];
49+
50+
const newData = transformData(data, 'low');
51+
expect(newData).toEqual(data);
52+
});
53+
});

src/plots/box/adaptor.ts

+7-16
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { isFunction, map, isObject } from '@antv/util';
1+
import { isFunction, isObject } from '@antv/util';
22
import { Params } from '../../core/adaptor';
33
import { interaction, animation, theme } from '../../adaptor/common';
44
import { findGeometry } from '../../utils';
55
import { flow, pick, deepAssign } from '../../utils';
66
import { AXIS_META_CONFIG_KEYS } from '../../constant';
77
import { BoxOptions } from './types';
8-
import { BOX_RANGE, BOX_SYNC_NAME } from './constant';
8+
import { BOX_RANGE, BOX_SYNC_NAME, OUTLIERS_VIEW_ID } from './constant';
9+
import { transformData } from './utils';
910

1011
/**
1112
* 字段
@@ -24,17 +25,7 @@ function field(params: Params<BoxOptions>): Params<BoxOptions> {
2425
geometry.color(groupField, color).adjust('dodge');
2526
}
2627

27-
// formate data when `yField` is Array
28-
let data = options.data;
29-
if (Array.isArray(yField)) {
30-
const [low, q1, median, q3, high] = yField;
31-
data = map(data, (obj) => {
32-
obj[BOX_RANGE] = [obj[low], obj[q1], obj[median], obj[q3], obj[high]];
33-
return obj;
34-
});
35-
}
36-
37-
chart.data(data);
28+
chart.data(transformData(options.data, yField));
3829

3930
return params;
4031
}
@@ -45,7 +36,7 @@ function outliersPoint(params: Params<BoxOptions>): Params<BoxOptions> {
4536

4637
if (!outliersField) return params;
4738

48-
const outliersView = chart.createView({ padding });
39+
const outliersView = chart.createView({ padding, id: OUTLIERS_VIEW_ID });
4940
outliersView.data(data);
5041
outliersView.axis(false);
5142
const geometry = outliersView.point().position(`${xField}*${outliersField}`).shape('circle');
@@ -84,8 +75,8 @@ function meta(params: Params<BoxOptions>): Params<BoxOptions> {
8475
if (outliersField) {
8576
const syncName = BOX_SYNC_NAME;
8677
baseMeta = {
87-
[outliersField]: { sync: syncName },
88-
[yFieldName]: { sync: syncName },
78+
[outliersField]: { sync: syncName, nice: true },
79+
[yFieldName]: { sync: syncName, nice: true },
8980
};
9081
}
9182

src/plots/box/constant.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export const BOX_RANGE = '$$range$$';
22
export const BOX_RANGE_ALIAS = 'low-q1-median-q3-high';
33

44
export const BOX_SYNC_NAME = '$$y_outliers$$';
5+
6+
export const OUTLIERS_VIEW_ID = 'outliers_view';

src/plots/box/index.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,30 @@ import { deepAssign } from '../../utils';
33
import { Adaptor } from '../../core/adaptor';
44
import { BoxOptions } from './types';
55
import { adaptor } from './adaptor';
6-
import { BOX_RANGE, BOX_RANGE_ALIAS } from './constant';
6+
import { transformData } from './utils';
7+
import { BOX_RANGE, BOX_RANGE_ALIAS, OUTLIERS_VIEW_ID } from './constant';
78
export { BoxOptions };
89

910
export class Box extends Plot<BoxOptions> {
1011
/** 图表类型 */
1112
public type: string = 'box';
1213

14+
/**
15+
* @override
16+
* @param data
17+
*/
18+
public changeData(data) {
19+
this.updateOption({ data });
20+
const { yField } = this.options;
21+
22+
const outliersView = this.chart.views.find((v) => v.id === OUTLIERS_VIEW_ID);
23+
if (outliersView) {
24+
outliersView.data(data);
25+
}
26+
27+
this.chart.changeData(transformData(data, yField));
28+
}
29+
1330
/**
1431
* 获取 箱型图 默认配置项
1532
*/

src/plots/box/utils.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { map } from '@antv/util';
2+
import { BOX_RANGE } from './constant';
3+
import { BoxOptions } from './types';
4+
5+
/**
6+
* @desc 将数据转换为 box 需要的的图表数据,如果yField为数组,从data中解构出对应数组值并写入data,否则直接返回data
7+
* @param data
8+
* @param yField
9+
*/
10+
export const transformData = (data: BoxOptions['data'], yField: BoxOptions['yField']) => {
11+
let newData = data;
12+
// formate data when `yField` is Array
13+
if (Array.isArray(yField)) {
14+
const [low, q1, median, q3, high] = yField;
15+
newData = map(data, (obj) => {
16+
obj[BOX_RANGE] = [obj[low], obj[q1], obj[median], obj[q3], obj[high]];
17+
return obj;
18+
});
19+
}
20+
21+
return newData;
22+
};

0 commit comments

Comments
 (0)