/
histogram.ts
94 lines (84 loc) · 2.55 KB
/
histogram.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import { assign, forIn, pick } from '@antv/util';
import partition from '../../util/partition';
import { DataSet } from '../../data-set';
import { getField } from '../../util/option-parser';
import { View } from '../../view';
const DEFAULT_OPTIONS: Partial<Options> = {
as: ['x', 'count'],
bins: undefined,
offset: 0,
groupBy: [],
// field: '', // required
// binWidth: 10, // override bins
};
export interface Options {
as?: [string, string];
bins?: number | undefined;
offset?: number;
groupBy?: string[];
field: string;
binWidth?: number;
}
function nearestBin(value: number, scale: number, offset: number): [number, number] {
const temp = value - offset;
const div = Math.floor(temp / scale);
return [div * scale + offset, (div + 1) * scale + offset];
}
/** Sturges formula */
function sturges(dataLength: number): number {
return Math.ceil(Math.log(dataLength) / Math.LN2) + 1;
}
function transform(dataView: View, options: Options): void {
options = assign({} as Options, DEFAULT_OPTIONS, options);
const field = getField(options);
if (dataView.rows.length === 0) {
return;
}
const range = dataView.range(field);
const width = range[1] - range[0];
let binWidth = options.binWidth;
const bins = options.bins;
if (!binWidth && bins) {
if (bins <= 0) {
throw new TypeError('Invalid bins: it must be a positive number!');
}
binWidth = width / bins;
}
if (!binWidth && !bins) {
const binNumber = sturges(dataView.rows.length);
binWidth = width / binNumber;
}
const offset = options.offset % binWidth;
// grouping
const rows: any[] = [];
const groupBy = options.groupBy;
const groups = partition(dataView.rows, groupBy);
forIn(groups, (group: any[]) => {
const bins: any = {};
const column = group.map((row) => row[field]);
column.forEach((value) => {
const [x0, x1] = nearestBin(value, binWidth!, offset);
const binKey = `${x0}-${x1}`;
bins[binKey] = bins[binKey] || {
x0,
x1,
count: 0,
};
bins[binKey].count++;
});
const [asX, asCount] = options.as;
if (!asX || !asCount) {
throw new TypeError('Invalid as: it must be an array with 2 elements (e.g. [ "x", "count" ])!');
}
const meta = pick(group[0], groupBy);
forIn(bins, (bin) => {
const row = assign({}, meta);
row[asX] = [bin.x0, bin.x1];
row[asCount] = bin.count;
rows.push(row);
});
});
dataView.rows = rows;
}
DataSet.registerTransform('bin.histogram', transform);
DataSet.registerTransform('bin.dot', transform);