tsviewer 0.1.0

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.
@@ -0,0 +1,793 @@
1
+ <template>
2
+ <div class="timeseries-viewer-canvas">
3
+
4
+ <div id="canvasWrapper">
5
+ <timeseries-plot-canvas
6
+ ref="plotCanvas"
7
+ :c-width="cWidth"
8
+ :c-height="cHeight"
9
+ :start="start"
10
+ :ts_start="tsStart"
11
+ :ts_end="tsEnd"
12
+ :duration="duration"
13
+ :constants="constants"
14
+ :rs-period="rsPeriod"
15
+ :global-zoom-mult="globalZoomMult"
16
+ @channelsInitialized="channelsInitialized"
17
+ @setGlobalZoom="setGlobalZoom"
18
+ >
19
+ <canvas
20
+ id="axisArea"
21
+ ref="axisArea"
22
+ slot="axisCanvas"
23
+ class="canvas"
24
+ :width="_cpCanvasScaler(cWidth, pixelRatio, 0)"
25
+ :height="_cpCanvasScaler(cHeight, pixelRatio,0)"
26
+ :style="canvasStyle1"
27
+ />
28
+ <canvas
29
+ id="annArea"
30
+ ref="annArea"
31
+ slot="annCanvas"
32
+ class="canvas"
33
+ :width="_cpCanvasScaler(cWidth, pixelRatio, 0)"
34
+ :height="_cpCanvasScaler(pHeight, pixelRatio,0)"
35
+ :style="canvasStyle2"
36
+ />
37
+ </timeseries-plot-canvas>
38
+
39
+ <canvas
40
+ id="cursorArea"
41
+ ref="cursorArea"
42
+ class="canvas"
43
+ :width="_cpCanvasScaler(cWidth + 5, pixelRatio, 0)"
44
+ :height="_cpCanvasScaler(cHeight, pixelRatio,0)"
45
+ :style="canvasStyle3"
46
+ />
47
+
48
+ <!-- <timeseries-annotation-canvas-->
49
+ <!-- ref="annCanvas"-->
50
+ <!-- :c-width="cWidth"-->
51
+ <!-- :c-height="cHeight"-->
52
+ <!-- :constants="constants"-->
53
+ <!-- :annotations-canvas="this.$refs.annArea"-->
54
+ <!-- :pixel-ratio="pixelRatio"-->
55
+ <!-- :rs-period="rsPeriod"-->
56
+ <!-- :start="start"-->
57
+ <!-- :duration="duration"-->
58
+ <!-- :ts-end="tsEnd"-->
59
+ <!-- :pointer-mode="pointerMode"-->
60
+ <!-- @annLayersInitialized="onAnnLayersInitialized"-->
61
+ <!-- @annotationsReceived="onAnnotationsReceived"-->
62
+ <!-- @closeAnnotationLayerWindow="onCloseAnnotationLayerWindow"-->
63
+ <!-- @updateAnnotation="onUpdateAnnotation"-->
64
+ <!-- />-->
65
+
66
+ <canvas
67
+ id="iArea"
68
+ ref="iArea"
69
+ class="canvas"
70
+ :width="_cpCanvasScaler(cWidth, pixelRatio, 0)"
71
+ :height="_cpCanvasScaler(cHeight, pixelRatio,0)"
72
+ :style="canvasStyle1"
73
+ tabindex="-1"
74
+ @wheel="_onMouseWheel"
75
+ @mousemove="_onMouseMove"
76
+ @mousedown="_onMouseDown"
77
+ @mouseup="_onMouseUp"
78
+ @mouseout="_onMouseOut"
79
+ @mouseenter="_onMouseEnter"
80
+ />
81
+ </div>
82
+ </div>
83
+ </template>
84
+
85
+ <script>
86
+ /* eslint-disable no-case-declarations */
87
+ import { find, propEq } from 'ramda'
88
+ import ViewerActiveTool from '@/mixins/viewer-active-tool'
89
+ import Request from '@/mixins/request'
90
+ import TimeseriesPlotCanvas from '@/components/TSPlotCanvas.vue'
91
+ export default {
92
+ name: 'TimeseriesViewerCanvas',
93
+ components:{
94
+ TimeseriesPlotCanvas
95
+ // 'timeseries-annotation-canvas': () => import('@/components/viewers/TSViewer/TSAnnotationCanvas.vue')
96
+ },
97
+ mixins: [
98
+ Request,
99
+ ViewerActiveTool
100
+ ],
101
+ props: {
102
+ windowHeight: Number,
103
+ windowWidth: Number,
104
+ duration: Number,
105
+ start: Number,
106
+ cHeight: Number,
107
+ cWidth: Number,
108
+ globalZoomMult: Number,
109
+ constants: Object,
110
+ tsStart: Number,
111
+ tsEnd: Number,
112
+ cursorLoc: Number,
113
+ },
114
+ computed: {
115
+ viewerChannels: function() {
116
+ return this.$store.getters.viewerChannels
117
+ },
118
+ viewerAnnotations: function() {
119
+ return this.$store.getters.viewerAnnotations
120
+ },
121
+ viewerActiveTool: function() {
122
+ return this.$store.getters.viewerActiveTool
123
+ },
124
+ pHeight: function() {
125
+ return this.cHeight -20;
126
+ },
127
+ cursorWidth: function() {
128
+ return this.cWidth + this.constants['CURSOROFFSET']
129
+ },
130
+ blurCanvas: function() {
131
+ return this.$refs.blurCanvas
132
+ },
133
+ canvasStyle1: function() {
134
+ return {
135
+ width: this.cWidth + 'px',
136
+ height: this.cHeight + 'px'
137
+ }
138
+ },
139
+ canvasStyle2: function() {
140
+ return {
141
+ width: this.cWidth + 'px',
142
+ height: this.pHeight + 'px'
143
+ }
144
+ },
145
+ canvasStyle3: function() {
146
+ return {
147
+ width: this.cursorWidth + 'px',
148
+ height: this.cHeight + 'px'
149
+ }
150
+ },
151
+ },
152
+ watch: {
153
+ cHeight: function () {
154
+ this.resize()
155
+ },
156
+ cWidth: function () {
157
+ this.resize()
158
+ },
159
+ start: function() {
160
+ this.renderAll()
161
+ },
162
+ duration: function() {
163
+ this.$refs.plotCanvas.invalidate()
164
+ this.updateRsPeriod(this.cWidth, this.duration)
165
+ this.renderAll()
166
+ },
167
+ globalZoomMult: function() {
168
+ this.$nextTick(() => {
169
+ this.$refs.plotCanvas.throttledDataRender()
170
+ })
171
+ },
172
+ viewerChannels: function () {
173
+ this.$nextTick(() => {
174
+ this.renderAll()
175
+ })
176
+ },
177
+ pointerMode: function () {
178
+ this.$refs.iArea.removeAttribute('col_resize');
179
+ this.$refs.iArea.removeAttribute('active');
180
+ this.$refs.iArea.removeAttribute('point')
181
+ switch (this.pointerMode) {
182
+ case 'cursor_hover':
183
+ console.log('hover')
184
+ this.$refs.iArea.removeAttribute('point')
185
+ this.$refs.iArea.setAttribute('cursor_hover', true)
186
+ break
187
+ case 'annResize-left':
188
+ console.log('left')
189
+ this.$refs.iArea.setAttribute('col_resize', true)
190
+ break
191
+ case 'annResize-right':
192
+ console.log('right')
193
+ this.$refs.iArea.setAttribute('col_resize', true)
194
+ break
195
+ case 'annSelect':
196
+ this.$refs.iArea.setAttribute('active', true)
197
+ break
198
+ case 'pan':
199
+ break;
200
+ case 'pointer':
201
+ this.$refs.iArea.setAttribute('point', true);
202
+ break;
203
+ case 'annotate':
204
+ this.$refs.iArea.setAttribute('point', true);
205
+ break;
206
+ default:
207
+ console.log('other')
208
+ this.$refs.iArea.removeAttribute('point');
209
+ this.$refs.iArea.removeAttribute('cursor_hover');
210
+ break
211
+ }
212
+ this.$nextTick(() => {
213
+ this.renderAnnotationCanvas()
214
+ })
215
+ }
216
+ },
217
+ data: function () {
218
+ return {
219
+ summary: {},
220
+ rsPeriod:0,
221
+ pageSize:0,
222
+ lastrRsUpdate:0,
223
+ pixelRatio: 1, // Pixel Ratio of screen (1 for regular, >1 for high-res screens)
224
+ mouseDown: false,
225
+ resizeClicked: false,
226
+ pointerMode: 'pan',
227
+ trackDirection: false,
228
+ startDragCoord: {x: 0, y: 0},
229
+ defaultLabels: ['Event', 'Artifact', 'Seizure', 'Mark', 'Stim On', 'Stim Off', 'Start', 'Stop'],
230
+ labelSelect: 0
231
+ }
232
+ },
233
+ mounted: function () {
234
+ this.pixelRatio = this.getScreenPixelRatio();
235
+ },
236
+ methods: {
237
+ // resetFocusedAnnotation: function() {
238
+ // this.$refs.annCanvas.resetFocusedAnnotation()
239
+ // },
240
+ // createAnnotationLayer: function(newLayer) {
241
+ // this.$refs.annCanvas.createAnnotationLayer(newLayer)
242
+ // },
243
+ // getNextAnnotation: function() {
244
+ // let cursorOffset = (this.cursorLoc*this.cWidth - this.constants['CURSOROFFSET']) * this.rsPeriod
245
+ // let nextAnn = this.$refs.annCanvas.findNextAnnotation(this.start + cursorOffset)
246
+ // return nextAnn.start - cursorOffset
247
+ // },
248
+ // getPreviousAnnotation: function() {
249
+ // let cursorOffset = (this.cursorLoc*this.cWidth - this.constants['CURSOROFFSET']) * this.rsPeriod
250
+ // let nextAnn = this.$refs.annCanvas.findPreviousAnnotation(this.start + cursorOffset)
251
+ // return nextAnn.start - cursorOffset
252
+ // },
253
+ // onUpdateAnnotation: function(annotation) {
254
+ // this.$emit('updateAnnotation', annotation)
255
+ // },
256
+ // onCloseAnnotationLayerWindow: function() {
257
+ // this.$emit('closeAnnotationLayerWindow')
258
+ // },
259
+ onAnnotationsReceived: function() {
260
+ this.renderAll(100)
261
+ },
262
+ onAnnLayersInitialized: function() {
263
+ this.$emit('annLayersInitialized')
264
+ },
265
+ setGlobalZoom: function(value) {
266
+ this.$emit('setGlobalZoom', value)
267
+ },
268
+ channelsInitialized: function(channels) {
269
+ this.$emit('channelsInitialized', channels)
270
+ },
271
+ _onMouseWheel: function(e) {
272
+ e.stopPropagation();
273
+ e.preventDefault();
274
+ if(e.shiftKey) {
275
+ if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
276
+ if (e.deltaY >0) {
277
+ this.$emit('setDuration', this.duration*1.1 )
278
+ } else {
279
+ this.$emit('setDuration', this.duration/1.1 )
280
+ }
281
+ }
282
+ }else{
283
+ if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
284
+ if (e.deltaY > 0 ) {
285
+ this.$emit('setGlobalZoom',this.globalZoomMult*1.2);
286
+ } else {
287
+ this.$emit('setGlobalZoom',this.globalZoomMult/1.2);
288
+ }
289
+ }
290
+ }
291
+ },
292
+ _onMouseUp: function(e) {
293
+ this.resizeClicked = false;
294
+ this.mouseDown = false;
295
+ switch (this.pointerMode) {
296
+ case 'pointer':
297
+ this.clearICanvas()
298
+ const append = e.metaKey;
299
+ const yEnd = e.clientY - this.$refs.iArea.getBoundingClientRect().top
300
+ const yStart = this.startDragCoord.y - this.$refs.iArea.getBoundingClientRect().top
301
+ const channels = this.viewerChannels.map(channel => {
302
+ if (append === false) {
303
+ channel.selected = false
304
+ }
305
+ if( (channel.rowBaseline > yStart && channel.rowBaseline < yEnd) ||
306
+ (channel.rowBaseline < yStart && channel.rowBaseline > yEnd)) {
307
+ channel.selected = true
308
+ }
309
+ return channel
310
+ })
311
+ this.$store.dispatch('setChannels', channels)
312
+ break
313
+ case 'annSelect':
314
+ this.clearICanvas()
315
+ // this.$refs.annCanvas.selectFocusedAnn()
316
+ break
317
+ case 'annotate':
318
+ let curLIndex = null;
319
+ for (let i=0; i<this.viewerAnnotations.length; i++) {
320
+ if (this.viewerAnnotations[i].selected) {
321
+ curLIndex = i;
322
+ break;
323
+ }
324
+ }
325
+ // No layers
326
+ if (curLIndex === null) {
327
+ return;
328
+ }
329
+ const selectedChannels = this.$store.getters['viewerSelectedChannels']
330
+ const allChannels = selectedChannels.length === this.viewerChannels.length || selectedChannels.length === 0
331
+ const duration = (e.clientX - this.startDragCoord.x) * this.rsPeriod
332
+ const startTime = this.startDragTimeStamp + ( (this.startDragCoord.x - this.$refs.iArea.getBoundingClientRect().left) * this.rsPeriod)
333
+ const newAnn = {
334
+ name: '',
335
+ id: null,
336
+ label: this.defaultLabels[this.labelSelect],
337
+ description: '',
338
+ start: startTime,
339
+ duration: duration,
340
+ end: startTime + duration,
341
+ cStart: null,
342
+ cEnd: null,
343
+ selected: true,
344
+ channelIds: selectedChannels,
345
+ allChannels: allChannels,
346
+ layer_id: this.viewerAnnotations[curLIndex].id,
347
+ userId: null
348
+ };
349
+ this.$store.dispatch('setActiveAnnotation', newAnn)
350
+ this.$emit("addAnnotation", startTime, duration, allChannels, this.defaultLabels[this.labelSelect], '', this.viewerAnnotations[curLIndex] )
351
+ break;
352
+ case 'annResize-left':
353
+ case 'annResize-right':
354
+ // this.$refs.annCanvas.onMouseUp()
355
+ }
356
+ },
357
+ clearICanvas: function() {
358
+ const iCanvas = this.$refs.iArea;
359
+ const ctx = iCanvas.getContext('2d');
360
+ ctx.clearRect(0, 0, this.cWidth, this.cHeight);
361
+ },
362
+ _onMouseDown: function(evt) {
363
+ this.mouseDown = true;
364
+ this.startDragTimeStamp = this.start
365
+ this.startDragCoord.x = evt.clientX
366
+ this.startDragCoord.y = evt.clientY
367
+ // switch(this.pointerMode) {
368
+ // case 'annResize-left':
369
+ // case 'annResize-right':
370
+ // this.resizeClicked = true
371
+ // this.$refs.annCanvas.onMouseDown(evt.clientX - cCoord.left, evt.clientY - cCoord.top)
372
+ // break;
373
+ // }
374
+ },
375
+ _onMouseOut: function() {
376
+ this.mouseDown = false;
377
+ },
378
+ _onMouseEnter: function(e) {
379
+ if (e.buttons === 1) {
380
+ this.mouseDown = true;
381
+ } else {
382
+ this.mouseDown = false;
383
+ }
384
+ },
385
+ _onMouseMove: function(e) {
386
+ e.preventDefault();
387
+ e.stopPropagation();
388
+ switch (this.viewerActiveTool) {
389
+ case 'pan':
390
+ if( this.mouseDown){
391
+ // Update StartTime and get new data
392
+ const setStart = this.startDragTimeStamp - ((e.clientX-this.startDragCoord.x) * this.rsPeriod);
393
+ this.$emit('setStart', setStart)
394
+ } else {
395
+ // this.pointerMode = this.$refs.annCanvas.onMouseMove(mX,mY, this.pointerMode, this.mouseDown)
396
+ }
397
+ break;
398
+ case 'pointer':
399
+ if( this.mouseDown){
400
+ this.renderSelectBox(e.clientX, e.clientY);
401
+ } else {
402
+ // this.pointerMode = this.$refs.annCanvas.onMouseMove(mX,mY, this.pointerMode , this.mouseDown)
403
+ }
404
+ break;
405
+ case 'annotate':
406
+ if( this.mouseDown && this.pointerMode == 'annotate') {
407
+ this.renderAnnotationBox(e.clientX)
408
+ } else if (this.mouseDown && ['annResize-left', 'annResize-right'].includes(this.pointerMode)) {
409
+ // this.pointerMode = this.$refs.annCanvas.onMouseMove(mX,mY, this.pointerMode, this.mouseDown)
410
+ this.renderAll()
411
+ } else {
412
+ // this.pointerMode = this.$refs.annCanvas.onMouseMove(mX,mY, this.pointerMode, this.mouseDown)
413
+ }
414
+ break;
415
+ }
416
+ },
417
+ resize: function() {
418
+ this.updateRsPeriod(this.cWidth, this.duration)
419
+ this.renderAll(25)
420
+ },
421
+ updateRsPeriod: function(w, d) {
422
+ const oldRs = this.rsPeriod;
423
+ const newPeriod = (d / w);
424
+ if (newPeriod !== oldRs) {
425
+ this.invalidateCache = true;
426
+ this.lastRsUpdate = Date.now();
427
+ this.rsPeriod = newPeriod;
428
+ this.pageSize = Math.floor(this.duration / this.constants['PAGESIZEDIVIDER']);
429
+ }
430
+ },
431
+ _cpCanvasScaler: function(sz, pixelRatio, offset) {
432
+ return pixelRatio * (sz + offset);
433
+ },
434
+ renderAll: function(delay, requestLeadingEdge = true) {
435
+ if (!this.renderFnc || delay !== this.renderThrottle || this.requestLeadingEdge !== requestLeadingEdge) {
436
+ this.renderFnc = this.throttle(this._renderAll, delay, {leading: requestLeadingEdge} );
437
+ this.renderThrottle = delay;
438
+ this.requestLeadingEdge = requestLeadingEdge;
439
+ }
440
+ this.renderFnc(this);
441
+ },
442
+ // Returns a function, that, when invoked, will only be triggered at most once
443
+ // during a given window of time. Normally, the throttled function will run
444
+ // as much as it can, without ever going more than once per `wait` duration;
445
+ // but if you'd like to disable the execution on the leading edge, pass
446
+ // `{leading: false}`. To disable execution on the trailing edge, ditto.
447
+ throttle: function(func, wait, options) {
448
+ let context;
449
+ let args;
450
+ let result;
451
+ let timeout = null;
452
+ let previous = 0;
453
+ if (!options) options = {};
454
+ const later = function() {
455
+ previous = options.leading === false ? 0 : Date.now();
456
+ timeout = null;
457
+ result = func.apply(context, args);
458
+ if (!timeout) context = args = null;
459
+ };
460
+ return function() {
461
+ const now = Date.now();
462
+ if (!previous && options.leading === false) previous = now;
463
+ const remaining = wait - (now - previous);
464
+ context = this;
465
+ args = arguments;
466
+ if (remaining <= 0 || remaining > wait) {
467
+ if (timeout) {
468
+ clearTimeout(timeout);
469
+ timeout = null;
470
+ }
471
+ previous = now;
472
+ result = func.apply(context, args);
473
+ if (!timeout) context = args = null;
474
+ } else if (!timeout && options.trailing !== false) {
475
+ timeout = setTimeout(later, remaining);
476
+ }
477
+ return result;
478
+ };
479
+ },
480
+ renderAnnotationCanvas: function() {
481
+ this.clearICanvas()
482
+ // this.$refs.annCanvas.render()
483
+ },
484
+ _renderAll: function() {
485
+ this.$nextTick(() => {
486
+ this._renderAxis();
487
+ this._renderCursor();
488
+ this.$refs.plotCanvas.renderAll()
489
+ // TODO: Bring back when supporting public annotations.
490
+ // this.$refs.annCanvas.render()
491
+ })
492
+ },
493
+ // Render the X and Y axis, and time ticks
494
+ _renderAxis: function() {
495
+ const pa = this.$refs.axisArea;
496
+ const ctx = pa.getContext('2d');
497
+ ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
498
+ // clear canvas
499
+ ctx.clearRect(0, 0, this.cWidth, this.cHeight);
500
+ ctx.stroke();
501
+ // render side/bottom line
502
+ ctx.beginPath();
503
+ ctx.lineWidth = 1;
504
+ ctx.moveTo(this.constants.XOFFSET+0.5, 0.5);
505
+ ctx.lineTo(this.constants.XOFFSET+0.5, (this.pHeight +0.5));
506
+ ctx.lineTo(this.cWidth+0.5, (this.pHeight+0.5));
507
+ ctx.stroke();
508
+ // Render y-ticks
509
+ let tickOffset = (((this.pHeight/(2*this.nrVisibleChannels))+0.5)<<1)>>1;
510
+ ctx.lineWidth=0.5;
511
+ for (let i = 0; i< this.nrVisibleChannels; i++) {
512
+ ctx.beginPath();
513
+ ctx.moveTo(this.constants.XOFFSET - 2, tickOffset);
514
+ ctx.lineTo(this.constants.XOFFSET + 2, tickOffset);
515
+ ctx.stroke();
516
+ tickOffset += this.pHeight/this.nrVisibleChannels;
517
+ }
518
+ // --- --- --- --- ---
519
+ // Render x-ticks
520
+ const gridSpacing = this.constants['XGRIDSPACING'] * Math.ceil(this.duration / 100000000);
521
+ const nrGridLines = Math.ceil(this.duration / gridSpacing) + 1;
522
+ const labelDecimator = Math.ceil( this.constants['NRPXPERLABEL'] / (gridSpacing / this.rsPeriod));
523
+ let curXLabelIdx = 1;
524
+ // XLOC1 is time to first timeIndicator line
525
+ const xLoc1 = (gridSpacing - (this.start % gridSpacing) ) % gridSpacing;
526
+ // Iterate over all potential time-lines
527
+ for(let i = 0; i < nrGridLines; i++) {
528
+ // actual time of the timeline = canvas start time + xLoc1 time
529
+ let realX = this.start + xLoc1 + i * gridSpacing;
530
+ if (realX > this.tsEnd) {
531
+ break;
532
+ }
533
+ // curLoc is pixel coordinates of realX
534
+ const realOffset = realX - this.start;
535
+ const curLoc = ( this.constants['XOFFSET'] + (realOffset / this.rsPeriod) );
536
+ const roundedCurLoc = Math.round(curLoc);
537
+ if (roundedCurLoc > 1) {
538
+ ctx.save();
539
+ ctx.beginPath();
540
+ ctx.lineWidth=0.5;
541
+ ctx.strokeStyle='rgb(235,235,235)';
542
+ ctx.moveTo(roundedCurLoc + 0.5, 0.5);
543
+ ctx.lineTo(roundedCurLoc+ 0.5, this.pHeight -0.5);
544
+ ctx.stroke();
545
+ ctx.restore();
546
+ // Render X-Label when appropriate
547
+ const test = (((realX / gridSpacing) + gridSpacing / 10) % labelDecimator) |0;
548
+ if( test === 1 || labelDecimator === 1 ) {
549
+ ctx.beginPath();
550
+ ctx.lineWidth = 1;
551
+ ctx.moveTo(roundedCurLoc+ 0.5, this.pHeight - 3);
552
+ ctx.lineTo(roundedCurLoc+ 0.5, this.pHeight + 3);
553
+ ctx.stroke();
554
+ const d = new Date(realX/1000);
555
+ ctx.font = '12px sans-serif';
556
+ ctx.fillStyle = 'rgb(150,150,150)';
557
+ ctx.fillText(this.getUTCTimeString(d), roundedCurLoc - 20.5, this.cHeight-0.2);
558
+ curXLabelIdx = curXLabelIdx + 1;
559
+ }
560
+ }
561
+ }
562
+ },
563
+ // Render the selection box on click-drag using point for selecting channels
564
+ renderSelectBox: function(curX, curY) {
565
+ const iCanvas = this.$refs.iArea;
566
+ const ctx = iCanvas.getContext('2d');
567
+ ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
568
+ ctx.clearRect(0, 0, this.cWidth, this.cHeight);
569
+ ctx.beginPath();
570
+ ctx.lineWidth=2;
571
+ ctx.strokeStyle='#295eff';
572
+ ctx.setLineDash([5, 5, 15, 5]);
573
+ const xStart = curX - iCanvas.getBoundingClientRect().left;
574
+ const yStart = curY - iCanvas.getBoundingClientRect().top;
575
+ ctx.rect(xStart, yStart, -curX+this.startDragCoord.x, -curY+this.startDragCoord.y);
576
+ ctx.stroke();
577
+ },
578
+ _renderCursor: function() {
579
+ // --- --- --- --- ---
580
+ // Render cursor
581
+ const pa = this.$refs.cursorArea;
582
+ const ctx = pa.getContext('2d');
583
+ ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
584
+ ctx.clearRect(0, 0, (this.cWidth + this.constants['CURSOROFFSET']), this.cHeight);
585
+ ctx.save()
586
+ ctx.beginPath();
587
+ ctx.fillStyle = 'red';
588
+ const cCursorLoc = this.cursorLoc * this.cWidth
589
+ if (cCursorLoc > this.constants['CURSOROFFSET']) {
590
+ ctx.strokeStyle = 'red';
591
+ ctx.moveTo(cCursorLoc, 0);
592
+ ctx.lineTo(cCursorLoc, this.pHeight);
593
+ ctx.stroke();
594
+ } else {
595
+ ctx.moveTo(cCursorLoc, this.pHeight);
596
+ }
597
+ ctx.beginPath();
598
+ ctx.lineTo(cCursorLoc - 5, this.pHeight + 8);
599
+ ctx.lineTo(cCursorLoc + 5, this.pHeight + 8);
600
+ ctx.lineTo(cCursorLoc, this.pHeight );
601
+ ctx.fill();
602
+ ctx.restore();
603
+ },
604
+ // Render an annotation during dragging mouse while creating annotation
605
+ renderAnnotationBox: function(curX) {
606
+ const iCanvas = this.$refs.iArea
607
+ const ctx = iCanvas.getContext('2d');
608
+ ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
609
+ const annotationHeight = this.constants['ANNOTATIONLABELHEIGHT'];
610
+ const halfAnnotationHeight = (annotationHeight/2) |0;
611
+ // Find active layer
612
+ let curLIndex = null;
613
+ for (let i=0; i<this.viewerAnnotations.length; i++) {
614
+ if (this.viewerAnnotations[i].selected) {
615
+ curLIndex = i;
616
+ break;
617
+ }
618
+ }
619
+ // No active Layers
620
+ if (curLIndex === null) {
621
+ return;
622
+ }
623
+ ctx.save();
624
+ ctx.clearRect(0, 0, this.cWidth, this.cHeight);
625
+ ctx.lineWidth = 1;
626
+ // Determine if the user is adding annotation to specific channels
627
+ const selectedChannels = this.$store.getters['viewerSelectedChannels']
628
+ const allChannels = selectedChannels.length === this.viewerChannels.length || selectedChannels.length === 0
629
+ const xStart = curX - iCanvas.getBoundingClientRect().left;
630
+ const dx = -curX + this.startDragCoord.x
631
+ if (allChannels) {
632
+ ctx.fillStyle = 'rgba(0,0,0,0.1)';
633
+ ctx.fillRect(xStart, 0, dx, this.pHeight);
634
+ ctx.fillStyle = this.viewerAnnotations[curLIndex].color;
635
+ ctx.strokeStyle = ctx.fillStyle;
636
+ let lblStart = xStart -1;
637
+ let lblEnd = dx +2;
638
+ if (dx < 0) {
639
+ lblStart = xStart +1;
640
+ lblEnd = dx-2;
641
+ }
642
+ ctx.fillRect(lblStart, 0, lblEnd, annotationHeight);
643
+ ctx.setLineDash([5, 5, 5, 5]);
644
+ ctx.beginPath();
645
+ ctx.moveTo(xStart, annotationHeight);
646
+ ctx.lineTo(xStart, this.pHeight );
647
+ ctx.stroke();
648
+ ctx.beginPath();
649
+ ctx.moveTo(xStart+dx, annotationHeight);
650
+ ctx.lineTo(xStart+dx, this.pHeight );
651
+ ctx.stroke();
652
+ } else {
653
+ let minOffset = this.cHeight | 0;
654
+ let maxOffset = 0;
655
+ const channelConfig = this.viewerChannels;
656
+ ctx.fillStyle = this.viewerAnnotations[curLIndex].color;
657
+ ctx.strokeStyle=ctx.fillStyle;
658
+ let lblStart = xStart -1;
659
+ let lblEnd = dx +2;
660
+ if (dx < 0) {
661
+ lblStart = xStart +1;
662
+ lblEnd = dx-2;
663
+ }
664
+ for (let ch=0; ch< channelConfig.length; ch++) {
665
+ const curChannelView = channelConfig[ch];
666
+ if(curChannelView.selected && curChannelView.visible) {
667
+ const channelOffset = curChannelView.rowBaseline |0;
668
+ if (channelOffset < minOffset) { minOffset = channelOffset; }
669
+ if (channelOffset > maxOffset) { maxOffset = channelOffset; }
670
+ ctx.fillRect(lblStart, channelOffset - halfAnnotationHeight, lblEnd, annotationHeight);
671
+ }
672
+ }
673
+ ctx.fillStyle = 'rgba(0,0,0,0.1)';
674
+ ctx.setLineDash([5, 5, 5, 5]);
675
+ ctx.fillRect(xStart -1, minOffset+halfAnnotationHeight,
676
+ dx +2, maxOffset-minOffset -annotationHeight);
677
+ ctx.beginPath();
678
+ ctx.moveTo(xStart, minOffset +halfAnnotationHeight);
679
+ ctx.lineTo(xStart, maxOffset-halfAnnotationHeight );
680
+ ctx.moveTo(xStart+dx, minOffset +halfAnnotationHeight);
681
+ ctx.lineTo(xStart+dx, maxOffset -halfAnnotationHeight);
682
+ ctx.stroke();
683
+ }
684
+ ctx.restore();
685
+ },
686
+ getScreenPixelRatio: function() {
687
+ let ctx = this.$refs.iArea.getContext('2d');
688
+ let dpr = window.devicePixelRatio || 1
689
+ let bsr = ctx.webkitBackingStorePixelRatio ||
690
+ ctx.mozBackingStorePixelRatio ||
691
+ ctx.msBackingStorePixelRatio ||
692
+ ctx.oBackingStorePixelRatio ||
693
+ ctx.backingStorePixelRatio || 1;
694
+ return dpr / bsr;
695
+ },
696
+ getUTCTimeString: function(d) {
697
+ return ( ('0' + d.getUTCHours()).slice(-2) + ':' +
698
+ ('0' + d.getUTCMinutes()).slice(-2) + ':' + ('0' + d.getUTCSeconds()).slice(-2) );
699
+ },
700
+ setFilters: function(payload) {
701
+ let input0 = parseFloat(payload.input0)
702
+ let input1 = parseFloat(payload.input1)
703
+ let message = {};
704
+ switch (payload.filterType) {
705
+ case 'clear':
706
+ message = {'channelFiltersToClear':payload.selChannels};
707
+ break;
708
+ case 'bandpass':
709
+ let bp_center = (input0 + input1) / 2;
710
+ let bp_width = Math.abs( (input1 - input0) / 2 )
711
+ message = {'filter':payload.filterType, 'filterParameters': [4, bp_center, bp_width], 'channels':payload.selChannels};
712
+ break;
713
+ case 'highpass':
714
+ message = {'filter':payload.filterType, 'filterParameters': [4, input0], 'channels':payload.selChannels};
715
+ break;
716
+ case 'lowpass':
717
+ message = {'filter':payload.filterType, 'filterParameters': [4, input0], 'channels':payload.selChannels};
718
+ break;
719
+ case 'bandstop':
720
+ let bs_width = 10;
721
+ const bs_center = payload.notchFreq;
722
+ message = {'filter':payload.filterType, 'filterParameters': [4, bs_center, bs_width], 'channels':payload.selChannels};
723
+ break;
724
+ default:
725
+ return;
726
+ }
727
+ this.$refs.plotCanvas.sendFilterMessage(message)
728
+ for (let i=0; i<payload.selChannels.length; i++) {
729
+ let channelId = payload.selChannels[i]
730
+ let channel = find(propEq('id', channelId), this.viewerChannels)
731
+ if (payload.filterType === 'clear') {
732
+ channel.filter = {}
733
+ } else {
734
+ channel.filter = {
735
+ type: payload.filterType,
736
+ input0: input0,
737
+ input1: input1,
738
+ notchFreq: payload.notchFreq
739
+ }
740
+ }
741
+ this.$store.dispatch('updateChannel', channel)
742
+ }
743
+ this.$refs.plotCanvas.invalidate();
744
+ this.renderAll();
745
+ }
746
+ }
747
+ }
748
+ </script>
749
+
750
+ <style lang="scss" scoped>
751
+ .timeseries-viewer-canvas {
752
+ display: flex;
753
+ background-color: white;
754
+ flex: 1;
755
+ }
756
+ #canvasWrapper {
757
+ position: relative;
758
+ }
759
+ #channelCanvas {
760
+ display: flex;
761
+ }
762
+ .canvas {
763
+ position: absolute;
764
+ top: 0;
765
+ left: 0;
766
+ margin-left: 5px;
767
+ cursor: ew-resize;
768
+ outline: none;
769
+ }
770
+ .canvas[active]{
771
+ cursor: pointer;
772
+ }
773
+ .canvas[col_resize]{
774
+ cursor: col-resize;
775
+ }
776
+ .canvas[point]{
777
+ cursor: default;
778
+ }
779
+ .canvas[cursor_hover]{
780
+ cursor: col-resize;
781
+ }
782
+ #cursorArea {
783
+ margin-left: 0;
784
+ }
785
+ #annotationPopover {
786
+ position: absolute;
787
+ opacity: 0;
788
+ display: none;
789
+ top: 75px;
790
+ z-index: 1000;
791
+ left: 400px;
792
+ }
793
+ </style>