tsviewer 0.1.0 → 1.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.
Files changed (37) hide show
  1. package/README.md +9 -0
  2. package/dist/tsviewer.css +1 -0
  3. package/dist/tsviewer.es.js +21456 -0
  4. package/dist/tsviewer.umd.js +26 -33259
  5. package/package.json +35 -32
  6. package/babel.config.js +0 -5
  7. package/dist/demo.html +0 -1
  8. package/dist/tsviewer.common.js +0 -33247
  9. package/dist/tsviewer.common.js.map +0 -1
  10. package/dist/tsviewer.umd.js.map +0 -1
  11. package/dist/tsviewer.umd.min.js +0 -4
  12. package/dist/tsviewer.umd.min.js.map +0 -1
  13. package/jsconfig.json +0 -19
  14. package/public/favicon.ico +0 -0
  15. package/public/index.html +0 -17
  16. package/src/App.vue +0 -41
  17. package/src/assets/icons/blackfynn-amplitude-zoom.js +0 -11
  18. package/src/assets/icons/blackfynn-timescale.js +0 -11
  19. package/src/assets/icons/icon-controller-pause.js +0 -11
  20. package/src/assets/icons/icon-controller-play.js +0 -11
  21. package/src/assets/icons/icon-next-page.js +0 -11
  22. package/src/assets/icons/icon-previous-page.js +0 -11
  23. package/src/assets/icons/icon-stopwatch.js +0 -11
  24. package/src/assets/icons/index.js +0 -7
  25. package/src/components/TSPlotCanvas.vue +0 -1868
  26. package/src/components/TSScrubber.vue +0 -420
  27. package/src/components/TSViewer.vue +0 -595
  28. package/src/components/TSViewerCanvas.vue +0 -793
  29. package/src/components/TSViewerToolbar.vue +0 -356
  30. package/src/components/index.js +0 -13
  31. package/src/main.js +0 -23
  32. package/src/mixins/request/index.js +0 -100
  33. package/src/mixins/ts-annotation/index.js +0 -202
  34. package/src/mixins/viewer-active-tool/index.js +0 -36
  35. package/src/store/index.js +0 -184
  36. package/src/utils/constants.js +0 -15
  37. package/vue.config.js +0 -12
