/* * Copyright (C) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Graph } from './Graph'; import { Rect } from './Rect'; import { TimeRange } from './RangeRuler'; import { Flag } from './Flag'; import { ns2s, ns2x, randomRgbColor, TimerShaftElement } from '../TimerShaftElement'; import { TraceRow } from '../base/TraceRow'; import { SpApplication } from '../../../SpApplication'; import { Utils } from '../base/Utils'; export enum StType { TEMP, //临时的 PERM, // 永久的 } export class SlicesTime { private _id: string; startTime: number = 0; endTime: number = 0; startNS: number; endNS: number; color: string = ''; startX: number; endX: number; selected: boolean = true; hidden: boolean = false; text: string = ''; type: number = StType.PERM; // 默认类型为永久的 constructor( startTime: number = 0, endTime: number = 0, startNS: number, endNS: number, startX: number, endX: number, color: string, text: string, selected: boolean = true ) { this._id = Utils.uuid(); this.startTime = startTime; this.endTime = endTime; this.startNS = startNS; this.endNS = endNS; this.color = color; this.startX = startX; this.endX = endX; this.text = text; this.selected = selected; } get id(): string { return this._id; } } const TRIWIDTH: number = 10; // 定义三角形的边长 const TEXT_FONT: string = '12px Microsoft YaHei'; // 文本字体格式 export class SportRuler extends Graph { static isMouseInSportRuler = false; public flagList: Array = []; public slicesTimeList: Array = []; isRangeSelect: boolean = false; //region selection private hoverFlag: Flag = new Flag(-1, 0, 0, 0, 0); private lineColor: string | null = null; private rulerW = 0; private _range: TimeRange = {} as TimeRange; private readonly notifyHandler: | ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined; private readonly flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined; private readonly rangeClickHandler: ((sliceTime: SlicesTime | undefined | null) => void) | undefined; private invertedTriangleTime: number | null | undefined = null; private slicesTime: { startTime: number | null | undefined; endTime: number | null | undefined; color: string | null; } | null = { startTime: null, endTime: null, color: null, }; private timerShaftEL: TimerShaftElement | undefined | null; private timeArray: Array = []; private countArray: Array = []; private durArray: Array = []; private mouseIn: boolean = false; constructor( timerShaftEL: TimerShaftElement, frame: Rect, notifyHandler: (hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void, flagClickHandler: (flag: Flag | undefined | null) => void, rangeClickHandler: (sliceTime: SlicesTime | undefined | null) => void ) { super(timerShaftEL.canvas, timerShaftEL.ctx!, frame); this.notifyHandler = notifyHandler; this.flagClickHandler = flagClickHandler; this.rangeClickHandler = rangeClickHandler; this.timerShaftEL = timerShaftEL; } get range(): TimeRange { return this._range; } set range(value: TimeRange) { this._range = value; this.draw(); } set times(timeArray: Array) { this.timeArray = timeArray; } set counts(countArray: Array) { this.countArray = countArray; } set durations(durArray: Array) { this.durArray = durArray; } modifyFlagList(flag: Flag | null | undefined): void { if (flag) { if (flag.hidden) { let i = this.flagList.findIndex((it) => it.time === flag.time); this.flagList.splice(i, 1); } else { let i = this.flagList.findIndex((it) => it.time === flag.time); this.flagList[i] = flag; } } else { this.flagList.forEach((it) => (it.selected = false)); } this.draw(); } modifySicesTimeList(slicestime: SlicesTime | null | undefined): void { if (slicestime) { let i = this.slicesTimeList.findIndex((it) => it.id === slicestime.id); if (slicestime.hidden) { this.slicesTimeList.splice(i, 1); let selectionParam = this.timerShaftEL?.selectionMap.get(slicestime.id); this.timerShaftEL?.selectionMap.delete(slicestime.id); if (selectionParam) { this.timerShaftEL?.selectionList.splice(this.timerShaftEL?.selectionList.indexOf(selectionParam), 1); } } else { this.slicesTimeList[i] = slicestime; } } else { this.slicesTimeList.forEach((it) => (it.selected = false)); } this.draw(); } draw(): void { this.draBasicsRuler(); //绘制旗子 this.flagList.forEach((flagObj: Flag, b) => { if (flagObj.time >= this.range.startNS && flagObj.time <= this.range.endNS) { flagObj.x = Math.round( (this.rulerW * (flagObj.time - this.range.startNS)) / (this.range.endNS - this.range.startNS) ); this.drawFlag(flagObj.x, flagObj.color, flagObj.selected, flagObj.text, flagObj.type); } }); !this.hoverFlag.hidden && this.drawFlag(this.hoverFlag.x, this.hoverFlag.color, true, this.hoverFlag.text); //If region selection is enabled, the serial number draws a line on the axis to show the length of the box selection if (this.isRangeSelect) { this.drawRangeSelect(); } if (this.invertedTriangleTime !== null && typeof this.invertedTriangleTime !== undefined) { this.drawInvertedTriangle( this.invertedTriangleTime!, document.querySelector('sp-application')!.dark ? '#FFFFFF' : '#000000' ); } this.slicesTimeList.forEach((slicesTime) => { this.drawSlicesMarks(slicesTime); }); } draBasicsRuler(): void { this.rulerW = this.canvas!.offsetWidth; this.context2D.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height + 1); this.context2D.beginPath(); this.lineColor = window.getComputedStyle(this.canvas!, null).getPropertyValue('color'); this.context2D.lineWidth = 1; this.context2D.strokeStyle = this.lineColor; //"#dadada" this.context2D.moveTo(this.frame.x, this.frame.y); this.context2D.lineTo(this.frame.x + this.frame.width, this.frame.y); this.context2D.stroke(); this.context2D.closePath(); this.context2D.beginPath(); this.context2D.strokeStyle = '#999999'; this.context2D.lineWidth = 3; this.context2D.moveTo(this.frame.x, this.frame.y); this.context2D.lineTo(this.frame.x, this.frame.y + this.frame.height); this.context2D.stroke(); this.context2D.closePath(); this.context2D.beginPath(); this.context2D.strokeStyle = this.lineColor; //"#999999" this.context2D.lineWidth = 1; this.context2D.fillStyle = '#999999'; this.context2D.font = '8px sans-serif'; this.range.xs?.forEach((item, index) => { this.context2D.moveTo(item, this.frame.y); this.context2D.lineTo(item, this.frame.y + this.frame.height); this.context2D.fillText(`${this.range.xsTxt[index]}`, item + 3, this.frame.y + 12); }); this.context2D.stroke(); this.context2D.closePath(); } private initRangeSelect(): void { let range = TraceRow.rangeSelectObject; this.context2D.beginPath(); if (document.querySelector('sp-application')!.dark) { this.context2D.strokeStyle = '#FFF'; this.context2D.fillStyle = '#FFF'; } else { this.context2D.strokeStyle = '#000'; this.context2D.fillStyle = '#000'; } let startX = ns2x(range?.startNS || 0, this.range.startNS, this.range.endNS, this.range.totalNS, this.frame); let endX = ns2x(range?.endNS || 0, this.range.startNS, this.range.endNS, this.range.totalNS, this.frame); let lineWidth = endX - startX; let txt = ns2s((range?.endNS || 0) - (range?.startNS || 0)); this.context2D.moveTo(startX, this.frame.y + 22); this.context2D.lineTo(endX, this.frame.y + 22); this.context2D.moveTo(startX, this.frame.y + 22 - 5); this.context2D.lineTo(startX, this.frame.y + 22 + 5); this.context2D.moveTo(endX, this.frame.y + 22 - 5); this.context2D.lineTo(endX, this.frame.y + 22 + 5); let textWidth = this.context2D.measureText(txt).width; if (lineWidth > textWidth) { this.context2D.fillText(`${txt}`, startX + (lineWidth - textWidth) / 2, this.frame.y + 20); } else { if (endX + textWidth >= this.frame.width) { this.context2D.fillText(`${txt}`, startX - 5 - textWidth, this.frame.y + 20); } else { this.context2D.fillText(`${txt}`, endX + 5, this.frame.y + 20); } } } drawRangeSelect(): void { this.initRangeSelect(); if (this.timeArray.length > 0 && TraceRow.rangeSelectObject) { // 页面可视框选区域的宽度 let rangeSelectWidth = TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!; // 每段宽度必须大于总数的宽度 // 10,2+8,2是线的宽度,8是留的空隙,不然很不好看 // 分section段 let section = Math.floor( rangeSelectWidth / (this.context2D.measureText(String(this.timeArray.length)).width + 10) ); // 最多画二十段 section < 20 ? (section = section) : (section = 20); // 最少一段 section < 1 ? (section = 1) : (section = section); // 框选泳道图并放大左右移动后,框选的部分区域会移出可视区域, // TraceRow.rangeSelectObject的开始结束时间仍然是框选时的时间,要和this.range进行比较取可视框选范围的时间 let startNS; let endNS; TraceRow.rangeSelectObject!.startNS! > this.range.startNS ? (startNS = TraceRow.rangeSelectObject!.startNS!) : (startNS = this.range.startNS); TraceRow.rangeSelectObject!.endNS! > this.range.endNS ? (endNS = this.range.endNS) : (endNS = TraceRow.rangeSelectObject!.endNS!); // 每一格的时间 let sectionTime = (endNS - startNS) / section; let countArr = new Uint32Array(section); let count: number = 0; //某段时间的调用栈数量 const isEbpf = this.durArray && this.durArray.length > 0; const processTimeArray = new Array(this.timeArray.length).fill(false); for (let i = 1; i <= section; i++) { count = 0; for (let j = 0; j < this.timeArray.length; j++) { if (processTimeArray[j]) { continue; } let inRange = this.freshInRange(j, startNS, sectionTime, i); // 如果该时间小于第一个分割点的时间,计数加1,从而算出一段时间的时间数量 if (inRange) { // nm统计模式则统计每个时间的count if (this.countArray && this.countArray[j] > 0) { count += this.countArray[j]; } else { count++; } countArr[i - 1] = count; processTimeArray[j] = true; } else { // 如果遇到大于分割点的时间,就跳过该分割点,计算下一个分割点的时间点数量 continue; } } this.drawRangeSelectFillText(rangeSelectWidth, section, i, countArr); } } this.context2D.stroke(); this.context2D.closePath(); } private drawRangeSelectFillText(rangeSelectWidth: number, section: number, i: number, countArr: Uint32Array): void { let x = TraceRow.rangeSelectObject!.startX! + (rangeSelectWidth / section) * i; if (i !== section) { this.context2D.moveTo(x, this.frame.y + 22); this.context2D.lineTo(x, this.frame.y + 22 + 5); } // 每一格的数量的数字宽度 let countTextWidth = this.context2D.measureText(String(countArr[i - 1])).width; // 文本的开始位置 = 框选的开始位置 + 格数 + (一格的宽度 - 文本的宽度) / 2 let textY = TraceRow.rangeSelectObject!.startX! + (rangeSelectWidth / section) * (i - 1) + (rangeSelectWidth / section - countTextWidth) / 2; this.context2D.fillStyle = `#f00`; this.context2D.font = `12px sans-serif`; this.context2D.fillText(String(countArr[i - 1]), textY, this.frame.y + 22 + 12); } private freshInRange(j: number, startNS: number, sectionTime: number, i: number): boolean { let inRange = false; const itemTime = this.timeArray[j]; // ebpf需要考虑dur if (this.durArray && this.durArray.length > 0) { const dur = this.durArray[j]; if (itemTime === this.range.endNS) { // 如果时间点刚好和时间轴结束时间一样会导致该时间点没有计数,所以此情况需要的判断条件要多个等号 inRange = itemTime >= startNS + sectionTime * (i - 1) && itemTime <= startNS + sectionTime * i && itemTime >= this.range.startNS && itemTime <= this.range.endNS; } else { // 判断时间点是否在某时间段内时,一般情况下和左边界相同算在该时间段,和右边界相同算在下一段, inRange = itemTime + dur >= startNS + sectionTime * (i - 1) && itemTime < startNS + sectionTime * i && itemTime + dur >= this.range.startNS && itemTime < this.range.endNS; } } else { if (itemTime === this.range.endNS) { inRange = itemTime >= startNS + sectionTime * (i - 1) && itemTime <= startNS + sectionTime * i && itemTime >= this.range.startNS && itemTime <= this.range.endNS; } else { inRange = itemTime >= startNS + sectionTime * (i - 1) && itemTime < startNS + sectionTime * i && itemTime >= this.range.startNS && itemTime < this.range.endNS; } } return inRange; } drawTriangle(time: number, type: string): unknown { let num; if (time !== null && typeof time !== undefined) { let i = this.flagList.findIndex((it) => it.time === time); if (type === 'triangle') { let triangle = this.flagList.findIndex((it) => it.type === type); if (i !== -1) { if (triangle !== -1) { this.flagList[i].type === '' ? this.flagList.splice(triangle, 1) : ''; } } else { if (triangle === -1) { this.flagList.forEach((it) => (it.selected = false)); this.flagList.push(new Flag(0, 125, 18, 18, time, randomRgbColor(), '', true, 'triangle')); } else { this.flagList.forEach((it) => (it.selected = false)); this.flagList[triangle].time = time; this.flagList[triangle].selected = true; } } } else if (type === 'square') { if (i !== -1) { this.flagList[i].type = ''; } else { let triangle = this.flagList.findIndex((it) => it.type === 'triangle'); if (triangle !== -1) { this.flagList[triangle].type = ''; this.draw(); this.flagChangeHandler('1'); num = this.flagList[triangle].time; } } } else if (type === 'inverted') { this.invertedTriangleTime = time; } this.draw(); this.flagChangeHandler('2'); } return num; } flagChangeHandler(from?: string): void { this.notifyHandler && this.notifyHandler( !this.hoverFlag.hidden ? this.hoverFlag : null, this.flagList.find((it) => it.selected) || null ); } removeTriangle(type: string): void { if (type === 'inverted') { if (this.invertedTriangleTime !== null) { this.flagChangeHandler('3'); } this.invertedTriangleTime = null; } else { this.flagChangeHandler('3'); } this.draw(); } drawInvertedTriangle(time: number, color: string = '#000000'): void { if (time !== null && typeof time !== undefined) { let x = Math.round((this.rulerW * (time - this.range.startNS)) / (this.range.endNS - this.range.startNS)); this.context2D.beginPath(); this.context2D.fillStyle = color; this.context2D.strokeStyle = color; // ----------------修改小倒三角位置的绘制--------------------- if (sessionStorage.getItem('expand') === 'true') { //展开 this.context2D.moveTo(x - 3, 141); this.context2D.lineTo(x + 3, 141); this.context2D.lineTo(x, 145); } else if (sessionStorage.getItem('expand') === 'false') { this.context2D.moveTo(x - 3, 141 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x + 3, 141 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x, 145 - Number(sessionStorage.getItem('foldHeight'))); } this.context2D.fill(); this.context2D.closePath(); this.context2D.stroke(); } } setSlicesMark( startTime: number | null = null, endTime: number | null = null, shiftKey: boolean | null = null ): SlicesTime | null { let findSlicesTime = this.slicesTimeList.find((it) => it.startTime === startTime && it.endTime === endTime); if (findSlicesTime && this.slicesTimeList.length > 0) { return null; } else { let newSlicestime: SlicesTime | null = null; if (startTime !== null && typeof startTime !== undefined && endTime !== null && typeof endTime !== undefined) { this.slicesTime = { startTime: startTime <= endTime ? startTime : endTime, endTime: startTime <= endTime ? endTime : startTime, color: null, }; let startX = Math.round( (this.rulerW * (startTime - this.range.startNS)) / (this.range.endNS - this.range.startNS) ); let endX = Math.round((this.rulerW * (endTime - this.range.startNS)) / (this.range.endNS - this.range.startNS)); this.slicesTime.color = randomRgbColor() || '#ff0000'; let text = ''; newSlicestime = new SlicesTime( this.slicesTime.startTime || 0, this.slicesTime.endTime || 0, this.range.startNS, this.range.endNS, startX, endX, this.slicesTime.color, text, true ); if (!shiftKey) { this.clearTempSlicesTime(); // 清除临时对象 // 如果没有按下shift键,则把当前slicestime对象的类型设为临时类型。 newSlicestime.type = StType.TEMP; } this.slicesTimeList.forEach((slicestime) => (slicestime.selected = false)); newSlicestime.selected = true; this.slicesTimeList.push(newSlicestime); } else { this.clearTempSlicesTime(); // 清除临时对象 this.slicesTime = { startTime: null, endTime: null, color: null }; } this.range.slicesTime = this.slicesTime; this.draw(); this.timerShaftEL?.render(); return newSlicestime; } } // 清除临时对象 clearTempSlicesTime(): void { // 清除以前放入的临时对象 this.slicesTimeList.forEach((slicestime, index) => { slicestime.selected = false; if (slicestime.type === StType.TEMP) { this.slicesTimeList.splice(index, 1); let selectionParam = this.timerShaftEL?.selectionMap.get(slicestime.id); if (selectionParam && selectionParam !== undefined) { this.timerShaftEL?.selectionList.splice(this.timerShaftEL?.selectionList.indexOf(selectionParam), 1); this.timerShaftEL?.selectionMap.delete(slicestime.id); } } }); } clearHoverFlag(): void { this.hoverFlag.hidden = true; } showHoverFlag(): void { this.hoverFlag.hidden = false; } private drawSlicesTimeText(slicesTime: SlicesTime, startX: number, endX: number): number[] { this.context2D.beginPath(); this.context2D.strokeStyle = slicesTime.color; this.context2D.fillStyle = slicesTime.color; this.range.slicesTime.color = slicesTime.color; //紫色 // ---------------------------------------修改标记绘制位置------------------------- if (sessionStorage.getItem('expand') === 'true') { //展开 this.context2D.moveTo(startX + TRIWIDTH, 132); this.context2D.lineTo(startX, 142); this.context2D.lineTo(startX, 132); this.context2D.lineTo(startX + TRIWIDTH, 132); this.context2D.lineTo(endX - TRIWIDTH, 132); this.context2D.lineTo(endX, 132); this.context2D.lineTo(endX, 142); this.context2D.lineTo(endX - TRIWIDTH, 132); } else if (sessionStorage.getItem('expand') === 'false') { this.context2D.moveTo(startX + TRIWIDTH, 132 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(startX, 142 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(startX, 132 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(startX + TRIWIDTH, 132 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(endX - TRIWIDTH, 132 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(endX, 132 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(endX, 142 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(endX - TRIWIDTH, 132 - Number(sessionStorage.getItem('foldHeight'))); } this.context2D.closePath(); slicesTime.selected && this.context2D.fill(); this.context2D.stroke(); this.context2D.beginPath(); if (document.querySelector('sp-application')!.dark) { this.context2D.strokeStyle = '#FFF'; this.context2D.fillStyle = '#FFF'; } else { this.context2D.strokeStyle = '#000'; this.context2D.fillStyle = '#000'; } let lineWidth = endX - startX; let txt = ns2s((slicesTime.endTime || 0) - (slicesTime.startTime || 0)); this.context2D.moveTo(startX, this.frame.y + 22); this.context2D.lineTo(endX, this.frame.y + 22); this.context2D.moveTo(startX, this.frame.y + 22 - 5); this.context2D.lineTo(startX, this.frame.y + 22 + 5); this.context2D.moveTo(endX, this.frame.y + 22 - 5); this.context2D.lineTo(endX, this.frame.y + 22 + 5); let txtWidth = this.context2D.measureText(txt).width; this.context2D.fillStyle = '#FFF'; //为了解决文字重叠问题。在时间刻度的文字下面绘制一个小方块 this.context2D.fillRect(startX + (lineWidth - txtWidth) / 2, this.frame.y + 10, txtWidth + 2, 10); this.context2D.fillStyle = 'black'; if (lineWidth > txtWidth) { this.context2D.fillText(`${txt}`, startX + (lineWidth - txtWidth) / 2, this.frame.y + 20); } else { if (endX + txtWidth >= this.frame.width) { this.context2D.fillText(`${txt}`, startX - 5 - txtWidth, this.frame.y + 20); } else { this.context2D.fillText(`${txt}`, endX + 5, this.frame.y + 20); } } this.context2D.stroke(); this.context2D.closePath(); return [lineWidth, txtWidth]; } drawSlicesMarks(slicesTime: SlicesTime): void { if ( slicesTime.startTime !== null && typeof slicesTime.startTime !== undefined && slicesTime.endTime !== null && typeof slicesTime.endTime !== undefined ) { let startX = Math.round( (this.rulerW * (slicesTime.startTime - this.range.startNS)) / (this.range.endNS - this.range.startNS) ); let endX = Math.round( (this.rulerW * (slicesTime.endTime - this.range.startNS)) / (this.range.endNS - this.range.startNS) ); // 放大、缩小、左右移动之后重置小三角的x轴坐标 slicesTime.startX = startX; slicesTime.endX = endX; let [lineWidth, txtWidth] = this.drawSlicesTimeText(slicesTime, startX, endX); // 画框选的备注文字---begin----------------- let text = slicesTime.text; if (text) { this.context2D.beginPath(); if (document.querySelector('sp-application')!.dark) { this.context2D.strokeStyle = '#FFF'; this.context2D.fillStyle = '#FFF'; } else { this.context2D.strokeStyle = '#000'; this.context2D.fillStyle = '#000'; } let textWidth = this.context2D.measureText(text).width; if (textWidth > 0) { this.context2D.fillStyle = 'black'; this.context2D.font = TEXT_FONT; if (lineWidth > txtWidth) { this.context2D.fillText(`${text}`, startX + (lineWidth - textWidth) / 2, this.frame.y + 43); } else { if (endX + textWidth >= this.frame.width) { this.context2D.fillText(`${text}`, startX - 5 - textWidth, this.frame.y + 43); } else { this.context2D.fillText(`${text}`, endX + 5, this.frame.y + 43); } } } this.context2D.stroke(); this.context2D.closePath(); } // 画框选的备注文字---end----------------- } } //绘制旗子 drawFlag( x: number, color: string = '#999999', isFill: boolean = false, textStr: string = '', type: string = '' ): void { if (x < 0) { return; } this.context2D.beginPath(); this.context2D.fillStyle = color; this.context2D.strokeStyle = color; // ------------------修改旗子位置---------------------------- if (sessionStorage.getItem('expand') === 'true') { this.context2D.moveTo(x, 125); if (type === 'triangle') { this.context2D.lineTo(x + 15, 131); } else { this.context2D.lineTo(x + 10, 125); this.context2D.lineTo(x + 10, 127); this.context2D.lineTo(x + 18, 127); this.context2D.lineTo(x + 18, 137); this.context2D.lineTo(x + 10, 137); this.context2D.lineTo(x + 10, 135); } this.context2D.lineTo(x + 2, 135); this.context2D.lineTo(x + 2, 142); this.context2D.lineTo(x, 142); } else { this.context2D.moveTo(x, 125 - Number(sessionStorage.getItem('foldHeight'))); if (type === 'triangle') { this.context2D.lineTo(x + 15, 131 - Number(sessionStorage.getItem('foldHeight'))); } else { this.context2D.lineTo(x + 10, 125 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x + 10, 127 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x + 18, 127 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x + 18, 137 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x + 10, 137 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x + 10, 135 - Number(sessionStorage.getItem('foldHeight'))); } this.context2D.lineTo(x + 2, 135 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x + 2, 142 - Number(sessionStorage.getItem('foldHeight'))); this.context2D.lineTo(x, 142 - Number(sessionStorage.getItem('foldHeight'))); } this.context2D.closePath(); isFill && this.context2D.fill(); this.context2D.stroke(); if (textStr !== '') { this.context2D.font = TEXT_FONT; const { width } = this.context2D.measureText(textStr); this.context2D.fillStyle = 'rgba(255, 255, 255, 0.8)'; // // -------------------修改旗子上的字位置----------------------- if (sessionStorage.getItem('expand') === 'true') { this.context2D.fillRect(x + 21, 132, width + 4, 12); this.context2D.fillStyle = 'black'; this.context2D.fillText(textStr, x + 23, 142); } else { this.context2D.fillRect(x + 21, 132 - Number(sessionStorage.getItem('foldHeight')), width + 4, 12); this.context2D.fillStyle = 'black'; this.context2D.fillText(textStr, x + 23, 142 - Number(sessionStorage.getItem('foldHeight'))); } this.context2D.stroke(); } } /** * 查找鼠标所在位置是否存在"帽子"对象,为了操作方便,框选时把三角形的边长宽度左右各加一个像素。 * @param x 水平坐标值 * @returns */ findSlicesTime(x: number, y: number): SlicesTime | null { // --------------------------修改旗子和标记的小三角重叠时的情况------------------- let slicestime; if (sessionStorage.getItem('expand') === 'false') { //折叠 slicestime = this.slicesTimeList.find((slicesTime) => { return ( ((x >= slicesTime.startX - 1 && x <= slicesTime.startX + TRIWIDTH + 1) || // 选中了帽子的左边三角形区域 (x >= slicesTime.endX - TRIWIDTH - 1 && x <= slicesTime.endX + 1)) && // 选中了帽子的右边三角形区域 y >= 132 - Number(sessionStorage.getItem('foldHeight')) && y <= 142 - Number(sessionStorage.getItem('foldHeight')) ); }); } else if (sessionStorage.getItem('expand') === 'true') { slicestime = this.slicesTimeList.find((slicesTime) => { return ( ((x >= slicesTime.startX - 1 && x <= slicesTime.startX + TRIWIDTH + 1) || // 选中了帽子的左边三角形区域 (x >= slicesTime.endX - TRIWIDTH - 1 && x <= slicesTime.endX + 1)) && // 选中了帽子的右边三角形区域 y >= 132 && y <= 142 ); }); } if (!slicestime) { return null; } return slicestime; } mouseUp(ev: MouseEvent): void { if (this.edgeDetection(ev)) { let x = ev.offsetX - (this.canvas?.offsetLeft || 0); // 鼠标点击的x轴坐标 let y = ev.offsetY; // 鼠标点击的y轴坐标 let findSlicestime = this.findSlicesTime(x, y); // 查找帽子 if (findSlicestime) { // 如果找到帽子,则选中帽子。 this.slicesTimeList.forEach((slicestime) => (slicestime.selected = false)); findSlicestime.selected = true; this.rangeClickHandler && this.rangeClickHandler(findSlicestime); } else { // 如果没有找到帽子,则绘制旗子,此处避免旗子和帽子重叠。 // 查找旗子 let findFlag = this.flagList.find( (it) => (x >= it.x && x <= it.x + 18 && it.type !== 'triangle') || (x === it.x && it.type === 'triangle') ); this.flagList.forEach((it) => (it.selected = false)); if (findFlag) { findFlag.selected = true; } else { let flagAtRulerTime = Math.round(((this.range.endNS - this.range.startNS) * x) / this.rulerW); let flag = new Flag(x, 125, 18, 18, flagAtRulerTime + this.range.startNS, randomRgbColor(), '', true, ''); this.flagList.push(flag); } this.flagClickHandler && this.flagClickHandler(this.flagList.find((it) => it.selected)); // 绘制旗子 } } } mouseMove(ev: MouseEvent): void { if (this.edgeDetection(ev)) { this.mouseIn = true; let x = ev.offsetX - (this.canvas?.offsetLeft || 0); let flg = this.flagList.find((it) => x >= it.x && x <= it.x + 18); if (flg) { this.hoverFlag.hidden = true; } else { this.hoverFlag.hidden = false; this.hoverFlag.x = x; this.hoverFlag.color = '#999999'; } this.flagChangeHandler('4'); this.draw(); } else { this.hoverFlag.hidden = true; } } mouseOut(ev: MouseEvent): void { if (!this.hoverFlag.hidden) { this.hoverFlag.hidden = true; if (this.mouseIn) { this.flagChangeHandler('5'); } } if (this.mouseIn) { this.mouseIn = false; this.draw(); } } edgeDetection(ev: MouseEvent): boolean { let x = ev.offsetX - (this.canvas?.offsetLeft || 0); let y = ev.offsetY - (this.canvas?.offsetTop || 0); SportRuler.isMouseInSportRuler = x > 0 && x < this.canvas!.offsetWidth && ev.offsetY - this.frame.y > 20 && ev.offsetY - this.frame.y < this.frame.height; return SportRuler.isMouseInSportRuler; } }