Skip to content

Commit bbda837

Browse files
authored
fix(#2116): percent transform should keep data order (#2118)
1 parent 60210b1 commit bbda837

File tree

6 files changed

+133
-57
lines changed

6 files changed

+133
-57
lines changed

__tests__/bugs/issue-2116-spec.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Column } from '../../src';
2+
import { createDiv } from '../utils/dom';
3+
4+
const data = [
5+
{ time: '09', key: '有小车有任务', type: null, value: 1 },
6+
{ time: '09', key: '无小车无任务', type: null, value: 1 },
7+
{ time: '09', key: '无小车有任务', type: null, value: 1 },
8+
{ time: '10', key: '有小车有任务', type: null, value: 1 },
9+
{ time: '10', key: '无小车无任务', type: null, value: 1 },
10+
{ time: '10', key: '无小车有任务', type: null, value: 1 },
11+
{ time: '11', key: '有小车有任务', type: null, value: 1 },
12+
{ time: '11', key: '无小车无任务', type: null, value: 1 },
13+
{ time: '11', key: '无小车有任务', type: null, value: 1 },
14+
{ time: '12', key: '有小车有任务', type: null, value: 1 },
15+
{ time: '12', key: '无小车无任务', type: null, value: 1 },
16+
{ time: '12', key: '无小车有任务', type: null, value: 1 },
17+
{ time: '13', key: '有小车有任务', type: null, value: 1 },
18+
{ time: '13', key: '无小车无任务', type: null, value: 1 },
19+
{ time: '13', key: '无小车有任务', type: null, value: 1 },
20+
{ time: '00', key: '有小车有任务', type: null, value: 1 },
21+
{ time: '00', key: '无小车无任务', type: null, value: 1 },
22+
{ time: '00', key: '无小车有任务', type: null, value: 1 },
23+
];
24+
25+
describe('#2116', () => {
26+
test('percent column x Axis order', async () => {
27+
const plot = new Column(createDiv(), {
28+
data,
29+
xField: 'time',
30+
yField: 'value',
31+
seriesField: 'key',
32+
isPercent: true,
33+
isStack: true,
34+
appendPadding: 8,
35+
legend: {
36+
position: 'top',
37+
},
38+
});
39+
40+
plot.render();
41+
42+
// 保持数据顺序
43+
expect(plot.chart.getData().map((d) => d.time)).toEqual(data.map((d) => d.time));
44+
45+
expect(
46+
plot.chart
47+
.getXScale()
48+
.getTicks()
49+
.map((t) => t.text)
50+
).toEqual(['09', '10', '11', '12', '13', '00']);
51+
52+
plot.destroy();
53+
});
54+
});

__tests__/unit/utils/number-spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { isRealNumber } from '../../../src/utils/number';
2+
3+
describe('number', () => {
4+
it('isRealNumber', () => {
5+
expect(isRealNumber(null)).toBe(false);
6+
expect(isRealNumber(true)).toBe(false);
7+
expect(isRealNumber('a')).toBe(false);
8+
expect(isRealNumber(undefined)).toBe(false);
9+
10+
expect(isRealNumber(1)).toBe(true);
11+
expect(isRealNumber(0)).toBe(true);
12+
expect(isRealNumber(0.00001)).toBe(true);
13+
expect(isRealNumber(-1)).toBe(true);
14+
});
15+
});
+30-33
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,35 @@
1-
import DataSet from '@antv/data-set';
21
import { percent } from '../../../../src/utils/transform/percent';
32

