@hpcc-js/marshaller 2.28.7 → 2.28.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +43 -43
- package/dist/index.es6.js +25 -13
- package/dist/index.es6.js.map +1 -1
- package/dist/index.js +25 -13
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +2 -2
- package/dist/index.min.js.map +1 -1
- package/package.json +14 -14
- package/src/__package__.ts +3 -3
- package/src/dashy.css +239 -239
- package/src/dashy.ts +521 -521
- package/src/ddl1/DDLApi.ts +229 -229
- package/src/ddl1/FlyoutButton.ts +120 -120
- package/src/ddl1/Graph.ts +93 -93
- package/src/ddl1/HTML.ts +77 -77
- package/src/ddl1/HipieDDL.ts +2437 -2437
- package/src/ddl1/HipieDDLMixin.ts +380 -380
- package/src/ddl1/Tabbed.ts +91 -91
- package/src/ddl1/TargetMarshaller.ts +57 -57
- package/src/ddl2/PopupManager.ts +89 -89
- package/src/ddl2/activities/activity.ts +431 -431
- package/src/ddl2/activities/databomb.ts +237 -237
- package/src/ddl2/activities/datasource.ts +52 -52
- package/src/ddl2/activities/dspicker.ts +106 -106
- package/src/ddl2/activities/filter.ts +542 -542
- package/src/ddl2/activities/form.ts +153 -153
- package/src/ddl2/activities/groupby.ts +439 -439
- package/src/ddl2/activities/hipiepipeline.ts +114 -114
- package/src/ddl2/activities/limit.ts +49 -49
- package/src/ddl2/activities/logicalfile.ts +62 -62
- package/src/ddl2/activities/nullview.ts +12 -12
- package/src/ddl2/activities/project.ts +764 -764
- package/src/ddl2/activities/rest.ts +568 -568
- package/src/ddl2/activities/roxie.ts +490 -490
- package/src/ddl2/activities/sampledata.json +16264 -16264
- package/src/ddl2/activities/sort.ts +176 -176
- package/src/ddl2/activities/wuresult.ts +395 -395
- package/src/ddl2/dashboard.css +13 -13
- package/src/ddl2/dashboard.ts +330 -330
- package/src/ddl2/dashboardDockPanel.ts +123 -123
- package/src/ddl2/dashboardGrid.ts +202 -202
- package/src/ddl2/ddl.ts +410 -410
- package/src/ddl2/ddleditor.ts +60 -60
- package/src/ddl2/dsTable.ts +238 -238
- package/src/ddl2/dvTable.ts +31 -31
- package/src/ddl2/graphadapter.ts +297 -297
- package/src/ddl2/javascriptadapter.ts +354 -354
- package/src/ddl2/model/element.ts +398 -398
- package/src/ddl2/model/visualization.ts +351 -351
- package/src/ddl2/model/vizChartPanel.ts +149 -149
- package/src/ddl2/pipelinePanel.css +4 -4
- package/src/ddl2/pipelinePanel.ts +465 -465
- package/src/index.ts +26 -26
- package/types/__package__.d.ts +2 -2
- package/types-3.4/__package__.d.ts +2 -2
|
@@ -1,439 +1,439 @@
|
|
|
1
|
-
import { PropertyExt, publish } from "@hpcc-js/common";
|
|
2
|
-
import { DDL2 } from "@hpcc-js/ddl-shim";
|
|
3
|
-
import { hashSum } from "@hpcc-js/util";
|
|
4
|
-
import { deviation as d3Deviation, max as d3Max, mean as d3Mean, median as d3Median, min as d3Min, sum as d3Sum, variance as d3Variance } from "d3-array";
|
|
5
|
-
import { nest as d3Nest } from "d3-collection";
|
|
6
|
-
import { Activity, IActivityError, ReferencedFields } from "./activity";
|
|
7
|
-
|
|
8
|
-
export class GroupByColumn extends PropertyExt {
|
|
9
|
-
private _owner: GroupBy;
|
|
10
|
-
|
|
11
|
-
@publish(undefined, "set", "Field", function (this: GroupByColumn) { return this.columns(); }, {
|
|
12
|
-
optional: true,
|
|
13
|
-
validate: (w: GroupByColumn): boolean => w.columns().indexOf(w.label()) >= 0
|
|
14
|
-
})
|
|
15
|
-
label: publish<this, string>;
|
|
16
|
-
label_valid: () => boolean;
|
|
17
|
-
|
|
18
|
-
validate(prefix: string): IActivityError[] {
|
|
19
|
-
const retVal: IActivityError[] = [];
|
|
20
|
-
if (!this.label_valid()) {
|
|
21
|
-
retVal.push({
|
|
22
|
-
source: `${prefix}.label`,
|
|
23
|
-
msg: `Invalid label: "${this.label()}"`,
|
|
24
|
-
hint: `expected ${JSON.stringify(this.columns())}`
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
return retVal;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
constructor() {
|
|
31
|
-
super();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
owner(): GroupBy;
|
|
35
|
-
owner(_: GroupBy): this;
|
|
36
|
-
owner(_?: GroupBy): GroupBy | this {
|
|
37
|
-
if (!arguments.length) return this._owner;
|
|
38
|
-
this._owner = _;
|
|
39
|
-
return this;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
valid(): boolean {
|
|
43
|
-
return !!this.label();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
toDDL(): string {
|
|
47
|
-
return this.label();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
fromDDL(label: string): this {
|
|
51
|
-
return this
|
|
52
|
-
.label(label)
|
|
53
|
-
;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
static fromDDL(label: string): GroupByColumn {
|
|
57
|
-
return new GroupByColumn().fromDDL(label);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
hash(): string {
|
|
61
|
-
return hashSum(this.label());
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
columns() {
|
|
65
|
-
return this._owner.inFieldIDs();
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
GroupByColumn.prototype._class += " GroupByColumn";
|
|
69
|
-
|
|
70
|
-
// ===========================================================================
|
|
71
|
-
export type AggrFuncCallback = (item: any) => number;
|
|
72
|
-
export type AggrFunc = (leaves: any[], callback: AggrFuncCallback) => number;
|
|
73
|
-
function localCount(leaves: any[], callback: AggrFuncCallback): number {
|
|
74
|
-
return leaves.length;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const d3Aggr: { [key: string]: AggrFunc } = {
|
|
78
|
-
count: localCount,
|
|
79
|
-
min: d3Min,
|
|
80
|
-
max: d3Max,
|
|
81
|
-
mean: d3Mean,
|
|
82
|
-
median: d3Median,
|
|
83
|
-
variance: d3Variance,
|
|
84
|
-
deviation: d3Deviation,
|
|
85
|
-
sum: d3Sum
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
export type AggregateType = "count" | "min" | "max" | "sum" | "mean" | "median" | "variance" | "deviation";
|
|
89
|
-
export class AggregateField extends PropertyExt {
|
|
90
|
-
private _owner: GroupBy;
|
|
91
|
-
|
|
92
|
-
@publish(null, "string", "new Field ID", null, { optional: true, disable: (w: AggregateField) => !w.hasColumn() })
|
|
93
|
-
fieldID: publish<this, string>;
|
|
94
|
-
@publish("count", "set", "Aggregation Type", ["count", "min", "max", "sum", "mean", "median", "variance", "deviation"], { optional: true, disable: (w: AggregateField) => !w.fieldID() })
|
|
95
|
-
aggrType: publish<this, AggregateType>;
|
|
96
|
-
@publish(null, "set", "Aggregation Field", function (this: AggregateField) { return this.columns(); }, {
|
|
97
|
-
optional: true,
|
|
98
|
-
disable: (w: AggregateField) => w.disableAggrColumn(),
|
|
99
|
-
validate: (w: AggregateField): boolean => w.columns().indexOf(w.aggrColumn()) >= 0
|
|
100
|
-
})
|
|
101
|
-
aggrColumn: publish<this, string>;
|
|
102
|
-
aggrColumn_valid: () => boolean;
|
|
103
|
-
@publish(null, "set", "Base Count Field", function (this: AggregateField) { return this.columns(); }, {
|
|
104
|
-
optional: true,
|
|
105
|
-
disable: (w: AggregateField) => w.disableBaseCountColumn(),
|
|
106
|
-
validate: (w: AggregateField): boolean => w.columns().indexOf(w.baseCountColumn()) >= 0
|
|
107
|
-
})
|
|
108
|
-
baseCountColumn: publish<this, string>;
|
|
109
|
-
baseCountColumn_valid: () => boolean;
|
|
110
|
-
|
|
111
|
-
disableAggrColumn(): boolean {
|
|
112
|
-
return !this.fieldID() || !this.aggrType() || this.aggrType() === "count";
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
disableBaseCountColumn(): boolean {
|
|
116
|
-
return !this.fieldID() || !this.aggrType() || this.aggrType() !== "mean";
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
validate(prefix: string): IActivityError[] {
|
|
120
|
-
const retVal: IActivityError[] = [];
|
|
121
|
-
if (!this.aggrColumn_valid()) {
|
|
122
|
-
retVal.push({
|
|
123
|
-
source: `${prefix}.${this.fieldID()}.aggrColumn`,
|
|
124
|
-
msg: `Invalid aggrColumn: "${this.aggrColumn()}"`,
|
|
125
|
-
hint: `expected ${JSON.stringify(this.columns())}`
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
if (!this.baseCountColumn_valid()) {
|
|
129
|
-
retVal.push({
|
|
130
|
-
source: `${prefix}.${this.fieldID()}.aggrColumn`,
|
|
131
|
-
msg: `Invalid baseCountColumn: "${this.baseCountColumn()}"`,
|
|
132
|
-
hint: `expected ${JSON.stringify(this.columns())}`
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
return retVal;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
constructor() {
|
|
139
|
-
super();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
owner(): GroupBy;
|
|
143
|
-
owner(_: GroupBy): this;
|
|
144
|
-
owner(_?: GroupBy): GroupBy | this {
|
|
145
|
-
if (!arguments.length) return this._owner;
|
|
146
|
-
this._owner = _;
|
|
147
|
-
return this;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
valid(): boolean {
|
|
151
|
-
return !!this.fieldID();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
toDDL(): DDL2.IAggregate | DDL2.ICount {
|
|
155
|
-
if (this.aggrType() === "count") {
|
|
156
|
-
return {
|
|
157
|
-
fieldID: this.fieldID(),
|
|
158
|
-
type: "count"
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
fieldID: this.fieldID(),
|
|
163
|
-
type: this.aggrType() as DDL2.IAggregateType,
|
|
164
|
-
inFieldID: this.aggrColumn(),
|
|
165
|
-
baseCountFieldID: this.baseCountColumn()
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
fromDDL(ddl: DDL2.IAggregate | DDL2.ICount): this {
|
|
170
|
-
const retVal = this
|
|
171
|
-
.fieldID(ddl.fieldID)
|
|
172
|
-
.aggrType(ddl.type)
|
|
173
|
-
;
|
|
174
|
-
if (ddl.type !== "count") {
|
|
175
|
-
retVal.aggrColumn(ddl.inFieldID);
|
|
176
|
-
retVal.baseCountColumn(ddl.baseCountFieldID);
|
|
177
|
-
}
|
|
178
|
-
return retVal;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
static fromDDL(ddl: DDL2.IAggregate | DDL2.ICount): AggregateField {
|
|
182
|
-
return new AggregateField().fromDDL(ddl);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
hash(): string {
|
|
186
|
-
return hashSum({
|
|
187
|
-
label: this.fieldID(),
|
|
188
|
-
aggrType: this.aggrType(),
|
|
189
|
-
aggrColumn: this.aggrColumn()
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
columns() {
|
|
194
|
-
return this._owner.inFieldIDs();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
hasColumn() {
|
|
198
|
-
return this.columns().length;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
aggregate(values: Array<{ [key: string]: any }>) {
|
|
202
|
-
return d3Aggr[this.aggrType() as string](values, leaf => +leaf[this.aggrColumn()]);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
aggrFunc(): (leaves: any[]) => number {
|
|
206
|
-
const aggrFunc = d3Aggr[this.aggrType() as string];
|
|
207
|
-
const aggrColumn = this.aggrColumn();
|
|
208
|
-
const baseCountColumn = this.baseCountColumn();
|
|
209
|
-
if (baseCountColumn) {
|
|
210
|
-
return (values: any[]) => {
|
|
211
|
-
return aggrFunc(values, leaf => +leaf[aggrColumn] / +leaf[baseCountColumn]);
|
|
212
|
-
};
|
|
213
|
-
} else {
|
|
214
|
-
return (values: any[]) => {
|
|
215
|
-
return aggrFunc(values, leaf => +leaf[aggrColumn]);
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
AggregateField.prototype._class += " AggregateField";
|
|
221
|
-
|
|
222
|
-
// ===========================================================================
|
|
223
|
-
export class GroupBy extends Activity {
|
|
224
|
-
|
|
225
|
-
@publish([], "propertyArray", "Source Columns", null, { autoExpand: GroupByColumn })
|
|
226
|
-
column: publish<this, GroupByColumn[]>;
|
|
227
|
-
@publish([], "propertyArray", "Computed Fields", null, { autoExpand: AggregateField })
|
|
228
|
-
computedFields: publish<this, AggregateField[]>;
|
|
229
|
-
@publish(false, "boolean", "Show details")
|
|
230
|
-
details: publish<this, boolean>;
|
|
231
|
-
@publish(false, "boolean", "Show groupBy fileds in details")
|
|
232
|
-
fullDetails: publish<this, boolean>;
|
|
233
|
-
|
|
234
|
-
validate(): IActivityError[] {
|
|
235
|
-
let retVal: IActivityError[] = [];
|
|
236
|
-
for (const gbColumn of this.validGroupBy()) {
|
|
237
|
-
retVal = retVal.concat(gbColumn.validate("GroupBy.column"));
|
|
238
|
-
}
|
|
239
|
-
for (const cfs of this.validComputedFields()) {
|
|
240
|
-
retVal = retVal.concat(cfs.validate("GroupBy.computedFields"));
|
|
241
|
-
}
|
|
242
|
-
return retVal;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
constructor() {
|
|
246
|
-
super();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
toDDL(): DDL2.IGroupBy {
|
|
250
|
-
return {
|
|
251
|
-
type: "groupby",
|
|
252
|
-
groupByIDs: this.fieldIDs(),
|
|
253
|
-
aggregates: this.aggregates()
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
fromDDL(ddl: DDL2.IGroupBy): this {
|
|
258
|
-
return this
|
|
259
|
-
.fieldIDs(ddl.groupByIDs)
|
|
260
|
-
.aggregates(ddl.aggregates)
|
|
261
|
-
;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
static fromDDL(ddl: DDL2.IGroupBy): GroupBy {
|
|
265
|
-
return new GroupBy().fromDDL(ddl);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
fieldIDs(): string[];
|
|
269
|
-
fieldIDs(_: string[]): this;
|
|
270
|
-
fieldIDs(_?: string[]): string[] | this {
|
|
271
|
-
if (!arguments.length) return this.validGroupBy().map(gb => gb.toDDL());
|
|
272
|
-
this.column(_.map(fieldID => GroupByColumn.fromDDL(fieldID)));
|
|
273
|
-
return this;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
aggregates(): DDL2.AggregateType[];
|
|
277
|
-
aggregates(_: DDL2.AggregateType[]): this;
|
|
278
|
-
aggregates(_?: DDL2.AggregateType[]): DDL2.AggregateType[] | this {
|
|
279
|
-
if (!arguments.length) return this.validComputedFields().map(cf => cf.toDDL());
|
|
280
|
-
this.computedFields(_.map(aggrType => AggregateField.fromDDL(aggrType)));
|
|
281
|
-
return this;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Activity
|
|
285
|
-
hash(): string {
|
|
286
|
-
return hashSum({
|
|
287
|
-
groupBy: this.column().map(gb => gb.hash()),
|
|
288
|
-
computedFields: this.computedFields().map(cf => cf.hash())
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
appendGroupBys(columns: [{ field: string }]): this {
|
|
293
|
-
for (const column of columns) {
|
|
294
|
-
this.column().push(new GroupByColumn()
|
|
295
|
-
.owner(this)
|
|
296
|
-
.label(column.field)
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
return this;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
validGroupBy(): GroupByColumn[] {
|
|
303
|
-
return this.column().filter(groupBy => groupBy.valid());
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
exists(): boolean {
|
|
307
|
-
return this.validGroupBy().length > 0;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
inFieldIDs(): string[] {
|
|
311
|
-
return this.inFields().map(field => field.id);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
field(fieldID: string): DDL2.IField | null {
|
|
315
|
-
for (const field of this.inFields()) {
|
|
316
|
-
if (field.id === fieldID) {
|
|
317
|
-
return field;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
appendComputedFields(aggregateFields: [{ label: string, type: AggregateType, column?: string }]): this {
|
|
324
|
-
for (const aggregateField of aggregateFields) {
|
|
325
|
-
const aggrField = new AggregateField()
|
|
326
|
-
.owner(this)
|
|
327
|
-
.fieldID(aggregateField.label)
|
|
328
|
-
.aggrType(aggregateField.type)
|
|
329
|
-
;
|
|
330
|
-
if (aggregateField.column !== void 0) {
|
|
331
|
-
aggrField.aggrColumn(aggregateField.column);
|
|
332
|
-
}
|
|
333
|
-
this.computedFields().push(aggrField);
|
|
334
|
-
}
|
|
335
|
-
return this;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
validComputedFields() {
|
|
339
|
-
return this.computedFields().filter(computedField => computedField.valid());
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
hasComputedFields(): boolean {
|
|
343
|
-
return this.validComputedFields().length > 0;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
computeFields(inFields: ReadonlyArray<DDL2.IField>): () => ReadonlyArray<DDL2.IField> {
|
|
347
|
-
if (!this.exists()) return super.computeFields(inFields);
|
|
348
|
-
const retVal: DDL2.IField[] = [];
|
|
349
|
-
const groups: GroupByColumn[] = this.validGroupBy();
|
|
350
|
-
for (const groupBy of groups) {
|
|
351
|
-
const groupByField = this.field(groupBy.label());
|
|
352
|
-
const field = {
|
|
353
|
-
type: groupByField ? groupByField.type : "string",
|
|
354
|
-
id: groupBy.label()
|
|
355
|
-
} as DDL2.IField;
|
|
356
|
-
retVal.push(field);
|
|
357
|
-
}
|
|
358
|
-
for (const cf of this.computedFields()) {
|
|
359
|
-
if (cf.fieldID()) {
|
|
360
|
-
const computedField: DDL2.IField = {
|
|
361
|
-
id: cf.fieldID(),
|
|
362
|
-
type: "number"
|
|
363
|
-
};
|
|
364
|
-
retVal.push(computedField);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if (this.details()) {
|
|
368
|
-
let detailsTarget: DDL2.IField[] = retVal;
|
|
369
|
-
if (this.exists()) {
|
|
370
|
-
const rows: DDL2.IField = {
|
|
371
|
-
id: "values",
|
|
372
|
-
type: "dataset",
|
|
373
|
-
children: []
|
|
374
|
-
};
|
|
375
|
-
retVal.push(rows);
|
|
376
|
-
detailsTarget = rows.children;
|
|
377
|
-
}
|
|
378
|
-
const columns = groups.map(groupBy => groupBy.label());
|
|
379
|
-
detailsTarget.push(...this.inFields().filter(field => {
|
|
380
|
-
return this.fullDetails() || columns.indexOf(field.id) < 0;
|
|
381
|
-
}));
|
|
382
|
-
}
|
|
383
|
-
return () => retVal;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
referencedFields(refs: ReferencedFields): void {
|
|
387
|
-
super.referencedFields(refs);
|
|
388
|
-
const fieldIDs: string[] = [];
|
|
389
|
-
for (const gb of this.validGroupBy()) {
|
|
390
|
-
fieldIDs.push(gb.label());
|
|
391
|
-
}
|
|
392
|
-
for (const cf of this.validComputedFields()) {
|
|
393
|
-
if (cf.aggrColumn()) {
|
|
394
|
-
fieldIDs.push(cf.aggrColumn());
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
super.resolveInFields(refs, fieldIDs);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
computeData(): ReadonlyArray<object> {
|
|
401
|
-
const data = super.computeData();
|
|
402
|
-
if (data.length === 0 || !this.exists()) return data;
|
|
403
|
-
const columnLabels: string[] = this.validGroupBy().map(gb => gb.label());
|
|
404
|
-
const computedFields = this.validComputedFields().map(cf => {
|
|
405
|
-
return { label: cf.fieldID(), aggrFunc: cf.aggrFunc() };
|
|
406
|
-
});
|
|
407
|
-
const retVal = d3Nest()
|
|
408
|
-
.key((row: { [key: string]: any }) => {
|
|
409
|
-
let key = "";
|
|
410
|
-
for (const groupByLabel of columnLabels) {
|
|
411
|
-
key += ":" + row[groupByLabel];
|
|
412
|
-
}
|
|
413
|
-
return key;
|
|
414
|
-
})
|
|
415
|
-
.entries(data as object[]).map(_row => {
|
|
416
|
-
const row: {
|
|
417
|
-
[key: string]: any
|
|
418
|
-
} = _row;
|
|
419
|
-
delete row.key;
|
|
420
|
-
for (const groupByLabel of columnLabels) {
|
|
421
|
-
row[groupByLabel] = row.values[0][groupByLabel];
|
|
422
|
-
}
|
|
423
|
-
for (const cf of computedFields) {
|
|
424
|
-
row[cf.label] = cf.aggrFunc(row.values);
|
|
425
|
-
}
|
|
426
|
-
return row;
|
|
427
|
-
})
|
|
428
|
-
;
|
|
429
|
-
const outFields = this.outFields();
|
|
430
|
-
return retVal.map(row => {
|
|
431
|
-
const retVal = {};
|
|
432
|
-
for (const field of outFields) {
|
|
433
|
-
retVal[field.id] = row[field.id];
|
|
434
|
-
}
|
|
435
|
-
return retVal;
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
GroupBy.prototype._class += " GroupBy";
|
|
1
|
+
import { PropertyExt, publish } from "@hpcc-js/common";
|
|
2
|
+
import { DDL2 } from "@hpcc-js/ddl-shim";
|
|
3
|
+
import { hashSum } from "@hpcc-js/util";
|
|
4
|
+
import { deviation as d3Deviation, max as d3Max, mean as d3Mean, median as d3Median, min as d3Min, sum as d3Sum, variance as d3Variance } from "d3-array";
|
|
5
|
+
import { nest as d3Nest } from "d3-collection";
|
|
6
|
+
import { Activity, IActivityError, ReferencedFields } from "./activity";
|
|
7
|
+
|
|
8
|
+
export class GroupByColumn extends PropertyExt {
|
|
9
|
+
private _owner: GroupBy;
|
|
10
|
+
|
|
11
|
+
@publish(undefined, "set", "Field", function (this: GroupByColumn) { return this.columns(); }, {
|
|
12
|
+
optional: true,
|
|
13
|
+
validate: (w: GroupByColumn): boolean => w.columns().indexOf(w.label()) >= 0
|
|
14
|
+
})
|
|
15
|
+
label: publish<this, string>;
|
|
16
|
+
label_valid: () => boolean;
|
|
17
|
+
|
|
18
|
+
validate(prefix: string): IActivityError[] {
|
|
19
|
+
const retVal: IActivityError[] = [];
|
|
20
|
+
if (!this.label_valid()) {
|
|
21
|
+
retVal.push({
|
|
22
|
+
source: `${prefix}.label`,
|
|
23
|
+
msg: `Invalid label: "${this.label()}"`,
|
|
24
|
+
hint: `expected ${JSON.stringify(this.columns())}`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return retVal;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
owner(): GroupBy;
|
|
35
|
+
owner(_: GroupBy): this;
|
|
36
|
+
owner(_?: GroupBy): GroupBy | this {
|
|
37
|
+
if (!arguments.length) return this._owner;
|
|
38
|
+
this._owner = _;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
valid(): boolean {
|
|
43
|
+
return !!this.label();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
toDDL(): string {
|
|
47
|
+
return this.label();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fromDDL(label: string): this {
|
|
51
|
+
return this
|
|
52
|
+
.label(label)
|
|
53
|
+
;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static fromDDL(label: string): GroupByColumn {
|
|
57
|
+
return new GroupByColumn().fromDDL(label);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
hash(): string {
|
|
61
|
+
return hashSum(this.label());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
columns() {
|
|
65
|
+
return this._owner.inFieldIDs();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
GroupByColumn.prototype._class += " GroupByColumn";
|
|
69
|
+
|
|
70
|
+
// ===========================================================================
|
|
71
|
+
export type AggrFuncCallback = (item: any) => number;
|
|
72
|
+
export type AggrFunc = (leaves: any[], callback: AggrFuncCallback) => number;
|
|
73
|
+
function localCount(leaves: any[], callback: AggrFuncCallback): number {
|
|
74
|
+
return leaves.length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const d3Aggr: { [key: string]: AggrFunc } = {
|
|
78
|
+
count: localCount,
|
|
79
|
+
min: d3Min,
|
|
80
|
+
max: d3Max,
|
|
81
|
+
mean: d3Mean,
|
|
82
|
+
median: d3Median,
|
|
83
|
+
variance: d3Variance,
|
|
84
|
+
deviation: d3Deviation,
|
|
85
|
+
sum: d3Sum
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type AggregateType = "count" | "min" | "max" | "sum" | "mean" | "median" | "variance" | "deviation";
|
|
89
|
+
export class AggregateField extends PropertyExt {
|
|
90
|
+
private _owner: GroupBy;
|
|
91
|
+
|
|
92
|
+
@publish(null, "string", "new Field ID", null, { optional: true, disable: (w: AggregateField) => !w.hasColumn() })
|
|
93
|
+
fieldID: publish<this, string>;
|
|
94
|
+
@publish("count", "set", "Aggregation Type", ["count", "min", "max", "sum", "mean", "median", "variance", "deviation"], { optional: true, disable: (w: AggregateField) => !w.fieldID() })
|
|
95
|
+
aggrType: publish<this, AggregateType>;
|
|
96
|
+
@publish(null, "set", "Aggregation Field", function (this: AggregateField) { return this.columns(); }, {
|
|
97
|
+
optional: true,
|
|
98
|
+
disable: (w: AggregateField) => w.disableAggrColumn(),
|
|
99
|
+
validate: (w: AggregateField): boolean => w.columns().indexOf(w.aggrColumn()) >= 0
|
|
100
|
+
})
|
|
101
|
+
aggrColumn: publish<this, string>;
|
|
102
|
+
aggrColumn_valid: () => boolean;
|
|
103
|
+
@publish(null, "set", "Base Count Field", function (this: AggregateField) { return this.columns(); }, {
|
|
104
|
+
optional: true,
|
|
105
|
+
disable: (w: AggregateField) => w.disableBaseCountColumn(),
|
|
106
|
+
validate: (w: AggregateField): boolean => w.columns().indexOf(w.baseCountColumn()) >= 0
|
|
107
|
+
})
|
|
108
|
+
baseCountColumn: publish<this, string>;
|
|
109
|
+
baseCountColumn_valid: () => boolean;
|
|
110
|
+
|
|
111
|
+
disableAggrColumn(): boolean {
|
|
112
|
+
return !this.fieldID() || !this.aggrType() || this.aggrType() === "count";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
disableBaseCountColumn(): boolean {
|
|
116
|
+
return !this.fieldID() || !this.aggrType() || this.aggrType() !== "mean";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
validate(prefix: string): IActivityError[] {
|
|
120
|
+
const retVal: IActivityError[] = [];
|
|
121
|
+
if (!this.aggrColumn_valid()) {
|
|
122
|
+
retVal.push({
|
|
123
|
+
source: `${prefix}.${this.fieldID()}.aggrColumn`,
|
|
124
|
+
msg: `Invalid aggrColumn: "${this.aggrColumn()}"`,
|
|
125
|
+
hint: `expected ${JSON.stringify(this.columns())}`
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (!this.baseCountColumn_valid()) {
|
|
129
|
+
retVal.push({
|
|
130
|
+
source: `${prefix}.${this.fieldID()}.aggrColumn`,
|
|
131
|
+
msg: `Invalid baseCountColumn: "${this.baseCountColumn()}"`,
|
|
132
|
+
hint: `expected ${JSON.stringify(this.columns())}`
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return retVal;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
constructor() {
|
|
139
|
+
super();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
owner(): GroupBy;
|
|
143
|
+
owner(_: GroupBy): this;
|
|
144
|
+
owner(_?: GroupBy): GroupBy | this {
|
|
145
|
+
if (!arguments.length) return this._owner;
|
|
146
|
+
this._owner = _;
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
valid(): boolean {
|
|
151
|
+
return !!this.fieldID();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
toDDL(): DDL2.IAggregate | DDL2.ICount {
|
|
155
|
+
if (this.aggrType() === "count") {
|
|
156
|
+
return {
|
|
157
|
+
fieldID: this.fieldID(),
|
|
158
|
+
type: "count"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
fieldID: this.fieldID(),
|
|
163
|
+
type: this.aggrType() as DDL2.IAggregateType,
|
|
164
|
+
inFieldID: this.aggrColumn(),
|
|
165
|
+
baseCountFieldID: this.baseCountColumn()
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fromDDL(ddl: DDL2.IAggregate | DDL2.ICount): this {
|
|
170
|
+
const retVal = this
|
|
171
|
+
.fieldID(ddl.fieldID)
|
|
172
|
+
.aggrType(ddl.type)
|
|
173
|
+
;
|
|
174
|
+
if (ddl.type !== "count") {
|
|
175
|
+
retVal.aggrColumn(ddl.inFieldID);
|
|
176
|
+
retVal.baseCountColumn(ddl.baseCountFieldID);
|
|
177
|
+
}
|
|
178
|
+
return retVal;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static fromDDL(ddl: DDL2.IAggregate | DDL2.ICount): AggregateField {
|
|
182
|
+
return new AggregateField().fromDDL(ddl);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
hash(): string {
|
|
186
|
+
return hashSum({
|
|
187
|
+
label: this.fieldID(),
|
|
188
|
+
aggrType: this.aggrType(),
|
|
189
|
+
aggrColumn: this.aggrColumn()
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
columns() {
|
|
194
|
+
return this._owner.inFieldIDs();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
hasColumn() {
|
|
198
|
+
return this.columns().length;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
aggregate(values: Array<{ [key: string]: any }>) {
|
|
202
|
+
return d3Aggr[this.aggrType() as string](values, leaf => +leaf[this.aggrColumn()]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
aggrFunc(): (leaves: any[]) => number {
|
|
206
|
+
const aggrFunc = d3Aggr[this.aggrType() as string];
|
|
207
|
+
const aggrColumn = this.aggrColumn();
|
|
208
|
+
const baseCountColumn = this.baseCountColumn();
|
|
209
|
+
if (baseCountColumn) {
|
|
210
|
+
return (values: any[]) => {
|
|
211
|
+
return aggrFunc(values, leaf => +leaf[aggrColumn] / +leaf[baseCountColumn]);
|
|
212
|
+
};
|
|
213
|
+
} else {
|
|
214
|
+
return (values: any[]) => {
|
|
215
|
+
return aggrFunc(values, leaf => +leaf[aggrColumn]);
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
AggregateField.prototype._class += " AggregateField";
|
|
221
|
+
|
|
222
|
+
// ===========================================================================
|
|
223
|
+
export class GroupBy extends Activity {
|
|
224
|
+
|
|
225
|
+
@publish([], "propertyArray", "Source Columns", null, { autoExpand: GroupByColumn })
|
|
226
|
+
column: publish<this, GroupByColumn[]>;
|
|
227
|
+
@publish([], "propertyArray", "Computed Fields", null, { autoExpand: AggregateField })
|
|
228
|
+
computedFields: publish<this, AggregateField[]>;
|
|
229
|
+
@publish(false, "boolean", "Show details")
|
|
230
|
+
details: publish<this, boolean>;
|
|
231
|
+
@publish(false, "boolean", "Show groupBy fileds in details")
|
|
232
|
+
fullDetails: publish<this, boolean>;
|
|
233
|
+
|
|
234
|
+
validate(): IActivityError[] {
|
|
235
|
+
let retVal: IActivityError[] = [];
|
|
236
|
+
for (const gbColumn of this.validGroupBy()) {
|
|
237
|
+
retVal = retVal.concat(gbColumn.validate("GroupBy.column"));
|
|
238
|
+
}
|
|
239
|
+
for (const cfs of this.validComputedFields()) {
|
|
240
|
+
retVal = retVal.concat(cfs.validate("GroupBy.computedFields"));
|
|
241
|
+
}
|
|
242
|
+
return retVal;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
constructor() {
|
|
246
|
+
super();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
toDDL(): DDL2.IGroupBy {
|
|
250
|
+
return {
|
|
251
|
+
type: "groupby",
|
|
252
|
+
groupByIDs: this.fieldIDs(),
|
|
253
|
+
aggregates: this.aggregates()
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fromDDL(ddl: DDL2.IGroupBy): this {
|
|
258
|
+
return this
|
|
259
|
+
.fieldIDs(ddl.groupByIDs)
|
|
260
|
+
.aggregates(ddl.aggregates)
|
|
261
|
+
;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
static fromDDL(ddl: DDL2.IGroupBy): GroupBy {
|
|
265
|
+
return new GroupBy().fromDDL(ddl);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fieldIDs(): string[];
|
|
269
|
+
fieldIDs(_: string[]): this;
|
|
270
|
+
fieldIDs(_?: string[]): string[] | this {
|
|
271
|
+
if (!arguments.length) return this.validGroupBy().map(gb => gb.toDDL());
|
|
272
|
+
this.column(_.map(fieldID => GroupByColumn.fromDDL(fieldID)));
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
aggregates(): DDL2.AggregateType[];
|
|
277
|
+
aggregates(_: DDL2.AggregateType[]): this;
|
|
278
|
+
aggregates(_?: DDL2.AggregateType[]): DDL2.AggregateType[] | this {
|
|
279
|
+
if (!arguments.length) return this.validComputedFields().map(cf => cf.toDDL());
|
|
280
|
+
this.computedFields(_.map(aggrType => AggregateField.fromDDL(aggrType)));
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Activity
|
|
285
|
+
hash(): string {
|
|
286
|
+
return hashSum({
|
|
287
|
+
groupBy: this.column().map(gb => gb.hash()),
|
|
288
|
+
computedFields: this.computedFields().map(cf => cf.hash())
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
appendGroupBys(columns: [{ field: string }]): this {
|
|
293
|
+
for (const column of columns) {
|
|
294
|
+
this.column().push(new GroupByColumn()
|
|
295
|
+
.owner(this)
|
|
296
|
+
.label(column.field)
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
validGroupBy(): GroupByColumn[] {
|
|
303
|
+
return this.column().filter(groupBy => groupBy.valid());
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
exists(): boolean {
|
|
307
|
+
return this.validGroupBy().length > 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
inFieldIDs(): string[] {
|
|
311
|
+
return this.inFields().map(field => field.id);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
field(fieldID: string): DDL2.IField | null {
|
|
315
|
+
for (const field of this.inFields()) {
|
|
316
|
+
if (field.id === fieldID) {
|
|
317
|
+
return field;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
appendComputedFields(aggregateFields: [{ label: string, type: AggregateType, column?: string }]): this {
|
|
324
|
+
for (const aggregateField of aggregateFields) {
|
|
325
|
+
const aggrField = new AggregateField()
|
|
326
|
+
.owner(this)
|
|
327
|
+
.fieldID(aggregateField.label)
|
|
328
|
+
.aggrType(aggregateField.type)
|
|
329
|
+
;
|
|
330
|
+
if (aggregateField.column !== void 0) {
|
|
331
|
+
aggrField.aggrColumn(aggregateField.column);
|
|
332
|
+
}
|
|
333
|
+
this.computedFields().push(aggrField);
|
|
334
|
+
}
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
validComputedFields() {
|
|
339
|
+
return this.computedFields().filter(computedField => computedField.valid());
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
hasComputedFields(): boolean {
|
|
343
|
+
return this.validComputedFields().length > 0;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
computeFields(inFields: ReadonlyArray<DDL2.IField>): () => ReadonlyArray<DDL2.IField> {
|
|
347
|
+
if (!this.exists()) return super.computeFields(inFields);
|
|
348
|
+
const retVal: DDL2.IField[] = [];
|
|
349
|
+
const groups: GroupByColumn[] = this.validGroupBy();
|
|
350
|
+
for (const groupBy of groups) {
|
|
351
|
+
const groupByField = this.field(groupBy.label());
|
|
352
|
+
const field = {
|
|
353
|
+
type: groupByField ? groupByField.type : "string",
|
|
354
|
+
id: groupBy.label()
|
|
355
|
+
} as DDL2.IField;
|
|
356
|
+
retVal.push(field);
|
|
357
|
+
}
|
|
358
|
+
for (const cf of this.computedFields()) {
|
|
359
|
+
if (cf.fieldID()) {
|
|
360
|
+
const computedField: DDL2.IField = {
|
|
361
|
+
id: cf.fieldID(),
|
|
362
|
+
type: "number"
|
|
363
|
+
};
|
|
364
|
+
retVal.push(computedField);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (this.details()) {
|
|
368
|
+
let detailsTarget: DDL2.IField[] = retVal;
|
|
369
|
+
if (this.exists()) {
|
|
370
|
+
const rows: DDL2.IField = {
|
|
371
|
+
id: "values",
|
|
372
|
+
type: "dataset",
|
|
373
|
+
children: []
|
|
374
|
+
};
|
|
375
|
+
retVal.push(rows);
|
|
376
|
+
detailsTarget = rows.children;
|
|
377
|
+
}
|
|
378
|
+
const columns = groups.map(groupBy => groupBy.label());
|
|
379
|
+
detailsTarget.push(...this.inFields().filter(field => {
|
|
380
|
+
return this.fullDetails() || columns.indexOf(field.id) < 0;
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
return () => retVal;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
referencedFields(refs: ReferencedFields): void {
|
|
387
|
+
super.referencedFields(refs);
|
|
388
|
+
const fieldIDs: string[] = [];
|
|
389
|
+
for (const gb of this.validGroupBy()) {
|
|
390
|
+
fieldIDs.push(gb.label());
|
|
391
|
+
}
|
|
392
|
+
for (const cf of this.validComputedFields()) {
|
|
393
|
+
if (cf.aggrColumn()) {
|
|
394
|
+
fieldIDs.push(cf.aggrColumn());
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
super.resolveInFields(refs, fieldIDs);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
computeData(): ReadonlyArray<object> {
|
|
401
|
+
const data = super.computeData();
|
|
402
|
+
if (data.length === 0 || !this.exists()) return data;
|
|
403
|
+
const columnLabels: string[] = this.validGroupBy().map(gb => gb.label());
|
|
404
|
+
const computedFields = this.validComputedFields().map(cf => {
|
|
405
|
+
return { label: cf.fieldID(), aggrFunc: cf.aggrFunc() };
|
|
406
|
+
});
|
|
407
|
+
const retVal = d3Nest()
|
|
408
|
+
.key((row: { [key: string]: any }) => {
|
|
409
|
+
let key = "";
|
|
410
|
+
for (const groupByLabel of columnLabels) {
|
|
411
|
+
key += ":" + row[groupByLabel];
|
|
412
|
+
}
|
|
413
|
+
return key;
|
|
414
|
+
})
|
|
415
|
+
.entries(data as object[]).map(_row => {
|
|
416
|
+
const row: {
|
|
417
|
+
[key: string]: any
|
|
418
|
+
} = _row;
|
|
419
|
+
delete row.key;
|
|
420
|
+
for (const groupByLabel of columnLabels) {
|
|
421
|
+
row[groupByLabel] = row.values[0][groupByLabel];
|
|
422
|
+
}
|
|
423
|
+
for (const cf of computedFields) {
|
|
424
|
+
row[cf.label] = cf.aggrFunc(row.values);
|
|
425
|
+
}
|
|
426
|
+
return row;
|
|
427
|
+
})
|
|
428
|
+
;
|
|
429
|
+
const outFields = this.outFields();
|
|
430
|
+
return retVal.map(row => {
|
|
431
|
+
const retVal = {};
|
|
432
|
+
for (const field of outFields) {
|
|
433
|
+
retVal[field.id] = row[field.id];
|
|
434
|
+
}
|
|
435
|
+
return retVal;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
GroupBy.prototype._class += " GroupBy";
|