• 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';
17import { Rect } from './Rect';
18import { TimeRange } from './RangeRuler';
19import { Flag } from './Flag';
20import { ns2s, ns2x, randomRgbColor, TimerShaftElement } from '../TimerShaftElement';
21import { TraceRow } from '../base/TraceRow';
22import { SpApplication } from '../../../SpApplication';
23import { Utils } from '../base/Utils';
24
25export enum StType {
26  TEMP, //临时的
27  PERM, // 永久的
28}
29
30export class SlicesTime {
31  private _id: string;
32  startTime: number = 0;
33  endTime: number = 0;
34  startNS: number;
35  endNS: number;
36  color: string = '';
37  startX: number;
38  endX: number;
39  selected: boolean = true;
40  hidden: boolean = false;
41  text: string = '';
42  type: number = StType.PERM; // 默认类型为永久的
43  constructor(
44    startTime: number = 0,
45    endTime: number = 0,
46    startNS: number,
47    endNS: number,
48    startX: number,
49    endX: number,
50    color: string,
51    text: string,
52    selected: boolean = true
53  ) {
54    this._id = Utils.uuid();
55    this.startTime = startTime;
56    this.endTime = endTime;
57    this.startNS = startNS;
58    this.endNS = endNS;
59    this.color = color;
60    this.startX = startX;
61    this.endX = endX;
62    this.text = text;
63    this.selected = selected;
64  }
65
66  get id(): string {
67    return this._id;
68  }
69}
70
71const TRIWIDTH: number = 10; // 定义三角形的边长
72const TEXT_FONT: string = '12px Microsoft YaHei'; // 文本字体格式
73export class SportRuler extends Graph {
74  static isMouseInSportRuler = false;
75  public flagList: Array<Flag> = [];
76  public slicesTimeList: Array<SlicesTime> = [];
77  isRangeSelect: boolean = false; //region selection
78  private hoverFlag: Flag = new Flag(-1, 0, 0, 0, 0);
79  private lineColor: string | null = null;
80  private rulerW = 0;
81  private _range: TimeRange = {} as TimeRange;
82  private readonly notifyHandler:
83    | ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void)
84    | undefined;
85  private readonly flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined;
86  private readonly rangeClickHandler: ((sliceTime: SlicesTime | undefined | null) => void) | undefined;
87  private invertedTriangleTime: number | null | undefined = null;
88  private slicesTime: {
89    startTime: number | null | undefined;
90    endTime: number | null | undefined;
91    color: string | null;
92  } | null = {
93      startTime: null,
94      endTime: null,
95      color: null,
96    };
97  private timerShaftEL: TimerShaftElement | undefined | null;
98  private timeArray: Array<number> = [];
99  private countArray: Array<number> = [];
100  private durArray: Array<number> = [];
101  private mouseIn: boolean = false;
102  constructor(
103    timerShaftEL: TimerShaftElement,
104    frame: Rect,
105    notifyHandler: (hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void,
106    flagClickHandler: (flag: Flag | undefined | null) => void,
107    rangeClickHandler: (sliceTime: SlicesTime | undefined | null) => void
108  ) {
109    super(timerShaftEL.canvas, timerShaftEL.ctx!, frame);
110    this.notifyHandler = notifyHandler;
111    this.flagClickHandler = flagClickHandler;
112    this.rangeClickHandler = rangeClickHandler;
113    this.timerShaftEL = timerShaftEL;
114  }
115
116  get range(): TimeRange {
117    return this._range;
118  }
119
120  set range(value: TimeRange) {
121    this._range = value;
122    this.draw();
123  }
124
125  set times(timeArray: Array<number>) {
126    this.timeArray = timeArray;
127  }
128
129  set counts(countArray: Array<number>) {
130    this.countArray = countArray;
131  }
132
133  set durations(durArray: Array<number>) {
134    this.durArray = durArray;
135  }
136
137  modifyFlagList(flag: Flag | null | undefined): void {
138    if (flag) {
139      if (flag.hidden) {
140        let i = this.flagList.findIndex((it) => it.time === flag.time);
141        this.flagList.splice(i, 1);
142      } else {
143        let i = this.flagList.findIndex((it) => it.time === flag.time);
144        this.flagList[i] = flag;
145      }
146    } else {
147      this.flagList.forEach((it) => (it.selected = false));
148    }
149    this.draw();
150  }
151
152  modifySicesTimeList(slicestime: SlicesTime | null | undefined): void {
153    if (slicestime) {
154      let i = this.slicesTimeList.findIndex((it) => it.id === slicestime.id);
155      if (slicestime.hidden) {
156        this.slicesTimeList.splice(i, 1);
157        let selectionParam = this.timerShaftEL?.selectionMap.get(slicestime.id);
158        this.timerShaftEL?.selectionMap.delete(slicestime.id);
159        if (selectionParam) {
160          this.timerShaftEL?.selectionList.splice(this.timerShaftEL?.selectionList.indexOf(selectionParam), 1);
161        }
162      } else {
163        this.slicesTimeList[i] = slicestime;
164      }
165    } else {
166      this.slicesTimeList.forEach((it) => (it.selected = false));
167    }
168    this.draw();
169  }
170
171  draw(): void {
172    this.draBasicsRuler();
173    //绘制旗子
174    this.flagList.forEach((flagObj: Flag, b) => {
175      if (flagObj.time >= this.range.startNS && flagObj.time <= this.range.endNS) {
176        flagObj.x = Math.round(
177          (this.rulerW * (flagObj.time - this.range.startNS)) / (this.range.endNS - this.range.startNS)
178        );
179        this.drawFlag(flagObj.x, flagObj.color, flagObj.selected, flagObj.text, flagObj.type);
180      }
181    });
182    !this.hoverFlag.hidden && this.drawFlag(this.hoverFlag.x, this.hoverFlag.color, true, this.hoverFlag.text);
183    //If region selection is enabled, the serial number draws a line on the axis to show the length of the box selection
184    if (this.isRangeSelect) {
185      this.drawRangeSelect();
186    }
187    if (this.invertedTriangleTime !== null && typeof this.invertedTriangleTime !== undefined) {
188      this.drawInvertedTriangle(
189        this.invertedTriangleTime!,
190        document.querySelector<SpApplication>('sp-application')!.dark ? '#FFFFFF' : '#000000'
191      );
192    }
193    this.slicesTimeList.forEach((slicesTime) => {
194      this.drawSlicesMarks(slicesTime);
195    });
196  }
197
198  draBasicsRuler(): void {
199    this.rulerW = this.canvas!.offsetWidth;
200    this.context2D.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height + 1);
201    this.context2D.beginPath();
202    this.lineColor = window.getComputedStyle(this.canvas!, null).getPropertyValue('color');
203    this.context2D.lineWidth = 1;
204    this.context2D.strokeStyle = this.lineColor; //"#dadada"
205    this.context2D.moveTo(this.frame.x, this.frame.y);
206    this.context2D.lineTo(this.frame.x + this.frame.width, this.frame.y);
207    this.context2D.stroke();
208    this.context2D.closePath();
209    this.context2D.beginPath();
210    this.context2D.strokeStyle = '#999999';
211    this.context2D.lineWidth = 3;
212    this.context2D.moveTo(this.frame.x, this.frame.y);
213    this.context2D.lineTo(this.frame.x, this.frame.y + this.frame.height);
214    this.context2D.stroke();
215    this.context2D.closePath();
216    this.context2D.beginPath();
217    this.context2D.strokeStyle = this.lineColor; //"#999999"
218    this.context2D.lineWidth = 1;
219    this.context2D.fillStyle = '#999999';
220    this.context2D.font = '8px sans-serif';
221    this.range.xs?.forEach((item, index) => {
222      this.context2D.moveTo(item, this.frame.y);
223      this.context2D.lineTo(item, this.frame.y + this.frame.height);
224      this.context2D.fillText(`${this.range.xsTxt[index]}`, item + 3, this.frame.y + 12);
225    });
226    this.context2D.stroke();
227    this.context2D.closePath();
228  }
229
230  private initRangeSelect(): void {
231    let range = TraceRow.rangeSelectObject;
232    this.context2D.beginPath();
233    if (document.querySelector<SpApplication>('sp-application')!.dark) {
234      this.context2D.strokeStyle = '#FFF';
235      this.context2D.fillStyle = '#FFF';
236    } else {
237      this.context2D.strokeStyle = '#000';
238      this.context2D.fillStyle = '#000';
239    }
240    let startX = ns2x(range?.startNS || 0, this.range.startNS, this.range.endNS, this.range.totalNS, this.frame);
241    let endX = ns2x(range?.endNS || 0, this.range.startNS, this.range.endNS, this.range.totalNS, this.frame);
242    let lineWidth = endX - startX;
243    let txt = ns2s((range?.endNS || 0) - (range?.startNS || 0));
244    this.context2D.moveTo(startX, this.frame.y + 22);
245    this.context2D.lineTo(endX, this.frame.y + 22);
246    this.context2D.moveTo(startX, this.frame.y + 22 - 5);
247    this.context2D.lineTo(startX, this.frame.y + 22 + 5);
248    this.context2D.moveTo(endX, this.frame.y + 22 - 5);
249    this.context2D.lineTo(endX, this.frame.y + 22 + 5);
250    let textWidth = this.context2D.measureText(txt).width;
251    if (lineWidth > textWidth) {
252      this.context2D.fillText(`${txt}`, startX + (lineWidth - textWidth) / 2, this.frame.y + 20);
253    } else {
254      if (endX + textWidth >= this.frame.width) {
255        this.context2D.fillText(`${txt}`, startX - 5 - textWidth, this.frame.y + 20);
256      } else {
257        this.context2D.fillText(`${txt}`, endX + 5, this.frame.y + 20);
258      }
259    }
260  }
261
262  drawRangeSelect(): void {
263    this.initRangeSelect();
264    if (this.timeArray.length > 0 && TraceRow.rangeSelectObject) {
265      // 页面可视框选区域的宽度
266      let rangeSelectWidth = TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!;
267      // 每段宽度必须大于总数的宽度
268      // 10,2+8,2是线的宽度,8是留的空隙,不然很不好看
269      // 分section段
270      let section = Math.floor(
271        rangeSelectWidth / (this.context2D.measureText(String(this.timeArray.length)).width + 10)
272      );
273      // 最多画二十段
274      section < 20 ? (section = section) : (section = 20);
275      // 最少一段
276      section < 1 ? (section = 1) : (section = section);
277      // 框选泳道图并放大左右移动后,框选的部分区域会移出可视区域,
278      // TraceRow.rangeSelectObject的开始结束时间仍然是框选时的时间,要和this.range进行比较取可视框选范围的时间
279      let startNS;
280      let endNS;
281      TraceRow.rangeSelectObject!.startNS! > this.range.startNS
282        ? (startNS = TraceRow.rangeSelectObject!.startNS!)
283        : (startNS = this.range.startNS);
284      TraceRow.rangeSelectObject!.endNS! > this.range.endNS
285        ? (endNS = this.range.endNS)
286        : (endNS = TraceRow.rangeSelectObject!.endNS!);
287      // 每一格的时间
288      let sectionTime = (endNS - startNS) / section;
289      let countArr = new Uint32Array(section);
290      let count: number = 0; //某段时间的调用栈数量
291      const isEbpf = this.durArray && this.durArray.length > 0;
292      const processTimeArray = new Array<Boolean>(this.timeArray.length).fill(false);
293      for (let i = 1; i <= section; i++) {
294        count = 0;
295        for (let j = 0; j < this.timeArray.length; j++) {
296          if (processTimeArray[j]) {
297            continue;
298          }
299          let inRange = this.freshInRange(j, startNS, sectionTime, i);
300          // 如果该时间小于第一个分割点的时间,计数加1,从而算出一段时间的时间数量
301          if (inRange) {
302            // nm统计模式则统计每个时间的count
303            if (this.countArray && this.countArray[j] > 0) {
304              count += this.countArray[j];
305            } else {
306              count++;
307            }
308            countArr[i - 1] = count;
309            processTimeArray[j] = true;
310          } else {
311            // 如果遇到大于分割点的时间,就跳过该分割点,计算下一个分割点的时间点数量
312            continue;
313          }
314        }
315        this.drawRangeSelectFillText(rangeSelectWidth, section, i, countArr);
316      }
317    }
318    this.context2D.stroke();
319    this.context2D.closePath();
320  }
321
322  private drawRangeSelectFillText(rangeSelectWidth: number, section: number, i: number, countArr: Uint32Array): void {
323    let x = TraceRow.rangeSelectObject!.startX! + (rangeSelectWidth / section) * i;
324    if (i !== section) {
325      this.context2D.moveTo(x, this.frame.y + 22);
326      this.context2D.lineTo(x, this.frame.y + 22 + 5);
327    }
328    // 每一格的数量的数字宽度
329    let countTextWidth = this.context2D.measureText(String(countArr[i - 1])).width;
330    // 文本的开始位置 = 框选的开始位置 + 格数 + (一格的宽度 - 文本的宽度) / 2
331    let textY =
332      TraceRow.rangeSelectObject!.startX! +
333      (rangeSelectWidth / section) * (i - 1) +
334      (rangeSelectWidth / section - countTextWidth) / 2;
335    this.context2D.fillStyle = `#f00`;
336    this.context2D.font = `12px sans-serif`;
337    this.context2D.fillText(String(countArr[i - 1]), textY, this.frame.y + 22 + 12);
338  }
339
340  private freshInRange(j: number, startNS: number, sectionTime: number, i: number): boolean {
341    let inRange = false;
342    const itemTime = this.timeArray[j];
343    // ebpf需要考虑dur
344    if (this.durArray && this.durArray.length > 0) {
345      const dur = this.durArray[j];
346      if (itemTime === this.range.endNS) {
347        // 如果时间点刚好和时间轴结束时间一样会导致该时间点没有计数,所以此情况需要的判断条件要多个等号
348        inRange =
349          itemTime >= startNS + sectionTime * (i - 1) &&
350          itemTime <= startNS + sectionTime * i &&
351          itemTime >= this.range.startNS &&
352          itemTime <= this.range.endNS;
353      } else {
354        // 判断时间点是否在某时间段内时,一般情况下和左边界相同算在该时间段,和右边界相同算在下一段,
355        inRange =
356          itemTime + dur >= startNS + sectionTime * (i - 1) &&
357          itemTime < startNS + sectionTime * i &&
358          itemTime + dur >= this.range.startNS &&
359          itemTime < this.range.endNS;
360      }
361    } else {
362      if (itemTime === this.range.endNS) {
363        inRange =
364          itemTime >= startNS + sectionTime * (i - 1) &&
365          itemTime <= startNS + sectionTime * i &&
366          itemTime >= this.range.startNS &&
367          itemTime <= this.range.endNS;
368      } else {
369        inRange =
370          itemTime >= startNS + sectionTime * (i - 1) &&
371          itemTime < startNS + sectionTime * i &&
372          itemTime >= this.range.startNS &&
373          itemTime < this.range.endNS;
374      }
375    }
376    return inRange;
377  }
378
379  drawTriangle(time: number, type: string): unknown {
380    let num;
381    if (time !== null && typeof time !== undefined) {
382      let i = this.flagList.findIndex((it) => it.time === time);
383      if (type === 'triangle') {
384        let triangle = this.flagList.findIndex((it) => it.type === type);
385        if (i !== -1) {
386          if (triangle !== -1) {
387            this.flagList[i].type === '' ? this.flagList.splice(triangle, 1) : '';
388          }
389        } else {
390          if (triangle === -1) {
391            this.flagList.forEach((it) => (it.selected = false));
392            this.flagList.push(new Flag(0, 125, 18, 18, time, randomRgbColor(), '', true, 'triangle'));
393          } else {
394            this.flagList.forEach((it) => (it.selected = false));
395            this.flagList[triangle].time = time;
396            this.flagList[triangle].selected = true;
397          }
398        }
399      } else if (type === 'square') {
400        if (i !== -1) {
401          this.flagList[i].type = '';
402        } else {
403          let triangle = this.flagList.findIndex((it) => it.type === 'triangle');
404          if (triangle !== -1) {
405            this.flagList[triangle].type = '';
406            this.draw();
407            this.flagChangeHandler('1');
408            num = this.flagList[triangle].time;
409          }
410        }
411      } else if (type === 'inverted') {
412        this.invertedTriangleTime = time;
413      }
414      this.draw();
415      this.flagChangeHandler('2');
416    }
417    return num;
418  }
419
420  flagChangeHandler(from?: string): void {
421    this.notifyHandler &&
422      this.notifyHandler(
423        !this.hoverFlag.hidden ? this.hoverFlag : null,
424        this.flagList.find((it) => it.selected) || null
425      );
426  }
427
428  removeTriangle(type: string): void {
429    if (type === 'inverted') {
430      if (this.invertedTriangleTime !== null) {
431        this.flagChangeHandler('3');
432      }
433      this.invertedTriangleTime = null;
434    } else {
435      this.flagChangeHandler('3');
436    }
437    this.draw();
438  }
439
440  drawInvertedTriangle(time: number, color: string = '#000000'): void {
441    if (time !== null && typeof time !== undefined) {
442      let x = Math.round((this.rulerW * (time - this.range.startNS)) / (this.range.endNS - this.range.startNS));
443      this.context2D.beginPath();
444      this.context2D.fillStyle = color;
445      this.context2D.strokeStyle = color;
446      // ----------------修改小倒三角位置的绘制---------------------
447      if (sessionStorage.getItem('expand') === 'true') {
448        //展开
449        this.context2D.moveTo(x - 3, 141);
450        this.context2D.lineTo(x + 3, 141);
451        this.context2D.lineTo(x, 145);
452      } else if (sessionStorage.getItem('expand') === 'false') {
453        this.context2D.moveTo(x - 3, 141 - Number(sessionStorage.getItem('foldHeight')));
454        this.context2D.lineTo(x + 3, 141 - Number(sessionStorage.getItem('foldHeight')));
455        this.context2D.lineTo(x, 145 - Number(sessionStorage.getItem('foldHeight')));
456      }
457      this.context2D.fill();
458      this.context2D.closePath();
459      this.context2D.stroke();
460    }
461  }
462
463  setSlicesMark(
464    startTime: number | null = null,
465    endTime: number | null = null,
466    shiftKey: boolean | null = null
467  ): SlicesTime | null {
468    let findSlicesTime = this.slicesTimeList.find((it) => it.startTime === startTime && it.endTime === endTime);
469    if (findSlicesTime && this.slicesTimeList.length > 0) {
470      return null;
471    } else {
472      let newSlicestime: SlicesTime | null = null;
473      if (startTime !== null && typeof startTime !== undefined && endTime !== null && typeof endTime !== undefined) {
474        this.slicesTime = {
475          startTime: startTime <= endTime ? startTime : endTime,
476          endTime: startTime <= endTime ? endTime : startTime,
477          color: null,
478        };
479        let startX = Math.round(
480          (this.rulerW * (startTime - this.range.startNS)) / (this.range.endNS - this.range.startNS)
481        );
482        let endX = Math.round((this.rulerW * (endTime - this.range.startNS)) / (this.range.endNS - this.range.startNS));
483        this.slicesTime.color = randomRgbColor() || '#ff0000';
484        let text = '';
485        newSlicestime = new SlicesTime(
486          this.slicesTime.startTime || 0,
487          this.slicesTime.endTime || 0,
488          this.range.startNS,
489          this.range.endNS,
490          startX,
491          endX,
492          this.slicesTime.color,
493          text,
494          true
495        );
496        if (!shiftKey) {
497          this.clearTempSlicesTime(); // 清除临时对象
498          // 如果没有按下shift键,则把当前slicestime对象的类型设为临时类型。
499          newSlicestime.type = StType.TEMP;
500        }
501        this.slicesTimeList.forEach((slicestime) => (slicestime.selected = false));
502        newSlicestime.selected = true;
503        this.slicesTimeList.push(newSlicestime);
504      } else {
505        this.clearTempSlicesTime(); // 清除临时对象
506        this.slicesTime = { startTime: null, endTime: null, color: null };
507      }
508      this.range.slicesTime = this.slicesTime;
509      this.draw();
510      this.timerShaftEL?.render();
511      return newSlicestime;
512    }
513  }
514
515  // 清除临时对象
516  clearTempSlicesTime(): void {
517    // 清除以前放入的临时对象
518    this.slicesTimeList.forEach((slicestime, index) => {
519      slicestime.selected = false;
520      if (slicestime.type === StType.TEMP) {
521        this.slicesTimeList.splice(index, 1);
522        let selectionParam = this.timerShaftEL?.selectionMap.get(slicestime.id);
523        if (selectionParam && selectionParam !== undefined) {
524          this.timerShaftEL?.selectionList.splice(this.timerShaftEL?.selectionList.indexOf(selectionParam), 1);
525          this.timerShaftEL?.selectionMap.delete(slicestime.id);
526        }
527      }
528    });
529  }
530
531  clearHoverFlag(): void {
532    this.hoverFlag.hidden = true;
533  }
534
535  showHoverFlag(): void {
536    this.hoverFlag.hidden = false;
537  }
538
539  private drawSlicesTimeText(slicesTime: SlicesTime, startX: number, endX: number): number[] {
540    this.context2D.beginPath();
541    this.context2D.strokeStyle = slicesTime.color;
542    this.context2D.fillStyle = slicesTime.color;
543    this.range.slicesTime.color = slicesTime.color; //紫色
544    // ---------------------------------------修改标记绘制位置-------------------------
545    if (sessionStorage.getItem('expand') === 'true') {
546      //展开
547      this.context2D.moveTo(startX + TRIWIDTH, 132);
548      this.context2D.lineTo(startX, 142);
549      this.context2D.lineTo(startX, 132);
550      this.context2D.lineTo(startX + TRIWIDTH, 132);
551
552      this.context2D.lineTo(endX - TRIWIDTH, 132);
553      this.context2D.lineTo(endX, 132);
554      this.context2D.lineTo(endX, 142);
555      this.context2D.lineTo(endX - TRIWIDTH, 132);
556    } else if (sessionStorage.getItem('expand') === 'false') {
557      this.context2D.moveTo(startX + TRIWIDTH, 132 - Number(sessionStorage.getItem('foldHeight')));
558      this.context2D.lineTo(startX, 142 - Number(sessionStorage.getItem('foldHeight')));
559      this.context2D.lineTo(startX, 132 - Number(sessionStorage.getItem('foldHeight')));
560      this.context2D.lineTo(startX + TRIWIDTH, 132 - Number(sessionStorage.getItem('foldHeight')));
561
562      this.context2D.lineTo(endX - TRIWIDTH, 132 - Number(sessionStorage.getItem('foldHeight')));
563      this.context2D.lineTo(endX, 132 - Number(sessionStorage.getItem('foldHeight')));
564      this.context2D.lineTo(endX, 142 - Number(sessionStorage.getItem('foldHeight')));
565      this.context2D.lineTo(endX - TRIWIDTH, 132 - Number(sessionStorage.getItem('foldHeight')));
566    }
567    this.context2D.closePath();
568    slicesTime.selected && this.context2D.fill();
569    this.context2D.stroke();
570    this.context2D.beginPath();
571    if (document.querySelector<SpApplication>('sp-application')!.dark) {
572      this.context2D.strokeStyle = '#FFF';
573      this.context2D.fillStyle = '#FFF';
574    } else {
575      this.context2D.strokeStyle = '#000';
576      this.context2D.fillStyle = '#000';
577    }
578    let lineWidth = endX - startX;
579    let txt = ns2s((slicesTime.endTime || 0) - (slicesTime.startTime || 0));
580    this.context2D.moveTo(startX, this.frame.y + 22);
581    this.context2D.lineTo(endX, this.frame.y + 22);
582    this.context2D.moveTo(startX, this.frame.y + 22 - 5);
583    this.context2D.lineTo(startX, this.frame.y + 22 + 5);
584    this.context2D.moveTo(endX, this.frame.y + 22 - 5);
585    this.context2D.lineTo(endX, this.frame.y + 22 + 5);
586    let txtWidth = this.context2D.measureText(txt).width;
587    this.context2D.fillStyle = '#FFF'; //为了解决文字重叠问题。在时间刻度的文字下面绘制一个小方块
588    this.context2D.fillRect(startX + (lineWidth - txtWidth) / 2, this.frame.y + 10, txtWidth + 2, 10);
589    this.context2D.fillStyle = 'black';
590    if (lineWidth > txtWidth) {
591      this.context2D.fillText(`${txt}`, startX + (lineWidth - txtWidth) / 2, this.frame.y + 20);
592    } else {
593      if (endX + txtWidth >= this.frame.width) {
594        this.context2D.fillText(`${txt}`, startX - 5 - txtWidth, this.frame.y + 20);
595      } else {
596        this.context2D.fillText(`${txt}`, endX + 5, this.frame.y + 20);
597      }
598    }
599    this.context2D.stroke();
600    this.context2D.closePath();
601    return [lineWidth, txtWidth];
602  }
603
604  drawSlicesMarks(slicesTime: SlicesTime): void {
605    if (
606      slicesTime.startTime !== null &&
607      typeof slicesTime.startTime !== undefined &&
608      slicesTime.endTime !== null &&
609      typeof slicesTime.endTime !== undefined
610    ) {
611      let startX = Math.round(
612        (this.rulerW * (slicesTime.startTime - this.range.startNS)) / (this.range.endNS - this.range.startNS)
613      );
614      let endX = Math.round(
615        (this.rulerW * (slicesTime.endTime - this.range.startNS)) / (this.range.endNS - this.range.startNS)
616      );
617      // 放大、缩小、左右移动之后重置小三角的x轴坐标
618      slicesTime.startX = startX;
619      slicesTime.endX = endX;
620      let [lineWidth, txtWidth] = this.drawSlicesTimeText(slicesTime, startX, endX);
621      // 画框选的备注文字---begin-----------------
622      let text = slicesTime.text;
623      if (text) {
624        this.context2D.beginPath();
625        if (document.querySelector<SpApplication>('sp-application')!.dark) {
626          this.context2D.strokeStyle = '#FFF';
627          this.context2D.fillStyle = '#FFF';
628        } else {
629          this.context2D.strokeStyle = '#000';
630          this.context2D.fillStyle = '#000';
631        }
632
633        let textWidth = this.context2D.measureText(text).width;
634        if (textWidth > 0) {
635          this.context2D.fillStyle = 'black';
636          this.context2D.font = TEXT_FONT;
637          if (lineWidth > txtWidth) {
638            this.context2D.fillText(`${text}`, startX + (lineWidth - textWidth) / 2, this.frame.y + 43);
639          } else {
640            if (endX + textWidth >= this.frame.width) {
641              this.context2D.fillText(`${text}`, startX - 5 - textWidth, this.frame.y + 43);
642            } else {
643              this.context2D.fillText(`${text}`, endX + 5, this.frame.y + 43);
644            }
645          }
646        }
647        this.context2D.stroke();
648        this.context2D.closePath();
649      }
650      // 画框选的备注文字---end-----------------
651    }
652  }
653
654  //绘制旗子
655  drawFlag(
656    x: number,
657    color: string = '#999999',
658    isFill: boolean = false,
659    textStr: string = '',
660    type: string = ''
661  ): void {
662    if (x < 0) {
663      return;
664    }
665    this.context2D.beginPath();
666    this.context2D.fillStyle = color;
667    this.context2D.strokeStyle = color;
668    // ------------------修改旗子位置----------------------------
669    if (sessionStorage.getItem('expand') === 'true') {
670      this.context2D.moveTo(x, 125);
671      if (type === 'triangle') {
672        this.context2D.lineTo(x + 15, 131);
673      } else {
674        this.context2D.lineTo(x + 10, 125);
675        this.context2D.lineTo(x + 10, 127);
676        this.context2D.lineTo(x + 18, 127);
677        this.context2D.lineTo(x + 18, 137);
678        this.context2D.lineTo(x + 10, 137);
679        this.context2D.lineTo(x + 10, 135);
680      }
681      this.context2D.lineTo(x + 2, 135);
682      this.context2D.lineTo(x + 2, 142);
683      this.context2D.lineTo(x, 142);
684    } else {
685      this.context2D.moveTo(x, 125 - Number(sessionStorage.getItem('foldHeight')));
686      if (type === 'triangle') {
687        this.context2D.lineTo(x + 15, 131 - Number(sessionStorage.getItem('foldHeight')));
688      } else {
689        this.context2D.lineTo(x + 10, 125 - Number(sessionStorage.getItem('foldHeight')));
690        this.context2D.lineTo(x + 10, 127 - Number(sessionStorage.getItem('foldHeight')));
691        this.context2D.lineTo(x + 18, 127 - Number(sessionStorage.getItem('foldHeight')));
692        this.context2D.lineTo(x + 18, 137 - Number(sessionStorage.getItem('foldHeight')));
693        this.context2D.lineTo(x + 10, 137 - Number(sessionStorage.getItem('foldHeight')));
694        this.context2D.lineTo(x + 10, 135 - Number(sessionStorage.getItem('foldHeight')));
695      }
696      this.context2D.lineTo(x + 2, 135 - Number(sessionStorage.getItem('foldHeight')));
697      this.context2D.lineTo(x + 2, 142 - Number(sessionStorage.getItem('foldHeight')));
698      this.context2D.lineTo(x, 142 - Number(sessionStorage.getItem('foldHeight')));
699    }
700    this.context2D.closePath();
701    isFill && this.context2D.fill();
702    this.context2D.stroke();
703    if (textStr !== '') {
704      this.context2D.font = TEXT_FONT;
705      const { width } = this.context2D.measureText(textStr);
706      this.context2D.fillStyle = 'rgba(255, 255, 255, 0.8)'; //
707      // -------------------修改旗子上的字位置-----------------------
708      if (sessionStorage.getItem('expand') === 'true') {
709        this.context2D.fillRect(x + 21, 132, width + 4, 12);
710        this.context2D.fillStyle = 'black';
711        this.context2D.fillText(textStr, x + 23, 142);
712      } else {
713        this.context2D.fillRect(x + 21, 132 - Number(sessionStorage.getItem('foldHeight')), width + 4, 12);
714        this.context2D.fillStyle = 'black';
715        this.context2D.fillText(textStr, x + 23, 142 - Number(sessionStorage.getItem('foldHeight')));
716      }
717      this.context2D.stroke();
718    }
719  }
720
721  /**
722   * 查找鼠标所在位置是否存在"帽子"对象,为了操作方便,框选时把三角形的边长宽度左右各加一个像素。
723   * @param x 水平坐标值
724   * @returns
725   */
726  findSlicesTime(x: number, y: number): SlicesTime | null {
727    // --------------------------修改旗子和标记的小三角重叠时的情况-------------------
728    let slicestime;
729    if (sessionStorage.getItem('expand') === 'false') {
730      //折叠
731      slicestime = this.slicesTimeList.find((slicesTime) => {
732        return (
733          ((x >= slicesTime.startX - 1 && x <= slicesTime.startX + TRIWIDTH + 1) || // 选中了帽子的左边三角形区域
734            (x >= slicesTime.endX - TRIWIDTH - 1 && x <= slicesTime.endX + 1)) && // 选中了帽子的右边三角形区域
735          y >= 132 - Number(sessionStorage.getItem('foldHeight')) &&
736          y <= 142 - Number(sessionStorage.getItem('foldHeight'))
737        );
738      });
739    } else if (sessionStorage.getItem('expand') === 'true') {
740      slicestime = this.slicesTimeList.find((slicesTime) => {
741        return (
742          ((x >= slicesTime.startX - 1 && x <= slicesTime.startX + TRIWIDTH + 1) || // 选中了帽子的左边三角形区域
743            (x >= slicesTime.endX - TRIWIDTH - 1 && x <= slicesTime.endX + 1)) && // 选中了帽子的右边三角形区域
744          y >= 132 &&
745          y <= 142
746        );
747      });
748    }
749    if (!slicestime) {
750      return null;
751    }
752    return slicestime;
753  }
754
755  mouseUp(ev: MouseEvent): void {
756    if (this.edgeDetection(ev)) {
757      let x = ev.offsetX - (this.canvas?.offsetLeft || 0); // 鼠标点击的x轴坐标
758      let y = ev.offsetY; // 鼠标点击的y轴坐标
759      let findSlicestime = this.findSlicesTime(x, y); // 查找帽子
760      if (findSlicestime) {
761        // 如果找到帽子,则选中帽子。
762        this.slicesTimeList.forEach((slicestime) => (slicestime.selected = false));
763        findSlicestime.selected = true;
764        this.rangeClickHandler && this.rangeClickHandler(findSlicestime);
765      } else {
766        // 如果没有找到帽子,则绘制旗子,此处避免旗子和帽子重叠。
767        // 查找旗子
768        let findFlag = this.flagList.find(
769          (it) => (x >= it.x && x <= it.x + 18 && it.type !== 'triangle') || (x === it.x && it.type === 'triangle')
770        );
771        this.flagList.forEach((it) => (it.selected = false));
772        if (findFlag) {
773          findFlag.selected = true;
774        } else {
775          let flagAtRulerTime = Math.round(((this.range.endNS - this.range.startNS) * x) / this.rulerW);
776          let flag = new Flag(x, 125, 18, 18, flagAtRulerTime + this.range.startNS, randomRgbColor(), '', true, '');
777          this.flagList.push(flag);
778        }
779        this.flagClickHandler && this.flagClickHandler(this.flagList.find((it) => it.selected)); // 绘制旗子
780      }
781    }
782  }
783
784  mouseMove(ev: MouseEvent): void {
785    if (this.edgeDetection(ev)) {
786      this.mouseIn = true;
787      let x = ev.offsetX - (this.canvas?.offsetLeft || 0);
788      let flg = this.flagList.find((it) => x >= it.x && x <= it.x + 18);
789      if (flg) {
790        this.hoverFlag.hidden = true;
791      } else {
792        this.hoverFlag.hidden = false;
793        this.hoverFlag.x = x;
794        this.hoverFlag.color = '#999999';
795      }
796      this.flagChangeHandler('4');
797      this.draw();
798    } else {
799      this.hoverFlag.hidden = true;
800    }
801
802  }
803
804  mouseOut(ev: MouseEvent): void {
805    if (!this.hoverFlag.hidden) {
806      this.hoverFlag.hidden = true;
807      if (this.mouseIn) {
808        this.flagChangeHandler('5');
809      }
810    }
811    if (this.mouseIn) {
812      this.mouseIn = false;
813      this.draw();
814    }
815  }
816
817  edgeDetection(ev: MouseEvent): boolean {
818    let x = ev.offsetX - (this.canvas?.offsetLeft || 0);
819    let y = ev.offsetY - (this.canvas?.offsetTop || 0);
820    SportRuler.isMouseInSportRuler =
821      x > 0 &&
822      x < this.canvas!.offsetWidth &&
823      ev.offsetY - this.frame.y > 20 &&
824      ev.offsetY - this.frame.y < this.frame.height;
825    return SportRuler.isMouseInSportRuler;
826  }
827}
828