vaporous 0.0.2 → 0.0.4

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 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, method) {
49
+ method(operation, name, options) {
53
50
  const operations = {
54
51
  create: () => {
55
- this.savedMethods[name] = method
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
- const event = parser(line)
139
- if (event !== null) content.push(event)
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
- console.log(this.events)
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
 
@@ -206,11 +245,19 @@ class Vaporous {
206
245
  const arr = Object.keys(map).map(key => {
207
246
  const result = map[key]
208
247
 
248
+ let sortedCache = {}
209
249
  aggregations.forEach(aggregation => {
210
- if (aggregation.sortable) map[key]._statsRaw[aggregation.field].sort((a, b) => a - b)
250
+ const outputField = aggregation.outputField
251
+ const reference = map[key]._statsRaw[aggregation.field]
252
+
253
+ if (aggregation.sortable) {
254
+ sortedCache[aggregation.field] = reference.slice().sort((a, b) => a - b)
255
+ result[outputField] = aggregation.calculate(sortedCache[aggregation.field])
256
+ } else {
257
+ result[outputField] = aggregation.calculate(reference)
258
+ }
259
+
211
260
 
212
- const aggregationField = aggregation.outputField
213
- result[aggregationField] = aggregation.calculate(map[key])
214
261
  })
215
262
 
216
263
  delete map[key]._statsRaw
@@ -238,51 +285,63 @@ class Vaporous {
238
285
  }
239
286
 
240
287
  streamstats(...args) {
288
+ const backwardIterate = (event, i, by, maxBoundary = 0) => {
289
+ let backwardIndex = 0
290
+ const thisKey = keyFromEvent(event, by)
291
+ const byKey = thisKey
292
+
293
+ while (true) {
294
+ const target = i - backwardIndex
295
+
296
+ if (target < 0 || target < maxBoundary) break
297
+
298
+ const newKey = keyFromEvent(this.events[target], by)
299
+ if (thisKey !== newKey) break
300
+ backwardIndex++
301
+ }
302
+
303
+ return { byKey, start: i - backwardIndex + 1 }
304
+ }
305
+
306
+
241
307
  const window = args.filter(i => i instanceof Window)
242
308
  const by = args.filter(i => i instanceof By)
243
309
 
244
310
  // Perform some validation
245
311
  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
312
 
248
313
  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
- }
314
+ let start, byKey = "";
271
315
 
272
- backwardIndex++
273
- }
274
- start = Math.max(i - backwardIndex + 1, 0)
275
- }
316
+ // Refine to window size
317
+ if (window.length > 0) start = Math.max(i - window[0].size + 1, 0)
318
+ if (by.length !== 0) ({ start, byKey } = backwardIterate(event, i, by, start))
276
319
 
277
320
  const eventRange = this.events.slice(start, i + 1)
278
- Object.assign(event, this._stats(args, eventRange).map[byKey])
321
+ const embed = this._stats(args, eventRange).map[byKey]
322
+ Object.assign(event, {
323
+ _streamstats: embed
324
+ })
325
+ })
326
+
327
+ // We need to assign to a separate streamstats object to avoid collusions
328
+ // As streamstats iteratively updates the data but rlies on previous samples
329
+ // Modifying data in place corrupts the results of the query
330
+ this.events.forEach(event => {
331
+ Object.assign(event, event._streamstats)
332
+ delete event._streamstats
279
333
  })
280
334
 
281
335
  return this;
282
336
  }
283
337
 
338
+ delta(field, remapField, ...bys) {
339
+ this.streamstats(new Aggregation(field, 'range', remapField), new Window(2), ...bys)
340
+ return this;
341
+ }
342
+
284
343
  sort(order, ...keys) {
285
- this._events = _sort(order, this.events, keys)
344
+ this.events = _sort(order, this.events, ...keys)
286
345
  return this;
287
346
  }
288
347
 
@@ -294,14 +353,163 @@ class Vaporous {
294
353
  return this;
295
354
  }
296
355
 
297
- build(name, type, visualisationOptions) {
298
- const data = JSON.stringify(this.events)
356
+ build(name, type, { tab = 'Default', columns = 2, y2, y1Type, y2Type, y1Stacked, y2Stacked, sortX = 'asc', xTicks = false, trellisAxis = "shared" } = {}) {
357
+
358
+ const visualisationOptions = { tab, columns }
359
+
360
+
361
+ let bounds = {}
362
+
363
+ const isY2 = (data) => {
364
+ let y2Mapped = false;
365
+
366
+ if (y2 instanceof Array) {
367
+ y2Mapped = y2.includes(data)
368
+ }
369
+ else if (y2 instanceof RegExp) {
370
+ y2Mapped = y2.test(data)
371
+ }
372
+
373
+ return y2Mapped
374
+ }
375
+ const graphData = this.events.map((trellis, i) => {
376
+ if (type === 'Table') {
377
+ return trellis;
378
+ }
379
+
380
+ const dataOptions = {}
381
+
382
+ // For every event in this trellis restructure to chart.js
383
+ if (sortX) trellis = _sort(sortX, trellis, '_time')
384
+
385
+ const trellisName = this.graphFlags.at(-1).trellisName?.[i] || ""
386
+ const columnDefinitions = this.graphFlags.at(-1).columnDefinitions[i]
387
+
388
+ trellis.forEach(event => {
389
+ columnDefinitions.forEach(prop => {
390
+ if (!dataOptions[prop]) dataOptions[prop] = []
391
+ const val = event[prop]
392
+ dataOptions[prop].push(val)
393
+
394
+ if (!bounds[prop]) bounds[prop] = {
395
+ min: val,
396
+ max: val
397
+ }
398
+
399
+ if (val < bounds[prop].min) bounds[prop].min = val;
400
+ if (val > bounds[prop].max) bounds[prop].max = val
401
+
402
+ })
403
+ })
404
+
405
+
406
+ const _time = dataOptions._time
407
+ delete dataOptions._time
408
+
409
+ let y2WasMapped = false
410
+ const data = {
411
+ labels: _time,
412
+ datasets: Object.keys(dataOptions).map(data => {
413
+ const y2Mapped = isY2(data)
414
+ if (y2Mapped) y2WasMapped = y2Mapped
415
+
416
+ const base = {
417
+ label: data,
418
+ yAxisID: y2Mapped ? 'y2' : undefined,
419
+ data: dataOptions[data],
420
+ type: y2Mapped ? y2Type : y1Type,
421
+ // borderColor: 'red',
422
+ // backgroundColor: 'red',
423
+ }
424
+
425
+ if (type === 'Scatter') {
426
+ base.showLine = false
427
+ base.pointRadius = 8
428
+ base.pointStyle = 'rect'
429
+ } else if (type === 'Area') {
430
+ base.fill = 'origin'
431
+ } else if (type === 'Line') {
432
+ base.pointRadius = 0;
433
+ }
434
+ return base
435
+ })
436
+ };
437
+
438
+ const scales = {
439
+ y: {
440
+ type: 'linear',
441
+ display: true,
442
+ position: 'left',
443
+ stacked: y1Stacked
444
+ },
445
+ x: {
446
+ type: 'linear',
447
+ ticks: {
448
+ display: xTicks
449
+ }
450
+ }
451
+ }
452
+
453
+ if (y2WasMapped) scales.y2 = {
454
+ type: 'linear',
455
+ display: true,
456
+ position: 'right',
457
+ grid: {
458
+ drawOnChartArea: false
459
+ },
460
+ stacked: y2Stacked
461
+ }
462
+
463
+ return {
464
+ type: 'line',
465
+ data: data,
466
+ options: {
467
+ scales,
468
+ responsive: true,
469
+ plugins: {
470
+ legend: {
471
+ position: 'bottom',
472
+ },
473
+ title: {
474
+ display: true,
475
+ text: name + trellisName
476
+ }
477
+ }
478
+ }
479
+ }
480
+ })
481
+
482
+ if (trellisAxis === 'shared') {
483
+ // Do a second iteration to implement bounds
484
+ graphData.forEach(trellisGraph => {
485
+ Object.keys(bounds).forEach(bound => {
486
+ let axis = isY2(bound) ? 'y2' : 'y'
487
+ if (bound === '_time') axis = 'x';
488
+
489
+ const thisAxis = trellisGraph.options.scales[axis]
490
+ const { min, max } = bounds[bound]
491
+ if (!thisAxis.min) {
492
+ thisAxis.min = min
493
+ thisAxis.max = max
494
+ }
495
+ if (min < thisAxis.min) thisAxis.min = min
496
+ if (max > thisAxis.max) thisAxis.max = max
497
+
498
+ })
499
+
500
+ })
501
+ }
502
+
503
+ const data = JSON.stringify(graphData)
299
504
  const lastData = this.visualisationData.at(-1)
300
505
 
301
506
  if (lastData !== data) this.visualisationData.push(data)
302
507
  this.visualisations.push([name, type, visualisationOptions, this.visualisationData.length - 1, this.graphFlags[this.graphFlags.length - 1]])
303
508
 
304
- if (visualisationOptions.tab && !this.tabs.includes(visualisationOptions.tab)) this.tabs.push(visualisationOptions.tab)
509
+ if (visualisationOptions.tab && !this.tabs.includes(visualisationOptions.tab)) {
510
+ this.tabs.push(visualisationOptions.tab)
511
+ this.tabs = this.tabs.sort((a, b) => a.localeCompare(b))
512
+ }
305
513
 
306
514
  return this;
307
515
  }
@@ -309,8 +517,8 @@ class Vaporous {
309
517
  checkpoint(operation, name) {
310
518
 
311
519
  const operations = {
312
- create: () => this.checkpoints[name] = this.events,
313
- retrieve: () => this.events = this.checkpoints[name],
520
+ create: () => this.checkpoints[name] = structuredClone(this.events),
521
+ retrieve: () => this.events = structuredClone(this.checkpoints[name]),
314
522
  delete: () => delete this.checkpoints[name]
315
523
  }
316
524
 
@@ -322,10 +530,11 @@ class Vaporous {
322
530
  const arr = []
323
531
  this.events.forEach(event => {
324
532
  if (!event[target]) return arr.push(event)
325
- event[target].forEach((item) => {
533
+ event[target].forEach((item, i) => {
326
534
  arr.push({
327
535
  ...event,
328
- [target]: item
536
+ [target]: item,
537
+ [`_mvExpand_${target}`]: i
329
538
  })
330
539
  })
331
540
  })
@@ -334,53 +543,102 @@ class Vaporous {
334
543
  return this;
335
544
  }
336
545
 
337
- toGraph(x, y, series, trellis, options = {}) {
546
+ writeFile(title) {
547
+ fs.writeFileSync('./' + title, JSON.stringify(this.events))
548
+ return this;
549
+ }
550
+
551
+ toGraph(x, y, series, trellis = false) {
552
+
553
+ if (!(y instanceof Array)) y = [y]
554
+
555
+ const yAggregations = y.map(item => [
556
+ new Aggregation(item, 'list', item),
557
+ ]).flat()
338
558
 
339
559
  this.stats(
340
- new Aggregation(y, 'list', y),
560
+ ...yAggregations,
341
561
  new Aggregation(series, 'list', series),
342
562
  new Aggregation(trellis, 'values', 'trellis'),
343
563
  new By(x), trellis ? new By(trellis) : null
344
564
  )
345
565
 
346
- const trellisMap = {}
566
+ const trellisMap = {}, columnDefinitions = {}
347
567
 
348
568
  this.table(event => {
569
+ const _time = event[x]
570
+ 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
571
  const obj = {
350
- _time: event[x]
572
+ _time
351
573
  }
352
- event[series].forEach((series, i) => obj[series] = event[y][i])
574
+
575
+ event[series].forEach((series, i) => {
576
+ y.forEach(item => {
577
+ let name;
578
+ if (y.length === 1) {
579
+ if (series === undefined) name = item
580
+ else name = series
581
+ } else {
582
+ if (series !== undefined) name = `${series}_${item}`
583
+ else name = item
584
+ }
585
+ obj[name] = event[item][i]
586
+ })
587
+ })
353
588
 
354
589
  if (trellis) {
355
590
  const tval = event.trellis[0]
356
- if (!trellisMap[tval]) trellisMap[tval] = []
591
+ if (!trellisMap[tval]) {
592
+ trellisMap[tval] = []
593
+ columnDefinitions[tval] = {}
594
+ }
357
595
  trellisMap[tval].push(obj)
596
+ Object.keys(obj).forEach(key => {
597
+ columnDefinitions[tval][key] = true
598
+ })
599
+ } else {
600
+ Object.keys(obj).forEach(key => {
601
+ columnDefinitions[key] = true;
602
+ })
358
603
  }
359
604
 
360
605
  return obj
361
606
  })
362
607
 
363
608
  const graphFlags = {}
609
+
364
610
  if (trellis) {
365
611
  graphFlags.trellis = true;
366
612
  graphFlags.trellisName = Object.keys(trellisMap)
613
+ graphFlags.columnDefinitions = Object.keys(trellisMap).map(tval => {
614
+ const adjColumns = ['_time']
615
+ Object.keys(columnDefinitions[tval]).forEach(col => (col !== '_time') ? adjColumns.push(col) : null)
616
+ return adjColumns
617
+ })
367
618
  this.events = Object.keys(trellisMap).map(tval => trellisMap[tval])
619
+ } else {
620
+ const adjColumns = ['_time']
621
+ Object.keys(columnDefinitions).forEach(col => (col !== '_time') ? adjColumns.push(col) : null)
622
+
623
+ this.events = [this.events]
624
+ graphFlags.columnDefinitions = [adjColumns]
368
625
  }
369
626
 
370
- Object.assign(graphFlags, options)
371
627
  this.graphFlags.push(graphFlags)
372
628
  return this;
373
629
  }
374
630
 
375
- render() {
631
+ render(location = './Vaporous_generation.html') {
376
632
  const classSafe = (name) => name.replace(/[^a-zA-Z0-9]/g, "_")
377
633
 
378
- const createElement = (name, type, visualisationOptions, eventData, { trellis, y2, sortX, trellisName = "", y2Type, y1Type, stacked, y1Min, y2Min }) => {
634
+ const createElement = (name, type, visualisationOptions, eventData, { trellis, trellisName = "" }) => {
635
+
379
636
  if (classSafe(visualisationOptions.tab) !== selectedTab) return;
380
637
 
381
638
  eventData = visualisationData[eventData]
382
- if (!trellis) eventData = [eventData]
383
- else {
639
+
640
+ // TODO: migrate trellis functionality from here to tograph
641
+ if (trellis) {
384
642
  let pairs = trellisName.map((name, i) => [name, eventData[i]]);
385
643
  pairs = pairs.sort((a, b) => a[0].localeCompare(b[0]))
386
644
 
@@ -389,84 +647,41 @@ class Vaporous {
389
647
  eventData = pairs.map(p => p[1]);
390
648
  }
391
649
 
392
- eventData.forEach((trellis, i) => {
393
- const data = new google.visualization.DataTable();
394
-
395
- const series = {}, axis0 = { targetAxisIndex: 0 }, axis1 = { targetAxisIndex: 1 }
650
+ const columnCount = visualisationOptions.columns || 2
396
651
 
397
- if (y1Type) axis0.type = y1Type
398
- if (y2Type) axis1.type = y2Type
652
+ eventData.forEach((trellisData, i) => {
653
+ const parentHolder = document.createElement('div')
399
654
 
400
- // Create columns
401
- const columns = Object.keys(trellis[0])
402
- columns.forEach((key, i) => {
403
- data.addColumn(typeof trellis[0][key], key)
404
655
 
405
- if (y2 && i !== 0) {
406
- let match = false;
407
- if (y2 instanceof Array) { match = y2.includes(key) }
408
- else if (y2 instanceof RegExp) { match = y2.test(key) }
409
656
 
410
- if (match) series[i - 1] = axis1
411
- }
412
-
413
- if (!series[1 - i]) series[i - 1] = axis0
414
- })
415
-
416
- let rows = trellis.map(event => {
417
- return columns.map(key => event[key])
418
- })
419
-
420
- rows = _sort(sortX, rows, 0)
421
-
422
- data.addRows(rows);
423
-
424
- const columnCount = visualisationOptions.columns || 2
425
- const thisEntity = document.createElement('div')
426
- thisEntity.className = "parentHolder"
427
- thisEntity.style = `flex: 1 0 calc(${100 / columns}% - 6px); max-width: calc(${100 / columnCount}% - 6px);`
657
+ document.getElementById('content').appendChild(parentHolder)
428
658
 
659
+ parentHolder.style = `flex: 0 0 calc(${100 / columnCount}% - 8px); max-width: calc(${100 / columnCount}% - 8px);`
660
+ if (type === 'Table') {
661
+ new Tabulator(parentHolder, { data: trellisData, autoColumns: 'full', layout: "fitDataStretch", })
662
+ } else {
663
+ const graphEntity = document.createElement('canvas')
664
+ parentHolder.appendChild(graphEntity)
665
+ new Chart(graphEntity, trellisData)
666
+ }
429
667
 
430
- const thisGraph = document.createElement('div')
431
- thisGraph.className = "graphHolder"
432
- thisEntity.appendChild(thisGraph)
433
- document.getElementById('content').appendChild(thisEntity)
434
-
435
- const chartElement = new google.visualization[type](thisGraph)
436
-
437
- google.visualization.events.addListener(chartElement, 'select', (e) => {
438
- console.log(chartElement.getSelection()[1], chartElement.getSelection()[0])
439
- tokens[name] = trellis[chartElement.getSelection()[0].row]
440
- console.log(tokens[name])
441
- });
442
-
443
- const title = trellis ? name + trellisName[i] : name
444
-
445
- chartElement.draw(data, {
446
- series, showRowNumber: false, legend: { position: 'bottom' }, title, isStacked: stacked,
447
- width: document.body.scrollWidth / columnCount - (type === "LineChart" ? 12 : 24),
448
- animation: { duration: 500, startup: true },
449
- chartArea: { width: '85%', height: '75%' },
450
- vAxis: {
451
- viewWindow: {
452
- min: y1Min
453
- }
454
- }
455
- })
456
668
  })
457
669
  }
458
670
 
459
- const filePath = './Vaporous_generation.html'
671
+ const filePath = location
460
672
  fs.writeFileSync(filePath, `
461
673
  <html>
462
674
  <head>
675
+ <meta name="viewport" content="width=device-width, initial-scale=0.5">
463
676
  <style>
464
677
  ${styles}
465
678
  </style>
466
- <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
679
+
680
+ <link href="https://unpkg.com/tabulator-tables@6.3.1/dist/css/tabulator.min.css" rel="stylesheet">
681
+ <script type="text/javascript" src="https://unpkg.com/tabulator-tables@6.3.1/dist/js/tabulator.min.js"></script>
682
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
683
+
467
684
  <script type="text/javascript">
468
- google.charts.load('current', {'packages':['table', 'corechart']});
469
- google.charts.setOnLoadCallback(drawVis);
470
685
 
471
686
  const classSafe = ${classSafe.toString()}
472
687
 
@@ -505,7 +720,7 @@ class Vaporous {
505
720
  </html>
506
721
  `)
507
722
 
508
- console.log('File ouput created ', filePath)
723
+ console.log('File ouput created ', path.resolve(filePath))
509
724
  }
510
725
  }
511
726
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaporous",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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 some quality of life features
5
+
6
+ The query syntax is heavily inspired by splunk with more bias towards programmitic functionality
7
+
8
+ ## Examples
9
+
10
+ Interactive previews for two datasources are available
11
+
12
+ - [Virtualised temperature sensor data](https://lkashl.github.io/vaporous/pages/temp_sensors.html)
13
+ - [CSV delimited virtruvian data](https://lkashl.github.io/vaporous/pages/gym.html)
14
+
15
+ Examples of the source queries used can be referenced in the [examples folder](https://github.com/lkashl/vaporous/tree/main/examples)
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
+