• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import {ns2s, Rect} from "./ProcedureWorkerCommon.js";
17import {CpuStruct} from "../bean/CpuStruct.js";
18import {ColorUtils} from "../component/trace/base/ColorUtils.js";
19
20//绘制时间轴
21let timeRuler: TimeRuler | undefined;
22let rangeRuler: RangeRuler | undefined;
23let sportRuler: SportRuler | undefined;
24let offsetTop: number = 0;
25let offsetLeft: number = 0;
26
27// @ts-ignore
28export function timeline(canvas: OffscreenCanvas, ctx: OffscreenCanvasRenderingContext2D, startNS: number, endNS: number, totalNS: number, frame: Rect, keyPressCode: any, keyUpCode: any, mouseDown: any, mouseUp: any, mouseMove: any, mouseOut: any, _offsetLeft: number, _offsetTop: number, changeHandler: Function) {
29    offsetLeft = _offsetLeft;
30    offsetTop = _offsetTop;
31    if (timeRuler == undefined) {
32        timeRuler = new TimeRuler(canvas, ctx, new Rect(0, 0, frame.width, 20), totalNS);
33    }
34    if (!sportRuler) {
35        sportRuler = new SportRuler(canvas, ctx, new Rect(0, 100.5, frame.width, frame.height - 100));
36    }
37    if (!rangeRuler) {
38        rangeRuler = new RangeRuler(canvas, ctx!, new Rect(0, 25, frame.width, 75), {
39            startX: 0,
40            endX: frame.width,
41            startNS: 0,
42            endNS: totalNS,
43            totalNS: totalNS,
44            xs: [],
45            xsTxt: []
46        }, (a) => {
47            if (sportRuler) {
48                sportRuler.range = a;
49            }
50            changeHandler(a);
51        });
52    }
53
54    rangeRuler.frame.width = frame.width;
55    sportRuler.frame.width = frame.width;
56    timeRuler.frame.width = frame.width;
57    if (keyPressCode) {
58        rangeRuler.keyPress(keyPressCode);
59    } else if (keyUpCode) {
60        rangeRuler.keyUp(keyUpCode);
61    } else if (mouseDown) {
62        rangeRuler.mouseDown(mouseDown);
63    } else if (mouseUp) {
64        rangeRuler.mouseUp(mouseUp);
65    } else if (mouseMove) {
66        rangeRuler.mouseMove(mouseMove);
67    } else if (mouseOut) {
68        rangeRuler.mouseOut(mouseOut);
69    } else {
70        timeRuler.draw();
71        rangeRuler.draw();
72    }
73}
74
75export abstract class Graph {
76    // @ts-ignore
77    c: OffscreenCanvasRenderingContext2D;
78    // @ts-ignore
79    canvas: OffscreenCanvas | undefined | null;
80    frame: Rect;
81
82    // @ts-ignore
83    protected constructor(canvas: OffscreenCanvas | undefined | null, c: OffscreenCanvasRenderingContext2D, frame: Rect) {
84        this.canvas = canvas;
85        this.frame = frame;
86        this.c = c;
87    }
88
89    abstract draw(): void;
90}
91
92export class TimeRuler extends Graph {
93    totalNS: number
94    private stepSmall: number;
95    private step: number;
96    private stepNS: number;
97
98    // @ts-ignore
99    constructor(canvas: OffscreenCanvas | undefined | null, c: OffscreenCanvasRenderingContext2D, frame: Rect, totalNS: number = 10_000_000_000) {
100        super(canvas, c, frame)
101        this.totalNS = totalNS;
102        this.step = this.frame.width / 10;
103        this.stepSmall = this.frame.width / 100;
104        this.stepNS = this.totalNS / 10;
105    }
106
107    draw() {
108        this.step = this.frame.width / 10;
109        this.stepSmall = this.frame.width / 100;
110        this.stepNS = this.totalNS / 10;
111        this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height)
112        this.c.beginPath();
113        this.c.strokeStyle = "#999"
114        this.c.lineWidth = 1;
115        for (let i = 0; i <= 10; i++) {
116            let x = Math.floor(i * this.step) + this.frame.x;
117            this.c.moveTo(x, 0);
118            this.c.lineTo(x, this.frame.height);
119            if (i == 10) break;
120            for (let j = 1; j < 10; j++) {
121                this.c.moveTo(x + Math.floor(j * this.stepSmall), 0);
122                this.c.lineTo(x + Math.floor(j * this.stepSmall), this.frame.height / 4);
123            }
124            this.c.fillStyle = '#999'
125            this.c.fillText(`${ns2s(i * this.stepNS)}`, x + 5, this.frame.height - 1)
126        }
127        this.c.stroke();
128        this.c.closePath();
129    }
130}
131
132/**
133 * SportRuler
134 */
135export class SportRuler extends Graph {
136    public static rulerFlagObj: Flag | null = null;
137    public flagList: Array<Flag> = [];
138    public flagListIdx: number | null = null
139    public obj = [{x: 3}, {x: 2}];
140    lineColor: string | null = null;
141    private rangeFlag = new Flag(0, 0, 0, 0, 0);
142    private ruler_w = 1022;
143    private _range: TimeRange = {} as TimeRange;
144
145    // @ts-ignore
146    constructor(canvas: OffscreenCanvas | undefined | null, c: OffscreenCanvasRenderingContext2D, frame: Rect) {
147        super(canvas, c, frame)
148    }
149
150    get range(): TimeRange {
151        return this._range;
152    }
153
154    set range(value: TimeRange) {
155        this._range = value;
156        this.draw()
157    }
158
159    modifyFlagList(type: string, flag: any = {}) {
160        if (type == "amend") {
161            if (flag.text && this.flagListIdx !== null) {
162                this.flagList[this.flagListIdx].text = flag.text
163            }
164            if (flag.color && this.flagListIdx !== null) {
165                this.flagList[this.flagListIdx].color = flag.color
166            }
167        } else if (type == "remove") {
168            if (this.flagListIdx !== null) {
169                this.flagList.splice(this.flagListIdx, 1)
170            }
171        }
172        this.draw()
173    }
174
175    draw(): void {
176        this.ruler_w = this.frame.width;
177        this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height)
178        this.c.beginPath();
179        this.lineColor = "#dadada";
180        this.c.strokeStyle = this.lineColor //"#dadada"
181        this.c.lineWidth = 1;
182        this.c.moveTo(this.frame.x, this.frame.y)
183        this.c.lineTo(this.frame.x + this.frame.width, this.frame.y)
184        this.c.stroke();
185        this.c.closePath();
186        this.c.beginPath();
187        this.c.lineWidth = 3;
188        this.c.strokeStyle = "#999999"
189        this.c.moveTo(this.frame.x, this.frame.y)
190        this.c.lineTo(this.frame.x, this.frame.y + this.frame.height)
191        this.c.stroke();
192        this.c.closePath();
193        this.c.beginPath();
194        this.c.lineWidth = 1;
195        this.c.strokeStyle = this.lineColor;//"#999999"
196        this.c.fillStyle = '#999999'
197        this.c.font = '8px sans-serif'
198        this.range.xs?.forEach((it, i) => {
199            this.c.moveTo(it, this.frame.y)
200            this.c.lineTo(it, this.frame.y + this.frame.height)
201            this.c.fillText(`+${this.range.xsTxt[i]}`, it + 3, this.frame.y + 12)
202        })
203
204        this.c.stroke();
205        this.c.closePath();
206    }
207
208    // drawTheFlag
209    drawTheFlag(x: number, color: string = "#999999", isFill: boolean = false, text: string = "") {
210        this.c.beginPath();
211        this.c.fillStyle = color;
212        this.c.strokeStyle = color;
213        this.c.moveTo(x, 125);
214        this.c.lineTo(x + 10, 125);
215        this.c.lineTo(x + 10, 127);
216        this.c.lineTo(x + 18, 127);
217        this.c.lineTo(x + 18, 137);
218        this.c.lineTo(x + 10, 137);
219        this.c.lineTo(x + 10, 135);
220        this.c.lineTo(x + 2, 135);
221        this.c.lineTo(x + 2, 143);
222        this.c.lineTo(x, 143);
223        this.c.closePath()
224        if (isFill) {
225            this.c.fill()
226        }
227        this.c.stroke();
228
229
230        if (text !== "") {
231            this.c.font = "10px Microsoft YaHei"
232            const {width} = this.c.measureText(text);
233            this.c.fillStyle = 'rgba(255, 255, 255, 0.8)'; //
234            this.c.fillRect(x + 21, 132, width + 4, 12);
235            this.c.fillStyle = "black";
236            this.c.fillText(text, x + 23, 142);
237            this.c.stroke();
238        }
239    }
240
241    //随机生成十六位进制颜色
242    randomRgbColor() {
243        const letters = '0123456789ABCDEF';
244        let color = '#';
245        for (let i = 0; i < 6; i++) {
246            color += letters[Math.floor(Math.random() * 16)]
247        }
248        return color;
249    }
250
251    //鼠标点击绘画旗子、点击旗子
252    mouseUp(ev: MouseEvent) {
253    }
254
255    //选中的旗子
256    onFlagRangeEvent(flagObj: Flag, idx: number) {
257        SportRuler.rulerFlagObj = flagObj;
258        this.flagListIdx = idx;
259    }
260
261    //鼠标移动 绘画旗子
262    mouseMove(ev: MouseEvent) {
263        let x = ev.offsetX - (offsetLeft || 0)
264        let y = ev.offsetY - (offsetTop || 0)
265        if (y >= 50 && y < 200) {
266            this.draw()
267            if (y >= 123 && y < 142 && x > 0) {
268                let onFlagRange = this.flagList.findIndex((flagObj: Flag) => {
269                    let flag_x = Math.round(this.ruler_w * (flagObj.time - this.range.startNS) / (this.range.endNS - this.range.startNS));
270                    return (x >= flag_x && x <= flag_x + 18)
271                });
272            }
273        }
274    }
275}
276
277const markPadding = 5;
278
279export class Mark extends Graph {
280    name: string | undefined
281    inspectionFrame: Rect
282    private _isHover: boolean = false
283
284    // @ts-ignore
285    constructor(canvas: OffscreenCanvas | undefined | null, name: string, c: OffscreenCanvasRenderingContext2D, frame: Rect) {
286        super(canvas, c, frame);
287        this.name = name;
288        this.inspectionFrame = new Rect(frame.x - markPadding, frame.y, frame.width + markPadding * 2, frame.height)
289    }
290
291    get isHover(): boolean {
292        return this._isHover;
293    }
294
295    set isHover(value: boolean) {
296        this._isHover = value;
297    }
298
299    draw(): void {
300        this.c.beginPath();
301        this.c.lineWidth = 7
302        this.c.strokeStyle = '#999999'
303        this.c.moveTo(this.frame.x, this.frame.y);
304        this.c.lineTo(this.frame.x, this.frame.y + this.frame.height / 3)
305        this.c.stroke();
306        this.c.lineWidth = 1
307        this.c.strokeStyle = '#999999'
308        this.c.moveTo(this.frame.x, this.frame.y);
309        this.c.lineTo(this.frame.x, this.frame.y + this.frame.height)
310        this.c.stroke();
311        this.c.closePath();
312    }
313}
314
315export interface TimeRange {
316    totalNS: number
317    startX: number
318    endX: number
319    startNS: number
320    endNS: number
321    xs: Array<number>
322    xsTxt: Array<string>
323}
324
325export class RangeRuler extends Graph {
326    public rangeRect: Rect
327    public markA: Mark
328    public markB: Mark
329    public range: TimeRange;
330    mouseDownOffsetX = 0
331    mouseDownMovingMarkX = 0
332    movingMark: Mark | undefined | null;
333    isMouseDown: boolean = false;
334    isMovingRange: boolean = false;
335    isNewRange: boolean = false;
336    markAX: number = 0;
337    markBX: number = 0;
338    isPress: boolean = false
339    pressFrameId: number = -1
340    currentDuration: number = 0
341    centerXPercentage: number = 0;
342    animaStartTime: number | undefined
343    animTime: number = 100;
344    p: number = 800;
345    private readonly notifyHandler: (r: TimeRange) => void;
346    private scale: number = 0;
347    //缩放级别
348    private scales: Array<number> = [50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000, 50_000, 100_000, 200_000, 500_000,
349        1_000_000, 2_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000,
350        1_000_000_000, 2_000_000_000, 5_000_000_000, 10_000_000_000, 20_000_000_000, 50_000_000_000,
351        100_000_000_000, 200_000_000_000, 500_000_000_000];
352    private _cpuUsage: Array<{ cpu: number, ro: number, rate: number }> = []
353
354    // @ts-ignore
355    constructor(canvas: OffscreenCanvas | undefined | null, c: OffscreenCanvasRenderingContext2D, frame: Rect, range: TimeRange, notifyHandler: (r: TimeRange) => void) {
356        super(canvas, c, frame)
357        this.range = range;
358        this.notifyHandler = notifyHandler;
359        this.markA = new Mark(canvas, 'A', c, new Rect(range.startX, frame.y, 1, frame.height))
360        this.markB = new Mark(canvas, 'B', c, new Rect(range.endX, frame.y, 1, frame.height))
361        this.rangeRect = new Rect(range.startX, frame.y, range.endX - range.startX, frame.height)
362    }
363
364    set cpuUsage(value: Array<{ cpu: number, ro: number, rate: number }>) {
365        this._cpuUsage = value
366        this.draw();
367    }
368
369    drawCpuUsage() {
370        let maxNum = Math.round(this._cpuUsage.length / 100)
371        let miniHeight = Math.round(this.frame.height / CpuStruct.cpuCount);//每格高度
372        let miniWidth = Math.ceil(this.frame.width / 100);//每格宽度
373        for (let i = 0; i < this._cpuUsage.length; i++) {
374            //cpu: 0, ro: 0, rate: 0.987620037556431
375            let it = this._cpuUsage[i]
376            this.c.fillStyle = ColorUtils.MD_PALETTE[it.cpu]
377            this.c.globalAlpha = it.rate
378            this.c.fillRect(this.frame.x + miniWidth * it.ro, this.frame.y + it.cpu * miniHeight, miniWidth, miniHeight)
379        }
380    }
381
382    draw(discardNotify: boolean = false): void {
383        this.c.clearRect(this.frame.x - markPadding, this.frame.y, this.frame.width + markPadding * 2, this.frame.height)
384        this.c.beginPath();
385        if (this._cpuUsage.length > 0) {
386            this.drawCpuUsage()
387            this.c.globalAlpha = 0;
388        } else {
389            this.c.globalAlpha = 1;
390        }
391        //绘制选中区域
392        this.c.fillStyle = "#ffffff";
393        this.rangeRect.x = this.markA.frame.x < this.markB.frame.x ? this.markA.frame.x : this.markB.frame.x
394        this.rangeRect.width = Math.abs(this.markB.frame.x - this.markA.frame.x)
395        this.c.fillRect(this.rangeRect.x, this.rangeRect.y, this.rangeRect.width, this.rangeRect.height)
396        this.c.globalAlpha = 1;
397        this.c.globalAlpha = .5;
398        this.c.fillStyle = "#999999"
399        this.c.fillRect(this.frame.x, this.frame.y, this.rangeRect.x, this.rangeRect.height)
400        this.c.fillRect(this.rangeRect.x + this.rangeRect.width, this.frame.y, this.frame.width - this.rangeRect.width, this.rangeRect.height)
401        this.c.globalAlpha = 1;
402        this.c.closePath();
403        this.markA.draw();
404        this.markB.draw();
405        if (this.notifyHandler) {
406            this.range.startX = this.rangeRect.x
407            this.range.endX = this.rangeRect.x + this.rangeRect.width
408            this.range.startNS = this.range.startX * this.range.totalNS / (this.frame.width || 0)
409            this.range.endNS = this.range.endX * this.range.totalNS / (this.frame.width || 0)
410            let l20 = (this.range.endNS - this.range.startNS) / 20;
411            let min = 0;
412            let max = 0;
413            let weight = 0;
414            for (let index = 0; index < this.scales.length; index++) {
415                if (this.scales[index] > l20) {
416                    if (index > 0) {
417                        min = this.scales[index - 1];
418                    } else {
419                        min = 0;
420                    }
421                    max = this.scales[index];
422                    weight = (l20 - min) * 1.0 / (max - min);
423                    if (weight > 0.243) {
424                        this.scale = max;
425                    } else {
426                        this.scale = min;
427                    }
428                    break;
429                }
430            }
431            if (this.scale == 0) {
432                this.scale = this.scales[0];
433            }
434            let tmpNs = 0;
435            let yu = this.range.startNS % this.scale;
436            let realW = (this.scale * this.frame.width) / (this.range.endNS - this.range.startNS);
437            let startX = 0;
438            if (this.range.xs) {
439                this.range.xs.length = 0
440            } else {
441                this.range.xs = []
442            }
443            if (this.range.xsTxt) {
444                this.range.xsTxt.length = 0
445            } else {
446                this.range.xsTxt = []
447            }
448            if (yu != 0) {
449                let firstNodeWidth = ((this.scale - yu) / this.scale * realW);
450                startX += firstNodeWidth;
451                tmpNs += yu;
452                this.range.xs.push(startX)
453                this.range.xsTxt.push(ns2s(tmpNs))
454            }
455            while (tmpNs < this.range.endNS - this.range.startNS) {
456                startX += realW;
457                tmpNs += this.scale;
458                this.range.xs.push(startX)
459                this.range.xsTxt.push(ns2s(tmpNs))
460            }
461            if (!discardNotify) {
462                this.notifyHandler(this.range)
463            }
464        }
465    }
466
467    mouseDown(ev: MouseEvent) {
468        let x = ev.offsetX - (offsetLeft || 0)
469        let y = ev.offsetY - (offsetTop || 0)
470        this.isMouseDown = true;
471        this.mouseDownOffsetX = x;
472        if (this.markA.isHover) {
473            this.movingMark = this.markA;
474            this.mouseDownMovingMarkX = this.movingMark.frame.x || 0
475        } else if (this.markB.isHover) {
476            this.movingMark = this.markB;
477            this.mouseDownMovingMarkX = this.movingMark.frame.x || 0
478        } else {
479            this.movingMark = null;
480        }
481        if (this.rangeRect.containsWithPadding(x, y, 5, 0)) {
482            this.isMovingRange = true;
483            this.markAX = this.markA.frame.x;
484            this.markBX = this.markB.frame.x;
485        } else if (this.frame.containsWithMargin(x, y, 20, 0, 0, 0) && !this.rangeRect.containsWithMargin(x, y, 0, markPadding, 0, markPadding)) {
486            this.isNewRange = true;
487        }
488    }
489
490    mouseUp(ev: MouseEvent) {
491        this.isMouseDown = false;
492        this.isMovingRange = false;
493        this.isNewRange = false;
494        this.movingMark = null;
495    }
496
497    mouseMove(ev: MouseEvent) {
498        let x = ev.offsetX - (offsetLeft || 0);
499        let y = ev.offsetY - (offsetTop || 0)
500        this.centerXPercentage = x / (this.frame.width || 0)
501        if (this.centerXPercentage <= 0) {
502            this.centerXPercentage = 0
503        } else if (this.centerXPercentage >= 1) {
504            this.centerXPercentage = 1
505        }
506        let maxX = this.frame.width || 0
507        if (this.markA.inspectionFrame.contains(x, y)) {
508            this.markA.isHover = true
509        } else if (this.markB.inspectionFrame.contains(x, y)) {
510            this.markB.isHover = true;
511        } else {
512            this.markA.isHover = false;
513            this.markB.isHover = false;
514        }
515        if (this.movingMark) {
516            let result = x - this.mouseDownOffsetX + this.mouseDownMovingMarkX;
517            if (result >= 0 && result <= maxX) {
518                this.movingMark.frame.x = result
519            } else if (result < 0) {
520                this.movingMark.frame.x = 0
521            } else {
522                this.movingMark.frame.x = maxX
523            }
524            this.movingMark.inspectionFrame.x = this.movingMark.frame.x - markPadding
525            requestAnimationFrame(() => this.draw());
526        }
527        if (this.isMovingRange && this.isMouseDown) {
528            let result = x - this.mouseDownOffsetX;
529            let mA = result + this.markAX
530            let mB = result + this.markBX
531            if (mA >= 0 && mA <= maxX) {
532                this.markA.frame.x = mA
533            } else if (mA < 0) {
534                this.markA.frame.x = 0
535            } else {
536                this.markA.frame.x = maxX
537            }
538            this.markA.inspectionFrame.x = this.markA.frame.x - markPadding
539            if (mB >= 0 && mB <= maxX) {
540                this.markB.frame.x = mB;
541            } else if (mB < 0) {
542                this.markB.frame.x = 0
543            } else {
544                this.markB.frame.x = maxX
545            }
546            this.markB.inspectionFrame.x = this.markB.frame.x - markPadding
547            requestAnimationFrame(() => this.draw());
548        } else if (this.isNewRange) {
549            this.markA.frame.x = this.mouseDownOffsetX;
550            this.markA.inspectionFrame.x = this.mouseDownOffsetX - markPadding;
551            if (x >= 0 && x <= maxX) {
552                this.markB.frame.x = x;
553            } else if (x < 0) {
554                this.markB.frame.x = 0;
555            } else {
556                this.markB.frame.x = maxX;
557            }
558            this.markB.inspectionFrame.x = this.markB.frame.x - markPadding;
559            requestAnimationFrame(() => this.draw());
560        }
561    }
562
563    mouseOut(ev: MouseEvent) {
564        this.movingMark = null;
565    }
566
567    fillX() {
568        if (this.range.startNS < 0) this.range.startNS = 0;
569        if (this.range.endNS < 0) this.range.endNS = 0;
570        if (this.range.endNS > this.range.totalNS) this.range.endNS = this.range.totalNS;
571        if (this.range.startNS > this.range.totalNS) this.range.startNS = this.range.totalNS;
572        this.range.startX = this.range.startNS * (this.frame.width || 0) / this.range.totalNS
573        this.range.endX = this.range.endNS * (this.frame.width || 0) / this.range.totalNS
574        this.markA.frame.x = this.range.startX
575        this.markA.inspectionFrame.x = this.markA.frame.x - markPadding
576        this.markB.frame.x = this.range.endX
577        this.markB.inspectionFrame.x = this.markB.frame.x - markPadding
578    }
579
580    keyPress(ev: KeyboardEvent) {
581        if (this.animaStartTime === undefined) {
582            this.animaStartTime = new Date().getTime();
583        }
584        let startTime = new Date().getTime();
585        let duration = (startTime - this.animaStartTime);
586        if (duration < this.animTime) duration = this.animTime
587        this.currentDuration = duration
588        if (this.isPress) return
589        this.isPress = true
590        switch (ev.key.toLocaleLowerCase()) {
591            case "w":
592                let animW = () => {
593                    if (this.scale === 50) return;
594                    this.range.startNS += (this.centerXPercentage * this.currentDuration * 2 * this.scale / this.p);
595                    this.range.endNS -= ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale / this.p);
596                    this.fillX();
597                    this.draw();
598                    this.pressFrameId = requestAnimationFrame(animW)
599                }
600                this.pressFrameId = requestAnimationFrame(animW)
601                break;
602            case "s":
603                let animS = () => {
604                    if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return;
605                    this.range.startNS -= (this.centerXPercentage * this.currentDuration * 2 * this.scale / this.p);
606                    this.range.endNS += ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale / this.p);
607                    this.fillX();
608                    this.draw();
609                    this.pressFrameId = requestAnimationFrame(animS)
610                }
611                this.pressFrameId = requestAnimationFrame(animS)
612                break;
613            case "a":
614                let animA = () => {
615                    if (this.range.startNS == 0) return;
616                    let s = this.scale / this.p * this.currentDuration;
617                    this.range.startNS -= s;
618                    this.range.endNS -= s;
619                    this.fillX();
620                    this.draw();
621                    this.pressFrameId = requestAnimationFrame(animA)
622                }
623                this.pressFrameId = requestAnimationFrame(animA)
624                break;
625            case "d":
626                let animD = () => {
627                    if (this.range.endNS >= this.range.totalNS) return;
628                    this.range.startNS += this.scale / this.p * this.currentDuration;
629                    this.range.endNS += this.scale / this.p * this.currentDuration;
630                    this.fillX();
631                    this.draw();
632                    this.pressFrameId = requestAnimationFrame(animD)
633                }
634                this.pressFrameId = requestAnimationFrame(animD)
635                break;
636        }
637    }
638
639    keyUp(ev: KeyboardEvent) {
640        this.animaStartTime = undefined;
641        this.isPress = false
642        if (this.pressFrameId != -1) {
643            cancelAnimationFrame(this.pressFrameId)
644        }
645        let startTime = new Date().getTime();
646        switch (ev.key) {
647            case "w":
648                let animW = () => {
649                    if (this.scale === 50) return;
650                    let dur = (new Date().getTime() - startTime);
651                    this.range.startNS += (this.centerXPercentage * 100 * this.scale / this.p);
652                    this.range.endNS -= ((1 - this.centerXPercentage) * 100 * this.scale / this.p);
653                    this.fillX();
654                    this.draw();
655                    if (dur < 300) {
656                        requestAnimationFrame(animW)
657                    }
658                }
659                requestAnimationFrame(animW)
660                break;
661            case "s":
662                let animS = () => {
663                    if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return;
664                    let dur = (new Date().getTime() - startTime);
665                    this.range.startNS -= (this.centerXPercentage * 100 * this.scale / this.p);
666                    this.range.endNS += ((1 - this.centerXPercentage) * 100 * this.scale / this.p);
667                    this.fillX();
668                    this.draw();
669                    if (dur < 300) {
670                        requestAnimationFrame(animS)
671                    }
672                }
673                requestAnimationFrame(animS)
674                break;
675            case "a":
676                let animA = () => {
677                    if (this.range.startNS <= 0) return
678                    let dur = (new Date().getTime() - startTime);
679                    let s = this.scale * 80 / this.p;
680                    this.range.startNS -= s;
681                    this.range.endNS -= s;
682                    this.fillX();
683                    this.draw();
684                    if (dur < 300) {
685                        requestAnimationFrame(animA)
686                    }
687                }
688                animA();
689                break;
690            case "d":
691                let animD = () => {
692                    if (this.range.endNS >= this.range.totalNS) return;
693                    let dur = (new Date().getTime() - startTime);
694                    let s = this.scale * 80 / this.p;
695                    this.range.startNS += s;
696                    this.range.endNS += s;
697                    this.fillX();
698                    this.draw();
699                    if (dur < 300) {
700                        requestAnimationFrame(animD)
701                    }
702                }
703                animD();
704                break;
705        }
706    }
707}
708
709export class Flag {
710    x: number = 0
711    y: number = 0
712    width: number = 0
713    height: number = 0
714    time: number = 0
715    color: string = ""
716    selected: boolean = false
717    text: string = ""
718
719    constructor(x: number, y: number, width: number, height: number, time: number, color: string = "#999999", selected = false) {
720        this.x = x;
721        this.y = y;
722        this.width = width;
723        this.height = height;
724        this.time = time;
725        this.color = color;
726        this.selected = selected;
727    }
728}