UNPKG

2.43 kBJavaScriptView Raw
1import {partition} from './util/util';
2import {Transform, stableCompare} from 'vega-dataflow';
3import {dotbin} from 'vega-statistics';
4import {extent, identity, inherits, span} from 'vega-util';
5
6const Output = 'bin';
7
8/**
9 * Dot density binning for dot plot construction.
10 * Based on Leland Wilkinson, Dot Plots, The American Statistician, 1999.
11 * https://www.cs.uic.edu/~wilkinson/Publications/dotplots.pdf
12 * @constructor
13 * @param {object} params - The parameters for this operator.
14 * @param {function(object): *} params.field - The value field to bin.
15 * @param {Array<function(object): *>} [params.groupby] - An array of accessors to groupby.
16 * @param {number} [params.step] - The step size (bin width) within which dots should be
17 * stacked. Defaults to 1/30 of the extent of the data *field*.
18 * @param {boolean} [params.smooth=false] - A boolean flag indicating if dot density
19 * stacks should be smoothed to reduce variance.
20 */
21export default function DotBin(params) {
22 Transform.call(this, null, params);
23}
24
25DotBin.Definition = {
26 'type': 'DotBin',
27 'metadata': {'modifies': true},
28 'params': [
29 { 'name': 'field', 'type': 'field', 'required': true },
30 { 'name': 'groupby', 'type': 'field', 'array': true },
31 { 'name': 'step', 'type': 'number' },
32 { 'name': 'smooth', 'type': 'boolean', 'default': false },
33 { 'name': 'as', 'type': 'string', 'default': Output }
34 ]
35};
36
37const prototype = inherits(DotBin, Transform);
38
39prototype.transform = function(_, pulse) {
40 if (this.value && !(_.modified() || pulse.changed())) {
41 return pulse; // early exit
42 }
43
44 const source = pulse.materialize(pulse.SOURCE).source,
45 groups = partition(pulse.source, _.groupby, identity),
46 smooth = _.smooth || false,
47 field = _.field,
48 step = _.step || autostep(source, field),
49 sort = stableCompare((a, b) => field(a) - field(b)),
50 as = _.as || Output,
51 n = groups.length;
52
53 // compute dotplot bins per group
54 let min = Infinity, max = -Infinity, i = 0, j;
55 for (; i<n; ++i) {
56 const g = groups[i].sort(sort);
57 j = -1;
58 for (const v of dotbin(g, step, smooth, field)) {
59 if (v < min) min = v;
60 if (v > max) max = v;
61 g[++j][as] = v;
62 }
63 }
64
65 this.value = {
66 start: min,
67 stop: max,
68 step: step
69 };
70 return pulse.reflow(true).modifies(as);
71};
72
73function autostep(data, field) {
74 return span(extent(data, field)) / 30;
75}