vaporous 0.0.3 → 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
@@ -245,11 +245,19 @@ class Vaporous {
245
245
  const arr = Object.keys(map).map(key => {
246
246
  const result = map[key]
247
247
 
248
+ let sortedCache = {}
248
249
  aggregations.forEach(aggregation => {
249
- 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
+
250
260
 
251
- const aggregationField = aggregation.outputField
252
- result[aggregationField] = aggregation.calculate(map[key])
253
261
  })
254
262
 
255
263
  delete map[key]._statsRaw
@@ -345,11 +353,154 @@ class Vaporous {
345
353
  return this;
346
354
  }
347
355
 
348
- build(name, type, { tab = 'Default', columns = 2 } = {}) {
356
+ build(name, type, { tab = 'Default', columns = 2, y2, y1Type, y2Type, y1Stacked, y2Stacked, sortX = 'asc', xTicks = false, trellisAxis = "shared" } = {}) {
349
357
 
350
358
  const visualisationOptions = { tab, columns }
351
359
 
352
- const data = JSON.stringify(this.events)
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)
353
504
  const lastData = this.visualisationData.at(-1)
354
505
 
355
506
  if (lastData !== data) this.visualisationData.push(data)
@@ -397,12 +548,13 @@ class Vaporous {
397
548
  return this;
398
549
  }
399
550
 
400
- toGraph(x, y, series, trellis = false, options = {}) {
551
+ toGraph(x, y, series, trellis = false) {
401
552
 
402
553
  if (!(y instanceof Array)) y = [y]
403
- if (options.y2 instanceof RegExp) options.y2 = options.y2.toString()
404
554
 
405
- const yAggregations = y.map(item => new Aggregation(item, 'list', item))
555
+ const yAggregations = y.map(item => [
556
+ new Aggregation(item, 'list', item),
557
+ ]).flat()
406
558
 
407
559
  this.stats(
408
560
  ...yAggregations,
@@ -422,7 +574,14 @@ class Vaporous {
422
574
 
423
575
  event[series].forEach((series, i) => {
424
576
  y.forEach(item => {
425
- const name = y.length === 1 ? series : `${series}_${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
+ }
426
585
  obj[name] = event[item][i]
427
586
  })
428
587
  })
@@ -447,6 +606,7 @@ class Vaporous {
447
606
  })
448
607
 
449
608
  const graphFlags = {}
609
+
450
610
  if (trellis) {
451
611
  graphFlags.trellis = true;
452
612
  graphFlags.trellisName = Object.keys(trellisMap)
@@ -464,23 +624,14 @@ class Vaporous {
464
624
  graphFlags.columnDefinitions = [adjColumns]
465
625
  }
466
626
 
467
- Object.assign(graphFlags, options)
468
627
  this.graphFlags.push(graphFlags)
469
628
  return this;
470
629
  }
471
630
 
472
- render() {
631
+ render(location = './Vaporous_generation.html') {
473
632
  const classSafe = (name) => name.replace(/[^a-zA-Z0-9]/g, "_")
474
633
 
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
- }
634
+ const createElement = (name, type, visualisationOptions, eventData, { trellis, trellisName = "" }) => {
484
635
 
485
636
  if (classSafe(visualisationOptions.tab) !== selectedTab) return;
486
637
 
@@ -496,88 +647,41 @@ class Vaporous {
496
647
  eventData = pairs.map(p => p[1]);
497
648
  }
498
649
 
499
- eventData.forEach((trellisData, i) => {
500
- const data = new google.visualization.DataTable();
501
-
502
- const series = {}, axis0 = { targetAxisIndex: 0 }, axis1 = { targetAxisIndex: 1 }
503
-
504
- if (y1Type) axis0.type = y1Type
505
- if (y2Type) axis1.type = y2Type
506
-
507
- // Create columns
508
- const columns = columnDefinitions[i]
509
-
510
- columns.forEach((key, i) => {
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)
514
-
515
- if (y2 && i !== 0) {
516
- let match = false;
517
- if (y2 instanceof Array) { match = y2.includes(key) }
518
- else if (y2 instanceof RegExp) { match = y2.test(key) }
519
-
520
- if (match) series[i - 1] = axis1
521
- }
522
-
523
- if (!series[i - 1]) series[i - 1] = axis0
524
- })
525
-
526
- let rows = trellisData.map(event => {
527
- return columns.map(key => event[key])
528
- })
529
-
530
- rows = _sort(sortX, rows, 0)
531
-
532
- data.addRows(rows);
533
-
534
- const columnCount = visualisationOptions.columns || 2
535
- const thisEntity = document.createElement('div')
536
- thisEntity.className = "parentHolder"
537
- thisEntity.style = `flex: 1 0 calc(${100 / columns}% - 6px); max-width: calc(${100 / columnCount}% - 6px);`
650
+ const columnCount = visualisationOptions.columns || 2
538
651
 
652
+ eventData.forEach((trellisData, i) => {
653
+ const parentHolder = document.createElement('div')
539
654
 
540
- const thisGraph = document.createElement('div')
541
- thisGraph.className = "graphHolder"
542
- thisEntity.appendChild(thisGraph)
543
- document.getElementById('content').appendChild(thisEntity)
544
655
 
545
- const chartElement = new google.visualization[type](thisGraph)
546
656
 
547
- google.visualization.events.addListener(chartElement, 'select', (e) => {
548
- console.log(chartElement.getSelection()[1], chartElement.getSelection()[0])
549
- tokens[name] = trellisData[chartElement.getSelection()[0].row]
550
- console.log(tokens[name])
551
- });
657
+ document.getElementById('content').appendChild(parentHolder)
552
658
 
553
- const title = trellis ? name + trellisName[i] : name
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
+ }
554
667
 
555
- chartElement.draw(data, {
556
- series, showRowNumber: false, legend: { position: 'bottom' }, title, isStacked: stacked,
557
- width: document.body.scrollWidth / columnCount - (type === "LineChart" ? 12 : 24),
558
- animation: { duration: 500, startup: true },
559
- chartArea: { width: '85%', height: '75%' },
560
- vAxis: {
561
- viewWindow: {
562
- min: y1Min
563
- }
564
- },
565
- pointSize: type === 'ScatterChart' ? 2 : undefined
566
- })
567
668
  })
568
669
  }
569
670
 
570
- const filePath = './Vaporous_generation.html'
671
+ const filePath = location
571
672
  fs.writeFileSync(filePath, `
572
673
  <html>
573
674
  <head>
675
+ <meta name="viewport" content="width=device-width, initial-scale=0.5">
574
676
  <style>
575
677
  ${styles}
576
678
  </style>
577
- <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
+
578
684
  <script type="text/javascript">
579
- google.charts.load('current', {'packages':['table', 'corechart']});
580
- google.charts.setOnLoadCallback(drawVis);
581
685
 
582
686
  const classSafe = ${classSafe.toString()}
583
687
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaporous",
3
- "version": "0.0.3",
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": {
package/readme.md CHANGED
@@ -1,22 +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
-
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
+
package/styles.css CHANGED
@@ -1,79 +1,83 @@
1
- @import url('https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap');
2
-
3
- body {
4
- font-family: 'Roboto', Arial, sans-serif;
5
- }
6
-
7
- /* Chart-specific styles */
8
- .chart,
9
- .chart-container,
10
- canvas,
11
- svg,
12
- .chartjs-render-monitor {
13
- font-family: 'Roboto', Arial, sans-serif !important;
14
- }
15
-
16
- body {
17
- margin: 0;
18
- padding: 0;
19
- }
20
-
21
- .tabBar {
22
- display: flex;
23
- background: #dcdcdc;
24
- overflow: hidden;
25
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
26
- margin-bottom: 8px;
27
- width: 100%;
28
- height: 32px;
29
- }
30
-
31
- .tabs {
32
- flex: 1;
33
- padding: 8px 20px;
34
- cursor: pointer;
35
- background: none;
36
- border: none;
37
- outline: none;
38
- font-size: 1rem;
39
- color: #555;
40
- transition: background 0.3s, color 0.3s;
41
- text-align: center;
42
- }
43
-
44
- .tabs:not(.selectedTab):hover {
45
- background: #e0e0e0;
46
- color: #222;
47
- }
48
-
49
- .selectedTab {
50
- background: #1976d2;
51
- color: #fff;
52
- font-weight: bold;
53
- box-shadow: 0 2px 8px rgba(25, 118, 210, 0.15);
54
- transition: background 0.3s, color 0.3s;
55
- }
56
-
57
- #content {
58
- display: flex;
59
- flex-wrap: wrap;
60
- padding: 0px;
61
- }
62
-
63
- .parentHolder {
64
- display: flex;
65
- margin: 2px;
66
- border: 1px solid #d3d3d3;
67
- }
68
-
69
- .parentHolder::after {
70
- border: 2px solid red;
71
- /* Change color and width as needed */
72
- pointer-events: none;
73
- z-index: 2;
74
- }
75
-
76
- .tabContent {
77
- opacity: 1;
78
- transition: opacity 0.3s,
1
+ @import url('https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap');
2
+
3
+ body {
4
+ font-family: 'Roboto', Arial, sans-serif;
5
+ }
6
+
7
+ /* Chart-specific styles */
8
+ .chart,
9
+ .chart-container,
10
+ canvas,
11
+ svg,
12
+ .chartjs-render-monitor {
13
+ font-family: 'Roboto', Arial, sans-serif !important;
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ padding: 16px;
19
+ }
20
+
21
+ .tabBar {
22
+ display: flex;
23
+ overflow: hidden;
24
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
25
+ min-height: 32px;
26
+ margin-bottom: 8px;
27
+ margin-top: 8px;
28
+ margin-left: 8px;
29
+ margin-right: 8px;
30
+ }
31
+
32
+ .tabs {
33
+ border-radius: 8px;
34
+ padding: 8px 20px;
35
+ cursor: pointer;
36
+ background: none;
37
+ border: none;
38
+ outline: none;
39
+ font-size: 1rem;
40
+ color: #555;
41
+ transition: background 0.3s, color 0.3s;
42
+ text-align: center;
43
+ margin: 12px;
44
+ }
45
+
46
+ .tabs:not(.selectedTab):hover {
47
+ background: #e0e0e0;
48
+ color: #222;
49
+ }
50
+
51
+ .selectedTab {
52
+ background: #1976d2;
53
+ color: #fff;
54
+ font-weight: bold;
55
+ box-shadow: 0 2px 8px rgba(25, 118, 210, 0.15);
56
+ transition: background 0.3s, color 0.3s;
57
+ }
58
+
59
+ #content {
60
+ display: flex;
61
+ flex-wrap: wrap;
62
+ padding: 0px;
63
+ gap: 8px;
64
+ justify-content: space-between;
65
+ }
66
+
67
+ .parentHolder {
68
+ display: flex;
69
+ margin: 2px;
70
+ border: 1px solid #d3d3d3;
71
+ }
72
+
73
+ .parentHolder::after {
74
+ border: 2px solid red;
75
+ /* Change color and width as needed */
76
+ pointer-events: none;
77
+ z-index: 2;
78
+ }
79
+
80
+ .tabContent {
81
+ opacity: 1;
82
+ transition: opacity 0.3s,
79
83
  }
@@ -23,8 +23,8 @@ class Aggregation {
23
23
  return [...new Set(values)];
24
24
  }
25
25
 
26
- calculate(statObj) {
27
- return this[this.type](statObj._statsRaw[this.field])
26
+ calculate(val) {
27
+ return this[this.type](val)
28
28
  }
29
29
 
30
30
  max(values) {