4-
const { DataView } = DataSet;
5-
6-
const data = [
7-
{ country: 'Europe', year: '1750', value: 163 },
8-
{ country: 'Europe', year: '1800', value: 203 },
9-
{ country: 'Europe', year: '1850', value: 276 },
10-
{ country: 'Europe', year: '1900', value: 408 },
11-
{ country: 'Europe', year: '1950', value: 547 },
12-
{ country: 'Europe', year: '1999', value: 729 },
13-
{ country: 'Europe', year: '2050', value: 628 },
14-
{ country: 'Europe', year: '2100', value: 828 },
15-
{ country: 'Asia', year: '1750', value: 502 },
16-
{ country: 'Asia', year: '1800', value: 635 },
17-
{ country: 'Asia', year: '1850', value: 809 },
18-
{ country: 'Asia', year: '1900', value: 947 },
19-
{ country: 'Asia', year: '1950', value: 1402 },
20-
{ country: 'Asia', year: '1999', value: 3634 },
21-
{ country: 'Asia', year: '2050', value: 5268 },
22-
{ country: 'Asia', year: '2100', value: 7268 },
23-
];
24-
25-
const dv = new DataView();
26-
dv.source(data).transform({
27-
type: 'percent',
28-
field: 'value',
29-
dimension: 'country',
30-
groupBy: ['year'],
31-
as: 'as',
32-
});
33-
343
describe('percent', () => {
35-
it('percent', () => {
36-
expect(percent(data, 'value', 'year', 'as')).toEqual(dv.rows);
4+
it('keep order, only calculate number value', () => {
5+
const data = [
6+
{ time: '09', key: '有小车有任务', type: null, value: 1 },
7+
{ time: '09', key: '无小车有任务', type: null, value: 1 },
8+
{ time: '10', key: '有小车有任务', type: null, value: 1 },
9+
{ time: '10', key: '无小车无任务', type: null, value: 1 },
10+
{ time: '11', key: '有小车有任务', type: null, value: 1 },
11+
{ time: '11', key: '无小车无任务', type: null, value: NaN },
12+
{ time: '12', key: '有小车有任务', type: null, value: 1 },
13+
{ time: '12', key: '无小车无任务', type: null, value: 1 },
14+
{ time: '13', key: '有小车有任务', type: null, value: 'a' },
15+
{ time: '13', key: '无小车有任务', type: null, value: 1 },
16+
{ time: '00', key: '有小车有任务', type: null, value: 1 },
17+
{ time: '00', key: '无小车有任务', type: null, value: null },
18+
];
19+
20+
expect(percent(data, 'value', 'time', 'value')).toEqual([
21+
{ time: '09', key: '有小车有任务', type: null, value: 0.5 },
22+
{ time: '09', key: '无小车有任务', type: null, value: 0.5 },
23+
{ time: '10', key: '有小车有任务', type: null, value: 0.5 },
24+
{ time: '10', key: '无小车无任务', type: null, value: 0.5 },
25+
{ time: '11', key: '有小车有任务', type: null, value: 1 },
26+
{ time: '11', key: '无小车无任务', type: null, value: 0 },
27+
{ time: '12', key: '有小车有任务', type: null, value: 0.5 },
28+
{ time: '12', key: '无小车无任务', type: null, value: 0.5 },
29+
{ time: '13', key: '有小车有任务', type: null, value: 0 },
30+
{ time: '13', key: '无小车有任务', type: null, value: 1 },
31+
{ time: '00', key: '有小车有任务', type: null, value: 1 },
32+
{ time: '00', key: '无小车有任务', type: null, value: 0 },
33+
]);
3734
});
3835
});

src/utils/number.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* 是否真实的是数字
3+
* @param v
4+
*/
5+
export function isRealNumber(v: any) {
6+
return typeof v === 'number' && !isNaN(v);
7+
}

src/utils/transform/percent.ts

+27-24
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
import { groupBy, map, reduce, each } from '@antv/util';
2-
import { Data } from '../../types';
3-
4-
function sumBy(data: any, func: (d: any) => number): number {
5-
return reduce(
6-
data,
7-
(r: number, d: any) => {
8-
return (r += func(d));
9-
},
10-
0
11-
);
12-
}
1+
import { map, reduce } from '@antv/util';
2+
import { Data, Datum } from '../../types';
3+
import { isRealNumber } from '../number';
134

145
/**
156
* 对数据进行百分比化
@@ -19,20 +10,32 @@ function sumBy(data: any, func: (d: any) => number): number {
1910
* @param as
2011
*/
2112
export function percent(data: Data, measure: string, groupField: string, as: string) {
22-
// 1. 首先根据 dimension 分组
23-
const groupedData = groupBy(data, groupField);
13+
// 1. 先计算每一个分组的 max 值
14+
const sumMap = reduce(
15+
data,
16+
(map, datum: Datum) => {
17+
const groupValue = datum[groupField];
18+
let sum = map.has(groupValue) ? map.get(groupValue) : 0;
2419

25-
const result = [];
26-
each(groupedData, (v: Data) => {
27-
const total = sumBy(v, (o) => o[measure]);
20+
const v = datum[measure];
2821

29-
const percentage = map(v, (o) => ({
30-
...o,
31-
[as]: o[measure] / total,
32-
}));
22+
sum = isRealNumber(v) ? sum + v : sum;
23+
map.set(groupValue, sum);
3324

34-
result.push(...percentage);
35-
});
25+
return map;
26+
},
27+
new Map<string, number>()
28+
);
3629

37-
return result;
30+
// 2. 循环数组,计算占比
31+
return map(data, (datum: Datum) => {
32+
const v = datum[measure];
33+
const groupValue = datum[groupField];
34+
const percentage = isRealNumber(v) ? v / sumMap.get(groupValue) : 0;
35+
36+
return {
37+
...datum,
38+
[as]: percentage,
39+
};
40+
});
3841
}

0 commit comments

Comments
 (0)