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,420 @@
1
+ <template>
2
+ <div class="timeseries-scrubber">
3
+ <div class="dateWrap">
4
+ <div>{{ ts_start_str }}</div>
5
+ <div>{{ fullDateStr }}</div>
6
+ <div>{{ ts_end_str }}</div>
7
+ </div>
8
+ <div class="noselect">
9
+ <div id="scrubber" noselect>
10
+ <div id="canvasWrap" ref="canvasWrap">
11
+ <canvas id="segmentsCanvas" class="canvas" ref="segmentsCanvas"
12
+ :width="_cpCanvasScaler(cWidth, pixelRatio,0)"
13
+ :height="_cpCanvasScaler(viewportHeight-2, pixelRatio,0)"
14
+ :style="canvasStyle"></canvas>
15
+ <canvas id="annotationCanvas" class="canvas" ref="annotationCanvas"
16
+ :width="_cpCanvasScaler(cWidth, pixelRatio, 0)"
17
+ :height="_cpCanvasScaler(viewportHeight-2, pixelRatio,0)"
18
+ :style="canvasStyle"></canvas>
19
+ <canvas id="iCanvas" class="canvas" ref="iCanvas" :width="_cpCanvasScaler(cWidth, pixelRatio, 0)" :height="_cpCanvasScaler(viewportHeight, pixelRatio,0)"
20
+ @click="_onTap"
21
+ v-on:mousemove="_onMouseMove"
22
+ v-on:mousedown="_onMouseDown"
23
+ v-on:mouseup="_onMouseUp"
24
+ v-on:mouseenter="_onMouseEnter"
25
+ v-on:mouseout="_onMouseOut"
26
+ :style="iCanvasStyle"></canvas>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <script>
34
+ import { map } from 'ramda'
35
+ import ViewerActiveTool from '@/mixins/viewer-active-tool'
36
+ import Request from '@/mixins/request'
37
+ export default {
38
+ name: 'TimeseriesScrubber',
39
+ mixins: [
40
+ Request,
41
+ ViewerActiveTool
42
+ ],
43
+ props: {
44
+ ts_start: Number,
45
+ ts_end: Number,
46
+ cWidth: Number,
47
+ constants: Object,
48
+ start: Number,
49
+ duration: Number,
50
+ cursorLoc:Number,
51
+ labelWidth:Number
52
+ },
53
+ computed: {
54
+ activeViewer: function() {
55
+ return this.$store.getters.activeViewer
56
+ },
57
+ viewerChannels: function() {
58
+ return this.$store.getters.viewerChannels
59
+ },
60
+ viewerAnnotations: function() {
61
+ return this.$store.getters.viewerAnnotations
62
+ },
63
+ ts_start_str: function() {
64
+ return this.getUTCTimeString(this.ts_start)
65
+ },
66
+ ts_end_str: function() {
67
+ return this.getUTCTimeString(this.ts_end)
68
+ },
69
+ fullDateStr: function(){
70
+ if (this.hoverTxt !== '') {
71
+ return this.hoverTxt;
72
+ } else if(this.start > 0) {
73
+ const d = new Date(this.start/1000).toUTCString();
74
+ return d.substring(0, d.length - 3);
75
+ }
76
+ return ''
77
+ },
78
+ canvasStyle: function() {
79
+ return {
80
+ width: this.labelWidth + this.cWidth - 8 + 5 +'px',
81
+ height: '28px'
82
+ }
83
+ },
84
+ iCanvasStyle: function() {
85
+ return {
86
+ width: this.labelWidth + this.cWidth - 8 + 5 +'px',
87
+ height: '30px'
88
+ }
89
+ },
90
+ scrubberCWidth: function() {
91
+ return this.cWidth + this.labelWidth - 8 + 5
92
+ },
93
+ period: function() {
94
+ return Math.floor((this.ts_end - this.ts_start) / this.cWidth)
95
+ }
96
+ },
97
+ watch: {
98
+ start: function() {
99
+ this.render()
100
+ },
101
+ duration: function() {
102
+ this.render()
103
+ },
104
+ cWidth: function() {
105
+ this.render()
106
+ }
107
+ },
108
+ data: function () {
109
+ return {
110
+ pixelRatio: 1,
111
+ scrubberHeight: 28,
112
+ viewportHeight: 30,
113
+ mouseDown: false,
114
+ hoverTxt:'',
115
+ pointerMode: 'point',
116
+ patternCnvs: null,
117
+ annotations: [],
118
+ segmentSpans: [],
119
+ segments:[]
120
+ }
121
+ },
122
+ mounted: function () {
123
+ this.segments = new Array(5000);
124
+ this.segments = this.segments.fill(0, 0, 4999);
125
+ this.pixelRatio = this.getScreenPixelRatio()
126
+ this.patternCnvs = this.createPinstripeCanvas()
127
+ this.renderViewPort()
128
+ },
129
+ methods: {
130
+ _cpCanvasScaler: function(sz, pixelRatio, offset) {
131
+ return pixelRatio * (sz + offset);
132
+ },
133
+ getScreenPixelRatio: function() {
134
+ let ctx = this.$refs.iCanvas.getContext('2d');
135
+ let dpr = window.devicePixelRatio || 1
136
+ let bsr = ctx.webkitBackingStorePixelRatio ||
137
+ ctx.mozBackingStorePixelRatio ||
138
+ ctx.msBackingStorePixelRatio ||
139
+ ctx.oBackingStorePixelRatio ||
140
+ ctx.backingStorePixelRatio || 1;
141
+ return dpr / bsr;
142
+ },
143
+ getUTCTimeString: function(d) {
144
+ if(d > 0) {
145
+ d = d / 1000;
146
+ d = new Date(d);
147
+ return ( ('0' + d.getUTCHours()).slice(-2) + ':' +
148
+ ('0' + d.getUTCMinutes()).slice(-2) + ':' + ('0' + d.getUTCSeconds()).slice(-2) );
149
+ }
150
+ },
151
+ getUTCDateString: function(d, s) {
152
+ if (s !== '') {
153
+ return s;
154
+ } else if(d > 0) {
155
+ d = new Date(d/1000);
156
+ return ( d.toDateString() );
157
+ }
158
+ },
159
+ // ------- Mouse Interactions -------
160
+ _onTap: function(e) {
161
+ const cCoord = this.$refs.iCanvas.getBoundingClientRect();
162
+ const cClickOffset = e.clientX - cCoord.left;
163
+ const realStart = ( cClickOffset/this.scrubberCWidth ) * (this.ts_end - this.ts_start );
164
+ this.$emit('setStart', realStart + this.ts_start)
165
+ },
166
+ _onMouseMove: function(e) {
167
+ if (!this.mouseDown) {
168
+ const cCoord = this.$refs.iCanvas.getBoundingClientRect();
169
+ const cHoverOffset = e.clientX - cCoord.left;
170
+ const cEnd = this.cStart + this.cDuration;
171
+ const oldMode = this.pointerMode;
172
+ const inResizeArea = cHoverOffset > this.cStart - 10 && cHoverOffset < cEnd + 10;
173
+ if (inResizeArea) {
174
+ this.pointerMode = 'drag';
175
+ this.$refs.iCanvas.setAttribute('dragme', true);
176
+ this.$refs.iCanvas.removeAttribute('resizeme');
177
+ } else {
178
+ this.pointerMode = 'point';
179
+ this.$refs.iCanvas.removeAttribute('dragme');
180
+ this.$refs.iCanvas.removeAttribute('resizeme');
181
+ }
182
+ //update hoverTxt
183
+ const realStart = ( (cHoverOffset)/this.cWidth ) * (this.ts_end - this.ts_start ) + this.ts_start;
184
+ const d = new Date(realStart/1000).toUTCString();
185
+ this.hoverTxt = d.substring(0, d.length - 3);
186
+ if (oldMode !== this.pointerMode) {
187
+ this.render();
188
+ }
189
+ } else {
190
+ // is Dragging
191
+ const _dx = e.clientX - this.clickX
192
+ const realStart = ( (_dx)/this.cWidth ) * (this.ts_end - this.ts_start );
193
+ const setStart = this.startDragTime + realStart;
194
+ this.$emit('setStart', setStart)
195
+ const d = new Date((realStart+ this.ts_start)/1000);
196
+ this.hoverTxt = d.toUTCString();
197
+ }
198
+ },
199
+ _onMouseUp: function() {
200
+ this.mouseDown = false;
201
+ },
202
+ _onMouseDown: function(e) {
203
+ this.mouseDown = true;
204
+ this.clickX = e.clientX
205
+ this.startDragTime = this.start;
206
+ },
207
+ _onMouseEnter: function() {
208
+ this.mouseDown = false;
209
+ },
210
+ _onMouseOut: function() {
211
+ this.hoverTxt = '';
212
+ },
213
+ // -------
214
+ // ------- Annotation Functions -------
215
+ initSegmentSpans: function() {
216
+ // GET SEGMENTS AND GAPS
217
+ /*let fetchSpan = Math.min(this.constants['SEGMENTSPAN'], (this.ts_end - this.ts_start));
218
+ for (let i=0; i<this.viewerChannels.length; i++) {
219
+ this._requestSegmentSpan(this.viewerChannels[i].id,
220
+ i, this.ts_start, (this.ts_start + fetchSpan), 0)
221
+ }*/
222
+ },
223
+ getAnnotations: function() {
224
+ const layerIds = map(obj => obj.id, this.viewerAnnotations);
225
+ const baseUrl = `${this.config.apiUrl}/timeseries/${this.activeViewer.content.id}/annotations/window`;
226
+ var url = baseUrl + `?api_key=${this.$store.state.userToken}&aggregation=count&start=${this.ts_start}&end=${this.ts_end}&period=${this.period}&mergePeriods=true`
227
+ for (let i in layerIds) {
228
+ url = url + `&layerIds=${layerIds[i]}`
229
+ }
230
+ this.sendXhr(url)
231
+ .then(resp => {
232
+ console.log(resp)
233
+ this.annotations = resp;
234
+ this.render();
235
+ })
236
+ },
237
+ // -------
238
+ // ------- Render Functions -------
239
+ render: function() {
240
+ this.renderViewPort();
241
+ this.renderTimelimeLine();
242
+ this.renderSegments();
243
+ },
244
+ renderViewPort: function() {
245
+ this.$nextTick(() => {
246
+ const ctx = this.$refs.iCanvas.getContext('2d');
247
+ ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
248
+ ctx.clearRect(0, 0, this.cWidth, this.viewportHeight);
249
+ this.cStart = ( (((this.start - this.ts_start)/(this.ts_end-this.ts_start)) * this.cWidth ) + 0.5) |0;
250
+ this.cDuration = ((((this.duration)/(this.ts_end-this.ts_start)) * this.cWidth) +0.5) |0;
251
+ // Viewport
252
+ ctx.fillStyle = 'rgb(80,80,80)';
253
+ ctx.strokeStyle= 'rgb(80,80,80)';
254
+ ctx.strokeRect(this.cStart +0.5, 0.5, this.cDuration, this.viewportHeight-1);
255
+ ctx.fillRect(this.cStart - 2, (this.viewportHeight/2 - 5) | 0, 2, 10);
256
+ ctx.fillRect(this.cStart + this.cDuration + 1, (this.viewportHeight/2 - 5) | 0, 2, 10);
257
+ // // Cursor
258
+ const cursorCLoc = this.cStart + (this.cursorLoc*this.cDuration);
259
+ if( cursorCLoc > (this.cStart +0.5) ) {
260
+ ctx.strokeStyle = 'red';
261
+ ctx.beginPath();
262
+ ctx.moveTo(cursorCLoc, 0);
263
+ ctx.lineTo(cursorCLoc, this.viewportHeight-1)
264
+ ctx.stroke();
265
+ }
266
+ })
267
+ },
268
+ renderSegments: function() {
269
+ this.$nextTick(() => {
270
+ const ctx = this.$refs.segmentsCanvas.getContext('2d');
271
+ ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
272
+ ctx.fillStyle = ctx.createPattern(this.patternCnvs, 'repeat');
273
+ ctx.clearRect(0, 0, this.cWidth, this.viewportHeight);
274
+ for (let i = 1; i < this.segmentSpans.length; i += 2) {
275
+ const xStart = (this.cWidth * this.segmentSpans[i]) / 5000;
276
+ const xEnd = (this.cWidth * this.segmentSpans[i + 1]) / 5000;
277
+ ctx.fillRect(xStart, 2, xEnd - xStart, this.viewportHeight - 6)
278
+ }
279
+ })
280
+ },
281
+ renderTimelimeLine: function() {
282
+ const ctx = this.$refs.annotationCanvas.getContext('2d');
283
+ ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
284
+ ctx.clearRect(0, 0, this.cWidth, this.scrubberHeight);
285
+ const xStart = this.ts_start;
286
+ const xEnd = this.ts_end;
287
+ const annotationLayers = this.annotations;
288
+ let annotationIndex = 0;
289
+ const layerSpacing = 0;
290
+ const layerHeight = Math.floor(((this.scrubberHeight-2) / Object.keys(annotationLayers).length - layerSpacing));
291
+ const annPanelLayers = this.viewerAnnotations;
292
+ let color = 'rgb(0,0,0)';
293
+ for ( const annotation in annotationLayers ) {
294
+ // eslint-disable-next-line
295
+ if (annotationLayers.hasOwnProperty(annotation)) {
296
+ // find color
297
+ for (let i=0; i<annPanelLayers.length; i++) {
298
+ if (annPanelLayers[i].id === parseInt(annotation)) {
299
+ annotationIndex = i;
300
+ color = annPanelLayers[i].color;
301
+ break;
302
+ }
303
+ }
304
+ this.plotAnnotations(ctx, xStart, xEnd, layerSpacing, layerHeight, annotationLayers[annotation], annotationIndex, color);
305
+ }
306
+ }
307
+ },
308
+ plotAnnotations: function(ctx, xStart, xEnd, layerSpacing, layerHeight, annotations, rank, color) {
309
+ this.$nextTick(() => {
310
+ ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
311
+ ctx.fillStyle = color;
312
+ for (let i = 0; i < annotations.length; i++) {
313
+ if (annotations[i].value > 0) {
314
+ const xPosStart = ((annotations[i].start - xStart) / (xEnd - xStart)) * this.cWidth;
315
+ const xPosEnd = ((annotations[i].end - xStart) / (xEnd - xStart)) * this.cWidth;
316
+ let cw = xPosEnd - xPosStart;
317
+ if (cw < 1) {
318
+ cw = 1;
319
+ }
320
+ const yPos = 1 + rank * ((layerHeight - 1) + layerSpacing) + rank;
321
+ ctx.fillRect(xPosStart, yPos, cw, layerHeight);
322
+ }
323
+ }
324
+ })
325
+ },
326
+ /** Creates a canvas filled with a 45-degree pinstripe.
327
+ * @returns the filled HTMLCanvasElement. */
328
+ createPinstripeCanvas: function() {
329
+ const patternCanvas = document.createElement('canvas');
330
+ const pctx = patternCanvas.getContext('2d', { antialias: true });
331
+ const colour = 'rgb(220,220,220)';
332
+ const CANVAS_SIDE_LENGTH = 5;
333
+ const WIDTH = CANVAS_SIDE_LENGTH;
334
+ const HEIGHT = CANVAS_SIDE_LENGTH;
335
+ const DIVISIONS = 10;
336
+ patternCanvas.width = WIDTH;
337
+ patternCanvas.height = HEIGHT;
338
+ pctx.fillStyle = colour;
339
+ // Top line
340
+ pctx.beginPath();
341
+ pctx.moveTo(0, HEIGHT * (1 / DIVISIONS));
342
+ pctx.lineTo(WIDTH * (1 / DIVISIONS), 0);
343
+ pctx.lineTo(0, 0);
344
+ pctx.lineTo(0, HEIGHT * (1 / DIVISIONS));
345
+ pctx.fill();
346
+ // Middle line
347
+ pctx.beginPath();
348
+ pctx.moveTo(WIDTH, HEIGHT * (1 / DIVISIONS));
349
+ pctx.lineTo(WIDTH * (1 / DIVISIONS), HEIGHT);
350
+ pctx.lineTo(0, HEIGHT);
351
+ pctx.lineTo(0, HEIGHT * ((DIVISIONS - 1) / DIVISIONS));
352
+ pctx.lineTo(WIDTH * ((DIVISIONS - 1) / DIVISIONS), 0);
353
+ pctx.lineTo(WIDTH, 0);
354
+ pctx.lineTo(WIDTH, HEIGHT * (1 / DIVISIONS));
355
+ pctx.fill();
356
+ // Bottom line
357
+ pctx.beginPath();
358
+ pctx.moveTo(WIDTH, HEIGHT * ((DIVISIONS - 1) / DIVISIONS));
359
+ pctx.lineTo(WIDTH * ((DIVISIONS - 1) / DIVISIONS), HEIGHT);
360
+ pctx.lineTo(WIDTH, HEIGHT);
361
+ pctx.lineTo(WIDTH, HEIGHT * ((DIVISIONS - 1) / DIVISIONS));
362
+ pctx.fill();
363
+ return patternCanvas;
364
+ },
365
+ }
366
+ }
367
+ </script>
368
+
369
+ <style lang="scss" scoped>
370
+ .timeseries-scrubber {
371
+ //background: $white;
372
+ padding: 0px 8px 8px 8px;
373
+ }
374
+ .dateWrap {
375
+ padding: 8px 0;
376
+ font-size: 12px;
377
+ text-transform: uppercase;
378
+ color: #71747C;
379
+ display: flex;
380
+ flex-direction: row;
381
+ justify-content: space-between;
382
+ }
383
+ #scrubber {
384
+ background: white;
385
+ box-shadow: 0 0 0px 1px #c5c5c5 inset;
386
+ box-sizing: border-box;
387
+ position: relative;
388
+ display: flex;
389
+ }
390
+ #canvasWrap {
391
+ height: 30px;
392
+ position: relative;
393
+ }
394
+ .noselect {
395
+ -webkit-touch-callout: none; /* iOS Safari */
396
+ -webkit-user-select: none; /* Chrome/Safari/Opera */
397
+ -khtml-user-select: none; /* Konqueror */
398
+ -moz-user-select: none; /* Firefox */
399
+ -ms-user-select: none; /* Internet Explorer/Edge */
400
+ user-select: none; /* Non-prefixed version, currently not supported by any browser */
401
+ }
402
+ .canvas {
403
+ position: absolute;
404
+ top: 0;
405
+ left: 0;
406
+ cursor: pointer;
407
+ }
408
+ .canvas[dragme] {
409
+ cursor: move;
410
+ }
411
+ .canvas[resizeme] {
412
+ cursor: ew-resize;
413
+ }
414
+ #annotationCanvas {
415
+ margin-top: 1px;
416
+ }
417
+ #iCanvas {
418
+ margin-left: 0px
419
+ }
420
+ </style>