UNPKG

3.3 kBJavaScriptView Raw
1import Aggregate from './Aggregate';
2import {ValidAggregateOps} from './util/AggregateOps';
3import {accessor, accessorFields, ascending, inherits} from 'vega-util';
4
5/**
6 * Aggregate and pivot selected field values to become new fields.
7 * This operator is useful to construction cross-tabulations.
8 * @constructor
9 * @param {Array<function(object): *>} [params.groupby] - An array of accessors
10 * to groupby. These fields act just like groupby fields of an Aggregate transform.
11 * @param {function(object): *} params.field - The field to pivot on. The unique
12 * values of this field become new field names in the output stream.
13 * @param {function(object): *} params.value - The field to populate pivoted fields.
14 * The aggregate values of this field become the values of the new pivoted fields.
15 * @param {string} [params.op] - The aggregation operation for the value field,
16 * applied per cell in the output stream. The default is "sum".
17 * @param {number} [params.limit] - An optional parameter indicating the maximum
18 * number of pivoted fields to generate. The pivoted field names are sorted in
19 * ascending order prior to enforcing the limit.
20 */
21export default function Pivot(params) {
22 Aggregate.call(this, params);
23}
24
25Pivot.Definition = {
26 'type': 'Pivot',
27 'metadata': {'generates': true, 'changes': true},
28 'params': [
29 { 'name': 'groupby', 'type': 'field', 'array': true },
30 { 'name': 'field', 'type': 'field', 'required': true },
31 { 'name': 'value', 'type': 'field', 'required': true },
32 { 'name': 'op', 'type': 'enum', 'values': ValidAggregateOps, 'default': 'sum' },
33 { 'name': 'limit', 'type': 'number', 'default': 0 },
34 { 'name': 'key', 'type': 'field' }
35 ]
36};
37
38inherits(Pivot, Aggregate, {
39 _transform: Aggregate.prototype.transform,
40 transform(_, pulse) {
41 return this._transform(aggregateParams(_, pulse), pulse);
42 }
43});
44
45// Shoehorn a pivot transform into an aggregate transform!
46// First collect all unique pivot field values.
47// Then generate aggregate fields for each output pivot field.
48function aggregateParams(_, pulse) {
49 const key = _.field,
50 value = _.value,
51 op = (_.op === 'count' ? '__count__' : _.op) || 'sum',
52 fields = accessorFields(key).concat(accessorFields(value)),
53 keys = pivotKeys(key, _.limit || 0, pulse);
54
55 // if data stream content changes, pivot fields may change
56 // flag parameter modification to ensure re-initialization
57 if (pulse.changed()) _.set('__pivot__', null, null, true);
58
59 return {
60 key: _.key,
61 groupby: _.groupby,
62 ops: keys.map(() => op),
63 fields: keys.map(k => get(k, key, value, fields)),
64 as: keys.map(k => k + ''),
65 modified: _.modified.bind(_)
66 };
67}
68
69// Generate aggregate field accessor.
70// Output NaN for non-existent values; aggregator will ignore!
71function get(k, key, value, fields) {
72 return accessor(
73 d => key(d) === k ? value(d) : NaN,
74 fields,
75 k + ''
76 );
77}
78
79// Collect (and optionally limit) all unique pivot values.
80function pivotKeys(key, limit, pulse) {
81 const map = {},
82 list = [];
83
84 pulse.visit(pulse.SOURCE, t => {
85 const k = key(t);
86 if (!map[k]) {
87 map[k] = 1;
88 list.push(k);
89 }
90 });
91
92 list.sort(ascending);
93
94 return limit ? list.slice(0, limit) : list;
95}