-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcanvaswave.js
More file actions
428 lines (391 loc) · 16.1 KB
/
canvaswave.js
File metadata and controls
428 lines (391 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
// Class for display a wave in a canvas.
class CanvasWave {
// Build a new canvas wave viewer.
// @param cnv the target canvas.
// @param pal the color palette for displaying waves.
// @param wave the wave to display.
constructor(cnv,pal,wave) {
// Initialize the canvas, context and wave accessers.
this._canvas = cnv;
this._context = this._canvas.getContext("2d");
this._palette = pal;
this._wave = wave;
// Initialize the zoom factor compress level and position.
this._zoom = 100;
this._compress = 10;
this._position = 0;
// Initialize the list of signal drawings.
this._signals = [];
// Intialize the signal vertical drawing parameters.
this._top = 35;
this._size = 10;
this._corner = 5;
this._space = 5;
// Intialize the value ruler to 0.
this._ruler = 0;
}
// Add a signal to draw.
add(sig) {
// Create its compress levels.
sig.cclear(); // Ensure we start afresh.
let l = 10.0;
// Create 10 level of compression with power of 10 lengths.
for(let i = 0; i<10; ++i) {
sig.compress(l);
sig.clevel += 1;
l = l*10.0;
}
// Add the signal.
this._signals.push(sig);
}
// Delete a signal to draw.
del(sig) {
const idx = this._signals.indexOf(sig);
if (idx != -1) this._signals.splice(idx,1);
}
// Update the zoom factor.
// Also updates the current compress level.
set zoom(z) {
this._zoom = z;
this.update_clevel();
}
// Update the value of the clevel using the number of units per pixels.
update_clevel() {
this._clevel = Math.trunc(Math.log10((this.end-this.start)/this.width));
// console.log("toPx(1.0)=" + (this.width / (this.end-this.start)) + " clevel=" + this._clevel);
if (this._clevel < 0) { this._clevel = 0; }
if (this._clevel > 9) { this._clevel = 9; }
}
// Get the current compress level.
get clevel() {
return this._clevel;
}
// Update the position.
set position(pos) {
// console.log("new position: " + pos);
this._position = pos;
}
// Update the ruler position.
set ruler(pos) {
this._ruler = pos;
}
// Get the ruler position.
get ruler() {
return this._ruler;
}
// Get the wave being viewed.
get wave() {
return this._wave;
}
// Get the display width in pixels.
get width() {
return this._canvas.getBoundingClientRect().width;
}
// Get the display width in time unit.
get widthT() {
return this.end - this.start;
}
// Get the display height in pixels.
get height() {
return this._canvas.getBoundingClientRect().height;
}
// Get the height of one signal display.
get heightS() {
return (this._size*2 + this._space);
}
// Get the start time of display.
get start() {
return this._position;
}
// Get the end time of display.
// Note: computed to fit the screen, hence can go past the length of the
// wave.
get end() {
return this._position + (this._wave.length*(100-this._zoom))/100;
}
// Convert a time to a pixel x position.
toPx(val) {
return Math.trunc((val * this.width) / (this.end-this.start));
}
// Convert a pixel x position to a time.
toT(val) {
return (val * (this.end-this.start) / this.width) + this.start;
}
// Compute the width in pixels of a text.
textWidth(txt) {
return this._context.measureText(txt).width;
}
// Binary search of the index of a segment overlapping a position.
search_by_position(segs,pos) {
let idxS = 0;
let idxE = segs.length-1;
let idxM = Math.trunc((idxE+idxS) / 2);
while(idxS < idxE) {
// console.log("pos=" + pos +
// " segs[idxM].start=" + segs[idxM].start +
// " segs[idxM].end=" + segs[idxM].end +
// " idxS=" + idxS + " idxE=" + idxE + " idxM=" + idxM);
if (segs[idxM].start <= pos) {
if (segs[idxM].end >= pos) {
// Found.
return idxM;
} else {
// Not found and too much on the right.
idxS = idxM + 1;
}
} else {
// Not found and too much on the left.
idxE = idxM - 1;
}
idxM = Math.trunc((idxE+idxS) / 2);
}
/* End of iteration, return idxM as is. */
return idxM;
}
// Get the value of signal sig at the ruler position.
value(sig) {
// for(let seg of sig.csegs[0]) { // Use the non-compressed segments.
// if (seg.start <= this._ruler && seg.end > this._ruler) {
// return seg.value;
// }
// }
let idx = this.search_by_position(sig.csegs[0],this._ruler);
return idx == -1 ? "?" : sig.csegs[0][idx].value;
}
// Clears the viewer.
clear() {
this._context.clearRect(0, 0, this.width, this.height);
}
// Draw the axis and waves.
draw() {
// Estimate the number of gradations.
let num = Math.trunc(this.width / 40);
// Compute the gradation step.
let fstep = Math.trunc((this.end - this.start) / num);
let step = fstep;
// Ensure the step is 1, 2, 5, 10, 20 and so on.
let count = 0;
while(step > 10) { step = step / 10; count = count + 1; }
step = Math.trunc(step);
switch(step) {
case 0:
case 1:
step = 1;
break;
case 2:
case 3:
case 4:
step = 2;
break;
case 5:
case 6:
case 7:
case 8:
case 9:
step = 5;
break;
default:
step = 10;
break;
}
step = step * Math.pow(10,count);
// Compute the postion of the first gradation.
// let first_pos = step - this.start % step;
// if (first_pos == step) { first_pos = 0; }
let first_pos = this.start - this.start % step + step;
if (first_pos > this.start+step) { first_pos -= step; }
let pos = first_pos;
this._context.lineWidth = 1;
this._context.strokeStyle = this._palette.signal;
this._context.fillStyle = this._palette.text;
this._context.font = "12px Courier New";
// this._context.fillText("pos=" + pos + " zoom=" + this._zoom + " length=" + this._wave.length + " start=" + this.start + " end=" + this.end + " width=" + this.width + " num=" + num + " fstep=" + fstep + " step=" + step + " toPx(step)=" + this.toPx(step), 100, 250);
// Draw the graphics.
context.beginPath();
// Draw the gradations.
while(pos < this.end) {
this._context.moveTo(this.toPx(pos-this.start),14);
this._context.lineTo(this.toPx(pos-this.start),20);
pos += step;
}
// Draw the axis.
this._context.moveTo(0,17);
this._context.lineTo(this.width,17);
// Draw the ruler.
if (this._ruler >= this.start && this._ruler < this.end) {
for(let i=20; i<this.height-1; i += 4) {
this._context.moveTo(this.toPx(this._ruler-this.start), i);
this._context.lineTo(this.toPx(this._ruler-this.start), i+2);
}
}
// Draw the signals to display.
this._context.font = "14px Courier New"; // Bigger font for signals.
let y = this._top;
for(sig of this._signals) {
// Set the compress level for draing the signal.
sig.clevel = this.clevel
// console.log("sig=" + sig.id + " y=" + y);
if (sig.type > 1) {
// Get the range of segments to draw.
let drawS = this.search_by_position(sig.segs,this.start);
let drawE = this.search_by_position(sig.segs,this.end);
// Multi-bit case.
for(let i = drawS; i<=drawE; ++i) {
let seg = sig.segs[i];
// console.log(" seg: " + seg.start + "," + seg.end + " " + seg.value);
if(seg.start < this.end && seg.end > this.start) {
// Can draw.
// Compute the start.
let x0 = this.toPx(seg.start-this.start);
let l0 = x0 + this._corner;
if (x0 <= 0) { x0 = 0; l0 = x0; }
let x1 = this.toPx(seg.end-this.start);
let l1 = x1 - this._corner;
if (x1 >= this.width-1) { x1 = this.width-1; l1 = x1; }
if (i >= sig.segs.length-1) { l1 = this.width-1; }
// console.log("x0=" + x0 + " x1=" + x1 + " l0=" + l0 + " l1=" + l1);
if (x1 - x0 < this._corner*2 || x0 == 0) {
// Too short segment, or too much to the left.
// Draw only a vertical for start transition.
this._context.moveTo(x0,y-this._size);
this._context.lineTo(x0,y+this._size);
if (x1-x0 < 2) { continue; } // Can end here.
} else {
// Draw a normal start transition.
this._context.moveTo(x0+this._corner,y-this._size);
this._context.lineTo(x0,y);
this._context.lineTo(x0+this._corner,y+this._size);
}
// Draw the value lines.
this._context.moveTo(l0,y-this._size);
this._context.lineTo(l1,y-this._size);
this._context.moveTo(l0,y+this._size);
this._context.lineTo(l1,y+this._size);
// Draw the value text.
let txt = seg.value
let vPos = l0 + (l1-l0 - this.textWidth(txt)) / 2;
if (vPos < l0) {
// The text is too large, cut it.
let diffC = (l0-vPos)*2 / this.textWidth("0");
txt = txt.substring(0,txt.length-diffC-1) + "~";
vPos = l0 + this._corner;
if (vPos+this.textWidth(txt) > l1-this._corner) {
vPos = vPos - this._corner;
}
}
if (l1-this._corner > this.textWidth("0")) {
this._context.fillText(txt, Math.trunc(vPos), Math.trunc(y+this._size/2));
}
// Draw the end transition.
if (x1 == this.width-1 || i >= sig.segs.length-1 ||
x1-x0 < this._corner*2) {
// Too short segment, or too much to the right.
// Draw only a vertical for start transition.
this._context.moveTo(x1,y-this._size);
this._context.lineTo(x1,y+this._size);
} else {
this._context.moveTo(x1-this._corner,y-this._size);
this._context.lineTo(x1,y);
this._context.lineTo(x1-this._corner,y+this._size);
}
}
}
}
else {
// Single-bit case.
// Get the range of segments to draw.
let drawS = this.search_by_position(sig.segs,this.start);
let drawE = this.search_by_position(sig.segs,this.end);
// console.log("this.start=" + this.start +
// " this.end=" + this.end + " drawS=" + drawS +
// " drawE=" + drawE);
// Do the drawning.
let pX = -10;
for(let i = drawS; i<=drawE; ++i) {
let seg = sig.segs[i];
// console.log(" seg: " + seg.start + "," + seg.end + " " + seg.value);
if(seg.start >= this.end) {
// Nothing to draw any longer.
break;
}
if(seg.end > this.start) {
// Can draw.
// Compute the end and check if it worth it to draw.
let x1 = this.toPx(seg.end-this.start);
if (x1 >= this.width-1) { x1 = this.width-1; }
if (x1 - pX < 2) { continue; }
pX = x1;
// Compute the start.
let x0 = this.toPx(seg.start-this.start);
if (x0 <= 0) { x0 = 0; }
// Draw the start transition.
this._context.moveTo(x0,y-this._size);
this._context.lineTo(x0,y+this._size);
if (x1 - x0 < 2) { continue }
// Draw the value lines.
// console.log("seg.value=" + seg.value);
switch(seg.value) {
case "0":
this._context.moveTo(x0,y+this._size);
this._context.lineTo(x1,y+this._size);
break;
case "1":
this._context.moveTo(x0,y-this._size);
this._context.lineTo(x1,y-this._size);
break;
case "z":
this._context.moveTo(x0,y);
this._context.lineTo(x1,y);
break;
default:
for(let i = x0+this._corner; i< x1;
i += this._corner) {
this._context.moveTo(i,y-this._size);
this._context.lineTo(i-this._corner,y+this._size);
}
}
// Draw the end transition.
this._context.moveTo(x0,y-this._size);
this._context.lineTo(x0,y+this._size);
}
}
}
y += this._size*2 + this._space;
}
this._context.stroke();
// Draw the gradation values.
this._context.font = "12px Courier New"; // Smaller font for the axis.
// Calculate the max value width to adjust the frequency of text.
let textMax = this.textWidth(this.wave.length.toString());
let textFac = Math.ceil((textMax+4) / this.toPx(step));
switch(textFac) {
case 1:
case 2:
break;
case 3:
case 4:
case 5:
textFac = 5;
break;
default:
textFac = 10;
}
// Place the gradation values.
pos = first_pos;
// let val = Math.trunc(this.start / (step*textFac)) * (step*textFac);
// let val = first_pos - first_pos % (step*textFac);
// if (this.start % (step*textFac) != 0) { val += step*textFac*2; }
// if (this.start % (step*textFac) != 0) { pos += step; }
// if (this.start % (step*textFac) > step) { pos -= step; }
while(pos < this.end) {
// let txt = val.toString();
let txt = pos.toString();
let x =Math.round(this.toPx(pos-this.start)-this.textWidth(txt)/2)-1;
if (x<0 && pos == 0) { x = 0; }
/* No text overlap, can display. */
this._context.fillText(txt, x , 12);
pos += step * textFac;
// val += step * textFac;
}
}
}