• 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 {Graph} from "./Graph.js";
17import {Rect} from "./Rect.js";
18import {TimeRange} from "./RangeRuler.js";
19import {Flag} from "./Flag.js";
20import {ns2s, ns2x, randomRgbColor, TimerShaftElement} from "../TimerShaftElement.js";
21import {TraceRow} from "../base/TraceRow.js";
22import {SpApplication} from "../../../SpApplication.js";
23
24export class SportRuler extends Graph {
25    static isMouseInSportRuler = false;
26    public flagList: Array<Flag> = [];
27    isRangeSelect: boolean = false;//region selection
28    private hoverFlag: Flag = new Flag(-1, 0, 0, 0, 0);
29    private lineColor: string | null = null;
30    private rulerW = 0;
31    private _range: TimeRange = {} as TimeRange;
32    private readonly notifyHandler: ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined;
33    private readonly flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined;
34    private invertedTriangleTime: number | null | undefined = null;
35    private slicesTime: { startTime: number | null | undefined, endTime: number | null | undefined, color: string | null } | null = {
36        startTime: null,
37        endTime: null,
38        color: null
39    };
40    private timerShaftEL: TimerShaftElement|undefined|null;
41    constructor(timerShaftEL: TimerShaftElement, frame: Rect, notifyHandler: (hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void, flagClickHandler: (flag: Flag | undefined | null) => void) {
42        super(timerShaftEL.canvas, timerShaftEL.ctx!, frame)
43        this.notifyHandler = notifyHandler;
44        this.flagClickHandler = flagClickHandler;
45        this.timerShaftEL = timerShaftEL;
46    }
47
48    get range(): TimeRange {
49        return this._range;
50    }
51
52    set range(value: TimeRange) {
53        this._range = value;
54        this.draw()
55    }
56
57    modifyFlagList(flag: Flag | null | undefined) {
58        if (flag) {
59            if (flag.hidden) {
60                let i = this.flagList.findIndex(it => it.time == flag.time)
61                this.flagList.splice(i, 1)
62            } else {
63                let i = this.flagList.findIndex(it => it.time == flag.time)
64                this.flagList[i] = flag;
65            }
66        } else {
67            this.flagList.forEach(it => it.selected = false);
68        }
69        this.draw();
70    }
71
72    draw(): void {
73        this.rulerW = this.canvas!.offsetWidth
74        this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height + 1)
75        this.c.beginPath();
76        this.lineColor = window.getComputedStyle(this.canvas!, null).getPropertyValue("color");
77        this.c.strokeStyle = this.lineColor //"#dadada"
78        this.c.lineWidth = 1;
79        this.c.moveTo(this.frame.x, this.frame.y)
80        this.c.lineTo(this.frame.x + this.frame.width, this.frame.y)
81        this.c.stroke();
82        this.c.closePath();
83        this.c.beginPath();
84        this.c.lineWidth = 3;
85        this.c.strokeStyle = "#999999"
86        this.c.moveTo(this.frame.x, this.frame.y)
87        this.c.lineTo(this.frame.x, this.frame.y + this.frame.height)
88        this.c.stroke();
89        this.c.closePath();
90        this.c.beginPath();
91        this.c.lineWidth = 1;
92        this.c.strokeStyle = this.lineColor;//"#999999"
93        this.c.fillStyle = '#999999'
94        this.c.font = '8px sans-serif'
95        this.range.xs?.forEach((it, i) => {
96            this.c.moveTo(it, this.frame.y)
97            this.c.lineTo(it, this.frame.y + this.frame.height)
98            this.c.fillText(`+${this.range.xsTxt[i]}`, it + 3, this.frame.y + 12)
99        })
100        this.c.stroke();
101        this.c.closePath();
102        //绘制旗子
103        this.flagList.forEach((flagObj: Flag, b) => {
104            if (flagObj.time >= this.range.startNS && flagObj.time <= this.range.endNS) {
105                flagObj.x = Math.round(this.rulerW * (flagObj.time - this.range.startNS) / (this.range.endNS - this.range.startNS));
106                this.drawFlag(flagObj.x, flagObj.color, flagObj.selected, flagObj.text, flagObj.type)
107            }
108        })
109        !this.hoverFlag.hidden && this.drawFlag(this.hoverFlag.x, this.hoverFlag.color, true, this.hoverFlag.text)
110        //If region selection is enabled, the serial number draws a line on the axis to show the length of the box selection
111        if (this.isRangeSelect) {
112            let range = TraceRow.rangeSelectObject;
113            this.c.beginPath();
114            if (document.querySelector<SpApplication>("sp-application")!.dark) {
115                this.c.strokeStyle = "#FFF"
116                this.c.fillStyle = "#FFF"
117            } else {
118                this.c.strokeStyle = "#000"
119                this.c.fillStyle = "#000"
120            }
121            let startX = ns2x(range?.startNS || 0, this.range.startNS, this.range.endNS, this.range.totalNS, this.frame);
122            let endX = ns2x(range?.endNS || 0, this.range.startNS, this.range.endNS, this.range.totalNS, this.frame);
123            let lineWidth = endX - startX;
124            let txt = ns2s((range?.endNS || 0) - (range?.startNS || 0));
125            this.c.moveTo(startX, this.frame.y + 22);
126            this.c.lineTo(endX, this.frame.y + 22);
127            this.c.moveTo(startX, this.frame.y + 22 - 5);
128            this.c.lineTo(startX, this.frame.y + 22 + 5);
129            this.c.moveTo(endX, this.frame.y + 22 - 5);
130            this.c.lineTo(endX, this.frame.y + 22 + 5);
131            let txtWidth = this.c.measureText(txt).width;
132            if (lineWidth > txtWidth) {
133                this.c.fillText(`${txt}`, startX + (lineWidth - txtWidth) / 2, this.frame.y + 20)
134            } else {
135                if (endX + txtWidth >= this.frame.width) {
136                    this.c.fillText(`${txt}`, startX - 5 - txtWidth, this.frame.y + 20)
137                } else {
138                    this.c.fillText(`${txt}`, endX + 5, this.frame.y + 20)
139                }
140            }
141            this.c.stroke();
142            this.c.closePath();
143        }
144        if (this.invertedTriangleTime != null && typeof (this.invertedTriangleTime) != undefined) {
145            this.drawInvertedTriangle(this.invertedTriangleTime, document.querySelector<SpApplication>("sp-application")!.dark ? "#FFFFFF" : "#000000")
146        }
147        this.drawSlicesMark(this.slicesTime?.startTime, this.slicesTime?.endTime)
148    }
149
150    drawTriangle(time: number, type: string) {
151        if (time != null && typeof (time) != undefined) {
152            let i = this.flagList.findIndex(it => it.time == time);
153            if (type == "triangle") {
154                let triangle = this.flagList.findIndex(it => it.type == type);
155                if (i !== -1) {
156                    if (triangle !== -1) {
157                        this.flagList[i].type == "" ? this.flagList.splice(triangle, 1) : ""
158                    }
159                    this.flagList.forEach(it => it.selected = false)
160                    this.flagList[i].selected = true;
161
162                } else {
163                    if (triangle == -1) {
164                        this.flagList.forEach(it => it.selected = false)
165                        this.flagList.push(new Flag(0, 125, 18, 18, time, randomRgbColor(), true, "triangle"));
166                    } else {
167                        this.flagList.forEach(it => it.selected = false)
168                        this.flagList[triangle].time = time;
169                        this.flagList[triangle].selected = true;
170                    }
171                }
172            } else if (type == "square") {
173                if (i != -1) {
174                    this.flagList[i].type = "";
175                } else {
176                    let triangle = this.flagList.findIndex(it => it.type == "triangle");
177                    if (triangle !== -1) {
178                        this.flagList[triangle].type = "";
179                        this.draw();
180                        this.notifyHandler && this.notifyHandler(
181                            !this.hoverFlag.hidden ? this.hoverFlag : null,
182                            this.flagList.find(it => it.selected) || null
183                        )
184                        return this.flagList[triangle].time;
185                    }
186                }
187            } else if (type == "inverted") {
188                this.invertedTriangleTime = time
189            }
190            this.draw();
191            this.notifyHandler && this.notifyHandler(
192                !this.hoverFlag.hidden ? this.hoverFlag : null,
193                this.flagList.find(it => it.selected) || null
194            )
195        }
196    }
197
198    removeTriangle(type: string) {
199        if (type == "inverted") {
200            this.invertedTriangleTime = null;
201        } else {
202            let i = this.flagList.findIndex(it => it.type == type)
203            if (i !== -1) {
204                this.flagList.splice(i, 1)
205            }
206        }
207        this.draw();
208        this.notifyHandler && this.notifyHandler(
209            !this.hoverFlag.hidden ? this.hoverFlag : null,
210            this.flagList.find(it => it.selected) || null
211        )
212    }
213
214    drawInvertedTriangle(time: number, color: string = "#000000") {
215        if (time != null && typeof (time) != undefined) {
216            let x = Math.round(this.rulerW * (time - this.range.startNS) / (this.range.endNS - this.range.startNS));
217            this.c.beginPath();
218            this.c.fillStyle = color;
219            this.c.strokeStyle = color;
220            this.c.moveTo(x - 2, 142);
221            this.c.lineTo(x + 2, 142);
222            this.c.lineTo(x, 145);
223            this.c.fill()
224            this.c.closePath()
225            this.c.stroke();
226        }
227    }
228
229    setSlicesMark(startTime: number | null = null, endTime: number | null = null) {
230        if (startTime != null && typeof (startTime) != undefined && endTime != null && typeof (endTime) != undefined) {
231            this.slicesTime = {
232                startTime: startTime <= endTime ? startTime : endTime,
233                endTime: startTime <= endTime ? endTime : startTime,
234                color: null
235            };
236        } else {
237            this.slicesTime = {startTime: null, endTime: null, color: null};
238        }
239        this.range.slicesTime = this.slicesTime;
240        this.draw();
241        this.timerShaftEL?.render();
242    }
243
244    drawSlicesMark(startTime: number | null = null, endTime: number | null = null) {
245        if (startTime != null && typeof (startTime) != undefined && endTime != null && typeof (endTime) != undefined) {
246            let startX = Math.round(this.rulerW * (startTime - this.range.startNS) / (this.range.endNS - this.range.startNS));
247            let endX = Math.round(this.rulerW * (endTime - this.range.startNS) / (this.range.endNS - this.range.startNS));
248            this.c.beginPath();
249            if (document.querySelector<SpApplication>("sp-application")!.dark) {
250                this.c.strokeStyle = "#FFF"
251                this.c.fillStyle = "#FFF"
252                this.range.slicesTime.color = "#FFF"
253            } else {
254                this.c.strokeStyle = "#344596"
255                this.c.fillStyle = "#344596"
256                this.range.slicesTime.color = "#344596"
257            }
258            this.c.moveTo(startX + 9, 132);
259            this.c.lineTo(startX, 141);
260            this.c.lineTo(startX, 132);
261            this.c.lineTo(startX + 9, 132);
262
263            this.c.lineTo(endX - 9, 132);
264            this.c.lineTo(endX, 132);
265            this.c.lineTo(endX, 141);
266            this.c.lineTo(endX - 9, 132);
267            this.c.closePath()
268            this.c.stroke();
269
270
271            this.c.beginPath();
272            if (document.querySelector<SpApplication>("sp-application")!.dark) {
273                this.c.strokeStyle = "#FFF"
274                this.c.fillStyle = "#FFF"
275            } else {
276                this.c.strokeStyle = "#000"
277                this.c.fillStyle = "#000"
278            }
279            let lineWidth = endX - startX;
280            let txt = ns2s((endTime || 0) - (startTime || 0));
281            this.c.moveTo(startX, this.frame.y + 22);
282            this.c.lineTo(endX, this.frame.y + 22);
283            this.c.moveTo(startX, this.frame.y + 22 - 5);
284            this.c.lineTo(startX, this.frame.y + 22 + 5);
285            this.c.moveTo(endX, this.frame.y + 22 - 5);
286            this.c.lineTo(endX, this.frame.y + 22 + 5);
287            let txtWidth = this.c.measureText(txt).width;
288            if (lineWidth > txtWidth) {
289                this.c.fillText(`${txt}`, startX + (lineWidth - txtWidth) / 2, this.frame.y + 20)
290            } else {
291                if (endX + txtWidth >= this.frame.width) {
292                    this.c.fillText(`${txt}`, startX - 5 - txtWidth, this.frame.y + 20)
293                } else {
294                    this.c.fillText(`${txt}`, endX + 5, this.frame.y + 20)
295                }
296            }
297            this.c.stroke();
298            this.c.closePath();
299        }
300    }
301
302    //绘制旗子
303    drawFlag(x: number, color: string = "#999999", isFill: boolean = false, text: string = "", type: string = "") {
304        if (x < 0) return;
305        this.c.beginPath();
306        this.c.fillStyle = color;
307        this.c.strokeStyle = color;
308        this.c.moveTo(x, 125);
309        if (type == "triangle") {
310            this.c.lineTo(x + 15, 131);
311        } else {
312            this.c.lineTo(x + 10, 125);
313            this.c.lineTo(x + 10, 127);
314            this.c.lineTo(x + 18, 127);
315            this.c.lineTo(x + 18, 137);
316            this.c.lineTo(x + 10, 137);
317            this.c.lineTo(x + 10, 135);
318        }
319        this.c.lineTo(x + 2, 135);
320        this.c.lineTo(x + 2, 142);
321        this.c.lineTo(x, 142);
322        this.c.closePath()
323        isFill && this.c.fill()
324        this.c.stroke();
325        if (text !== "") {
326            this.c.font = "10px Microsoft YaHei"
327            const {width} = this.c.measureText(text);
328            this.c.fillStyle = 'rgba(255, 255, 255, 0.8)'; //
329            this.c.fillRect(x + 21, 132, width + 4, 12);
330            this.c.fillStyle = "black";
331            this.c.fillText(text, x + 23, 142);
332            this.c.stroke();
333        }
334    }
335
336    mouseUp(ev: MouseEvent) {
337        if (this.edgeDetection(ev)) {
338            this.flagList.forEach(it => it.selected = false)
339            let x = ev.offsetX - (this.canvas?.offsetLeft || 0)
340            let findFlag = this.flagList.find(it => x >= it.x && x <= it.x + 18)
341            if (findFlag) {
342                findFlag.selected = true;
343            } else {
344                let flagAtRulerTime = Math.round((this.range.endNS - this.range.startNS) * x / this.rulerW)
345                if (flagAtRulerTime > 0 && (this.range.startNS + flagAtRulerTime) < this.range.endNS) {
346                    this.flagList.push(new Flag(x, 125, 18, 18, flagAtRulerTime + this.range.startNS, randomRgbColor(), true));
347                }
348            }
349            this.flagClickHandler && this.flagClickHandler(this.flagList.find(it => it.selected));
350        }
351    }
352
353    mouseMove(ev: MouseEvent) {
354        if (this.edgeDetection(ev)) {
355            let x = ev.offsetX - (this.canvas?.offsetLeft || 0)
356            let flg = this.flagList.find((it) => x >= it.x && x <= it.x + 18);
357            if (flg) {
358                this.hoverFlag.hidden = true;
359            } else {
360                this.hoverFlag.hidden = false;
361                this.hoverFlag.x = x;
362                this.hoverFlag.color = "#999999";
363            }
364        } else {
365            this.hoverFlag.hidden = true;
366        }
367        this.draw();
368        this.notifyHandler && this.notifyHandler(
369            !this.hoverFlag.hidden ? this.hoverFlag : null,
370            this.flagList.find(it => it.selected) || null
371        )
372    }
373
374
375    edgeDetection(ev: MouseEvent): boolean {
376        let x = ev.offsetX - (this.canvas?.offsetLeft || 0)
377        let y = ev.offsetY - (this.canvas?.offsetTop || 0)
378        SportRuler.isMouseInSportRuler = x > 0 && x < this.canvas!.offsetWidth && ev.offsetY - this.frame.y > 20 && ev.offsetY - this.frame.y < this.frame.height;
379        return SportRuler.isMouseInSportRuler;
380    }
381}
382