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