UNPKG

3.64 kBJavaScriptView Raw
1import Aggregate from './Aggregate';
2import {ValidAggregateOps} from './util/AggregateOps';
3import {accessor, accessorFields, 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
38var prototype = inherits(Pivot, Aggregate);
39
40prototype._transform = prototype.transform;
41
42prototype.transform = function(_, pulse) {
43 return this._transform(aggregateParams(_, pulse), pulse);
44};
45
46// Shoehorn a pivot transform into an aggregate transform!
47// First collect all unique pivot field values.
48// Then generate aggregate fields for each output pivot field.
49function aggregateParams(_, pulse) {
50 var key = _.field,
51 value = _.value,
52 op = (_.op === 'count' ? '__count__' : _.op) || 'sum',
53 fields = accessorFields(key).concat(accessorFields(value)),
54 keys = pivotKeys(key, _.limit || 0, pulse);
55
56 // if data stream content changes, pivot fields may change
57 // flag parameter modification to ensure re-initialization
58 if (pulse.changed()) _.set('__pivot__', null, null, true);
59
60 return {
61 key: _.key,
62 groupby: _.groupby,
63 ops: keys.map(function() { return op; }),
64 fields: keys.map(function(k) { return get(k, key, value, fields); }),
65 as: keys.map(function(k) { return k + ''; }),
66 modified: _.modified.bind(_)
67 };
68}
69
70// Generate aggregate field accessor.
71// Output NaN for non-existent values; aggregator will ignore!
72function get(k, key, value, fields) {
73 return accessor(
74 function(d) { return key(d) === k ? value(d) : NaN; },
75 fields,
76 k + ''
77 );
78}
79
80// Collect (and optionally limit) all unique pivot values.
81function pivotKeys(key, limit, pulse) {
82 var map = {},
83 list = [];
84
85 pulse.visit(pulse.SOURCE, function(t) {
86 var k = key(t);
87 if (!map[k]) {
88 map[k] = 1;
89 list.push(k);
90 }
91 });
92
93 // TODO? Move this comparator to vega-util?
94 list.sort(function(u, v) {
95 return (u<v||u==null) && v!=null ? -1
96 : (u>v||v==null) && u!=null ? 1
97 : ((v=v instanceof Date?+v:v),(u=u instanceof Date?+u:u))!==u && v===v ? -1
98 : v!==v && u===u ? 1 : 0;
99 });
100
101 return limit ? list.slice(0, limit) : list;
102}