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 {ns2s} from "../TimerShaftElement.js"; 19import {ColorUtils} from "../base/ColorUtils.js"; 20import {CpuStruct} from "../../../bean/CpuStruct.js"; 21 22const markPadding = 5; 23 24export class Mark extends Graph { 25 get isHover(): boolean { 26 return this._isHover; 27 } 28 29 set isHover(value: boolean) { 30 this._isHover = value; 31 if (value) { 32 document.body.style.cursor = 'ew-resize' 33 } else { 34 document.body.style.cursor = 'default' 35 } 36 } 37 38 name: string | undefined 39 private _isHover: boolean = false 40 inspectionFrame: Rect 41 42 constructor(canvas: HTMLCanvasElement | undefined | null, name: string, c: CanvasRenderingContext2D, frame: Rect) { 43 super(canvas, c, frame); 44 this.name = name; 45 this.inspectionFrame = new Rect(frame.x - markPadding, frame.y, frame.width + markPadding * 2, frame.height) 46 } 47 48 draw(): void { 49 this.c.beginPath(); 50 this.c.lineWidth = 7 51 this.c.strokeStyle = '#999999' 52 this.c.moveTo(this.frame.x, this.frame.y); 53 this.c.lineTo(this.frame.x, this.frame.y + this.frame.height / 3) 54 this.c.stroke(); 55 this.c.lineWidth = 1 56 this.c.strokeStyle = '#999999' 57 this.c.moveTo(this.frame.x, this.frame.y); 58 this.c.lineTo(this.frame.x, this.frame.y + this.frame.height) 59 this.c.stroke(); 60 this.c.closePath(); 61 } 62} 63 64export interface TimeRange { 65 totalNS: number 66 startX: number 67 endX: number 68 startNS: number 69 endNS: number 70 xs: Array<number> 71 xsTxt: Array<string> 72} 73 74export class RangeRuler extends Graph { 75 public rangeRect: Rect 76 public markA: Mark 77 public markB: Mark 78 public range: TimeRange; 79 private readonly notifyHandler: (r: TimeRange) => void; 80 private scale: number = 0; 81 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, 82 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, 83 1_000_000_000, 2_000_000_000, 5_000_000_000, 10_000_000_000, 20_000_000_000, 50_000_000_000, 84 100_000_000_000, 200_000_000_000, 500_000_000_000]; 85 86 constructor(canvas: HTMLCanvasElement | undefined | null, c: CanvasRenderingContext2D, frame: Rect, range: TimeRange, notifyHandler: (r: TimeRange) => void) { 87 super(canvas, c, frame) 88 this.range = range; 89 this.notifyHandler = notifyHandler; 90 this.markA = new Mark(canvas, 'A', c, new Rect(range.startX, frame.y, 1, frame.height)) 91 this.markB = new Mark(canvas, 'B', c, new Rect(range.endX, frame.y, 1, frame.height)) 92 this.rangeRect = new Rect(range.startX, frame.y, range.endX - range.startX, frame.height) 93 } 94 95 private _cpuUsage: Array<{ cpu: number, ro: number, rate: number }> = [] 96 97 set cpuUsage(value: Array<{ cpu: number, ro: number, rate: number }>) { 98 this._cpuUsage = value 99 this.draw(); 100 } 101 102 drawCpuUsage() { 103 let maxNum = Math.round(this._cpuUsage.length / 100) 104 let miniHeight = Math.round(this.frame.height / CpuStruct.cpuCount); 105 let miniWidth = Math.ceil(this.frame.width / 100); 106 for (let i = 0; i < this._cpuUsage.length; i++) { 107 let it = this._cpuUsage[i] 108 this.c.fillStyle = ColorUtils.MD_PALETTE[it.cpu] 109 this.c.globalAlpha = it.rate 110 this.c.fillRect(this.frame.x + miniWidth * it.ro, this.frame.y + it.cpu * miniHeight, miniWidth, miniHeight) 111 } 112 } 113 114 draw(discardNotify: boolean = false): void { 115 this.c.clearRect(this.frame.x - markPadding, this.frame.y, this.frame.width + markPadding * 2, this.frame.height) 116 this.c.beginPath(); 117 if (this._cpuUsage.length > 0) { 118 this.drawCpuUsage() 119 this.c.globalAlpha = 0; 120 } else { 121 this.c.globalAlpha = 1; 122 } 123 124 this.c.fillStyle = window.getComputedStyle(this.canvas!, null).getPropertyValue("background-color"); 125 this.rangeRect.x = this.markA.frame.x < this.markB.frame.x ? this.markA.frame.x : this.markB.frame.x 126 this.rangeRect.width = Math.abs(this.markB.frame.x - this.markA.frame.x) 127 this.c.fillRect(this.rangeRect.x, this.rangeRect.y, this.rangeRect.width, this.rangeRect.height) 128 this.c.globalAlpha = 1; 129 this.c.globalAlpha = .5; 130 this.c.fillStyle = "#999999" 131 this.c.fillRect(this.frame.x, this.frame.y, this.rangeRect.x, this.rangeRect.height) 132 this.c.fillRect(this.rangeRect.x + this.rangeRect.width, this.frame.y, this.frame.width - this.rangeRect.width, this.rangeRect.height) 133 this.c.globalAlpha = 1; 134 this.c.closePath(); 135 this.markA.draw(); 136 this.markB.draw(); 137 if (this.notifyHandler) { 138 this.range.startX = this.rangeRect.x 139 this.range.endX = this.rangeRect.x + this.rangeRect.width 140 this.range.startNS = this.range.startX * this.range.totalNS / (this.canvas?.clientWidth || 0) 141 this.range.endNS = this.range.endX * this.range.totalNS / (this.canvas?.clientWidth || 0) 142 let l20 = (this.range.endNS - this.range.startNS) / 20; 143 let min = 0; 144 let max = 0; 145 let weight = 0; 146 for (let index = 0; index < this.scales.length; index++) { 147 if (this.scales[index] > l20) { 148 if (index > 0) { 149 min = this.scales[index - 1]; 150 } else { 151 min = 0; 152 } 153 max = this.scales[index]; 154 weight = (l20 - min) * 1.0 / (max - min); 155 if (weight > 0.243) { 156 this.scale = max; 157 } else { 158 this.scale = min; 159 } 160 break; 161 } 162 } 163 if (this.scale == 0) { 164 this.scale = this.scales[0]; 165 } 166 let tmpNs = 0; 167 let yu = this.range.startNS % this.scale; 168 let realW = (this.scale * this.frame.width) / (this.range.endNS - this.range.startNS); 169 let startX = 0; 170 if (this.range.xs) { 171 this.range.xs.length = 0 172 } else { 173 this.range.xs = [] 174 } 175 if (this.range.xsTxt) { 176 this.range.xsTxt.length = 0 177 } else { 178 this.range.xsTxt = [] 179 } 180 if (yu != 0) { 181 let firstNodeWidth = ((this.scale - yu) / this.scale * realW); 182 startX += firstNodeWidth; 183 tmpNs += yu; 184 this.range.xs.push(startX) 185 this.range.xsTxt.push(ns2s(tmpNs)) 186 } 187 while (tmpNs < this.range.endNS - this.range.startNS) { 188 startX += realW; 189 tmpNs += this.scale; 190 this.range.xs.push(startX) 191 this.range.xsTxt.push(ns2s(tmpNs)) 192 } 193 if (!discardNotify) { 194 this.notifyHandler(this.range) 195 } 196 } 197 } 198 199 200 mouseDownOffsetX = 0 201 mouseDownMovingMarkX = 0 202 movingMark: Mark | undefined | null; 203 isMouseDown: boolean = false; 204 isMovingRange: boolean = false; 205 isNewRange: boolean = false; 206 markAX: number = 0; 207 markBX: number = 0; 208 isPress: boolean = false 209 pressFrameId: number = -1 210 currentDuration: number = 0 211 212 mouseDown(ev: MouseEvent) { 213 let x = ev.offsetX - (this.canvas?.offsetLeft || 0) 214 let y = ev.offsetY - (this.canvas?.offsetTop || 0) 215 this.isMouseDown = true; 216 this.mouseDownOffsetX = x; 217 if (this.markA.isHover) { 218 this.movingMark = this.markA; 219 this.mouseDownMovingMarkX = this.movingMark.frame.x || 0 220 } else if (this.markB.isHover) { 221 this.movingMark = this.markB; 222 this.mouseDownMovingMarkX = this.movingMark.frame.x || 0 223 } else { 224 this.movingMark = null; 225 } 226 if (this.rangeRect.containsWithPadding(x, y, 5, 0)) { 227 this.isMovingRange = true; 228 this.markAX = this.markA.frame.x; 229 this.markBX = this.markB.frame.x; 230 document.body.style.cursor = "move" 231 } else if (this.frame.containsWithMargin(x, y, 20, 0, 0, 0) && !this.rangeRect.containsWithMargin(x, y, 0, markPadding, 0, markPadding)) { 232 this.isNewRange = true; 233 } 234 } 235 236 mouseUp(ev: MouseEvent) { 237 this.isMouseDown = false; 238 this.isMovingRange = false; 239 this.isNewRange = false; 240 this.movingMark = null; 241 } 242 243 centerXPercentage: number = 0; 244 245 mouseMove(ev: MouseEvent) { 246 let x = ev.offsetX - (this.canvas?.offsetLeft || 0); 247 let y = ev.offsetY - (this.canvas?.offsetTop || 0) 248 this.centerXPercentage = x / (this.canvas?.clientWidth || 0) 249 if (this.centerXPercentage <= 0) { 250 this.centerXPercentage = 0 251 } else if (this.centerXPercentage >= 1) { 252 this.centerXPercentage = 1 253 } 254 let maxX = this.canvas?.clientWidth || 0 255 if (this.markA.inspectionFrame.contains(x, y)) { 256 this.markA.isHover = true 257 } else if (this.markB.inspectionFrame.contains(x, y)) { 258 this.markB.isHover = true; 259 } else { 260 this.markA.isHover = false; 261 this.markB.isHover = false; 262 } 263 if (this.movingMark) { 264 let result = x - this.mouseDownOffsetX + this.mouseDownMovingMarkX; 265 if (result >= 0 && result <= maxX) { 266 this.movingMark.frame.x = result 267 } else if (result < 0) { 268 this.movingMark.frame.x = 0 269 } else { 270 this.movingMark.frame.x = maxX 271 } 272 this.movingMark.inspectionFrame.x = this.movingMark.frame.x - markPadding 273 requestAnimationFrame(() => this.draw()); 274 } else if (this.rangeRect.containsWithPadding(x, y, markPadding, 0)) { 275 document.body.style.cursor = "move" 276 } else if (this.frame.containsWithMargin(x, y, 20, 0, 0, 0) && !this.rangeRect.containsWithMargin(x, y, 0, markPadding, 0, markPadding)) { 277 document.body.style.cursor = "crosshair"; 278 } 279 if (this.isMovingRange && this.isMouseDown) { 280 let result = x - this.mouseDownOffsetX; 281 let mA = result + this.markAX 282 let mB = result + this.markBX 283 if (mA >= 0 && mA <= maxX) { 284 this.markA.frame.x = mA 285 } else if (mA < 0) { 286 this.markA.frame.x = 0 287 } else { 288 this.markA.frame.x = maxX 289 } 290 this.markA.inspectionFrame.x = this.markA.frame.x - markPadding 291 if (mB >= 0 && mB <= maxX) { 292 this.markB.frame.x = mB; 293 } else if (mB < 0) { 294 this.markB.frame.x = 0 295 } else { 296 this.markB.frame.x = maxX 297 } 298 this.markB.inspectionFrame.x = this.markB.frame.x - markPadding 299 requestAnimationFrame(() => this.draw()); 300 } else if (this.isNewRange) { 301 this.markA.frame.x = this.mouseDownOffsetX; 302 this.markA.inspectionFrame.x = this.mouseDownOffsetX - markPadding; 303 if (x >= 0 && x <= maxX) { 304 this.markB.frame.x = x; 305 } else if (x < 0) { 306 this.markB.frame.x = 0; 307 } else { 308 this.markB.frame.x = maxX; 309 } 310 this.markB.inspectionFrame.x = this.markB.frame.x - markPadding; 311 requestAnimationFrame(() => this.draw()); 312 } 313 } 314 315 mouseOut(ev: MouseEvent) { 316 this.movingMark = null; 317 } 318 319 animaStartTime: number | undefined 320 animTime: number = 100; 321 p: number = 800; 322 323 fillX() { 324 if (this.range.startNS < 0) this.range.startNS = 0; 325 if (this.range.endNS < 0) this.range.endNS = 0; 326 if (this.range.endNS > this.range.totalNS) this.range.endNS = this.range.totalNS; 327 if (this.range.startNS > this.range.totalNS) this.range.startNS = this.range.totalNS; 328 this.range.startX = this.range.startNS * (this.canvas?.clientWidth || 0) / this.range.totalNS 329 this.range.endX = this.range.endNS * (this.canvas?.clientWidth || 0) / this.range.totalNS 330 this.markA.frame.x = this.range.startX 331 this.markA.inspectionFrame.x = this.markA.frame.x - markPadding 332 this.markB.frame.x = this.range.endX 333 this.markB.inspectionFrame.x = this.markB.frame.x - markPadding 334 } 335 336 keyPress(ev: KeyboardEvent) { 337 if (this.animaStartTime === undefined) { 338 this.animaStartTime = new Date().getTime(); 339 } 340 let startTime = new Date().getTime(); 341 let duration = (startTime - this.animaStartTime); 342 if (duration < this.animTime) duration = this.animTime 343 this.currentDuration = duration 344 if (this.isPress) return 345 this.isPress = true 346 switch (ev.key.toLocaleLowerCase()) { 347 case "w": 348 let animW = () => { 349 if (this.scale === 50) return; 350 this.range.startNS += (this.centerXPercentage * this.currentDuration * 2 * this.scale / this.p); 351 this.range.endNS -= ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale / this.p); 352 this.fillX(); 353 this.draw(); 354 this.pressFrameId = requestAnimationFrame(animW) 355 } 356 this.pressFrameId = requestAnimationFrame(animW) 357 break; 358 case "s": 359 let animS = () => { 360 if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return; 361 this.range.startNS -= (this.centerXPercentage * this.currentDuration * 2 * this.scale / this.p); 362 this.range.endNS += ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale / this.p); 363 this.fillX(); 364 this.draw(); 365 this.pressFrameId = requestAnimationFrame(animS) 366 } 367 this.pressFrameId = requestAnimationFrame(animS) 368 break; 369 case "a": 370 let animA = () => { 371 if (this.range.startNS == 0) return; 372 let s = this.scale / this.p * this.currentDuration; 373 this.range.startNS -= s; 374 this.range.endNS -= s; 375 this.fillX(); 376 this.draw(); 377 this.pressFrameId = requestAnimationFrame(animA) 378 } 379 this.pressFrameId = requestAnimationFrame(animA) 380 break; 381 case "d": 382 let animD = () => { 383 if (this.range.endNS >= this.range.totalNS) return; 384 this.range.startNS += this.scale / this.p * this.currentDuration; 385 this.range.endNS += this.scale / this.p * this.currentDuration; 386 this.fillX(); 387 this.draw(); 388 this.pressFrameId = requestAnimationFrame(animD) 389 } 390 this.pressFrameId = requestAnimationFrame(animD) 391 break; 392 } 393 } 394 395 keyUp(ev: KeyboardEvent) { 396 this.animaStartTime = undefined; 397 this.isPress = false 398 if (this.pressFrameId != -1) { 399 cancelAnimationFrame(this.pressFrameId) 400 } 401 let startTime = new Date().getTime(); 402 switch (ev.key) { 403 case "w": 404 let animW = () => { 405 if (this.scale === 50) return; 406 let dur = (new Date().getTime() - startTime); 407 this.range.startNS += (this.centerXPercentage * 100 * this.scale / this.p); 408 this.range.endNS -= ((1 - this.centerXPercentage) * 100 * this.scale / this.p); 409 this.fillX(); 410 this.draw(); 411 if (dur < 200) { 412 requestAnimationFrame(animW) 413 } 414 } 415 requestAnimationFrame(animW) 416 break; 417 case "s": 418 let animS = () => { 419 if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return; 420 let dur = (new Date().getTime() - startTime); 421 this.range.startNS -= (this.centerXPercentage * 100 * this.scale / this.p); 422 this.range.endNS += ((1 - this.centerXPercentage) * 100 * this.scale / this.p); 423 this.fillX(); 424 this.draw(); 425 if (dur < 200) { 426 requestAnimationFrame(animS) 427 } 428 } 429 requestAnimationFrame(animS) 430 break; 431 case "a": 432 let animA = () => { 433 if (this.range.startNS <= 0) return 434 let dur = (new Date().getTime() - startTime); 435 let s = this.scale * 80 / this.p; 436 this.range.startNS -= s; 437 this.range.endNS -= s; 438 this.fillX(); 439 this.draw(); 440 if (dur < 200) { 441 requestAnimationFrame(animA) 442 } 443 } 444 animA(); 445 break; 446 case "d": 447 let animD = () => { 448 if (this.range.endNS >= this.range.totalNS) return; 449 let dur = (new Date().getTime() - startTime); 450 let s = this.scale * 80 / this.p; 451 this.range.startNS += s; 452 this.range.endNS += s; 453 this.fillX(); 454 this.draw(); 455 if (dur < 200) { 456 requestAnimationFrame(animD) 457 } 458 } 459 animD(); 460 break; 461 } 462 } 463} 464