@@ -1,1868 +0,0 @@
1
- <template>
2
- <div class="timeseries-plot-canvas">
3
- <canvas id="blurArea" class="canvas" ref="blurArea"
4
- :width="_cpCanvasScaler(cWidth, pixelRatio, 0)"
5
- :height="_cpCanvasScaler(pHeight, pixelRatio, 0)"
6
- :style="canvasStyle" />
7
- <slot name="axisCanvas">
8
- </slot>
9
- <slot name="annCanvas">
10
- </slot>
11
- <canvas id="plotArea" class="canvas" ref="plotArea"
12
- :width="_cpCanvasScaler(cWidth, pixelRatio, 0)"
13
- :height="_cpCanvasScaler(pHeight, pixelRatio, 0)"
14
- :style="canvasStyle">
15
- </canvas>
16
- </div>
17
- </template>
18
- <script>
19
- import {
20
- compose,
21
- head,
22
- propEq,
23
- propOr,
24
- path,
25
- findIndex,
26
- sum,
27
- map,
28
- take,
29
- sortBy,
30
- prop
31
- } from 'ramda'
32
-
33
- import protobuf from 'protobufjs';
34
-
35
- export default {
36
- name: 'TimeseriesPlotCanvas',
37
-
38
- props: {
39
- cHeight: Number,
40
- cWidth: Number,
41
- start: Number,
42
- duration: Number,
43
- constants: Object,
44
- rsPeriod: Number,
45
- ts_start: Number,
46
- ts_end: Number,
47
- globalZoomMult: Number,
48
- },
49
- watch: {
50
- rsPeriod: function() {
51
- this.invalidate();
52
- this.requestedPages.clear();
53
- },
54
- viewerMontageScheme: function () {
55
- this.sendMontageMessage(this.viewerMontageScheme)
56
- }
57
-
58
- },
59
- computed: {
60
- activeViewer: function() {
61
- return this.$store.getters.activeViewer
62
- },
63
- viewerChannels: function() {
64
- return this.$store.getters.viewerChannels
65
- },
66
- viewerMontageScheme: function() {
67
- return this.$store.getters.viewerMontageScheme
68
- },
69
- canvasStyle: function() {
70
- return {
71
- width: this.cWidth + 'px',
72
- height: this.pHeight + 'px'
73
- }
74
- },
75
- pHeight: function () {
76
- return this.cHeight - 20
77
- },
78
- timeSeriesUrl: function() {
79
- return `wss://${process.env.VUE_APP_PENNSIEVE_API_LOCATION}/streaming/discover/ts/query?session=` + this.userToken + '&package=' + encodeURIComponent(this.packageId)
80
- },
81
- packageId: function() {
82
- return propOr('', 'packageId', this.activeViewer)
83
- },
84
- userToken: function() {
85
- return this.$store.getters.userToken
86
- }
87
- },
88
- data: function () {
89
- return {
90
- proto: {
91
- 'nested': {
92
- 'Event': {
93
- 'fields': {
94
- 'source': {
95
- 'type': 'string',
96
- 'id': 1
97
- },
98
- 'pageStart': {
99
- 'type': 'uint64',
100
- 'id': 2
101
- },
102
- 'pageEnd': {
103
- 'type': 'uint64',
104
- 'id': 3
105
- },
106
- 'samplePeriod': {
107
- 'type': 'double',
108
- 'id': 4
109
- },
110
- 'pointsPerEvent': {
111
- 'type': 'uint64',
112
- 'id': 5
113
- },
114
- 'isResampled': {
115
- 'type': 'bool',
116
- 'id': 6
117
- },
118
- 'data': {
119
- 'rule': 'repeated',
120
- 'type': 'double',
121
- 'id': 7
122
- },
123
- 'times': {
124
- 'rule': 'repeated',
125
- 'type': 'uint64',
126
- 'id': 8
127
- },
128
- 'spikeGroup': {
129
- 'rule': 'repeated',
130
- 'type': 'uint32',
131
- 'id': 9
132
- }
133
- }
134
- },
135
- 'Instruction': {
136
- 'fields': {
137
- 'command': {
138
- 'type': 'string',
139
- 'id': 1
140
- },
141
- 'argument': {
142
- 'type': 'string',
143
- 'id': 2
144
- }
145
- }
146
- },
147
- 'Segment': {
148
- 'fields': {
149
- 'startTs': {
150
- 'type': 'uint64',
151
- 'id': 1
152
- },
153
- 'source': {
154
- 'type': 'string',
155
- 'id': 2
156
- },
157
- 'lastUsed': {
158
- 'type': 'uint64',
159
- 'id': 3
160
- },
161
- 'unit': {
162
- 'type': 'string',
163
- 'id': 4
164
- },
165
- 'samplePeriod': {
166
- 'type': 'double',
167
- 'id': 5
168
- },
169
- 'requestedSamplePeriod': {
170
- 'type': 'double',
171
- 'id': 6
172
- },
173
- 'pageStart': {
174
- 'type': 'uint64',
175
- 'id': 7
176
- },
177
- 'isMinMax': {
178
- 'type': 'bool',
179
- 'id': 8
180
- },
181
- 'unitM': {
182
- 'type': 'uint64',
183
- 'id': 9
184
- },
185
- 'segmentType': {
186
- 'type': 'string',
187
- 'id': 10
188
- },
189
- 'nrPoints': {
190
- 'type': 'uint64',
191
- 'id': 11
192
- },
193
- 'data': {
194
- 'rule': 'repeated',
195
- 'type': 'double',
196
- 'id': 12
197
- },
198
- 'pageEnd': {
199
- 'type': 'uint64',
200
- 'id': 13
201
- },
202
- 'channelName': {
203
- 'type': 'string',
204
- 'id': 14
205
- }
206
- }
207
- },
208
- 'IngestSegment': {
209
- 'fields': {
210
- 'channelId': {
211
- 'type': 'string',
212
- 'id': 1
213
- },
214
- 'startTime': {
215
- 'type': 'uint64',
216
- 'id': 2
217
- },
218
- 'samplePeriod': {
219
- 'type': 'double',
220
- 'id': 3
221
- },
222
- 'data': {
223
- 'rule': 'repeated',
224
- 'type': 'double',
225
- 'id': 4
226
- }
227
- }
228
- },
229
- 'TimeSeriesMessage': {
230
- 'fields': {
231
- 'segment': {
232
- 'type': 'Segment',
233
- 'id': 3
234
- },
235
- 'event': {
236
- 'rule': 'repeated',
237
- 'type': 'Event',
238
- 'id': 4
239
- },
240
- 'instruction': {
241
- 'type': 'Instruction',
242
- 'id': 5
243
- },
244
- 'ingestSegment': {
245
- 'type': 'IngestSegment',
246
- 'id': 6
247
- },
248
- 'totalResponses': {
249
- 'type': 'uint64',
250
- 'id': 7
251
- },
252
- 'responseSequenceId': {
253
- 'type': 'uint64',
254
- 'id': 8
255
- }
256
- }
257
- }
258
- }
259
- },
260
- pixelRatio: 1,
261
- requestedPages: Object,
262
- chData: [],
263
- aSyncrequests: [],
264
- aSyncPreRequests: [],
265
- pageSize: 15000000,
266
- viewData: {
267
- start: 0,
268
- duration: 0,
269
- channels: []
270
- },
271
- renderCounter: 0,
272
- lastDataRender: null,
273
- lastViewPageRequest: null,
274
- prefetchTimer: Object,
275
- prefetchDelay: 100,
276
- autoScale:0,
277
- channelsReady:false,
278
- prevStart:0,
279
- prevDuration:0,
280
- websocket: Object,
281
- initWebsocket: true,
282
- segment: null,
283
- timeSeriesMessage:null,
284
- timeSeriesError:null,
285
- channelsList:null,
286
- catchUpRequestPages:null,
287
- }
288
- },
289
-
290
- mounted: function () {
291
-
292
- this.requestedPages = new Map();
293
- this.chData = [];
294
- this.aSyncrequests = [];
295
- this.throttledGetRenderData = this.throttle(this.renderDataOnMessage, 250, {leading:false});
296
- this.throttledDataRender = this.throttle(this._renderData, 50);
297
-
298
- // Timer loop to prefetch pages --> initiated in requestData()
299
- const that = this;
300
- this.preFetchRequestFnc = function() {
301
- const nrPending = that.aSyncPreRequests.length;
302
- if (nrPending > 0) {
303
- if (that.requestedPages.size < 3) {
304
- that.requestDataFromServer([that.aSyncPreRequests[0]]);
305
- that.aSyncPreRequests.splice(0, 1);
306
- }
307
- } else {
308
- clearInterval(that.prefetchTimer);
309
- that.isPrefetching = false;
310
- }
311
- }
312
-
313
- const protobufInstance = protobuf.Root.fromJSON(this.proto)
314
- this.segment = protobufInstance.Segment;
315
- this.timeSeriesMessage = protobufInstance.TimeSeriesMessage
316
- this.timeSeriesError = protobufInstance.TimeSeriesError
317
- this.channelsList = protobufInstance.ChannelsList
318
-
319
- this.requestedPages = new Map();
320
- this.openWebsocket()
321
-
322
- },
323
-
324
- methods: {
325
-
326
- throttle: function(func, wait, options) {
327
- let context;
328
- let args;
329
- let result;
330
- let timeout = null;
331
- let previous = 0;
332
- if (!options) options = {};
333
- const later = function() {
334
- previous = options.leading === false ? 0 : Date.now();
335
- timeout = null;
336
- result = func.apply(context, args);
337
- if (!timeout) context = args = null;
338
- };
339
- return function() {
340
- const now = Date.now();
341
- if (!previous && options.leading === false) previous = now;
342
- const remaining = wait - (now - previous);
343
- context = this;
344
- args = arguments;
345
- if (remaining <= 0 || remaining > wait) {
346
- if (timeout) {
347
- clearTimeout(timeout);
348
- timeout = null;
349
- }
350
- previous = now;
351
- result = func.apply(context, args);
352
- if (!timeout) context = args = null;
353
- } else if (!timeout && options.trailing !== false) {
354
- timeout = setTimeout(later, remaining);
355
- }
356
- return result;
357
- };
358
- },
359
- viewSegmComparator: function(a, b) {
360
- if (a.startTs< b.startTs) return -1;
361
- if (a.startTs > b.startTs) return 1;
362
- return 0;
363
- },
364
- _cpCanvasScaler: function(sz, pixelRatio, offset) {
365
- return pixelRatio * (sz + offset);
366
- },
367
- getScreenPixelRatio: function() {
368
- let ctx = this.$refs.plotArea.getContext('2d');
369
- let dpr = window.devicePixelRatio || 1
370
- let bsr = ctx.webkitBackingStorePixelRatio ||
371
- ctx.mozBackingStorePixelRatio ||
372
- ctx.msBackingStorePixelRatio ||
373
- ctx.oBackingStorePixelRatio ||
374
- ctx.backingStorePixelRatio || 1;
375
-
376
- return dpr / bsr;
377
- },
378
- getChannelId: function(channel) {
379
- const isViewingMontage = this.viewerMontageScheme !== 'NOT_MONTAGED'
380
- let id = propOr('', 'id', channel)
381
- let list = []
382
- if (isViewingMontage) {
383
- list = id.split('_')
384
- id = list.length ? head(list) : id // remove channel name from id
385
- }
386
- return id
387
- },
388
- initChannels: function(channels) {
389
- const chObjects = [];
390
- if (channels.length > 0) {
391
-
392
- const channelConfig = []
393
-
394
- for (let ic=0; ic<channels.length; ic++) {
395
- const curC = channels[ic].content;
396
- const curId = this.getChannelId(curC)
397
- const curChannel = {
398
- id: curId,
399
- label: curC.name,
400
- type: curC.channelType,
401
- segments: [],
402
- start: curC.start,
403
- end: curC.end,
404
- sampleFreq: curC.rate,
405
- unit: curC.unit,
406
- gaps: [], //channels[ic].gaps
407
- virtualId: curC.virtualId
408
- };
409
-
410
- const label = curChannel.label.split(/[ _-]/, 3)
411
- const label_prefix = label[0];
412
- let label_value = ( (label.length > 1) ? parseFloat(label[1]) : 0);
413
- label_value = ( (isNaN(label_value) ? label[1] : label_value));
414
-
415
- channelConfig.push({
416
- id: curChannel.id, // id of channel
417
- type: curChannel.type, // Type of channel (CONTINUOUS, UNIT)
418
- label: curChannel.label, // Label of channel
419
- label_split: label, // array of label segments
420
- label_prefix: label_prefix, // prefix for label
421
- label_value: label_value, // value for label
422
- dataSegments: [], // vector of segments rnages (start end ...)
423
- rank: ic, // rank for determining display order
424
- visible: true, // channel visible in viewer?
425
- plotAgainst: null, // for montaging
426
- rowBaseline: null, // offset for centering
427
- rowScale: 1, // for individual zoom
428
- rowAdjust: 0, // offset for centering
429
- selected: false, // Item is selected
430
- hover: false, // User is hovering over channel
431
- unit: curC.unit, // unit of data
432
- sf: curC.rate, // sampling rate
433
- filter: {}, // filter object (type, var0, var1, notch)
434
- hideFilter: true,
435
- isEditing: false,
436
- virtualId: curChannel.virtualId
437
- });
438
-
439
- chObjects.push(curChannel);
440
- }
441
-
442
- channelConfig.sort(function(a, b) {
443
-
444
- // If split into more than 2 segments, just sort on entire string
445
- if (a.label_split.length > 2 || b.label_split.length > 2) {
446
- return (a.label > b.label) ? 1 : ((b.label > a.label) ? -1 : 0);
447
- }
448
-
449
- // If prefix is similar, sort on value
450
- if (a.label_prefix === b.label_prefix) {
451
- return (a.label_value > b.label_value) ? 1 : ((b.label_value > a.label_value) ? -1 : 0);
452
- }
453
-
454
- // Sort on prefix
455
- return (a.label_prefix > b.label_prefix) ? 1 : ((b.label_prefix > a.label_prefix) ? -1 : 0);
456
- } );
457
-
458
- // Update rank on sorted channels
459
- for (let i = 0; i < channelConfig.length; i++) {
460
- channelConfig[i].rank = i;
461
- }
462
-
463
- this.$store.dispatch('setChannels', channelConfig)
464
- this.$emit('channelsInitialized', channels)
465
-
466
- }
467
-
468
- this.computeSummary(chObjects)
469
- this.chData = chObjects;
470
- this.autoScale = channels.length;
471
- this.channelsReady = true;
472
-
473
- return Promise.resolve()
474
-
475
- },
476
- computeSummary: function(channels) {
477
-
478
- if (channels.length === 0) {
479
- this.globalGaps = null
480
- return
481
- }
482
-
483
- this.globalGaps = path(['gaps', 0], channels);
484
-
485
- },
486
- // Find segment that contains timepoint val --> search over startPage property
487
- segmIndexOf: function(segmArray, val, first, startAtIndex) {
488
-
489
- if (!startAtIndex) {
490
- startAtIndex = 0;
491
- }
492
- let index;
493
- index = this._indexOfStart(segmArray, val, startAtIndex, segmArray.length - 1, first);
494
-
495
- if (index === -1) {
496
- index = 0;
497
- } else if (index < 0) {
498
- index = -index - 2;
499
- }
500
- return index;
501
- },
502
-
503
- // Find segment offset in segment array
504
- _indexOfStart: function(segmArray, val, min, max, firstIndex) {
505
-
506
- /*
507
- Return code status:
508
- Positive number: first index of the value
509
- Negative 1 (-1): belongs before the start of the dataset
510
- Other negative value: belongs after (-value - 2)
511
- */
512
- if (max < min) {
513
- let pred;
514
- if (max >= 0) {
515
- //return -max - 2;
516
- pred = max;
517
- } else {
518
- //return max;
519
- pred = -max - 2;
520
- }
521
- if (pred === -1) {
522
- return pred;
523
- }
524
- const predVal = segmArray[pred].pageStart;
525
- while (pred >= 0 && segmArray[pred].pageStart === predVal) {
526
- pred--;
527
- }
528
- pred++;
529
- return -pred - 2;
530
- }
531
-
532
- const mid = parseInt((min + max) / 2);
533
-
534
- if (segmArray[mid].pageStart > val) {
535
- return this._indexOfStart(segmArray, val, min, mid - 1, firstIndex);
536
- } else if (segmArray[mid].pageStart < val) {
537
- return this._indexOfStart(segmArray, val, mid + 1, max, firstIndex);
538
- } else {
539
- let index = mid;
540
- if (firstIndex) {
541
- while (index >= 0 && segmArray[index].pageStart === val) {
542
- index--;
543
- }
544
- index++;
545
- } else {
546
- while (index < segmArray.length && segmArray[index].pageStart === val) {
547
- index++;
548
- }
549
- index--;
550
- }
551
- return index;
552
- }
553
- },
554
- // Initiate render of data --> check visible channels and create viewData object.
555
- generatePoints: function() {
556
-
557
- // If not initialized --> return
558
- if (!this.viewerChannels) {
559
- return
560
- }
561
-
562
- this.viewData.start = this.start;
563
- this.viewData.duration = this.duration;
564
-
565
- // Get hidden channels and view-ids
566
- const hiddenChannels = [];
567
- const showChannels = [];
568
- const curChanViewIds = [];
569
-
570
- // Get IDS from channelConfig
571
- for (let i = 0; i < this.viewerChannels.length; i++) {
572
- const curConfig = this.viewerChannels[i];
573
- const id = this.getChannelId(curConfig.id)
574
- if (!curConfig.visible) {
575
- hiddenChannels.push(id);
576
- }
577
- curChanViewIds.push(id);
578
- }
579
-
580
- // Get IDS from viewData channels
581
- const viewDataChIds = [];
582
- for (let i = 0; i< this.viewData.channels.length; i++) {
583
- const channel = this.viewData.channels[i]
584
- const channelId = this.getChannelId(channel)
585
- viewDataChIds.push(channelId);
586
- }
587
-
588
- // Iterate over all channels and populate channelConfig
589
- const remViewChannels = [];
590
- for (let iChan = 0; iChan < this.chData.length; iChan++) {
591
- const curChan = this.chData[iChan];
592
- const curChanId = this.getChannelId(curChan)
593
- // Only return visible channels
594
- if (hiddenChannels.indexOf(curChanId) >= 0) {
595
- remViewChannels.push(curChanId);
596
- } else {
597
- showChannels.push(curChan);
598
- }
599
- }
600
-
601
- // removing channels from viewData when not shown.
602
- // Iterate backwards to prevent issues of indexing
603
- remViewChannels.sort();
604
- for (let i=(remViewChannels.length-1); i>=0; i--) {
605
- this.splice('viewData.channels', remViewChannels[i], 1);
606
- viewDataChIds.splice(remViewChannels[i], 1);
607
- }
608
-
609
- // Get viewDataChannels
610
- for (let i=0; i< showChannels.length; i++) {
611
- const curShowChannel = showChannels[i]
612
- const showChannelId = this.getChannelId(curShowChannel);
613
- const idx = viewDataChIds.indexOf(showChannelId);
614
- if (idx < 0 ) {
615
- this.viewData.channels.push({
616
- id: showChannelId,
617
- mean: null,
618
- firstRenderedIndex: 0,
619
- lastRenderedIndex: 0,
620
- blocks: []
621
- });
622
-
623
- }
624
- }
625
-
626
- // Request data on visible channels
627
- // Only request when pageForward key is released
628
- // if (!this.spacePressed && this.selectedTab === 'timeseries') {
629
- this.requestData(showChannels, this.start, this.duration);
630
- // }
631
- },
632
- // Getting data from cache and queue new reqeusts from server
633
- requestData: function(showChannels, start, duration ) {
634
-
635
- // If number of requestedpages is getting to big, reset websocket connection
636
-
637
- // TODO: BRING BACK
638
- // if (this.requestedPages.size > 20 ) {
639
- // clearTimeout(this.preFetchTime);
640
- // this.aSyncPreRequests = [];
641
- // this.reRequestPages();
642
- // this.openWebsocket();
643
- // return;
644
- // }
645
-
646
-
647
- // Init asnch requests for viewport pages
648
- this.aSyncRequests = [];
649
-
650
- // If we rerender the same viewport (as data comes in) --> don't change prefetch
651
- let updatePrefetchPages = false
652
- if (this.prevStart !== start || this.prevDuration !== duration) {
653
- this.aSyncPreRequests= []
654
- updatePrefetchPages = true
655
- }
656
-
657
- this.prevStart = start;
658
- this.prevDuration = duration;
659
-
660
- // Timestamp of first value in viewport
661
- const viewDataChIds = [];
662
- for (let i=0; i<this.viewData.channels.length; i++) {
663
- const cChannel = this.viewData.channels[i]
664
- viewDataChIds.push(cChannel);
665
- }
666
-
667
- // Iterate over channels and populate aSyncRequests and/or chanViewData
668
- for (let iChan = 0; iChan < showChannels.length; iChan++) {
669
- let continuationSegment = false;
670
- const curChan = showChannels[iChan];
671
- const chDataSegments = curChan.segments;
672
- const curChanId = this.getChannelId(curChan)
673
- // Get dataView
674
- const idx = findIndex(propEq('id', curChanId), viewDataChIds)
675
- const chanViewData = this.viewData.channels[idx];
676
- chanViewData.blocks = [];
677
-
678
- // Populate chanViewData until all data in scope is added, or requested from server.
679
- // Independent of actual request, populate viewData by pageSegments.
680
- let curTime = Math.floor(start/ this.pageSize) * this.pageSize;
681
- if (curTime < 0) {
682
- curTime = 0;
683
- }
684
-
685
- let firstSegment = 0;
686
- let segmOffset = 0;
687
- if (chDataSegments.length > 0) {
688
- firstSegment = this.segmIndexOf(chDataSegments, curTime, true, 0);
689
- }
690
-
691
- // Iterate over blocks in viewport and add to chanView, or asyncRequests
692
- let endRequestTime = start + duration + this.constants['PREFETCHPAGES']*this.pageSize;
693
- if (endRequestTime > this.ts_end) {
694
- endRequestTime = this.ts_end;
695
- }
696
-
697
- let curSegm;
698
- if (chDataSegments.length > 0) {
699
- curSegm = chDataSegments[firstSegment];
700
- }
701
- while ((curTime < endRequestTime) || continuationSegment) {
702
- continuationSegment = false;
703
-
704
- // If current time == start of current page in cache then page is correct.
705
- let inRange = false;
706
- if (curSegm) {
707
- inRange = curTime >= curSegm.pageStart && curTime <= curSegm.pageEnd;
708
- }
709
- if (inRange) {
710
- /*
711
- Data is already cached
712
- */
713
- let isViewPage = curSegm.startTs < start + duration;
714
-
715
- segmOffset += 1;
716
- //curTime = curSegm.startTs + curSegm.nrPoints*curSegm.samplePeriod;
717
- curTime = curSegm.pageEnd;
718
-
719
- // Only add to viewData if segment is not a prefetch page
720
- if (isViewPage) {
721
- chanViewData.blocks.push(curSegm);
722
- }
723
-
724
- const prevPageTime = curSegm.pageStart;
725
- curSegm = chDataSegments[firstSegment + segmOffset];
726
- if (!this.isStreaming && curSegm && prevPageTime === curSegm.pageStart) {
727
- continuationSegment = true;
728
- }
729
-
730
- } else {
731
- /*
732
- Data is not present on client and needs to be requested from server
733
- */
734
-
735
- // Check if segment is already being requested
736
- if (this.requestedPages.get(curTime)) {
737
- curTime += this.pageSize;
738
- if (curSegm && curTime >= curSegm.pageEnd) {
739
- while (curSegm.pageEnd < curTime) {
740
- segmOffset += 1;
741
- curSegm = chDataSegments[firstSegment + segmOffset];
742
- if (!curSegm) {
743
- break;
744
- }
745
- }
746
- }
747
- continue;
748
- }
749
-
750
- let isViewPage = curTime < start + duration;
751
-
752
-
753
- // Check if requested range is already requested by other channel --> update
754
- let isAdded = false;
755
- if (isViewPage) {
756
-
757
- //remove from pre-request
758
- for (let iA in this.aSyncPreRequests) {
759
- if (this.aSyncPreRequests[iA].start === curTime) {
760
- this.aSyncPreRequests.splice(iA, 1);
761
- break;
762
- }
763
- }
764
-
765
-
766
- for (let iA in this.aSyncRequests) {
767
- if (this.aSyncRequests[iA].start === curTime) {
768
- this.aSyncRequests[iA].channels.push(curChan);
769
- isAdded = true;
770
- }
771
- }
772
- if (!isAdded) {
773
- this.aSyncRequests.push({
774
- channels: [curChan],
775
- start: curTime,
776
- duration: this.pageSize,
777
- isInViewport: true,
778
- pixelWidth: Math.ceil(this.rsPeriod)});
779
- }
780
-
781
-
782
- } else {
783
- if (updatePrefetchPages) {
784
- for (let iA in this.aSyncPreRequests) {
785
- if (this.aSyncPreRequests[iA].start === curTime) {
786
- this.aSyncPreRequests[iA].channels.push(curChan);
787
- isAdded = true;
788
- }
789
- }
790
- if(!isAdded) {
791
- this.aSyncPreRequests.push({
792
- channels: [curChan],
793
- start: curTime,
794
- duration: this.pageSize,
795
- isInViewport: false,
796
- pixelWidth: Math.ceil(this.rsPeriod)});
797
- }
798
- }
799
- }
800
- curTime += this.pageSize;
801
- if (curSegm && curTime >= curSegm.pageEnd) {
802
- while (curSegm.pageEnd < curTime) {
803
- segmOffset += 1;
804
- curSegm = chDataSegments[firstSegment + segmOffset];
805
- if (!curSegm) {
806
- break;
807
- }
808
- }
809
- }
810
- }
811
- }
812
-
813
- // Sort the ChannelView pages
814
- chanViewData.blocks.sort(this.viewSegmComparator);
815
- }
816
-
817
- // Directly request all pages that are in the viewport
818
- if (this.aSyncRequests.length > 0) {
819
-
820
- // order requests to make sure request with start in viewport is first.
821
- let firstRequest = 0;
822
- for (let i=0; i<this.aSyncRequests.length; i++) {
823
- if (this.aSyncRequests[i].start >= this.start && this.aSyncRequests[i].start < (this.start +this.duration)) {
824
- firstRequest = i;
825
- }
826
- }
827
-
828
- this.requestDataFromServer(this.aSyncRequests, firstRequest);
829
- this.lastViewPageRequest = Date.now();
830
- }
831
-
832
- // Requests Pre-fetch pages throttled, after viewport pages are available
833
- if (this.aSyncPreRequests.length > 0 ) {
834
- if (!this.isPrefetching) {
835
- this.prefetchTimer = setInterval(this.preFetchRequestFnc, 150);
836
- this.isPrefetching = true;
837
- }
838
- }
839
- },
840
- // Request pages from the Server
841
- requestDataFromServer: function(requests, firstRequest = 0) {
842
- // Do the actual Websocket requests.
843
- if (requests.length > 0) {
844
-
845
- const datasetEndTime = this.ts_end;
846
- // check for last block
847
- for(let i = 0; i < requests.length; i++) {
848
- let curRequest;
849
- if (i === 0 ) {
850
- curRequest = requests[firstRequest];
851
- } else if (i === firstRequest) {
852
- curRequest = requests[0];
853
- } else {
854
- curRequest = requests[i];
855
- }
856
-
857
- // check for last block
858
- let requestEndTime = curRequest.start + curRequest.duration;
859
- if (requestEndTime > datasetEndTime) {
860
- requestEndTime = datasetEndTime;
861
- }
862
-
863
- const ws = this._websocket;
864
- if (ws && ws.readyState === 1 && curRequest.pixelWidth>0) {
865
-
866
- const virtualChannels = curRequest.channels.map(channel => {
867
- return { id: channel.id, name: channel.label, }
868
- })
869
-
870
- const req = {
871
- session: this.userToken,
872
- minMax: true,
873
- startTime: curRequest.start,
874
- endTime: requestEndTime,
875
- packageId: this.packageId,
876
- pixelWidth: curRequest.pixelWidth,
877
- virtualChannels
878
- };
879
- const reqJson = JSON.stringify(req);
880
- ws.send(reqJson);
881
-
882
- // ChannelCounter will capture number of expected packages for each channel
883
- // This number is set when first package arrives.
884
- const nrChannels = curRequest.channels.length;
885
- const channelCounter = new Map();
886
- for (let j=0; j < nrChannels; j++) {
887
- channelCounter.set(curRequest.channels[j].id, NaN);
888
- }
889
-
890
- this.requestedPages.set(curRequest.start,
891
- { count: nrChannels,
892
- counter: channelCounter,
893
- subPageCount: NaN,
894
- ts: (Date.now() + 550),
895
- inViewport: curRequest.isInViewport} );
896
-
897
- } else {
898
- console.log('Websocket Closed')
899
- return false;
900
- }
901
- }
902
- this.aSyncrequests = [];
903
- }
904
- return true;
905
- },
906
-
907
- invalidate: function() {
908
- console.log('Invalidating')
909
-
910
- clearTimeout(this.preFetchTime);
911
- this.aSyncPreRequests = [];
912
- this.globalGaps = [];
913
- this.requestedPages.clear()
914
- for (let i=0; i<this.chData.length; i++) {
915
- this.chData[i].segments = [];
916
- }
917
- for (let i=0; i<this.viewData.channels.length; i++) {
918
- this.viewData.channels[i].blocks = [];
919
- }
920
- this.renderAll()
921
- },
922
- // _________________
923
- // RENDER METHODS
924
-
925
- renderAll: function() {
926
- this.generatePoints();
927
- this._renderData();
928
- },
929
- // computes the channelConfig properties based on current settings of viewer.
930
- _computeChannelViews: function() {
931
-
932
- // find channel order based on rank
933
- const mapped = this.viewerChannels.map(function(el, i) { return { index: i, value: el.rank }; })
934
- mapped.sort(function(a, b) {return +(a.value > b.value) || +(a.value === b.value) - 1;});
935
- const rankedIds = mapped.map(function(el) {return el.index;});
936
- const interChannelDist = this.pHeight / this.nrVisibleChannels;
937
-
938
- // update properties
939
- // TODO: Bring back
940
- let curIdx = 0;
941
- let updatedBaseline = null
942
- for (let i = 0; i < rankedIds.length; i++) {
943
- const curRow = this.viewerChannels[rankedIds[i]];
944
- const oldBaseline = curRow.rowBaseline
945
- if (curRow.visible) {
946
- updatedBaseline = (0.5*interChannelDist) + curIdx * interChannelDist;
947
- // curRow.rowBaseline = (0.5*interChannelDist) + curIdx * interChannelDist;
948
- curIdx++;
949
- }
950
- if (oldBaseline != updatedBaseline) {
951
- this.$store.dispatch('updateViewChannel', {
952
- channelId: curRow.id,
953
- data: {
954
- rowBaseline: updatedBaseline
955
- }
956
- })
957
-
958
-
959
- }
960
-
961
- }
962
- },
963
- // Compute canvas coordinates from datarray
964
- getPointCoords: function(channelInfo, channelData, isRedraw) {
965
- // Iterate over channelData on single channel ---> channelData are continuous in time
966
- let segmLength = channelData.blocks.length;
967
-
968
- // find mean of rendered data
969
- switch (channelInfo.type) {
970
- case 'UNIT':
971
- if (!isRedraw) {
972
- for (let iSegm = 0; iSegm < segmLength; iSegm++) {
973
-
974
- const curSeg = channelData.blocks[iSegm];
975
-
976
- // Iterate over points in segment and set cx, cy, and cy2
977
- const curCData = curSeg.cData;
978
- const curData = curSeg.parsedData;
979
- const xOffset = this.constants['XOFFSET'];
980
-
981
- // create local variable for speed
982
- const length = curSeg.parsedData[0].length;
983
- const cXArray = curCData[0];
984
- const cYArray = curCData[1];
985
- const cY2Array = curCData[2];
986
- const XArray = curData[0];
987
-
988
- // RowBaseline is offset of channel in viewPort
989
- const rowBaseLine = channelInfo.rowBaseline | 0;
990
-
991
- // satrtTime
992
- const startT = this.start;
993
-
994
- // Pixel Period
995
- const rsP = this.rsPeriod;
996
-
997
- const spikeHeigth = (this.cHeight / (2 * (this.nrVisibleChannels + 1))) |0;
998
-
999
-
1000
- for(let iPoint = 0; iPoint < length; iPoint++) {
1001
-
1002
- cXArray[iPoint] = (((xOffset + (XArray[iPoint] - startT ) / (rsP))));
1003
- cYArray[iPoint] = rowBaseLine - spikeHeigth;
1004
- cY2Array[iPoint] = rowBaseLine + spikeHeigth;
1005
- }
1006
- }
1007
- }
1008
- break;
1009
-
1010
- case 'CONTINUOUS':
1011
- // eslint-disable-next-line
1012
- let totalPointsInMean = 0
1013
-
1014
- if (!isRedraw) {
1015
- channelData.mean = 0;
1016
- channelData.median = 0;
1017
- for (let iSegm = 0; iSegm < segmLength; iSegm++) {
1018
- const curBlock = channelData.blocks[iSegm];
1019
- if (curBlock.nrValidPoints > 0) {
1020
- channelData.mean = ( curBlock.sumElem +
1021
- (totalPointsInMean * channelData.mean)) / (totalPointsInMean + curBlock.nrValidPoints);
1022
- channelData.median = ( curBlock.median +
1023
- (totalPointsInMean * channelData.median)) / (totalPointsInMean + curBlock.nrValidPoints);
1024
- totalPointsInMean += curBlock.nrValidPoints;
1025
- }
1026
- }
1027
- }
1028
-
1029
- for (var iSegm = 0; iSegm < segmLength; iSegm++) {
1030
-
1031
- const curSeg = channelData.blocks[iSegm];
1032
-
1033
- // Find startIndex viewPort
1034
- let startIndex = Math.floor((this.start - curSeg.startTs ) / curSeg.samplePeriod);
1035
- curSeg.renderStartIndex = ((startIndex > 0) ? startIndex : 0)
1036
-
1037
- let endIndex = Math.floor( ((this.start + this.duration) - (curSeg.startTs + (curSeg.nrPoints*curSeg.samplePeriod))/curSeg.samplePeriod ));
1038
- curSeg.renderEndIndex = endIndex >= 0 ? (curSeg.nrPoints-1) : (curSeg.nrPoints + endIndex);
1039
-
1040
- // console.log('start: ' + this.start + " duration: " + this.duration)
1041
- // console.log('render-start: ' + curSeg.renderStartIndex + " render-end: " + curSeg.renderEndIndex)
1042
-
1043
- // Iterate over points in segment and set cx, cy, and cy2
1044
- const curCData = curSeg.cData;
1045
- const curData = curSeg.parsedData;
1046
- const curScale = this.globalZoomMult * channelInfo.rowScale;
1047
- const xOffset = this.constants['XOFFSET'];
1048
-
1049
- // create local variable for speed
1050
- const length = curSeg.parsedData[0].length;
1051
- const cXArray = curCData[0];
1052
- const cYArray = curCData[1];
1053
- const cY2Array = curCData[2];
1054
- const XArray = curData[0];
1055
- const YArray = curData[1];
1056
- const Y2Array = curData[2];
1057
-
1058
- // RowBaseline is offset of channel in viewPort
1059
- const rowBaseLine = channelInfo.rowBaseline;
1060
-
1061
- // chDatMean is mean value of channel in viewport
1062
- let chDatCenterer = 0;
1063
- if (this.constants['USEMEDIAN']) {
1064
- chDatCenterer = channelData.median;
1065
- } else {
1066
- chDatCenterer = channelData.mean;
1067
- }
1068
-
1069
- const rsp = this.rsPeriod;
1070
- const startT = this.start;
1071
- for(let iPoint = 0; iPoint < length; iPoint++) {
1072
-
1073
- cXArray[iPoint] = (((xOffset + (XArray[iPoint] - startT ) / rsp)));
1074
- // console.log('xOffset: ' + xOffset + " XArray[ipoint]: " + XArray[iPoint] + " startT: " + startT )
1075
- // console.log('cXArray[ipoint]: ' + cXArray[iPoint])
1076
- cYArray[iPoint] = (((rowBaseLine + (YArray[iPoint] - chDatCenterer) * curScale)));
1077
-
1078
- if (curSeg.isMinMax) {
1079
-
1080
- // If min and max are the same --> force 1 pixel line.
1081
- if (YArray[iPoint] === Y2Array[iPoint]) {
1082
- cY2Array[iPoint] = cYArray[iPoint] + 1;
1083
- }else {
1084
- cY2Array[iPoint] = (((rowBaseLine + (Y2Array[iPoint] - chDatCenterer) * curScale)));
1085
- }
1086
- }
1087
- }
1088
- }
1089
- break;
1090
- }
1091
- },
1092
- _renderData: function(isRedraw=false) {
1093
- const ba = this.$refs.blurArea;
1094
- const ctxb = ba.getContext('2d');
1095
- ctxb.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
1096
- ctxb.fillStyle = 'rgb(220,220,220)'
1097
-
1098
-
1099
- const pa = this.$refs.plotArea;
1100
- const ctx = pa.getContext('2d');
1101
- ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
1102
-
1103
- // clear canvas
1104
- ctx.clearRect(0, 0, this.cWidth, this.cHeight);
1105
- ctxb.clearRect(0, 0, this.cWidth, this.cHeight);
1106
-
1107
- // update number of visible channels
1108
- const nrVisCh = this.viewerChannels.reduce((accumulator, currentValue) => {
1109
- if(currentValue.visible) {
1110
- return accumulator + 1
1111
- }
1112
- return accumulator
1113
- }, 0)
1114
-
1115
- this.nrVisibleChannels = nrVisCh;
1116
-
1117
- // Update label Div
1118
- // this.$.labelTemplate.render();
1119
-
1120
- // Compute the channel views for returned data
1121
- this._computeChannelViews();
1122
-
1123
- ctx.save();
1124
- // let allTimes = 0;
1125
- for (let ch in this.viewData.channels ) {
1126
- // eslint-disable-next-line
1127
- if (this.viewData.channels.hasOwnProperty(ch)) {
1128
- // const chTimeStart = new Date().getTime();
1129
-
1130
- const curChannelData = this.viewData.channels[ch];
1131
-
1132
- // Get channelview for current channel
1133
- let curChannelView = null;
1134
- curChannelView = this.viewerChannels.find((elem) => {
1135
- return elem.id === curChannelData.id
1136
- })
1137
-
1138
- if(!curChannelView || !curChannelView.visible) {
1139
- continue;
1140
- }
1141
-
1142
- // Render segment placeholders
1143
- const startT = this.start;
1144
- const rsP = this.rsPeriod;
1145
- const xOffset = this.constants['XOFFSET'];
1146
- const blurHeight = Math.min( Math.round((this.cHeight/this.viewData.channels.length)- 2), 10);
1147
- for (let i = 0; i < curChannelView.dataSegments.length; i+=2) {
1148
- if (curChannelView.dataSegments[i + 1] < startT) {
1149
- continue
1150
- }
1151
- if (curChannelView.dataSegments[i] > (startT + this.duration)) {
1152
- break;
1153
- }
1154
- let xPos1 = Math.floor((((xOffset + (curChannelView.dataSegments[i] - startT ) / (rsP)))));
1155
- let xPos2 = Math.floor((((xOffset + (curChannelView.dataSegments[i + 1] - startT ) / (rsP)))));
1156
- let yPos = Math.floor(curChannelView.rowBaseline - blurHeight/2);
1157
- ctxb.fillRect(xPos1, yPos, xPos2-xPos1, blurHeight);
1158
- }
1159
-
1160
- // Get canvas points for current channel
1161
- this.getPointCoords(curChannelView, curChannelData, isRedraw);
1162
-
1163
- // check Channel-Type
1164
- const nrBlocks = curChannelData.blocks.length;
1165
- let channelType = 'Continuous';
1166
- if (nrBlocks) {
1167
- channelType = curChannelData.blocks[0].type;
1168
- }
1169
-
1170
- // draw channel segments.
1171
- if(curChannelView.hover) {
1172
- if (curChannelView.selected) {
1173
- ctx.strokeStyle = 'rgba(39,96,255,0.70)';
1174
- ctx.fillStyle = 'rgba(39,96,255,0.70)';
1175
- } else {
1176
- ctx.strokeStyle = 'rgba(39,96,255,0.60)';
1177
- ctx.fillStyle = 'rgba(39,96,255,0.60)';
1178
- }
1179
-
1180
- } else if(curChannelView.selected) {
1181
- ctx.strokeStyle = 'rgb(39,96,255)';
1182
- ctx.fillStyle = 'rgb(39,96,255)';
1183
- } else if (channelType === 'Neural') {
1184
- ctx.strokeStyle = 'rgb(120,120,120)';
1185
- } else {
1186
- ctx.strokeStyle = 'black';
1187
- ctx.fillStyle = 'black';
1188
- }
1189
-
1190
- let lastBlockEnd = null;
1191
- let doPolFill = true;
1192
- let realSamplePeriod = 1000000 * (1/curChannelView.sf);
1193
-
1194
- for (let block = 0; block < nrBlocks; block++) {
1195
-
1196
- const curBlock = curChannelData.blocks[block];
1197
-
1198
- if (curBlock.nrPoints === 0) {
1199
- continue;
1200
- }
1201
-
1202
- const curData = curBlock.cData;
1203
- const curDataLength = curBlock.nrPoints;
1204
- const xVec = curData[0];
1205
- const yVec = curData[1];
1206
- const y2Vec = curData[2];
1207
-
1208
- let startIndex = curBlock.renderStartIndex;
1209
- let endIndex = curBlock.renderEndIndex
1210
-
1211
- ctxb.clearRect(Math.floor(xVec[startIndex]), Math.floor(curChannelView.rowBaseline - blurHeight/2), Math.ceil(xVec[endIndex]-xVec[startIndex] + 2), blurHeight+1);
1212
-
1213
- // Check if minMax and render data
1214
- switch (curBlock.type) {
1215
- case 'Continuous':
1216
- case 'realtime':
1217
-
1218
- if (curBlock.isMinMax) {
1219
- /*
1220
- Data is served as min/max per timespan. Based on render settings
1221
- we render polygon through points, or render vertical lines.
1222
- Default is rendering polygon for accuracy.
1223
- */
1224
-
1225
- if ((curBlock.samplePeriod / realSamplePeriod) < 3) {
1226
- doPolFill = false;
1227
- }
1228
-
1229
- if (doPolFill) {
1230
-
1231
- ctx.beginPath();
1232
-
1233
- // set cursor to starting point of polygon
1234
- if (block > 0 ) {//&& !this.isStreaming){
1235
- if(xVec[startIndex] < (lastBlockEnd.x + 3)) {
1236
- ctx.moveTo(lastBlockEnd.x, lastBlockEnd.y);
1237
- } else {
1238
- ctx.moveTo(xVec[startIndex], yVec[startIndex]);
1239
- }
1240
-
1241
- } else{
1242
- ctx.moveTo(xVec[startIndex], yVec[startIndex]);
1243
- }
1244
-
1245
- // Draw lines in segment
1246
- for (let i = startIndex; i < (endIndex+1); i++) {
1247
- ctx.lineTo(xVec[i], yVec[i]);
1248
- }
1249
- for (let i2 = (endIndex-1); i2 >= startIndex; i2--) {
1250
- ctx.lineTo(xVec[i2], y2Vec[i2]);
1251
- }
1252
-
1253
- // Draw line to end of last segment
1254
- if (block > 0 ) {//&& !this.isStreaming){
1255
- if(xVec[startIndex] < (lastBlockEnd.x + 3)) {
1256
- ctx.lineTo(lastBlockEnd.x, lastBlockEnd.y2);
1257
- }
1258
- }
1259
-
1260
- ctx.closePath();
1261
- ctx.fill();
1262
- } else {
1263
-
1264
- ctx.beginPath();
1265
- for (let i = startIndex; i < (endIndex+1); i++) {
1266
-
1267
- ctx.lineTo(xVec[i], yVec[i]);
1268
- ctx.lineTo(xVec[i], y2Vec[i]);
1269
- ctx.moveTo(xVec[i], yVec[i]);
1270
- }
1271
- ctx.stroke();
1272
- }
1273
-
1274
- } else {
1275
- if(block===0) {
1276
- ctx.beginPath();
1277
- ctx.moveTo(xVec[startIndex], yVec[startIndex]);
1278
- } else if(xVec[startIndex] > (lastBlockEnd.x + 2)) {
1279
- ctx.moveTo(xVec[startIndex], yVec[startIndex]);
1280
- }
1281
- for (let i = startIndex; i < (endIndex+1); i++) {
1282
- ctx.lineTo(xVec[i], yVec[i]);
1283
- }
1284
- if (block===(nrBlocks-1)) {
1285
- ctx.stroke();
1286
- }
1287
-
1288
- }
1289
- break;
1290
- case 'Neural':
1291
-
1292
- /* is neural event data
1293
- render timestamps as vertical lines. We are currently assuming
1294
- single cluster of event --> single color. V2 should include
1295
- coloring based on cluster number.
1296
- */
1297
-
1298
- // Assume that the data is sorted by cluster
1299
- ctx.beginPath();
1300
- for (let i = 0; i < curDataLength; i++) {
1301
- ctx.moveTo(xVec[i], yVec[i] );
1302
- ctx.lineTo(xVec[i], y2Vec[i] );
1303
- }
1304
- ctx.stroke();
1305
-
1306
-
1307
- }
1308
-
1309
- lastBlockEnd = {
1310
- x: xVec[curDataLength -1],
1311
- y: yVec[curDataLength -1],
1312
- y2: y2Vec[curDataLength -1]
1313
- }
1314
-
1315
- }
1316
-
1317
- // const chTimeEnd = new Date().getTime();
1318
- // allTimes = allTimes +(chTimeEnd-chTimeStart);
1319
- }
1320
- }
1321
- ctx.restore();
1322
- // console.log('render time: ' + allTimes)
1323
- },
1324
-
1325
- // Function that is called with the throttledDataRenderer
1326
- renderDataOnMessage: function() {
1327
- this.generatePoints();
1328
- if (this.autoScale === 0) {
1329
- this.autoScale--; // Set to -1 (no autoscale)
1330
- this.autoScaleViewData();
1331
- } else {
1332
- this._renderData();
1333
- }
1334
-
1335
- },
1336
- // Method sets the global zoom multiplier based on the viewport data buffer.
1337
- // This is called once when first page is available
1338
- autoScaleViewData: function() {
1339
- console.log('autoscale')
1340
- let sumMedian = 0;
1341
- let nrSeg = 0;
1342
- let allChannels = this.viewData.channels;
1343
- const allSums = []
1344
- for (let i=0; i<allChannels.length; i++) {
1345
-
1346
-
1347
- let curBlocks = allChannels[i].blocks;
1348
- for (let j=0; j<curBlocks.length; j++) {
1349
- if(curBlocks[j].type !== 'Continuous') {
1350
- continue;
1351
- }
1352
-
1353
- sumMedian += this.standardDeviation(curBlocks[j].parsedData[1]);
1354
- nrSeg++;
1355
- allSums.push({channel: i, sumMedian})
1356
- }
1357
- }
1358
- const percentage = allSums.length * .80;
1359
- const median = this.calcSumMedian(percentage, allSums);
1360
- const avgStd = median / nrSeg;
1361
- if (!isNaN(avgStd)) {
1362
- this.$emit('setGlobalZoom', (this.cHeight / allSums.length) / (2 * avgStd))
1363
- }
1364
- },
1365
- /**
1366
- * Calculate median sum for list of channels
1367
- * @param {Number} percentage
1368
- * @param {Array} list
1369
- * @returns {Number}
1370
- */
1371
- calcSumMedian: (percentage, list) => compose(
1372
- sum,
1373
- map(prop('sumMedian')),
1374
- take(percentage),
1375
- sortBy(prop('sumMedian'))
1376
- )(list),
1377
-
1378
- // Helper function for autoscale
1379
- standardDeviation: function(values) {
1380
- const avg = this.average(values);
1381
-
1382
- const squareDiffs = values.map(function(value) {
1383
- const diff = value - avg;
1384
- const sqrDiff = diff * diff;
1385
- return sqrDiff;
1386
- });
1387
-
1388
- const avgSquareDiff = this.average(squareDiffs);
1389
-
1390
- const stdDev = Math.sqrt(avgSquareDiff);
1391
- return stdDev;
1392
- },
1393
- // Helper function for autoscale
1394
- average: function(data) {
1395
- const sum = data.reduce(function(sum, value) {
1396
- return sum + value;
1397
- }, 0);
1398
-
1399
- const avg = sum / data.length;
1400
- return avg;
1401
- },
1402
- // Get all pages that are partially returned and re-request
1403
- reRequestPages: function() {
1404
-
1405
- const requestPages = [];
1406
- this.requestedPages.forEach(function(value, key) {
1407
- // Only rerequest pages where we already have partial return
1408
- if (!isNaN(value.subPageCount)) {
1409
-
1410
- // only include channels with partial return
1411
- const channels = [];
1412
- value.counter.forEach(function(count, chId) {
1413
- if (!isNaN(count) && count > 0) {
1414
- channels.push(chId)
1415
- }
1416
- })
1417
-
1418
- if (channels.length > 0) {
1419
- requestPages.push({
1420
- channels: channels,
1421
- start: key,
1422
- duration: this.pageSize,
1423
- isInViewport: true,
1424
- pixelWidth: this.rsPeriod
1425
- });
1426
- }
1427
- }
1428
- }, this);
1429
-
1430
- // clear requestedPages
1431
- this.requestedPages.clear();
1432
-
1433
- this.catchUpRequestPages = requestPages;
1434
- },
1435
-
1436
-
1437
- // _________________
1438
- // WEBSOCKET METHODS
1439
- openWebsocket: function() {
1440
- //if the websocket is opening or already open, don't open a new one
1441
- if (this._websocket && (this._websocket.readyState === 0 || this._websocket.readyState === 1) ) {
1442
- return;
1443
- }
1444
-
1445
- let url = this.timeSeriesUrl
1446
- this._websocket = new WebSocket(url);
1447
- this._websocket.onopen = this._onWebsocketOpen.bind(this);
1448
- this._websocket.onclose = this._onWebsocketClose.bind(this);
1449
- this._websocket.onmessage = this._onWebsocketMessage.bind(this);
1450
-
1451
- },
1452
- sendMontageMessage: function (value) {
1453
- if (this._websocket && this._websocket.readyState === 1) {
1454
- const payload = { montage: value, packageId: this.packageId }
1455
- this._websocket.send(JSON.stringify(payload))
1456
- } else {
1457
- this.async(() => {this.sendMontageMessage(value)}, 500)
1458
- }
1459
- },
1460
- _onWebsocketClose: function() {
1461
- console.log('Websocket closed function')
1462
- clearTimeout(this.preFetchTime);
1463
- this.aSyncPreRequests = [];
1464
- this.openWebsocket();
1465
- },
1466
- _onWebsocketFinalClose: function() {
1467
- console.log('Websocket closed forever');
1468
- },
1469
- _onWebsocketOpen: function() {
1470
- console.log('Websocket is opened')
1471
- if (this.initWebsocket) {
1472
- let chIds = [];
1473
- for (let i=0; i<this.viewerChannels.length; i++) {
1474
- chIds.push(this.viewerChannels[i].id)
1475
- }
1476
- let message = {'channelFiltersToClear':chIds};
1477
- // clear filters
1478
- this.sendFilterMessage(message);
1479
- // clear montage
1480
- const payload = { montage: 'NOT_MONTAGED', packageId: this.packageId }
1481
- this._websocket.send(JSON.stringify(payload))
1482
- this.initWebsocket = false;
1483
- }
1484
-
1485
- if (this.catchUpRequestPages && this.catchUpRequestPages.length > 0) {
1486
- this.requestDataFromServer(this.catchUpRequestPages);
1487
- }
1488
- this.catchUpRequestPages = [];
1489
-
1490
- if(this.isStreaming) {
1491
- this.initStream();
1492
- }
1493
-
1494
- this.generatePoints();
1495
- },
1496
- _onWebsocketMessage: function(msg) {
1497
- // process json messages
1498
- if (typeof msg.data === 'string') {
1499
- let data = {}
1500
-
1501
- try {
1502
- data = JSON.parse(msg.data)
1503
- } catch (e) {
1504
- this.$store.dispatch('setViewerErrors', { error: 'JSON Parse Error' })
1505
- }
1506
-
1507
- if (data.channelDetails) {
1508
- // const baseChannels = this.activeViewer.channels
1509
- const virtualChannels = data.channelDetails.map(({ id, name, unit, channelType, end, start, rate }) => {
1510
- // const baseChannel = baseChannels.find(ch => (ch.content.id === id))
1511
- const content = {
1512
- id,
1513
- name,
1514
- channelType: channelType,
1515
- label: name,
1516
- unit: unit,
1517
- rate: rate,
1518
- start: start,
1519
- end: end,
1520
- virtualId: `${id}_${name}`
1521
- }
1522
- // ensure that channel ids are unique for montages
1523
- if (this.viewerMontageScheme !== 'NOT_MONTAGED') {
1524
- content.id = `${id}_${name}`
1525
- }
1526
- return { content, properties: [] }
1527
- })
1528
-
1529
- this.initChannels(virtualChannels)
1530
- .then(() => {
1531
- this.invalidate()
1532
- this.renderAll()
1533
- })
1534
- } else if (data.error) {
1535
- this.$store.dispatch('setViewerErrors', data)
1536
- }
1537
-
1538
- // short circuit
1539
- return
1540
- }
1541
-
1542
- // process protobuf messages
1543
- const myReader = new FileReader();
1544
- myReader.parent = this;
1545
- myReader.addEventListener('loadend', function(e) {
1546
- const buffer = e.srcElement ? e.srcElement.result : e.target.result; //arraybuffer object
1547
- const barray = new Uint8Array(buffer)
1548
-
1549
- const timeSeriesMessage = this.parent.timeSeriesMessage.decode(barray);
1550
- const segment = timeSeriesMessage.segment;
1551
-
1552
- // Handle Neural Data
1553
- if (timeSeriesMessage.event && timeSeriesMessage.event.length>0 && timeSeriesMessage.event[0].pageStart) {
1554
-
1555
- const tsEvent = timeSeriesMessage.event[0];
1556
- const dataPoints = [[], []];
1557
- const nrVal = tsEvent.times.length/2;
1558
-
1559
- let curI = 0;
1560
- for (let i = 0; i < nrVal; i++) {
1561
- dataPoints[0].push( tsEvent.times[curI]);
1562
- dataPoints[1].push( tsEvent.times[curI + 1]);
1563
- curI += 2;
1564
- }
1565
-
1566
- let cData = new Array(3);
1567
- let k=0;
1568
- while (k < 3) {
1569
- cData[k] = new Float32Array(dataPoints[0].length);
1570
- k++;
1571
- }
1572
-
1573
- const segm = {
1574
- chId: tsEvent.source,
1575
- lastUsed: 0,
1576
- unit: 'uV',
1577
- samplePeriod: tsEvent.samplePeriod,
1578
- pageStart: tsEvent.pageStart,
1579
- pageEnd: tsEvent.pageEnd,
1580
- startTs: tsEvent.pageStart,
1581
- isMinMax: tsEvent.isResampled,
1582
- unitM: 1,
1583
- type: 'Neural',
1584
- nrPoints: nrVal,
1585
- parsedData: dataPoints,
1586
- cData: cData
1587
- };
1588
-
1589
- this.parent.dataCallback({
1590
- pageStart: tsEvent.pageStart,
1591
- data: segm,
1592
- type: 'Neural',
1593
- nrResponses: timeSeriesMessage.totalResponses
1594
- });
1595
- }
1596
-
1597
- // Handle Regular Timeseries data package.
1598
- if (segment !== null) {
1599
- // Check if viewer is still interested in the arriving data
1600
- if (segment.requestedSamplePeriod !== Math.ceil(this.parent.rsPeriod)) {
1601
- return
1602
- }
1603
-
1604
- // create array from points of data.
1605
- let nrVal = null;
1606
- if (segment.isMinMax) {
1607
- // Create min/max vectors
1608
- nrVal = segment.data.length/2;
1609
- } else {
1610
- //its just one big list of values
1611
- nrVal = segment.data.length;
1612
- }
1613
-
1614
- const parsedData = new Array(3);
1615
- const startTs = segment.startTs
1616
-
1617
- let sumElem = 0;
1618
- let nrValidPoints = 0;
1619
- let i = 0;
1620
- while (i < 3) {
1621
- parsedData[i] = new Float64Array(nrVal);
1622
- i++;
1623
- }
1624
-
1625
- if (segment.isMinMax) {
1626
- // Create min/max vectors
1627
- let curI = 0;
1628
- for (let i = 0; i < nrVal; i++) {
1629
- let curY = -segment.data[curI];
1630
- let curY2 = -segment.data[curI + 1];
1631
- parsedData[0][i] = startTs + (i * segment.samplePeriod);
1632
- parsedData[1][i] = curY;
1633
- parsedData[2][i] = curY2;
1634
- if (!isNaN(curY)) {
1635
- nrValidPoints++;
1636
- sumElem += curY + (curY2 - curY)/2;
1637
- }
1638
- curI += 2;
1639
-
1640
- }
1641
-
1642
- } else {
1643
- //its just one big list of values
1644
- for (let i = 0; i < nrVal; i++) {
1645
- let curY = -segment.data[i];
1646
- parsedData[0][i] = startTs + (i * segment.samplePeriod);
1647
- parsedData[1][i] = curY;
1648
- if (!isNaN(curY)) {
1649
- nrValidPoints++;
1650
- sumElem += curY;
1651
- }
1652
- }
1653
- }
1654
-
1655
- let elemMedian = 0;
1656
- if (this.parent.constants['USEMEDIAN']) {
1657
- const sortedYvals = Array.prototype.slice.call(parsedData[1]).sort();
1658
- if (segment.isMinMax) {
1659
- elemMedian = sortedYvals[Math.round(sortedYvals.length/2)];
1660
- } else {
1661
- elemMedian = sortedYvals[Math.round(sortedYvals.length)];
1662
- }
1663
- }
1664
-
1665
- let cData = new Array(3);
1666
- let k=0;
1667
- while (k < 3) {
1668
- cData[k] = new Float32Array(parsedData[0].length);
1669
- k++;
1670
- }
1671
-
1672
- const segm = {
1673
- chId: segment.source,
1674
- lastUsed: segment.lastUsed,
1675
- unit:segment.unit,
1676
- samplePeriod: segment.samplePeriod,
1677
- pageStart: segment.pageStart,
1678
- pageEnd: segment.pageEnd,
1679
- startTs: startTs,
1680
- isMinMax: segment.isMinMax,
1681
- unitM: segment.unitM,
1682
- type: segment.segmentType,
1683
- nrPoints: nrVal,
1684
- cData: cData,
1685
- parsedData: parsedData,
1686
- median: elemMedian,
1687
- sumElem: sumElem,
1688
- nrValidPoints: nrValidPoints,
1689
- name: segment.channelName,
1690
- label: segment.channelName,
1691
- virtualId: `${segment.source}_${segment.channelName}`
1692
- };
1693
-
1694
- if(segm.nrPoints>0) {
1695
-
1696
- this.parent.dataCallback({
1697
- pageStart: segment.pageStart,
1698
- data: segm,
1699
- type: segment.segmentType,
1700
- nrResponses: timeSeriesMessage.totalResponses
1701
- });
1702
- } else {
1703
- // Check if already in Gap Array
1704
- this.parent.dataCallback({
1705
- pageStart: segment.pageStart,
1706
- data: segm,
1707
- nrResponses: timeSeriesMessage.totalResponses,
1708
- type: 'gap'
1709
- });
1710
-
1711
- }
1712
- }
1713
- });
1714
-
1715
- myReader.readAsArrayBuffer(msg.data);
1716
- },
1717
- sendFilterMessage: function(msg) {
1718
- if (this._websocket && this._websocket.readyState === 1) {
1719
- const parms = msg.filterParameters || [];
1720
- parms.forEach( p => {
1721
- if (!this.isTruthy(p)) {
1722
- return;
1723
- }
1724
- });
1725
- this._websocket.send(JSON.stringify(msg));
1726
- } else {
1727
- this.async(() => {this.sendFilterMessage(msg)}, 200);
1728
- }
1729
- },
1730
- isTruthy: val => val && val !== '' && !val.isNaN,
1731
- calcFilterType: function(low, high, notch) {
1732
- switch(true) {
1733
- case !this.isTruthy(low) && !this.isTruthy(high) && !notch:
1734
- return 'clear';
1735
- case this.isTruthy(low) && this.isTruthy(high):
1736
- return 'bandpass';
1737
- case this.isTruthy(low):
1738
- return 'highpass';
1739
- case this.isTruthy(high):
1740
- return 'lowpass';
1741
- default:
1742
- return 'bandstop';
1743
- }
1744
- },
1745
- // Callback method for async data fetch from server
1746
- dataCallback: function(obj) {
1747
-
1748
- // Autoscale counter is set to number > 0 for first request, and autoscale is triggered when
1749
- // counter reaches 0.
1750
- if (this.autoScale > 0) {
1751
- this.autoScale--;
1752
- }
1753
-
1754
- // Find channel for object in chData
1755
- let curChData = null;
1756
- for (let ch = 0; ch < this.chData.length; ch++) {
1757
- if (this.chData[ch].label === obj.data.label) {
1758
- curChData = this.chData[ch];
1759
- break;
1760
- }
1761
- }
1762
-
1763
- // Based on Obj type, handle placement in cache differently
1764
- switch(obj.type) {
1765
-
1766
- case 'gap':
1767
- case 'Neural': //neural and continuous data get put in the cache the same way
1768
- case 'Continuous':
1769
-
1770
- // Check if data already exists in chData (happens when zoom out, and back in before data is there)
1771
- // eslint-disable-next-line
1772
- let addData = false;
1773
- // eslint-disable-next-line
1774
- let curSegments = curChData && curChData.segments;
1775
- if (curSegments && obj.type !== 'gap') {
1776
- addData = true;
1777
- if (curSegments.length > 0) {
1778
- let fIndex = this.segmIndexOf(curSegments, obj.data.startTs, true, 0);
1779
-
1780
- // Iterate over all segments in CHData with same pageStart to see if page is already present
1781
- while( curSegments[fIndex] && curSegments[fIndex].pageStart === obj.data.pageStart) {
1782
- if (curSegments[fIndex].startTs === obj.data.startTs) {
1783
- addData = false;
1784
- break;
1785
- }
1786
- fIndex++;
1787
- }
1788
- }
1789
- }
1790
-
1791
- // Remove returned page from the list of requested Pages.
1792
- // eslint-disable-next-line
1793
- let requestedPage = this.requestedPages.get(obj.data.pageStart);
1794
- // array or page IDs (timestamp, based on viewport)
1795
- // array of channel IDs
1796
- if (requestedPage) {
1797
-
1798
- // Update time
1799
- requestedPage.ts = Date.now();
1800
-
1801
- let countForChannel = requestedPage.counter.get(obj.data.chId);
1802
- if (isNaN(countForChannel)) {
1803
- countForChannel = obj.nrResponses -1;
1804
- requestedPage.counter.set(obj.data.chId, countForChannel);
1805
- requestedPage.subPageCount = countForChannel;
1806
- } else {
1807
- countForChannel = countForChannel - 1;
1808
- requestedPage.counter.set(obj.data.chId, countForChannel);
1809
- }
1810
-
1811
- if (countForChannel <= 0) {
1812
- let isComplete = true;
1813
- for (let value2 of requestedPage.counter.values()) {
1814
- if (value2 > 0 || isNaN(value2)) {
1815
- isComplete = false;
1816
- break;
1817
- }
1818
- }
1819
- if (isComplete) {
1820
- this.requestedPages.delete(obj.data.pageStart);
1821
- // console.log('isComplete')
1822
- }
1823
-
1824
- }
1825
-
1826
- }
1827
-
1828
- // Add data to local cache
1829
- if (addData) {
1830
- curSegments.push(obj.data);
1831
-
1832
- // Resort segments in each channel afer adding data
1833
- curSegments.sort(function Comparator(a, b) {
1834
- if (a.startTs < b.startTs) return -1;
1835
- if (a.startTs > b.startTs) return 1;
1836
- return 0;
1837
- });
1838
-
1839
- // Check if returned page falls in viewport
1840
- if (obj.pageStart < (this.start + this.duration)) {
1841
- this.throttledGetRenderData();
1842
- }
1843
- }
1844
-
1845
- break;
1846
-
1847
- case 'realtime':
1848
- console.log('No Longer Supporting RealTime Data')
1849
- break;
1850
- }
1851
-
1852
- }
1853
-
1854
- }
1855
- }
1856
-
1857
- </script>
1858
-
1859
- <style lang="scss" scoped>
1860
- .canvas {
1861
- position: absolute;
1862
- top: 0;
1863
- left: 0;
1864
- margin-left: 5px;
1865
- cursor: ew-resize;
1866
- outline: none;
1867
- }
1868
- </style>