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