vaporous 0.0.2 → 0.0.3
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/Vaporous.js +179 -68
- package/package.json +2 -1
- package/readme.md +22 -0
- package/types/Aggregation.js +56 -52
- package/examples/sensors/exampleData/temp_0_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_1_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_2_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_3_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_4_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_5_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_6_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_7_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_8_10.jsonStream +0 -100
- package/examples/sensors/exampleData/temp_9_10.jsonStream +0 -100
- package/examples/sensors/query.js +0 -98
- package/examples/sensors/run.sh +0 -2
- package/examples/sensors/sensor_data.js +0 -55
package/Vaporous.js
CHANGED
|
@@ -11,6 +11,8 @@ const path = require('path')
|
|
|
11
11
|
|
|
12
12
|
const styles = fs.readFileSync(__dirname + '/styles.css')
|
|
13
13
|
|
|
14
|
+
const Papa = require('papaparse')
|
|
15
|
+
|
|
14
16
|
// These globals allow us to write functions from the HTML page directly without needing to stringify
|
|
15
17
|
class google { }
|
|
16
18
|
const document = {}
|
|
@@ -20,15 +22,10 @@ const keyFromEvent = (event, bys) => bys.map(i => event[i.bySplit]).join('|')
|
|
|
20
22
|
const _sort = (order, data, ...keys) => {
|
|
21
23
|
return data.sort((a, b) => {
|
|
22
24
|
let directive = 0;
|
|
23
|
-
keys.some(key => {
|
|
24
|
-
const type = typeof a[key];
|
|
25
|
-
|
|
26
|
-
if (type === 'number') {
|
|
27
|
-
directive = order === 'asc' ? a[key] - b[key] : b[key] - a[key];
|
|
28
|
-
} else if (type === 'string') {
|
|
29
|
-
directive = order === 'asc' ? a[key].localeCompare(b[key]) : b[key].localeCompare(a[key]);
|
|
30
|
-
}
|
|
31
25
|
|
|
26
|
+
keys.some(key => {
|
|
27
|
+
directive = typeof a[key] === 'number' ? a[key] - b[key] : a[key].localeCompare(b[key])
|
|
28
|
+
if (order === 'dsc') directive = directive * -1
|
|
32
29
|
if (directive !== 0) return true;
|
|
33
30
|
})
|
|
34
31
|
|
|
@@ -49,13 +46,13 @@ class Vaporous {
|
|
|
49
46
|
this.checkpoints = {}
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
method(operation, name,
|
|
49
|
+
method(operation, name, options) {
|
|
53
50
|
const operations = {
|
|
54
51
|
create: () => {
|
|
55
|
-
this.savedMethods[name] =
|
|
52
|
+
this.savedMethods[name] = options
|
|
56
53
|
},
|
|
57
54
|
retrieve: () => {
|
|
58
|
-
this.savedMethods[name](this)
|
|
55
|
+
this.savedMethods[name](this, options)
|
|
59
56
|
},
|
|
60
57
|
delete: () => {
|
|
61
58
|
delete this.savedMethods[name]
|
|
@@ -127,16 +124,51 @@ class Vaporous {
|
|
|
127
124
|
return this;
|
|
128
125
|
}
|
|
129
126
|
|
|
127
|
+
async csvLoad(parser) {
|
|
128
|
+
const tasks = this.events.map(obj => {
|
|
129
|
+
const content = []
|
|
130
|
+
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const thisStream = fs.createReadStream(obj._fileInput)
|
|
133
|
+
|
|
134
|
+
Papa.parse(thisStream, {
|
|
135
|
+
header: true,
|
|
136
|
+
skipEmptyLines: true,
|
|
137
|
+
step: (row) => {
|
|
138
|
+
try {
|
|
139
|
+
const event = parser(row)
|
|
140
|
+
if (event !== null) content.push(event)
|
|
141
|
+
} catch (err) {
|
|
142
|
+
reject(err)
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
complete: () => {
|
|
146
|
+
obj._raw = content
|
|
147
|
+
resolve(this)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
await Promise.all(tasks)
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
|
|
130
157
|
async fileLoad(delim, parser) {
|
|
131
158
|
const tasks = this.events.map(obj => {
|
|
132
159
|
const content = []
|
|
133
160
|
|
|
134
|
-
return new Promise(resolve => {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
135
162
|
fs.createReadStream(obj._fileInput)
|
|
136
163
|
.pipe(split2(delim))
|
|
137
164
|
.on('data', line => {
|
|
138
|
-
|
|
139
|
-
|
|
165
|
+
try {
|
|
166
|
+
const event = parser(line)
|
|
167
|
+
if (event !== null) content.push(event)
|
|
168
|
+
} catch (err) {
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
|
|
140
172
|
})
|
|
141
173
|
.on('end', () => {
|
|
142
174
|
obj._raw = content;
|
|
@@ -149,8 +181,15 @@ class Vaporous {
|
|
|
149
181
|
return this;
|
|
150
182
|
}
|
|
151
183
|
|
|
152
|
-
output() {
|
|
153
|
-
|
|
184
|
+
output(...args) {
|
|
185
|
+
if (args.length) {
|
|
186
|
+
console.log(this.events.map(event => {
|
|
187
|
+
return args.map(item => event[item])
|
|
188
|
+
}))
|
|
189
|
+
} else {
|
|
190
|
+
console.log(this.events)
|
|
191
|
+
}
|
|
192
|
+
|
|
154
193
|
return this;
|
|
155
194
|
}
|
|
156
195
|
|
|
@@ -238,51 +277,63 @@ class Vaporous {
|
|
|
238
277
|
}
|
|
239
278
|
|
|
240
279
|
streamstats(...args) {
|
|
280
|
+
const backwardIterate = (event, i, by, maxBoundary = 0) => {
|
|
281
|
+
let backwardIndex = 0
|
|
282
|
+
const thisKey = keyFromEvent(event, by)
|
|
283
|
+
const byKey = thisKey
|
|
284
|
+
|
|
285
|
+
while (true) {
|
|
286
|
+
const target = i - backwardIndex
|
|
287
|
+
|
|
288
|
+
if (target < 0 || target < maxBoundary) break
|
|
289
|
+
|
|
290
|
+
const newKey = keyFromEvent(this.events[target], by)
|
|
291
|
+
if (thisKey !== newKey) break
|
|
292
|
+
backwardIndex++
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return { byKey, start: i - backwardIndex + 1 }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
241
299
|
const window = args.filter(i => i instanceof Window)
|
|
242
300
|
const by = args.filter(i => i instanceof By)
|
|
243
301
|
|
|
244
302
|
// Perform some validation
|
|
245
303
|
if (window.length > 1) throw new Error('Only one window allowed in streamstats')
|
|
246
|
-
if (window.length > 0 && by.length > 0) throw new Error('Window and By not supported together in streamstats')
|
|
247
304
|
|
|
248
305
|
this.events.forEach((event, i) => {
|
|
249
|
-
let start, byKey;
|
|
250
|
-
if (window.length > 0) {
|
|
251
|
-
start = Math.max(i - window[0].size + 1, 0)
|
|
252
|
-
byKey = ""
|
|
253
|
-
} else if (by.length > 0) {
|
|
254
|
-
let backwardIndex = 0
|
|
255
|
-
const thisKey = keyFromEvent(event, by)
|
|
256
|
-
byKey = thisKey
|
|
257
|
-
let keyChange = false
|
|
258
|
-
while (!keyChange) {
|
|
259
|
-
const target = i - backwardIndex
|
|
260
|
-
|
|
261
|
-
if (target < 0) {
|
|
262
|
-
keyChange = true
|
|
263
|
-
break
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const newKey = keyFromEvent(this.events[target], by)
|
|
267
|
-
if (thisKey !== newKey) {
|
|
268
|
-
keyChange = true
|
|
269
|
-
break
|
|
270
|
-
}
|
|
306
|
+
let start, byKey = "";
|
|
271
307
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
308
|
+
// Refine to window size
|
|
309
|
+
if (window.length > 0) start = Math.max(i - window[0].size + 1, 0)
|
|
310
|
+
if (by.length !== 0) ({ start, byKey } = backwardIterate(event, i, by, start))
|
|
276
311
|
|
|
277
312
|
const eventRange = this.events.slice(start, i + 1)
|
|
278
|
-
|
|
313
|
+
const embed = this._stats(args, eventRange).map[byKey]
|
|
314
|
+
Object.assign(event, {
|
|
315
|
+
_streamstats: embed
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
// We need to assign to a separate streamstats object to avoid collusions
|
|
320
|
+
// As streamstats iteratively updates the data but rlies on previous samples
|
|
321
|
+
// Modifying data in place corrupts the results of the query
|
|
322
|
+
this.events.forEach(event => {
|
|
323
|
+
Object.assign(event, event._streamstats)
|
|
324
|
+
delete event._streamstats
|
|
279
325
|
})
|
|
280
326
|
|
|
281
327
|
return this;
|
|
282
328
|
}
|
|
283
329
|
|
|
330
|
+
delta(field, remapField, ...bys) {
|
|
331
|
+
this.streamstats(new Aggregation(field, 'range', remapField), new Window(2), ...bys)
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
|
|
284
335
|
sort(order, ...keys) {
|
|
285
|
-
this.
|
|
336
|
+
this.events = _sort(order, this.events, ...keys)
|
|
286
337
|
return this;
|
|
287
338
|
}
|
|
288
339
|
|
|
@@ -294,14 +345,20 @@ class Vaporous {
|
|
|
294
345
|
return this;
|
|
295
346
|
}
|
|
296
347
|
|
|
297
|
-
build(name, type,
|
|
348
|
+
build(name, type, { tab = 'Default', columns = 2 } = {}) {
|
|
349
|
+
|
|
350
|
+
const visualisationOptions = { tab, columns }
|
|
351
|
+
|
|
298
352
|
const data = JSON.stringify(this.events)
|
|
299
353
|
const lastData = this.visualisationData.at(-1)
|
|
300
354
|
|
|
301
355
|
if (lastData !== data) this.visualisationData.push(data)
|
|
302
356
|
this.visualisations.push([name, type, visualisationOptions, this.visualisationData.length - 1, this.graphFlags[this.graphFlags.length - 1]])
|
|
303
357
|
|
|
304
|
-
if (visualisationOptions.tab && !this.tabs.includes(visualisationOptions.tab))
|
|
358
|
+
if (visualisationOptions.tab && !this.tabs.includes(visualisationOptions.tab)) {
|
|
359
|
+
this.tabs.push(visualisationOptions.tab)
|
|
360
|
+
this.tabs = this.tabs.sort((a, b) => a.localeCompare(b))
|
|
361
|
+
}
|
|
305
362
|
|
|
306
363
|
return this;
|
|
307
364
|
}
|
|
@@ -309,8 +366,8 @@ class Vaporous {
|
|
|
309
366
|
checkpoint(operation, name) {
|
|
310
367
|
|
|
311
368
|
const operations = {
|
|
312
|
-
create: () => this.checkpoints[name] = this.events,
|
|
313
|
-
retrieve: () => this.events = this.checkpoints[name],
|
|
369
|
+
create: () => this.checkpoints[name] = structuredClone(this.events),
|
|
370
|
+
retrieve: () => this.events = structuredClone(this.checkpoints[name]),
|
|
314
371
|
delete: () => delete this.checkpoints[name]
|
|
315
372
|
}
|
|
316
373
|
|
|
@@ -322,10 +379,11 @@ class Vaporous {
|
|
|
322
379
|
const arr = []
|
|
323
380
|
this.events.forEach(event => {
|
|
324
381
|
if (!event[target]) return arr.push(event)
|
|
325
|
-
event[target].forEach((item) => {
|
|
382
|
+
event[target].forEach((item, i) => {
|
|
326
383
|
arr.push({
|
|
327
384
|
...event,
|
|
328
|
-
[target]: item
|
|
385
|
+
[target]: item,
|
|
386
|
+
[`_mvExpand_${target}`]: i
|
|
329
387
|
})
|
|
330
388
|
})
|
|
331
389
|
})
|
|
@@ -334,27 +392,55 @@ class Vaporous {
|
|
|
334
392
|
return this;
|
|
335
393
|
}
|
|
336
394
|
|
|
337
|
-
|
|
395
|
+
writeFile(title) {
|
|
396
|
+
fs.writeFileSync('./' + title, JSON.stringify(this.events))
|
|
397
|
+
return this;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
toGraph(x, y, series, trellis = false, options = {}) {
|
|
401
|
+
|
|
402
|
+
if (!(y instanceof Array)) y = [y]
|
|
403
|
+
if (options.y2 instanceof RegExp) options.y2 = options.y2.toString()
|
|
404
|
+
|
|
405
|
+
const yAggregations = y.map(item => new Aggregation(item, 'list', item))
|
|
338
406
|
|
|
339
407
|
this.stats(
|
|
340
|
-
|
|
408
|
+
...yAggregations,
|
|
341
409
|
new Aggregation(series, 'list', series),
|
|
342
410
|
new Aggregation(trellis, 'values', 'trellis'),
|
|
343
411
|
new By(x), trellis ? new By(trellis) : null
|
|
344
412
|
)
|
|
345
413
|
|
|
346
|
-
const trellisMap = {}
|
|
414
|
+
const trellisMap = {}, columnDefinitions = {}
|
|
347
415
|
|
|
348
416
|
this.table(event => {
|
|
417
|
+
const _time = event[x]
|
|
418
|
+
if (_time === null || _time === undefined) throw new Error(`To graph operation with params ${x}, ${y.join(',')} looks corrupt. x value resolves to null - the graph will not render`)
|
|
349
419
|
const obj = {
|
|
350
|
-
_time
|
|
420
|
+
_time
|
|
351
421
|
}
|
|
352
|
-
|
|
422
|
+
|
|
423
|
+
event[series].forEach((series, i) => {
|
|
424
|
+
y.forEach(item => {
|
|
425
|
+
const name = y.length === 1 ? series : `${series}_${item}`
|
|
426
|
+
obj[name] = event[item][i]
|
|
427
|
+
})
|
|
428
|
+
})
|
|
353
429
|
|
|
354
430
|
if (trellis) {
|
|
355
431
|
const tval = event.trellis[0]
|
|
356
|
-
if (!trellisMap[tval])
|
|
432
|
+
if (!trellisMap[tval]) {
|
|
433
|
+
trellisMap[tval] = []
|
|
434
|
+
columnDefinitions[tval] = {}
|
|
435
|
+
}
|
|
357
436
|
trellisMap[tval].push(obj)
|
|
437
|
+
Object.keys(obj).forEach(key => {
|
|
438
|
+
columnDefinitions[tval][key] = true
|
|
439
|
+
})
|
|
440
|
+
} else {
|
|
441
|
+
Object.keys(obj).forEach(key => {
|
|
442
|
+
columnDefinitions[key] = true;
|
|
443
|
+
})
|
|
358
444
|
}
|
|
359
445
|
|
|
360
446
|
return obj
|
|
@@ -364,7 +450,18 @@ class Vaporous {
|
|
|
364
450
|
if (trellis) {
|
|
365
451
|
graphFlags.trellis = true;
|
|
366
452
|
graphFlags.trellisName = Object.keys(trellisMap)
|
|
453
|
+
graphFlags.columnDefinitions = Object.keys(trellisMap).map(tval => {
|
|
454
|
+
const adjColumns = ['_time']
|
|
455
|
+
Object.keys(columnDefinitions[tval]).forEach(col => (col !== '_time') ? adjColumns.push(col) : null)
|
|
456
|
+
return adjColumns
|
|
457
|
+
})
|
|
367
458
|
this.events = Object.keys(trellisMap).map(tval => trellisMap[tval])
|
|
459
|
+
} else {
|
|
460
|
+
const adjColumns = ['_time']
|
|
461
|
+
Object.keys(columnDefinitions).forEach(col => (col !== '_time') ? adjColumns.push(col) : null)
|
|
462
|
+
|
|
463
|
+
this.events = [this.events]
|
|
464
|
+
graphFlags.columnDefinitions = [adjColumns]
|
|
368
465
|
}
|
|
369
466
|
|
|
370
467
|
Object.assign(graphFlags, options)
|
|
@@ -375,12 +472,22 @@ class Vaporous {
|
|
|
375
472
|
render() {
|
|
376
473
|
const classSafe = (name) => name.replace(/[^a-zA-Z0-9]/g, "_")
|
|
377
474
|
|
|
378
|
-
const createElement = (name, type, visualisationOptions, eventData, { trellis, y2, sortX, trellisName = "", y2Type, y1Type, stacked, y1Min, y2Min }) => {
|
|
475
|
+
const createElement = (name, type, visualisationOptions, eventData, { trellis, y2, sortX, trellisName = "", y2Type, y1Type, stacked, y1Min, y2Min, columnDefinitions }) => {
|
|
476
|
+
|
|
477
|
+
if (typeof y2 === 'string') {
|
|
478
|
+
y2 = y2.split("/")
|
|
479
|
+
const flags = y2.at(-1)
|
|
480
|
+
y2.pop()
|
|
481
|
+
const content = y2.splice(1).join("/")
|
|
482
|
+
y2 = new RegExp(content, flags)
|
|
483
|
+
}
|
|
484
|
+
|
|
379
485
|
if (classSafe(visualisationOptions.tab) !== selectedTab) return;
|
|
380
486
|
|
|
381
487
|
eventData = visualisationData[eventData]
|
|
382
|
-
|
|
383
|
-
|
|
488
|
+
|
|
489
|
+
// TODO: migrate trellis functionality from here to tograph
|
|
490
|
+
if (trellis) {
|
|
384
491
|
let pairs = trellisName.map((name, i) => [name, eventData[i]]);
|
|
385
492
|
pairs = pairs.sort((a, b) => a[0].localeCompare(b[0]))
|
|
386
493
|
|
|
@@ -389,7 +496,7 @@ class Vaporous {
|
|
|
389
496
|
eventData = pairs.map(p => p[1]);
|
|
390
497
|
}
|
|
391
498
|
|
|
392
|
-
eventData.forEach((
|
|
499
|
+
eventData.forEach((trellisData, i) => {
|
|
393
500
|
const data = new google.visualization.DataTable();
|
|
394
501
|
|
|
395
502
|
const series = {}, axis0 = { targetAxisIndex: 0 }, axis1 = { targetAxisIndex: 1 }
|
|
@@ -398,9 +505,12 @@ class Vaporous {
|
|
|
398
505
|
if (y2Type) axis1.type = y2Type
|
|
399
506
|
|
|
400
507
|
// Create columns
|
|
401
|
-
const columns =
|
|
508
|
+
const columns = columnDefinitions[i]
|
|
509
|
+
|
|
402
510
|
columns.forEach((key, i) => {
|
|
403
|
-
|
|
511
|
+
// TODO: we might have to iterate the dataseries to find this information - most likely update the column definition references
|
|
512
|
+
const colType = typeof trellisData[0][key]
|
|
513
|
+
data.addColumn(colType === 'undefined' ? "number" : colType, key)
|
|
404
514
|
|
|
405
515
|
if (y2 && i !== 0) {
|
|
406
516
|
let match = false;
|
|
@@ -410,10 +520,10 @@ class Vaporous {
|
|
|
410
520
|
if (match) series[i - 1] = axis1
|
|
411
521
|
}
|
|
412
522
|
|
|
413
|
-
if (!series[
|
|
523
|
+
if (!series[i - 1]) series[i - 1] = axis0
|
|
414
524
|
})
|
|
415
525
|
|
|
416
|
-
let rows =
|
|
526
|
+
let rows = trellisData.map(event => {
|
|
417
527
|
return columns.map(key => event[key])
|
|
418
528
|
})
|
|
419
529
|
|
|
@@ -436,7 +546,7 @@ class Vaporous {
|
|
|
436
546
|
|
|
437
547
|
google.visualization.events.addListener(chartElement, 'select', (e) => {
|
|
438
548
|
console.log(chartElement.getSelection()[1], chartElement.getSelection()[0])
|
|
439
|
-
tokens[name] =
|
|
549
|
+
tokens[name] = trellisData[chartElement.getSelection()[0].row]
|
|
440
550
|
console.log(tokens[name])
|
|
441
551
|
});
|
|
442
552
|
|
|
@@ -451,7 +561,8 @@ class Vaporous {
|
|
|
451
561
|
viewWindow: {
|
|
452
562
|
min: y1Min
|
|
453
563
|
}
|
|
454
|
-
}
|
|
564
|
+
},
|
|
565
|
+
pointSize: type === 'ScatterChart' ? 2 : undefined
|
|
455
566
|
})
|
|
456
567
|
})
|
|
457
568
|
}
|
|
@@ -505,7 +616,7 @@ class Vaporous {
|
|
|
505
616
|
</html>
|
|
506
617
|
`)
|
|
507
618
|
|
|
508
|
-
console.log('File ouput created ', filePath)
|
|
619
|
+
console.log('File ouput created ', path.resolve(filePath))
|
|
509
620
|
}
|
|
510
621
|
}
|
|
511
622
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vaporous",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Transition data to different structured states for analytical processing",
|
|
5
5
|
"main": "Vaporous.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"dayjs": "^1.11.18",
|
|
13
|
+
"papaparse": "^5.5.3",
|
|
13
14
|
"split2": "^4.2.0"
|
|
14
15
|
}
|
|
15
16
|
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Vaporous
|
|
2
|
+
Vaporous provides a chained query syntax for accessing unstructured data and converting it into interpretable analytics.
|
|
3
|
+
|
|
4
|
+
The tool is still in its early phases of development and is missing quality of life features for query writers
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Examples
|
|
9
|
+
You can find example queries [via git in the examples folder](https://github.com/lkashl/vaporous/tree/main/examples)
|
|
10
|
+
|
|
11
|
+
Interactive previews are available here:
|
|
12
|
+
|
|
13
|
+
- [Virtualised temperature sensor data](https://lkashl.github.io/vaporous/pages/temp_sensors.html)
|
|
14
|
+
- [CSV delimited virtruvian data](https://lkashl.github.io/vaporous/pages/gym.html)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## TODO List
|
|
18
|
+
- Support web page embedded Vaporous so clients can use browser folder storage as file input
|
|
19
|
+
- Add an error for if a user tries to generate a graph without first calling toGraph
|
|
20
|
+
- Intercept structual errors earlier and add validation to functions - not necessarily data as this casues overhaead
|
|
21
|
+
- Migrate reponsibility for tabular conversion from create element to the primary library to reduce overhead of graph generation
|
|
22
|
+
|
package/types/Aggregation.js
CHANGED
|
@@ -1,53 +1,57 @@
|
|
|
1
|
-
class Aggregation {
|
|
2
|
-
constructor(field, type, outputField = field, options) {
|
|
3
|
-
this.type = type;
|
|
4
|
-
this.field = field;
|
|
5
|
-
this.outputField = outputField
|
|
6
|
-
this.options = options;
|
|
7
|
-
this.sortable = ['max', 'min', 'percentile', 'median'].includes(type)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
count(values) {
|
|
11
|
-
return values.length
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
distinctCount(values) {
|
|
15
|
-
return new Set(values).size
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
list(values) {
|
|
19
|
-
return values;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
values(values) {
|
|
23
|
-
return [...new Set(values)];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
calculate(statObj) {
|
|
27
|
-
return this[this.type](statObj._statsRaw[this.field])
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
max(values) {
|
|
31
|
-
return values
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
min(values) {
|
|
35
|
-
return values[0]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return values
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
class Aggregation {
|
|
2
|
+
constructor(field, type, outputField = field, options) {
|
|
3
|
+
this.type = type;
|
|
4
|
+
this.field = field;
|
|
5
|
+
this.outputField = outputField
|
|
6
|
+
this.options = options;
|
|
7
|
+
this.sortable = ['max', 'min', 'percentile', 'median', 'range'].includes(type)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
count(values) {
|
|
11
|
+
return values.length
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
distinctCount(values) {
|
|
15
|
+
return new Set(values).size
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
list(values) {
|
|
19
|
+
return values;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
values(values) {
|
|
23
|
+
return [...new Set(values)];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
calculate(statObj) {
|
|
27
|
+
return this[this.type](statObj._statsRaw[this.field])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
max(values) {
|
|
31
|
+
return values.at(-1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
min(values) {
|
|
35
|
+
return values[0]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
range(values) {
|
|
39
|
+
return values.at(-1) - values[0]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
percentile(values) {
|
|
43
|
+
const index = Math.round(this.options / 100 * (values.length - 1));
|
|
44
|
+
return values[index]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
median(values) {
|
|
48
|
+
const index = Math.floor((values.length - 1) / 2);
|
|
49
|
+
return values[index]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
sum(values) {
|
|
53
|
+
return values.reduce((a, b) => a + b, 0)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
module.exports = Aggregation
|