1fb726d48Sopenharmony_ci/* 2fb726d48Sopenharmony_ci * Copyright (C) 2022 Huawei Device Co., Ltd. 3fb726d48Sopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License"); 4fb726d48Sopenharmony_ci * you may not use this file except in compliance with the License. 5fb726d48Sopenharmony_ci * You may obtain a copy of the License at 6fb726d48Sopenharmony_ci * 7fb726d48Sopenharmony_ci * http://www.apache.org/licenses/LICENSE-2.0 8fb726d48Sopenharmony_ci * 9fb726d48Sopenharmony_ci * Unless required by applicable law or agreed to in writing, software 10fb726d48Sopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS, 11fb726d48Sopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12fb726d48Sopenharmony_ci * See the License for the specific language governing permissions and 13fb726d48Sopenharmony_ci * limitations under the License. 14fb726d48Sopenharmony_ci */ 15fb726d48Sopenharmony_ci 16fb726d48Sopenharmony_ciimport { BaseElement, element } from '../../../base-ui/BaseElement'; 17fb726d48Sopenharmony_ciimport { Rect } from '../trace/timer-shaft/Rect'; 18fb726d48Sopenharmony_ciimport { ChartMode, ChartStruct, draw, setFuncFrame } from '../../bean/FrameChartStruct'; 19fb726d48Sopenharmony_ciimport { SpApplication } from '../../SpApplication'; 20fb726d48Sopenharmony_ciimport { Utils } from '../trace/base/Utils'; 21fb726d48Sopenharmony_ci 22fb726d48Sopenharmony_ciconst scaleHeight = 30; // 刻度尺高度 23fb726d48Sopenharmony_ciconst depthHeight = 20; // 调用栈高度 24fb726d48Sopenharmony_ciconst filterPixel = 2; // 过滤像素 25fb726d48Sopenharmony_ciconst textMaxWidth = 50; 26fb726d48Sopenharmony_ciconst scaleRatio = 0.2; // 缩放比例 27fb726d48Sopenharmony_ciconst ms10 = 10_000_000; 28fb726d48Sopenharmony_ciconst jsHapKeys = ['.hap', '.hsp', '.har']; 29fb726d48Sopenharmony_ciconst jsStackPath = ['.ts', '.ets', '.js']; 30fb726d48Sopenharmony_ciconst textStyle = '12px bold'; 31fb726d48Sopenharmony_ci 32fb726d48Sopenharmony_ciclass NodeValue { 33fb726d48Sopenharmony_ci size: number; 34fb726d48Sopenharmony_ci count: number; 35fb726d48Sopenharmony_ci dur: number; 36fb726d48Sopenharmony_ci eventCount: number; 37fb726d48Sopenharmony_ci 38fb726d48Sopenharmony_ci constructor() { 39fb726d48Sopenharmony_ci this.size = 0; 40fb726d48Sopenharmony_ci this.count = 0; 41fb726d48Sopenharmony_ci this.dur = 0; 42fb726d48Sopenharmony_ci this.eventCount = 0; 43fb726d48Sopenharmony_ci } 44fb726d48Sopenharmony_ci} 45fb726d48Sopenharmony_ci 46fb726d48Sopenharmony_ci@element('tab-framechart') 47fb726d48Sopenharmony_ciexport class FrameChart extends BaseElement { 48fb726d48Sopenharmony_ci private canvas!: HTMLCanvasElement; 49fb726d48Sopenharmony_ci private canvasContext!: CanvasRenderingContext2D; 50fb726d48Sopenharmony_ci private floatHint!: HTMLDivElement | undefined | null; // 悬浮框 51fb726d48Sopenharmony_ci 52fb726d48Sopenharmony_ci private rect: Rect = new Rect(0, 0, 0, 0); 53fb726d48Sopenharmony_ci private _mode = ChartMode.Byte; 54fb726d48Sopenharmony_ci private startX = 0; // 画布相对于整个界面的x坐标 55fb726d48Sopenharmony_ci private startY = 0; // 画布相对于整个界面的y坐标 56fb726d48Sopenharmony_ci private canvasX = -1; // 鼠标当前所在画布位置x坐标 57fb726d48Sopenharmony_ci private canvasY = -1; // 鼠标当前所在画布位置y坐标 58fb726d48Sopenharmony_ci private hintContent = ''; // 悬浮框内容。 html格式字符串 59fb726d48Sopenharmony_ci private rootNode!: ChartStruct; 60fb726d48Sopenharmony_ci private currentData: Array<ChartStruct> = []; 61fb726d48Sopenharmony_ci private xPoint = 0; // x in rect 62fb726d48Sopenharmony_ci private isFocusing = false; // 鼠标是否在画布范围内 63fb726d48Sopenharmony_ci private canvasScrollTop = 0; // Tab页上下滚动位置 64fb726d48Sopenharmony_ci private _maxDepth = 0; 65fb726d48Sopenharmony_ci private chartClickListenerList: Array<Function> = []; 66fb726d48Sopenharmony_ci private isUpdateCanvas = false; 67fb726d48Sopenharmony_ci private isClickMode = false; //是否为点选模式 68fb726d48Sopenharmony_ci _totalRootData: Array<ChartStruct> = [];//初始化顶部root的数据 69fb726d48Sopenharmony_ci private totalRootNode!: ChartStruct; 70fb726d48Sopenharmony_ci 71fb726d48Sopenharmony_ci /** 72fb726d48Sopenharmony_ci * set chart mode 73fb726d48Sopenharmony_ci * @param mode chart format for data mode 74fb726d48Sopenharmony_ci */ 75fb726d48Sopenharmony_ci set mode(mode: ChartMode) { 76fb726d48Sopenharmony_ci this._mode = mode; 77fb726d48Sopenharmony_ci } 78fb726d48Sopenharmony_ci 79fb726d48Sopenharmony_ci set data(val: Array<ChartStruct>) { 80fb726d48Sopenharmony_ci ChartStruct.lastSelectFuncStruct = undefined; 81fb726d48Sopenharmony_ci this.setSelectStatusRecursive(ChartStruct.selectFuncStruct, true); 82fb726d48Sopenharmony_ci ChartStruct.selectFuncStruct = undefined; 83fb726d48Sopenharmony_ci this.isClickMode = false; 84fb726d48Sopenharmony_ci this.currentData = val; 85fb726d48Sopenharmony_ci this.resetTrans(); 86fb726d48Sopenharmony_ci this.calDrawArgs(true); 87fb726d48Sopenharmony_ci } 88fb726d48Sopenharmony_ci 89fb726d48Sopenharmony_ci set tabPaneScrollTop(scrollTop: number) { 90fb726d48Sopenharmony_ci this.canvasScrollTop = scrollTop; 91fb726d48Sopenharmony_ci this.hideTip(); 92fb726d48Sopenharmony_ci } 93fb726d48Sopenharmony_ci 94fb726d48Sopenharmony_ci get totalRootData(): Array<ChartStruct> { 95fb726d48Sopenharmony_ci return this._totalRootData; 96fb726d48Sopenharmony_ci } 97fb726d48Sopenharmony_ci 98fb726d48Sopenharmony_ci set totalRootData(value: Array<ChartStruct>) { 99fb726d48Sopenharmony_ci this._totalRootData = value; 100fb726d48Sopenharmony_ci } 101fb726d48Sopenharmony_ci 102fb726d48Sopenharmony_ci private get total(): number { 103fb726d48Sopenharmony_ci return this.getNodeValue(this.rootNode); 104fb726d48Sopenharmony_ci } 105fb726d48Sopenharmony_ci 106fb726d48Sopenharmony_ci private getNodeValue(node: ChartStruct): number { 107fb726d48Sopenharmony_ci let result: number; 108fb726d48Sopenharmony_ci switch (this._mode) { 109fb726d48Sopenharmony_ci case ChartMode.Byte: 110fb726d48Sopenharmony_ci result = node.drawSize || node.size; 111fb726d48Sopenharmony_ci break; 112fb726d48Sopenharmony_ci case ChartMode.Count: 113fb726d48Sopenharmony_ci result = node.drawCount || node.count; 114fb726d48Sopenharmony_ci break; 115fb726d48Sopenharmony_ci case ChartMode.Duration: 116fb726d48Sopenharmony_ci result = node.drawDur || node.dur; 117fb726d48Sopenharmony_ci break; 118fb726d48Sopenharmony_ci case ChartMode.EventCount: 119fb726d48Sopenharmony_ci result = node.drawEventCount || node.eventCount; 120fb726d48Sopenharmony_ci break; 121fb726d48Sopenharmony_ci } 122fb726d48Sopenharmony_ci return result; 123fb726d48Sopenharmony_ci } 124fb726d48Sopenharmony_ci 125fb726d48Sopenharmony_ci /** 126fb726d48Sopenharmony_ci * add callback of chart click 127fb726d48Sopenharmony_ci * @param callback function of chart click 128fb726d48Sopenharmony_ci */ 129fb726d48Sopenharmony_ci public addChartClickListener(callback: Function): void { 130fb726d48Sopenharmony_ci if (this.chartClickListenerList.indexOf(callback) < 0) { 131fb726d48Sopenharmony_ci this.chartClickListenerList.push(callback); 132fb726d48Sopenharmony_ci } 133fb726d48Sopenharmony_ci } 134fb726d48Sopenharmony_ci 135fb726d48Sopenharmony_ci /** 136fb726d48Sopenharmony_ci * remove callback of chart click 137fb726d48Sopenharmony_ci * @param callback function of chart click 138fb726d48Sopenharmony_ci */ 139fb726d48Sopenharmony_ci public removeChartClickListener(callback: Function): void { 140fb726d48Sopenharmony_ci const index = this.chartClickListenerList.indexOf(callback); 141fb726d48Sopenharmony_ci if (index > -1) { 142fb726d48Sopenharmony_ci this.chartClickListenerList.splice(index, 1); 143fb726d48Sopenharmony_ci } 144fb726d48Sopenharmony_ci } 145fb726d48Sopenharmony_ci 146fb726d48Sopenharmony_ci private createRootNode(): void { 147fb726d48Sopenharmony_ci // 初始化root 148fb726d48Sopenharmony_ci this.rootNode = new ChartStruct(); 149fb726d48Sopenharmony_ci this.rootNode.symbol = 'root'; 150fb726d48Sopenharmony_ci this.rootNode.depth = 0; 151fb726d48Sopenharmony_ci this.rootNode.percent = 1; 152fb726d48Sopenharmony_ci this.rootNode.frame = new Rect(0, scaleHeight, this.canvas!.width, depthHeight); 153fb726d48Sopenharmony_ci for (const node of this.currentData!) { 154fb726d48Sopenharmony_ci this.rootNode.children.push(node); 155fb726d48Sopenharmony_ci this.rootNode.count += node.drawCount || node.count; 156fb726d48Sopenharmony_ci this.rootNode.size += node.drawSize || node.size; 157fb726d48Sopenharmony_ci this.rootNode.dur += node.drawDur || node.dur; 158fb726d48Sopenharmony_ci this.rootNode.eventCount += node.drawEventCount || node.eventCount; 159fb726d48Sopenharmony_ci node.parent = this.rootNode; 160fb726d48Sopenharmony_ci } 161fb726d48Sopenharmony_ci this.totalRootNode = new ChartStruct(); 162fb726d48Sopenharmony_ci this.totalRootNode.symbol = 'root'; 163fb726d48Sopenharmony_ci this.totalRootNode.depth = 0; 164fb726d48Sopenharmony_ci this.totalRootNode.percent = 1; 165fb726d48Sopenharmony_ci this.totalRootNode.frame = new Rect(0, scaleHeight, this.canvas!.width, depthHeight); 166fb726d48Sopenharmony_ci for (const node of this._totalRootData!) { 167fb726d48Sopenharmony_ci this.totalRootNode.children.push(node); 168fb726d48Sopenharmony_ci this.totalRootNode.count += node.drawCount || node.count; 169fb726d48Sopenharmony_ci this.totalRootNode.size += node.drawSize || node.size; 170fb726d48Sopenharmony_ci this.totalRootNode.dur += node.drawDur || node.dur; 171fb726d48Sopenharmony_ci this.totalRootNode.eventCount += node.drawEventCount || node.eventCount; 172fb726d48Sopenharmony_ci node.parent = this.totalRootNode; 173fb726d48Sopenharmony_ci } 174fb726d48Sopenharmony_ci } 175fb726d48Sopenharmony_ci 176fb726d48Sopenharmony_ci /** 177fb726d48Sopenharmony_ci * 1.计算调用栈最大深度 178fb726d48Sopenharmony_ci * 2.计算搜索情况下每个函数块显示的大小(非实际大小) 179fb726d48Sopenharmony_ci * 3.计算点选情况下每个函数块的显示大小(非实际大小) 180fb726d48Sopenharmony_ci * @param initRoot 是否初始化root节点 181fb726d48Sopenharmony_ci */ 182fb726d48Sopenharmony_ci private calDrawArgs(initRoot: boolean): void { 183fb726d48Sopenharmony_ci this._maxDepth = 0; 184fb726d48Sopenharmony_ci if (initRoot) { 185fb726d48Sopenharmony_ci this.createRootNode(); 186fb726d48Sopenharmony_ci } 187fb726d48Sopenharmony_ci this.initData(this.rootNode, 0, true); 188fb726d48Sopenharmony_ci this.selectInit(); 189fb726d48Sopenharmony_ci this.setRootValue(); 190fb726d48Sopenharmony_ci this.rect.width = this.canvas!.width; 191fb726d48Sopenharmony_ci this.rect.height = (this._maxDepth + 1) * depthHeight + scaleHeight; 192fb726d48Sopenharmony_ci this.canvas!.style.height = `${this.rect!.height}px`; 193fb726d48Sopenharmony_ci this.canvas!.height = Math.ceil(this.rect!.height); 194fb726d48Sopenharmony_ci } 195fb726d48Sopenharmony_ci 196fb726d48Sopenharmony_ci /** 197fb726d48Sopenharmony_ci * 点选情况下由点选来设置每个函数的显示Size 198fb726d48Sopenharmony_ci */ 199fb726d48Sopenharmony_ci private selectInit(): void { 200fb726d48Sopenharmony_ci const node = ChartStruct.selectFuncStruct; 201fb726d48Sopenharmony_ci if (node) { 202fb726d48Sopenharmony_ci const module = new NodeValue(); 203fb726d48Sopenharmony_ci node.drawCount = 0; 204fb726d48Sopenharmony_ci node.drawDur = 0; 205fb726d48Sopenharmony_ci node.drawSize = 0; 206fb726d48Sopenharmony_ci node.drawEventCount = 0; 207fb726d48Sopenharmony_ci for (let child of node.children) { 208fb726d48Sopenharmony_ci node.drawCount += child.searchCount; 209fb726d48Sopenharmony_ci node.drawDur += child.searchDur; 210fb726d48Sopenharmony_ci node.drawSize += child.searchSize; 211fb726d48Sopenharmony_ci node.drawEventCount += child.searchEventCount; 212fb726d48Sopenharmony_ci } 213fb726d48Sopenharmony_ci module.count = node.drawCount = node.drawCount || node.count; 214fb726d48Sopenharmony_ci module.dur = node.drawDur = node.drawDur || node.dur; 215fb726d48Sopenharmony_ci module.size = node.drawSize = node.drawSize || node.size; 216fb726d48Sopenharmony_ci module.eventCount = node.drawEventCount = node.drawEventCount || node.eventCount; 217fb726d48Sopenharmony_ci 218fb726d48Sopenharmony_ci this.setParentDisplayInfo(node, module, true); 219fb726d48Sopenharmony_ci this.setChildrenDisplayInfo(node); 220fb726d48Sopenharmony_ci this.clearOtherDisplayInfo(this.rootNode); 221fb726d48Sopenharmony_ci } 222fb726d48Sopenharmony_ci } 223fb726d48Sopenharmony_ci 224fb726d48Sopenharmony_ci private clearOtherDisplayInfo(node: ChartStruct): void { 225fb726d48Sopenharmony_ci for (const children of node.children) { 226fb726d48Sopenharmony_ci if (children.isChartSelect) { 227fb726d48Sopenharmony_ci this.clearOtherDisplayInfo(children); 228fb726d48Sopenharmony_ci continue; 229fb726d48Sopenharmony_ci } 230fb726d48Sopenharmony_ci children.drawCount = 0; 231fb726d48Sopenharmony_ci children.drawEventCount = 0; 232fb726d48Sopenharmony_ci children.drawSize = 0; 233fb726d48Sopenharmony_ci children.drawDur = 0; 234fb726d48Sopenharmony_ci this.clearOtherDisplayInfo(children); 235fb726d48Sopenharmony_ci } 236fb726d48Sopenharmony_ci } 237fb726d48Sopenharmony_ci 238fb726d48Sopenharmony_ci // 设置root显示区域value 以及占真实value的百分比 239fb726d48Sopenharmony_ci private setRootValue(): void { 240fb726d48Sopenharmony_ci let currentValue = ''; 241fb726d48Sopenharmony_ci let currentValuePercent = 1; 242fb726d48Sopenharmony_ci switch (this._mode) { 243fb726d48Sopenharmony_ci case ChartMode.Byte: 244fb726d48Sopenharmony_ci currentValue = Utils.getBinaryByteWithUnit(this.total); 245fb726d48Sopenharmony_ci currentValuePercent = this.total / this.rootNode.size; 246fb726d48Sopenharmony_ci break; 247fb726d48Sopenharmony_ci case ChartMode.Count: 248fb726d48Sopenharmony_ci currentValue = `${this.total}`; 249fb726d48Sopenharmony_ci currentValuePercent = this.total / this.totalRootNode.count; 250fb726d48Sopenharmony_ci break; 251fb726d48Sopenharmony_ci case ChartMode.Duration: 252fb726d48Sopenharmony_ci currentValue = Utils.getProbablyTime(this.total); 253fb726d48Sopenharmony_ci currentValuePercent = this.total / this.rootNode.dur; 254fb726d48Sopenharmony_ci break; 255fb726d48Sopenharmony_ci case ChartMode.EventCount: 256fb726d48Sopenharmony_ci currentValue = `${this.total}`; 257fb726d48Sopenharmony_ci currentValuePercent = this.total / this.totalRootNode.eventCount; 258fb726d48Sopenharmony_ci break; 259fb726d48Sopenharmony_ci } 260fb726d48Sopenharmony_ci let endStr = currentValuePercent ? ` (${(currentValuePercent * 100).toFixed(2)}%)` : ''; 261fb726d48Sopenharmony_ci this.rootNode.symbol = `Root : ${currentValue}${endStr}`; 262fb726d48Sopenharmony_ci } 263fb726d48Sopenharmony_ci 264fb726d48Sopenharmony_ci /** 265fb726d48Sopenharmony_ci * 判断lib中是否包含.ts .ets .js .hap 266fb726d48Sopenharmony_ci * @param str node.lib 267fb726d48Sopenharmony_ci * @returns 是否包含 268fb726d48Sopenharmony_ci */ 269fb726d48Sopenharmony_ci private isJsStack(str: string): boolean { 270fb726d48Sopenharmony_ci let keyList = jsStackPath; 271fb726d48Sopenharmony_ci if (this._mode === ChartMode.Count || this._mode === ChartMode.EventCount) { 272fb726d48Sopenharmony_ci keyList = jsStackPath.concat(jsHapKeys); 273fb726d48Sopenharmony_ci } 274fb726d48Sopenharmony_ci for (const format of keyList) { 275fb726d48Sopenharmony_ci if (str.indexOf(format) > 0) { 276fb726d48Sopenharmony_ci return true; 277fb726d48Sopenharmony_ci } 278fb726d48Sopenharmony_ci } 279fb726d48Sopenharmony_ci return false; 280fb726d48Sopenharmony_ci } 281fb726d48Sopenharmony_ci 282fb726d48Sopenharmony_ci private clearSuperfluousParams(node: ChartStruct): void { 283fb726d48Sopenharmony_ci node.id = undefined; 284fb726d48Sopenharmony_ci node.eventType = undefined; 285fb726d48Sopenharmony_ci node.parentId = undefined; 286fb726d48Sopenharmony_ci node.title = undefined; 287fb726d48Sopenharmony_ci node.eventType = undefined; 288fb726d48Sopenharmony_ci if (this.mode === ChartMode.Byte) { 289fb726d48Sopenharmony_ci node.self = undefined; 290fb726d48Sopenharmony_ci node.eventCount = 0; 291fb726d48Sopenharmony_ci } 292fb726d48Sopenharmony_ci if (this._mode !== ChartMode.Count && this._mode !== ChartMode.EventCount) { 293fb726d48Sopenharmony_ci node.eventCount = 0; 294fb726d48Sopenharmony_ci node.eventPercent = undefined; 295fb726d48Sopenharmony_ci } 296fb726d48Sopenharmony_ci } 297fb726d48Sopenharmony_ci 298fb726d48Sopenharmony_ci /** 299fb726d48Sopenharmony_ci * 计算调用栈最大深度,计算每个node显示大小 300fb726d48Sopenharmony_ci * @param node 函数块 301fb726d48Sopenharmony_ci * @param depth 当前递归深度 302fb726d48Sopenharmony_ci * @param calDisplay 该层深度是否需要计算显示大小 303fb726d48Sopenharmony_ci */ 304fb726d48Sopenharmony_ci private initData(node: ChartStruct, depth: number, calDisplay: boolean): void { 305fb726d48Sopenharmony_ci node.depth = depth; 306fb726d48Sopenharmony_ci depth++; 307fb726d48Sopenharmony_ci this.clearSuperfluousParams(node); 308fb726d48Sopenharmony_ci if (this.isJsStack(node.lib)) { 309fb726d48Sopenharmony_ci node.isJsStack = true; 310fb726d48Sopenharmony_ci } else { 311fb726d48Sopenharmony_ci node.isJsStack = false; 312fb726d48Sopenharmony_ci } 313fb726d48Sopenharmony_ci 314fb726d48Sopenharmony_ci //设置搜索以及点选的显示值,将点击/搜索的值设置为父节点的显示值 315fb726d48Sopenharmony_ci this.clearDisplayInfo(node); 316fb726d48Sopenharmony_ci if (node.isSearch && calDisplay) { 317fb726d48Sopenharmony_ci const module = new NodeValue(); 318fb726d48Sopenharmony_ci module.size = node.drawSize = node.searchSize = node.size; 319fb726d48Sopenharmony_ci module.count = node.drawCount = node.searchCount = node.count; 320fb726d48Sopenharmony_ci module.dur = node.drawDur = node.searchDur = node.dur; 321fb726d48Sopenharmony_ci module.eventCount = node.drawEventCount = node.searchEventCount = node.eventCount; 322fb726d48Sopenharmony_ci this.setParentDisplayInfo(node, module, false); 323fb726d48Sopenharmony_ci calDisplay = false; 324fb726d48Sopenharmony_ci } 325fb726d48Sopenharmony_ci 326fb726d48Sopenharmony_ci // 设置parent以及计算最大的深度 327fb726d48Sopenharmony_ci if (node.children && node.children.length > 0) { 328fb726d48Sopenharmony_ci for (const children of node.children) { 329fb726d48Sopenharmony_ci children.parent = node; 330fb726d48Sopenharmony_ci this.initData(children, depth, calDisplay); 331fb726d48Sopenharmony_ci } 332fb726d48Sopenharmony_ci } else { 333fb726d48Sopenharmony_ci this._maxDepth = Math.max(depth, this._maxDepth); 334fb726d48Sopenharmony_ci } 335fb726d48Sopenharmony_ci } 336fb726d48Sopenharmony_ci 337fb726d48Sopenharmony_ci // 递归设置node parent的显示大小 338fb726d48Sopenharmony_ci private setParentDisplayInfo(node: ChartStruct, module: NodeValue, isSelect?: boolean): void { 339fb726d48Sopenharmony_ci const parent = node.parent; 340fb726d48Sopenharmony_ci if (parent) { 341fb726d48Sopenharmony_ci if (isSelect) { 342fb726d48Sopenharmony_ci parent.isChartSelect = true; 343fb726d48Sopenharmony_ci parent.isChartSelectParent = true; 344fb726d48Sopenharmony_ci parent.drawCount = module.count; 345fb726d48Sopenharmony_ci parent.drawDur = module.dur; 346fb726d48Sopenharmony_ci parent.drawSize = module.size; 347fb726d48Sopenharmony_ci parent.drawEventCount = module.eventCount; 348fb726d48Sopenharmony_ci } else { 349fb726d48Sopenharmony_ci parent.searchCount += module.count; 350fb726d48Sopenharmony_ci parent.searchDur += module.dur; 351fb726d48Sopenharmony_ci parent.searchSize += module.size; 352fb726d48Sopenharmony_ci parent.searchEventCount += module.eventCount; 353fb726d48Sopenharmony_ci // 点击模式下不需要赋值draw value,由点击去 354fb726d48Sopenharmony_ci if (!this.isClickMode) { 355fb726d48Sopenharmony_ci parent.drawDur = parent.searchDur; 356fb726d48Sopenharmony_ci parent.drawCount = parent.searchCount; 357fb726d48Sopenharmony_ci parent.drawSize = parent.searchSize; 358fb726d48Sopenharmony_ci parent.drawEventCount = parent.searchEventCount; 359fb726d48Sopenharmony_ci } 360fb726d48Sopenharmony_ci } 361fb726d48Sopenharmony_ci this.setParentDisplayInfo(parent, module, isSelect); 362fb726d48Sopenharmony_ci } 363fb726d48Sopenharmony_ci } 364fb726d48Sopenharmony_ci 365fb726d48Sopenharmony_ci /** 366fb726d48Sopenharmony_ci * 点击与搜索同时触发情况下,由点击去设置绘制大小 367fb726d48Sopenharmony_ci * @param node 当前点选的函数 368fb726d48Sopenharmony_ci * @returns void 369fb726d48Sopenharmony_ci */ 370fb726d48Sopenharmony_ci private setChildrenDisplayInfo(node: ChartStruct): void { 371fb726d48Sopenharmony_ci if (node.children.length < 0) { 372fb726d48Sopenharmony_ci return; 373fb726d48Sopenharmony_ci } 374fb726d48Sopenharmony_ci for (const children of node.children) { 375fb726d48Sopenharmony_ci children.drawCount = children.searchCount || children.count; 376fb726d48Sopenharmony_ci children.drawDur = children.searchDur || children.dur; 377fb726d48Sopenharmony_ci children.drawSize = children.searchSize || children.size; 378fb726d48Sopenharmony_ci children.drawEventCount = children.searchEventCount || children.eventCount; 379fb726d48Sopenharmony_ci this.setChildrenDisplayInfo(children); 380fb726d48Sopenharmony_ci } 381fb726d48Sopenharmony_ci } 382fb726d48Sopenharmony_ci 383fb726d48Sopenharmony_ci private clearDisplayInfo(node: ChartStruct): void { 384fb726d48Sopenharmony_ci node.drawCount = 0; 385fb726d48Sopenharmony_ci node.drawDur = 0; 386fb726d48Sopenharmony_ci node.drawSize = 0; 387fb726d48Sopenharmony_ci node.drawEventCount = 0; 388fb726d48Sopenharmony_ci node.searchCount = 0; 389fb726d48Sopenharmony_ci node.searchDur = 0; 390fb726d48Sopenharmony_ci node.searchSize = 0; 391fb726d48Sopenharmony_ci node.searchEventCount = 0; 392fb726d48Sopenharmony_ci } 393fb726d48Sopenharmony_ci 394fb726d48Sopenharmony_ci /** 395fb726d48Sopenharmony_ci * 计算每个函数块的坐标信息以及绘制火焰图 396fb726d48Sopenharmony_ci */ 397fb726d48Sopenharmony_ci public async calculateChartData(): Promise<void> { 398fb726d48Sopenharmony_ci this.clearCanvas(); 399fb726d48Sopenharmony_ci this.canvasContext?.beginPath(); 400fb726d48Sopenharmony_ci this.canvasContext.font = textStyle; 401fb726d48Sopenharmony_ci // 绘制刻度线 402fb726d48Sopenharmony_ci this.drawCalibrationTails(); 403fb726d48Sopenharmony_ci // 绘制root节点 404fb726d48Sopenharmony_ci draw(this.canvasContext, this.rootNode); 405fb726d48Sopenharmony_ci // 设置子节点的位置以及宽高 406fb726d48Sopenharmony_ci this.setFrameData(this.rootNode); 407fb726d48Sopenharmony_ci // 绘制子节点 408fb726d48Sopenharmony_ci this.drawFrameChart(this.rootNode); 409fb726d48Sopenharmony_ci this.canvasContext?.closePath(); 410fb726d48Sopenharmony_ci } 411fb726d48Sopenharmony_ci 412fb726d48Sopenharmony_ci /** 413fb726d48Sopenharmony_ci * 清空画布 414fb726d48Sopenharmony_ci */ 415fb726d48Sopenharmony_ci public clearCanvas(): void { 416fb726d48Sopenharmony_ci this.canvasContext?.clearRect(0, 0, this.canvas!.width, this.canvas!.height); 417fb726d48Sopenharmony_ci } 418fb726d48Sopenharmony_ci 419fb726d48Sopenharmony_ci /** 420fb726d48Sopenharmony_ci * 在窗口大小变化时调整画布大小 421fb726d48Sopenharmony_ci */ 422fb726d48Sopenharmony_ci public updateCanvas(updateWidth: boolean, newWidth?: number): void { 423fb726d48Sopenharmony_ci if (this.canvas instanceof HTMLCanvasElement) { 424fb726d48Sopenharmony_ci this.canvas.style.width = `${100}%`; 425fb726d48Sopenharmony_ci this.canvas.style.height = `${this.rect!.height}px`; 426fb726d48Sopenharmony_ci if (this.canvas.clientWidth === 0 && newWidth) { 427fb726d48Sopenharmony_ci this.canvas.width = newWidth - depthHeight * 2; 428fb726d48Sopenharmony_ci } else { 429fb726d48Sopenharmony_ci this.canvas.width = this.canvas.clientWidth; 430fb726d48Sopenharmony_ci } 431fb726d48Sopenharmony_ci this.canvas.height = Math.ceil(this.rect!.height); 432fb726d48Sopenharmony_ci this.updateCanvasCoord(); 433fb726d48Sopenharmony_ci } 434fb726d48Sopenharmony_ci if ( 435fb726d48Sopenharmony_ci this.rect.width === 0 || 436fb726d48Sopenharmony_ci updateWidth || 437fb726d48Sopenharmony_ci Math.round(newWidth!) !== this.canvas!.width + depthHeight * 2 || 438fb726d48Sopenharmony_ci newWidth! > this.rect.width 439fb726d48Sopenharmony_ci ) { 440fb726d48Sopenharmony_ci this.rect.width = this.canvas!.width; 441fb726d48Sopenharmony_ci } 442fb726d48Sopenharmony_ci } 443fb726d48Sopenharmony_ci 444fb726d48Sopenharmony_ci /** 445fb726d48Sopenharmony_ci * 更新画布坐标 446fb726d48Sopenharmony_ci */ 447fb726d48Sopenharmony_ci private updateCanvasCoord(): void { 448fb726d48Sopenharmony_ci if (this.canvas instanceof HTMLCanvasElement) { 449fb726d48Sopenharmony_ci this.isUpdateCanvas = this.canvas.clientWidth !== 0; 450fb726d48Sopenharmony_ci if (this.canvas.getBoundingClientRect()) { 451fb726d48Sopenharmony_ci const box = this.canvas.getBoundingClientRect(); 452fb726d48Sopenharmony_ci const D = document.documentElement; 453fb726d48Sopenharmony_ci this.startX = box.left + Math.max(D.scrollLeft, document.body.scrollLeft) - D.clientLeft; 454fb726d48Sopenharmony_ci this.startY = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop + this.canvasScrollTop; 455fb726d48Sopenharmony_ci } 456fb726d48Sopenharmony_ci } 457fb726d48Sopenharmony_ci } 458fb726d48Sopenharmony_ci 459fb726d48Sopenharmony_ci /** 460fb726d48Sopenharmony_ci * 绘制刻度尺,分为100段,每10段画一条长线 461fb726d48Sopenharmony_ci */ 462fb726d48Sopenharmony_ci private drawCalibrationTails(): void { 463fb726d48Sopenharmony_ci const spApplication = <SpApplication>document.getElementsByTagName('sp-application')[0]; 464fb726d48Sopenharmony_ci this.canvasContext!.lineWidth = 0.5; 465fb726d48Sopenharmony_ci this.canvasContext?.moveTo(0, 0); 466fb726d48Sopenharmony_ci this.canvasContext?.lineTo(this.canvas!.width, 0); 467fb726d48Sopenharmony_ci for (let i = 0; i <= 10; i++) { 468fb726d48Sopenharmony_ci let startX = Math.floor((this.canvas!.width / 10) * i); 469fb726d48Sopenharmony_ci for (let j = 0; j < 10; j++) { 470fb726d48Sopenharmony_ci this.canvasContext!.lineWidth = 0.5; 471fb726d48Sopenharmony_ci const startItemX = startX + Math.floor((this.canvas!.width / 100) * j); 472fb726d48Sopenharmony_ci this.canvasContext?.moveTo(startItemX, 0); 473fb726d48Sopenharmony_ci this.canvasContext?.lineTo(startItemX, 10); 474fb726d48Sopenharmony_ci } 475fb726d48Sopenharmony_ci if (i === 0) { 476fb726d48Sopenharmony_ci continue; 477fb726d48Sopenharmony_ci } 478fb726d48Sopenharmony_ci this.canvasContext!.lineWidth = 1; 479fb726d48Sopenharmony_ci const sizeRatio = this.canvas!.width / this.rect.width; // scale ratio 480fb726d48Sopenharmony_ci if (spApplication.dark) { 481fb726d48Sopenharmony_ci this.canvasContext!.strokeStyle = '#888'; 482fb726d48Sopenharmony_ci } else { 483fb726d48Sopenharmony_ci this.canvasContext!.strokeStyle = '#ddd'; 484fb726d48Sopenharmony_ci } 485fb726d48Sopenharmony_ci this.canvasContext?.moveTo(startX, 0); 486fb726d48Sopenharmony_ci this.canvasContext?.lineTo(startX, this.canvas!.height); 487fb726d48Sopenharmony_ci if (spApplication.dark) { 488fb726d48Sopenharmony_ci this.canvasContext!.fillStyle = '#fff'; 489fb726d48Sopenharmony_ci } else { 490fb726d48Sopenharmony_ci this.canvasContext!.fillStyle = '#000'; 491fb726d48Sopenharmony_ci } 492fb726d48Sopenharmony_ci let calibration = ''; 493fb726d48Sopenharmony_ci switch (this._mode) { 494fb726d48Sopenharmony_ci case ChartMode.Byte: 495fb726d48Sopenharmony_ci calibration = Utils.getByteWithUnit(((this.total * sizeRatio) / 10) * i); 496fb726d48Sopenharmony_ci break; 497fb726d48Sopenharmony_ci case ChartMode.Duration: 498fb726d48Sopenharmony_ci calibration = Utils.getProbablyTime(((this.total * sizeRatio) / 10) * i); 499fb726d48Sopenharmony_ci break; 500fb726d48Sopenharmony_ci case ChartMode.EventCount: 501fb726d48Sopenharmony_ci case ChartMode.Count: 502fb726d48Sopenharmony_ci calibration = `${Math.ceil(((this.total * sizeRatio) / 10) * i)}`; 503fb726d48Sopenharmony_ci break; 504fb726d48Sopenharmony_ci } 505fb726d48Sopenharmony_ci const size = this.canvasContext!.measureText(calibration).width; 506fb726d48Sopenharmony_ci this.canvasContext?.fillText(calibration, startX - size - 5, depthHeight, textMaxWidth); 507fb726d48Sopenharmony_ci this.canvasContext?.stroke(); 508fb726d48Sopenharmony_ci } 509fb726d48Sopenharmony_ci } 510fb726d48Sopenharmony_ci 511fb726d48Sopenharmony_ci /** 512fb726d48Sopenharmony_ci * 设置每个node的宽高,开始坐标 513fb726d48Sopenharmony_ci * @param node 函数块 514fb726d48Sopenharmony_ci */ 515fb726d48Sopenharmony_ci private setFrameData(node: ChartStruct): void { 516fb726d48Sopenharmony_ci if (node.children.length > 0) { 517fb726d48Sopenharmony_ci for (const children of node.children) { 518fb726d48Sopenharmony_ci node.isDraw = false; 519fb726d48Sopenharmony_ci if (this.isClickMode && ChartStruct.selectFuncStruct) { 520fb726d48Sopenharmony_ci //处理点击逻辑,当前node为点选调用栈,children不是点选调用栈,width置为0 521fb726d48Sopenharmony_ci if (!children.isChartSelect) { 522fb726d48Sopenharmony_ci if (children.frame) { 523fb726d48Sopenharmony_ci children.frame.x = this.rootNode.frame?.x || 0; 524fb726d48Sopenharmony_ci children.frame.width = 0; 525fb726d48Sopenharmony_ci children.percent = 0; 526fb726d48Sopenharmony_ci } else { 527fb726d48Sopenharmony_ci children.frame = new Rect(0, 0, 0, 0); 528fb726d48Sopenharmony_ci } 529fb726d48Sopenharmony_ci this.setFrameData(children); 530fb726d48Sopenharmony_ci continue; 531fb726d48Sopenharmony_ci } 532fb726d48Sopenharmony_ci } 533fb726d48Sopenharmony_ci const childrenValue = this.getNodeValue(children); 534fb726d48Sopenharmony_ci setFuncFrame(children, this.rect, this.total, this._mode); 535fb726d48Sopenharmony_ci children.percent = childrenValue / this.total; 536fb726d48Sopenharmony_ci this.setFrameData(children); 537fb726d48Sopenharmony_ci } 538fb726d48Sopenharmony_ci } 539fb726d48Sopenharmony_ci } 540fb726d48Sopenharmony_ci 541fb726d48Sopenharmony_ci /** 542fb726d48Sopenharmony_ci * 计算有效数据,当node的宽度太小不足以绘制时 543fb726d48Sopenharmony_ci * 计算忽略node的size 544fb726d48Sopenharmony_ci * 忽略的size将转换成width,按照比例平摊到显示的node上 545fb726d48Sopenharmony_ci * @param node 当前node 546fb726d48Sopenharmony_ci * @param effectChildList 生效的node 547fb726d48Sopenharmony_ci */ 548fb726d48Sopenharmony_ci private calEffectNode(node: ChartStruct, effectChildList: Array<ChartStruct>): number { 549fb726d48Sopenharmony_ci const ignore = new NodeValue(); 550fb726d48Sopenharmony_ci for (const children of node.children) { 551fb726d48Sopenharmony_ci // 小于1px的不绘制,并将其size平均赋值给>1px的 552fb726d48Sopenharmony_ci if (children.frame!.width >= filterPixel) { 553fb726d48Sopenharmony_ci effectChildList.push(children); 554fb726d48Sopenharmony_ci } else { 555fb726d48Sopenharmony_ci if (node.isChartSelect || this.isSearch(node)) { 556fb726d48Sopenharmony_ci ignore.size += children.drawSize; 557fb726d48Sopenharmony_ci ignore.count += children.drawCount; 558fb726d48Sopenharmony_ci ignore.dur += children.drawDur; 559fb726d48Sopenharmony_ci ignore.eventCount += children.drawEventCount; 560fb726d48Sopenharmony_ci } else { 561fb726d48Sopenharmony_ci ignore.size += children.size; 562fb726d48Sopenharmony_ci ignore.count += children.count; 563fb726d48Sopenharmony_ci ignore.dur += children.dur; 564fb726d48Sopenharmony_ci ignore.eventCount += children.eventCount; 565fb726d48Sopenharmony_ci } 566fb726d48Sopenharmony_ci } 567fb726d48Sopenharmony_ci } 568fb726d48Sopenharmony_ci let result: number = 0; 569fb726d48Sopenharmony_ci switch (this._mode) { 570fb726d48Sopenharmony_ci case ChartMode.Byte: 571fb726d48Sopenharmony_ci result = ignore.size; 572fb726d48Sopenharmony_ci break; 573fb726d48Sopenharmony_ci case ChartMode.Count: 574fb726d48Sopenharmony_ci result = ignore.count; 575fb726d48Sopenharmony_ci break; 576fb726d48Sopenharmony_ci case ChartMode.Duration: 577fb726d48Sopenharmony_ci result = ignore.dur; 578fb726d48Sopenharmony_ci break; 579fb726d48Sopenharmony_ci case ChartMode.EventCount: 580fb726d48Sopenharmony_ci result = ignore.eventCount; 581fb726d48Sopenharmony_ci break; 582fb726d48Sopenharmony_ci } 583fb726d48Sopenharmony_ci return result; 584fb726d48Sopenharmony_ci } 585fb726d48Sopenharmony_ci 586fb726d48Sopenharmony_ci private isSearch(node: ChartStruct): boolean { 587fb726d48Sopenharmony_ci let result: boolean = false; 588fb726d48Sopenharmony_ci switch (this._mode) { 589fb726d48Sopenharmony_ci case ChartMode.Byte: 590fb726d48Sopenharmony_ci result = node.searchSize > 0; 591fb726d48Sopenharmony_ci break; 592fb726d48Sopenharmony_ci case ChartMode.Count: 593fb726d48Sopenharmony_ci result = node.searchCount > 0; 594fb726d48Sopenharmony_ci break; 595fb726d48Sopenharmony_ci case ChartMode.Duration: 596fb726d48Sopenharmony_ci result = node.searchDur > 0; 597fb726d48Sopenharmony_ci break; 598fb726d48Sopenharmony_ci case ChartMode.EventCount: 599fb726d48Sopenharmony_ci result = node.searchEventCount > 0; 600fb726d48Sopenharmony_ci break; 601fb726d48Sopenharmony_ci } 602fb726d48Sopenharmony_ci return result; 603fb726d48Sopenharmony_ci } 604fb726d48Sopenharmony_ci 605fb726d48Sopenharmony_ci /** 606fb726d48Sopenharmony_ci * 绘制每个函数色块 607fb726d48Sopenharmony_ci * @param node 函数块 608fb726d48Sopenharmony_ci */ 609fb726d48Sopenharmony_ci private drawFrameChart(node: ChartStruct): void { 610fb726d48Sopenharmony_ci const effectChildList: Array<ChartStruct> = []; 611fb726d48Sopenharmony_ci const nodeValue = this.getNodeValue(node); 612fb726d48Sopenharmony_ci 613fb726d48Sopenharmony_ci if (node.children && node.children.length > 0) { 614fb726d48Sopenharmony_ci const ignoreValue = this.calEffectNode(node, effectChildList); 615fb726d48Sopenharmony_ci let x = node.frame!.x; 616fb726d48Sopenharmony_ci if (effectChildList.length > 0) { 617fb726d48Sopenharmony_ci for (let children of effectChildList) { 618fb726d48Sopenharmony_ci children.frame!.x = x; 619fb726d48Sopenharmony_ci const childrenValue = this.getNodeValue(children); 620fb726d48Sopenharmony_ci children.frame!.width = (childrenValue / (nodeValue - ignoreValue)) * node.frame!.width; 621fb726d48Sopenharmony_ci x += children.frame!.width; 622fb726d48Sopenharmony_ci if (this.nodeInCanvas(children)) { 623fb726d48Sopenharmony_ci draw(this.canvasContext!, children); 624fb726d48Sopenharmony_ci this.drawFrameChart(children); 625fb726d48Sopenharmony_ci } 626fb726d48Sopenharmony_ci } 627fb726d48Sopenharmony_ci } else { 628fb726d48Sopenharmony_ci const firstChildren = node.children[0]; 629fb726d48Sopenharmony_ci firstChildren.frame!.x = node.frame!.x; 630fb726d48Sopenharmony_ci // perf parent有selfTime 需要所有children的count跟 631fb726d48Sopenharmony_ci firstChildren.frame!.width = node.frame!.width * (ignoreValue / nodeValue); 632fb726d48Sopenharmony_ci draw(this.canvasContext!, firstChildren); 633fb726d48Sopenharmony_ci this.drawFrameChart(firstChildren); 634fb726d48Sopenharmony_ci } 635fb726d48Sopenharmony_ci } 636fb726d48Sopenharmony_ci } 637fb726d48Sopenharmony_ci 638fb726d48Sopenharmony_ci /** 639fb726d48Sopenharmony_ci * 根据鼠标当前的坐标递归查找对应的函数块 640fb726d48Sopenharmony_ci * 641fb726d48Sopenharmony_ci * @param nodes 642fb726d48Sopenharmony_ci * @param canvasX 鼠标相对于画布开始点的x坐标 643fb726d48Sopenharmony_ci * @param canvasY 鼠标相对于画布开始点的y坐标 644fb726d48Sopenharmony_ci * @returns 当前鼠标位置的函数块 645fb726d48Sopenharmony_ci */ 646fb726d48Sopenharmony_ci private searchDataByCoord(nodes: Array<ChartStruct>, canvasX: number, canvasY: number): ChartStruct | null { 647fb726d48Sopenharmony_ci for (const node of nodes) { 648fb726d48Sopenharmony_ci if (node.frame?.contains(canvasX, canvasY)) { 649fb726d48Sopenharmony_ci return node; 650fb726d48Sopenharmony_ci } else { 651fb726d48Sopenharmony_ci const result = this.searchDataByCoord(node.children, canvasX, canvasY); 652fb726d48Sopenharmony_ci // if not found in this branch;search another branch 653fb726d48Sopenharmony_ci if (!result) { 654fb726d48Sopenharmony_ci continue; 655fb726d48Sopenharmony_ci } 656fb726d48Sopenharmony_ci return result; 657fb726d48Sopenharmony_ci } 658fb726d48Sopenharmony_ci } 659fb726d48Sopenharmony_ci return null; 660fb726d48Sopenharmony_ci } 661fb726d48Sopenharmony_ci 662fb726d48Sopenharmony_ci /** 663fb726d48Sopenharmony_ci * 显示悬浮框信息,更新位置 664fb726d48Sopenharmony_ci */ 665fb726d48Sopenharmony_ci private showTip(): void { 666fb726d48Sopenharmony_ci this.floatHint!.innerHTML = this.hintContent; 667fb726d48Sopenharmony_ci this.floatHint!.style.display = 'block'; 668fb726d48Sopenharmony_ci let x = this.canvasX; 669fb726d48Sopenharmony_ci let y = this.canvasY - this.canvasScrollTop; 670fb726d48Sopenharmony_ci //右边的函数块悬浮框显示在函数左边 671fb726d48Sopenharmony_ci if (this.canvasX + this.floatHint!.clientWidth > (this.canvas?.clientWidth || 0)) { 672fb726d48Sopenharmony_ci x -= this.floatHint!.clientWidth - 1; 673fb726d48Sopenharmony_ci } else { 674fb726d48Sopenharmony_ci x += scaleHeight; 675fb726d48Sopenharmony_ci } 676fb726d48Sopenharmony_ci //顶部悬浮框显示在函数下边,下半部分悬浮框显示在函数上边 677fb726d48Sopenharmony_ci if (y > this.floatHint!.clientHeight) { 678fb726d48Sopenharmony_ci y -= this.floatHint!.clientHeight - 1; 679fb726d48Sopenharmony_ci } 680fb726d48Sopenharmony_ci 681fb726d48Sopenharmony_ci this.floatHint!.style.transform = `translate(${x}px,${y}px)`; 682fb726d48Sopenharmony_ci } 683fb726d48Sopenharmony_ci 684fb726d48Sopenharmony_ci /** 685fb726d48Sopenharmony_ci * 递归设置传入node的parent以及children的isSelect 686fb726d48Sopenharmony_ci * 将上次点选的整条树的isSelect置为false 687fb726d48Sopenharmony_ci * 将本次点击的整条树的isSelect置为true 688fb726d48Sopenharmony_ci * @param node 点击的node 689fb726d48Sopenharmony_ci * @param isSelect 点选 690fb726d48Sopenharmony_ci */ 691fb726d48Sopenharmony_ci private setSelectStatusRecursive(node: ChartStruct | undefined, isSelect: boolean): void { 692fb726d48Sopenharmony_ci if (!node) { 693fb726d48Sopenharmony_ci return; 694fb726d48Sopenharmony_ci } 695fb726d48Sopenharmony_ci node.isChartSelect = isSelect; 696fb726d48Sopenharmony_ci 697fb726d48Sopenharmony_ci // 处理子节点及其子节点的子节点 698fb726d48Sopenharmony_ci const stack: ChartStruct[] = [node]; // 使用栈来实现循环处理 699fb726d48Sopenharmony_ci while (stack.length > 0) { 700fb726d48Sopenharmony_ci const currentNode = stack.pop(); 701fb726d48Sopenharmony_ci if (currentNode) { 702fb726d48Sopenharmony_ci currentNode.children.forEach((child) => { 703fb726d48Sopenharmony_ci child.isChartSelect = isSelect; 704fb726d48Sopenharmony_ci stack.push(child); 705fb726d48Sopenharmony_ci }); 706fb726d48Sopenharmony_ci } 707fb726d48Sopenharmony_ci } 708fb726d48Sopenharmony_ci 709fb726d48Sopenharmony_ci // 处理父节点 710fb726d48Sopenharmony_ci while (node?.parent) { 711fb726d48Sopenharmony_ci node.parent.isChartSelect = isSelect; 712fb726d48Sopenharmony_ci node.parent.isChartSelectParent = isSelect; 713fb726d48Sopenharmony_ci node = node.parent; 714fb726d48Sopenharmony_ci } 715fb726d48Sopenharmony_ci } 716fb726d48Sopenharmony_ci 717fb726d48Sopenharmony_ci /** 718fb726d48Sopenharmony_ci * 点选后重绘火焰图 719fb726d48Sopenharmony_ci */ 720fb726d48Sopenharmony_ci private clickRedraw(): void { 721fb726d48Sopenharmony_ci //将上次点选的isSelect置为false 722fb726d48Sopenharmony_ci if (ChartStruct.lastSelectFuncStruct) { 723fb726d48Sopenharmony_ci this.setSelectStatusRecursive(ChartStruct.lastSelectFuncStruct!, false); 724fb726d48Sopenharmony_ci } 725fb726d48Sopenharmony_ci // 递归设置点选的parent,children为点选状态 726fb726d48Sopenharmony_ci this.setSelectStatusRecursive(ChartStruct.selectFuncStruct!, true); 727fb726d48Sopenharmony_ci 728fb726d48Sopenharmony_ci this.calDrawArgs(false); 729fb726d48Sopenharmony_ci this.calculateChartData(); 730fb726d48Sopenharmony_ci } 731fb726d48Sopenharmony_ci 732fb726d48Sopenharmony_ci /** 733fb726d48Sopenharmony_ci * 点击w s的放缩算法 734fb726d48Sopenharmony_ci * @param index < 0 缩小 , > 0 放大 735fb726d48Sopenharmony_ci */ 736fb726d48Sopenharmony_ci private scale(index: number): void { 737fb726d48Sopenharmony_ci let newWidth = 0; 738fb726d48Sopenharmony_ci let deltaWidth = this.rect!.width * scaleRatio; 739fb726d48Sopenharmony_ci const ratio = 1 + scaleRatio; 740fb726d48Sopenharmony_ci if (index > 0) { 741fb726d48Sopenharmony_ci // zoom in 742fb726d48Sopenharmony_ci newWidth = this.rect!.width + deltaWidth; 743fb726d48Sopenharmony_ci const sizeRatio = this.canvas!.width / this.rect.width; // max scale 744fb726d48Sopenharmony_ci switch (this._mode) { 745fb726d48Sopenharmony_ci case ChartMode.Byte: 746fb726d48Sopenharmony_ci case ChartMode.Count: 747fb726d48Sopenharmony_ci case ChartMode.EventCount: 748fb726d48Sopenharmony_ci if (Math.round((this.total * sizeRatio) / ratio) <= 10) { 749fb726d48Sopenharmony_ci if (this.xPoint === 0) { 750fb726d48Sopenharmony_ci return; 751fb726d48Sopenharmony_ci } 752fb726d48Sopenharmony_ci newWidth = this.canvas!.width / (10 / this.total); 753fb726d48Sopenharmony_ci } 754fb726d48Sopenharmony_ci break; 755fb726d48Sopenharmony_ci case ChartMode.Duration: 756fb726d48Sopenharmony_ci if (Math.round((this.total * sizeRatio) / ratio) <= ms10) { 757fb726d48Sopenharmony_ci if (this.xPoint === 0) { 758fb726d48Sopenharmony_ci return; 759fb726d48Sopenharmony_ci } 760fb726d48Sopenharmony_ci newWidth = this.canvas!.width / (ms10 / this.total); 761fb726d48Sopenharmony_ci } 762fb726d48Sopenharmony_ci break; 763fb726d48Sopenharmony_ci } 764fb726d48Sopenharmony_ci deltaWidth = newWidth - this.rect!.width; 765fb726d48Sopenharmony_ci } else { 766fb726d48Sopenharmony_ci // zoom out 767fb726d48Sopenharmony_ci newWidth = this.rect!.width - deltaWidth; 768fb726d48Sopenharmony_ci if (newWidth < this.canvas!.width) { 769fb726d48Sopenharmony_ci newWidth = this.canvas!.width; 770fb726d48Sopenharmony_ci this.resetTrans(); 771fb726d48Sopenharmony_ci } 772fb726d48Sopenharmony_ci deltaWidth = this.rect!.width - newWidth; 773fb726d48Sopenharmony_ci } 774fb726d48Sopenharmony_ci // width not change 775fb726d48Sopenharmony_ci if (newWidth === this.rect.width) { 776fb726d48Sopenharmony_ci return; 777fb726d48Sopenharmony_ci } 778fb726d48Sopenharmony_ci this.translationByScale(index, deltaWidth, newWidth); 779fb726d48Sopenharmony_ci } 780fb726d48Sopenharmony_ci 781fb726d48Sopenharmony_ci private resetTrans(): void { 782fb726d48Sopenharmony_ci this.xPoint = 0; 783fb726d48Sopenharmony_ci } 784fb726d48Sopenharmony_ci 785fb726d48Sopenharmony_ci /** 786fb726d48Sopenharmony_ci * 放缩之后的平移算法 787fb726d48Sopenharmony_ci * @param index < 0 缩小 , > 0 放大 788fb726d48Sopenharmony_ci * @param deltaWidth 放缩增量 789fb726d48Sopenharmony_ci * @param newWidth 放缩后的宽度 790fb726d48Sopenharmony_ci */ 791fb726d48Sopenharmony_ci private translationByScale(index: number, deltaWidth: number, newWidth: number): void { 792fb726d48Sopenharmony_ci const translationValue = (deltaWidth * (this.canvasX - this.xPoint)) / this.rect.width; 793fb726d48Sopenharmony_ci if (index > 0) { 794fb726d48Sopenharmony_ci this.xPoint -= translationValue; 795fb726d48Sopenharmony_ci } else { 796fb726d48Sopenharmony_ci this.xPoint += translationValue; 797fb726d48Sopenharmony_ci } 798fb726d48Sopenharmony_ci this.rect!.width = newWidth; 799fb726d48Sopenharmony_ci 800fb726d48Sopenharmony_ci this.translationDraw(); 801fb726d48Sopenharmony_ci } 802fb726d48Sopenharmony_ci 803fb726d48Sopenharmony_ci /** 804fb726d48Sopenharmony_ci * 点击a d 平移 805fb726d48Sopenharmony_ci * @param index < 0 左移; >0 右移 806fb726d48Sopenharmony_ci */ 807fb726d48Sopenharmony_ci private translation(index: number): void { 808fb726d48Sopenharmony_ci const offset = this.canvas!.width / 10; 809fb726d48Sopenharmony_ci if (index < 0) { 810fb726d48Sopenharmony_ci this.xPoint += offset; 811fb726d48Sopenharmony_ci } else { 812fb726d48Sopenharmony_ci this.xPoint -= offset; 813fb726d48Sopenharmony_ci } 814fb726d48Sopenharmony_ci this.translationDraw(); 815fb726d48Sopenharmony_ci } 816fb726d48Sopenharmony_ci 817fb726d48Sopenharmony_ci /** 818fb726d48Sopenharmony_ci * judge position ro fit canvas and draw 819fb726d48Sopenharmony_ci */ 820fb726d48Sopenharmony_ci private translationDraw(): void { 821fb726d48Sopenharmony_ci // right trans limit 822fb726d48Sopenharmony_ci if (this.xPoint > 0) { 823fb726d48Sopenharmony_ci this.xPoint = 0; 824fb726d48Sopenharmony_ci } 825fb726d48Sopenharmony_ci // left trans limit 826fb726d48Sopenharmony_ci if (this.rect.width + this.xPoint < this.canvas!.width) { 827fb726d48Sopenharmony_ci this.xPoint = this.canvas!.width - this.rect.width; 828fb726d48Sopenharmony_ci } 829fb726d48Sopenharmony_ci this.rootNode.frame!.width = this.rect.width; 830fb726d48Sopenharmony_ci this.rootNode.frame!.x = this.xPoint; 831fb726d48Sopenharmony_ci this.calculateChartData(); 832fb726d48Sopenharmony_ci } 833fb726d48Sopenharmony_ci 834fb726d48Sopenharmony_ci private nodeInCanvas(node: ChartStruct): boolean { 835fb726d48Sopenharmony_ci if (!node.frame) { 836fb726d48Sopenharmony_ci return false; 837fb726d48Sopenharmony_ci } 838fb726d48Sopenharmony_ci return node.frame.x + node.frame.width >= 0 && node.frame.x < this.canvas.clientWidth; 839fb726d48Sopenharmony_ci } 840fb726d48Sopenharmony_ci private onMouseClick(e: MouseEvent): void { 841fb726d48Sopenharmony_ci if (e.button === 0) { 842fb726d48Sopenharmony_ci // mouse left button 843fb726d48Sopenharmony_ci if (ChartStruct.hoverFuncStruct && ChartStruct.hoverFuncStruct !== ChartStruct.selectFuncStruct) { 844fb726d48Sopenharmony_ci ChartStruct.lastSelectFuncStruct = ChartStruct.selectFuncStruct; 845fb726d48Sopenharmony_ci ChartStruct.selectFuncStruct = ChartStruct.hoverFuncStruct; 846fb726d48Sopenharmony_ci this.isClickMode = ChartStruct.selectFuncStruct !== this.rootNode; 847fb726d48Sopenharmony_ci this.rect.width = this.canvas!.clientWidth; 848fb726d48Sopenharmony_ci // 重置缩放 849fb726d48Sopenharmony_ci this.resetTrans(); 850fb726d48Sopenharmony_ci this.rootNode.frame!.x = this.xPoint; 851fb726d48Sopenharmony_ci this.rootNode.frame!.width = this.rect.width = this.canvas.clientWidth; 852fb726d48Sopenharmony_ci // 重新绘图 853fb726d48Sopenharmony_ci this.clickRedraw(); 854fb726d48Sopenharmony_ci document.dispatchEvent( 855fb726d48Sopenharmony_ci new CustomEvent('number_calibration', { 856fb726d48Sopenharmony_ci detail: { 857fb726d48Sopenharmony_ci time: ChartStruct.selectFuncStruct.tsArray, 858fb726d48Sopenharmony_ci counts: ChartStruct.selectFuncStruct.countArray, 859fb726d48Sopenharmony_ci durations: ChartStruct.selectFuncStruct.durArray, 860fb726d48Sopenharmony_ci }, 861fb726d48Sopenharmony_ci }) 862fb726d48Sopenharmony_ci ); 863fb726d48Sopenharmony_ci } 864fb726d48Sopenharmony_ci } 865fb726d48Sopenharmony_ci this.hideTip(); 866fb726d48Sopenharmony_ci } 867fb726d48Sopenharmony_ci 868fb726d48Sopenharmony_ci private hideTip(): void { 869fb726d48Sopenharmony_ci if (this.floatHint) { 870fb726d48Sopenharmony_ci this.floatHint.style.display = 'none'; 871fb726d48Sopenharmony_ci } 872fb726d48Sopenharmony_ci } 873fb726d48Sopenharmony_ci 874fb726d48Sopenharmony_ci /** 875fb726d48Sopenharmony_ci * 更新悬浮框内容 876fb726d48Sopenharmony_ci */ 877fb726d48Sopenharmony_ci private updateTipContent(): void { 878fb726d48Sopenharmony_ci const hoverNode = ChartStruct.hoverFuncStruct; 879fb726d48Sopenharmony_ci if (hoverNode) { 880fb726d48Sopenharmony_ci const name = hoverNode?.symbol.replace(/</g, '<').replace(/>/g, '>').split(' (')[0]; 881fb726d48Sopenharmony_ci const percent = ((hoverNode?.percent || 0) * 100).toFixed(2); 882fb726d48Sopenharmony_ci const threadPercent = this.getCurrentPercentOfThread(hoverNode); 883fb726d48Sopenharmony_ci const processPercent = this.getCurrentPercentOfProcess(hoverNode); 884fb726d48Sopenharmony_ci switch (this._mode) { 885fb726d48Sopenharmony_ci case ChartMode.Byte: 886fb726d48Sopenharmony_ci const size = Utils.getByteWithUnit(this.getNodeValue(hoverNode)); 887fb726d48Sopenharmony_ci const countPercent = ((this.getNodeValue(hoverNode) / this.total) * 100).toFixed(2); 888fb726d48Sopenharmony_ci this.hintContent = ` 889fb726d48Sopenharmony_ci <span class="bold">Symbol: </span> <span class="text">${name} </span> <br> 890fb726d48Sopenharmony_ci <span class="bold">Lib: </span> <span class="text">${hoverNode?.lib}</span> <br> 891fb726d48Sopenharmony_ci <span class="bold">Addr: </span> <span>${hoverNode?.addr}</span> <br> 892fb726d48Sopenharmony_ci <span class="bold">Size: </span> <span>${size} (${percent}%) </span> <br> 893fb726d48Sopenharmony_ci <span class="bold">Count: </span> <span>${hoverNode?.count} (${countPercent}%)</span>`; 894fb726d48Sopenharmony_ci break; 895fb726d48Sopenharmony_ci case ChartMode.Duration: 896fb726d48Sopenharmony_ci const duration = Utils.getProbablyTime(this.getNodeValue(hoverNode)); 897fb726d48Sopenharmony_ci this.hintContent = ` 898fb726d48Sopenharmony_ci <span class="bold">Name: </span> <span class="text">${name} </span> <br> 899fb726d48Sopenharmony_ci <span class="bold">Lib: </span> <span class="text">${hoverNode?.lib}</span> <br> 900fb726d48Sopenharmony_ci <span class="bold">Addr: </span> <span>${hoverNode?.addr}</span> <br> 901fb726d48Sopenharmony_ci <span class="bold">Duration: </span> <span>${duration}</span>`; 902fb726d48Sopenharmony_ci break; 903fb726d48Sopenharmony_ci case ChartMode.EventCount: 904fb726d48Sopenharmony_ci case ChartMode.Count: 905fb726d48Sopenharmony_ci const label = ChartMode.Count === this._mode ? 'Count' : 'EventCount'; 906fb726d48Sopenharmony_ci const count = this.getNodeValue(hoverNode); 907fb726d48Sopenharmony_ci this.hintContent = ` 908fb726d48Sopenharmony_ci <span class="bold">Name: </span> <span class="text">${name} </span> <br> 909fb726d48Sopenharmony_ci <span class="bold">Lib: </span> <span class="text">${hoverNode?.lib}</span> <br> 910fb726d48Sopenharmony_ci <span class="bold">Addr: </span> <span>${hoverNode?.addr}</span> <br> 911fb726d48Sopenharmony_ci <span class="bold">${label}: </span> <span> ${count}</span>`; 912fb726d48Sopenharmony_ci break; 913fb726d48Sopenharmony_ci } 914fb726d48Sopenharmony_ci if (this._mode !== ChartMode.Byte) { 915fb726d48Sopenharmony_ci if (threadPercent) { 916fb726d48Sopenharmony_ci this.hintContent += `<br> <span class="bold">% in current Thread:</span> <span>${threadPercent}%</span>`; 917fb726d48Sopenharmony_ci } 918fb726d48Sopenharmony_ci if (processPercent) { 919fb726d48Sopenharmony_ci this.hintContent += `<br> <span class="bold">% in current Process:</span> <span>${processPercent}%</span>`; 920fb726d48Sopenharmony_ci } 921fb726d48Sopenharmony_ci this.hintContent += `<br> <span class="bold">% in all Process: </span> <span> ${percent}%</span>`; 922fb726d48Sopenharmony_ci } 923fb726d48Sopenharmony_ci } 924fb726d48Sopenharmony_ci } 925fb726d48Sopenharmony_ci 926fb726d48Sopenharmony_ci private getCurrentPercent(node: ChartStruct, isThread: boolean): string { 927fb726d48Sopenharmony_ci const parentNode = this.findCurrentNode(node, isThread); 928fb726d48Sopenharmony_ci if (parentNode) { 929fb726d48Sopenharmony_ci return ((this.getNodeValue(node) / this.getNodeValue(parentNode)) * 100).toFixed(2); 930fb726d48Sopenharmony_ci } 931fb726d48Sopenharmony_ci return ''; 932fb726d48Sopenharmony_ci } 933fb726d48Sopenharmony_ci 934fb726d48Sopenharmony_ci private findCurrentNode(node: ChartStruct, isThread: boolean): ChartStruct | null { 935fb726d48Sopenharmony_ci while (node.parent) { 936fb726d48Sopenharmony_ci if ((isThread && node.parent.isThread) || (!isThread && node.parent.isProcess)) { 937fb726d48Sopenharmony_ci return node.parent; 938fb726d48Sopenharmony_ci } 939fb726d48Sopenharmony_ci node = node.parent; 940fb726d48Sopenharmony_ci } 941fb726d48Sopenharmony_ci return null; 942fb726d48Sopenharmony_ci } 943fb726d48Sopenharmony_ci 944fb726d48Sopenharmony_ci private getCurrentPercentOfThread(node: ChartStruct): string { 945fb726d48Sopenharmony_ci return this.getCurrentPercent(node, true); 946fb726d48Sopenharmony_ci } 947fb726d48Sopenharmony_ci 948fb726d48Sopenharmony_ci private getCurrentPercentOfProcess(node: ChartStruct): string { 949fb726d48Sopenharmony_ci return this.getCurrentPercent(node, false); 950fb726d48Sopenharmony_ci } 951fb726d48Sopenharmony_ci 952fb726d48Sopenharmony_ci /** 953fb726d48Sopenharmony_ci * mouse on canvas move event 954fb726d48Sopenharmony_ci */ 955fb726d48Sopenharmony_ci private onMouseMove(): void { 956fb726d48Sopenharmony_ci const lastNode = ChartStruct.hoverFuncStruct; 957fb726d48Sopenharmony_ci // 鼠标移动到root节点不作显示 958fb726d48Sopenharmony_ci const hoverRootNode = this.rootNode.frame?.contains(this.canvasX, this.canvasY); 959fb726d48Sopenharmony_ci if (hoverRootNode) { 960fb726d48Sopenharmony_ci ChartStruct.hoverFuncStruct = this.rootNode; 961fb726d48Sopenharmony_ci return; 962fb726d48Sopenharmony_ci } 963fb726d48Sopenharmony_ci // 查找鼠标所在那个node上 964fb726d48Sopenharmony_ci const searchResult = this.searchDataByCoord(this.currentData!, this.canvasX, this.canvasY); 965fb726d48Sopenharmony_ci if (searchResult && (searchResult.isDraw || searchResult.depth === 0)) { 966fb726d48Sopenharmony_ci ChartStruct.hoverFuncStruct = searchResult; 967fb726d48Sopenharmony_ci // 悬浮的node未改变,不需要更新悬浮框文字信息,不绘图 968fb726d48Sopenharmony_ci if (searchResult !== lastNode) { 969fb726d48Sopenharmony_ci this.updateTipContent(); 970fb726d48Sopenharmony_ci this.calculateChartData(); 971fb726d48Sopenharmony_ci } 972fb726d48Sopenharmony_ci this.showTip(); 973fb726d48Sopenharmony_ci } else { 974fb726d48Sopenharmony_ci this.hideTip(); 975fb726d48Sopenharmony_ci ChartStruct.hoverFuncStruct = undefined; 976fb726d48Sopenharmony_ci } 977fb726d48Sopenharmony_ci } 978fb726d48Sopenharmony_ci 979fb726d48Sopenharmony_ci /** 980fb726d48Sopenharmony_ci * 监听页面Size变化 981fb726d48Sopenharmony_ci */ 982fb726d48Sopenharmony_ci private listenerResize(): void { 983fb726d48Sopenharmony_ci new ResizeObserver(() => { 984fb726d48Sopenharmony_ci this.resizeChange(); 985fb726d48Sopenharmony_ci if (this.rootNode && this.canvas.clientWidth !== 0 && this.xPoint === 0) { 986fb726d48Sopenharmony_ci this.rootNode.frame!.width = this.canvas.clientWidth; 987fb726d48Sopenharmony_ci } 988fb726d48Sopenharmony_ci }).observe(this); 989fb726d48Sopenharmony_ci } 990fb726d48Sopenharmony_ci 991fb726d48Sopenharmony_ci public resizeChange(): void { 992fb726d48Sopenharmony_ci if (this.canvas!.getBoundingClientRect()) { 993fb726d48Sopenharmony_ci const box = this.canvas!.getBoundingClientRect(); 994fb726d48Sopenharmony_ci const element = document.documentElement; 995fb726d48Sopenharmony_ci this.startX = box.left + Math.max(element.scrollLeft, document.body.scrollLeft) - element.clientLeft; 996fb726d48Sopenharmony_ci this.startY = 997fb726d48Sopenharmony_ci box.top + Math.max(element.scrollTop, document.body.scrollTop) - element.clientTop + this.canvasScrollTop; 998fb726d48Sopenharmony_ci } 999fb726d48Sopenharmony_ci } 1000fb726d48Sopenharmony_ci 1001fb726d48Sopenharmony_ci public initElements(): void { 1002fb726d48Sopenharmony_ci this.canvas = this.shadowRoot!.querySelector('#canvas')!; 1003fb726d48Sopenharmony_ci this.canvasContext = this.canvas.getContext('2d')!; 1004fb726d48Sopenharmony_ci this.floatHint = this.shadowRoot?.querySelector('#float_hint'); 1005fb726d48Sopenharmony_ci 1006fb726d48Sopenharmony_ci this.canvas!.oncontextmenu = (): boolean => { 1007fb726d48Sopenharmony_ci return false; 1008fb726d48Sopenharmony_ci }; 1009fb726d48Sopenharmony_ci this.canvas!.onmouseup = (e): void => { 1010fb726d48Sopenharmony_ci this.onMouseClick(e); 1011fb726d48Sopenharmony_ci }; 1012fb726d48Sopenharmony_ci 1013fb726d48Sopenharmony_ci this.canvas!.onmousemove = (e): void => { 1014fb726d48Sopenharmony_ci if (!this.isUpdateCanvas) { 1015fb726d48Sopenharmony_ci this.updateCanvasCoord(); 1016fb726d48Sopenharmony_ci } 1017fb726d48Sopenharmony_ci this.canvasX = e.clientX - this.startX; 1018fb726d48Sopenharmony_ci this.canvasY = e.clientY - this.startY + this.canvasScrollTop; 1019fb726d48Sopenharmony_ci this.isFocusing = true; 1020fb726d48Sopenharmony_ci this.onMouseMove(); 1021fb726d48Sopenharmony_ci }; 1022fb726d48Sopenharmony_ci 1023fb726d48Sopenharmony_ci this.canvas!.onmouseleave = (): void => { 1024fb726d48Sopenharmony_ci this.isFocusing = false; 1025fb726d48Sopenharmony_ci this.hideTip(); 1026fb726d48Sopenharmony_ci }; 1027fb726d48Sopenharmony_ci 1028fb726d48Sopenharmony_ci document.addEventListener('keydown', (e) => { 1029fb726d48Sopenharmony_ci if (!this.isFocusing) { 1030fb726d48Sopenharmony_ci return; 1031fb726d48Sopenharmony_ci } 1032fb726d48Sopenharmony_ci switch (e.key.toLocaleLowerCase()) { 1033fb726d48Sopenharmony_ci case 'w': 1034fb726d48Sopenharmony_ci this.scale(1); 1035fb726d48Sopenharmony_ci break; 1036fb726d48Sopenharmony_ci case 's': 1037fb726d48Sopenharmony_ci this.scale(-1); 1038fb726d48Sopenharmony_ci break; 1039fb726d48Sopenharmony_ci case 'a': 1040fb726d48Sopenharmony_ci this.translation(-1); 1041fb726d48Sopenharmony_ci break; 1042fb726d48Sopenharmony_ci case 'd': 1043fb726d48Sopenharmony_ci this.translation(1); 1044fb726d48Sopenharmony_ci break; 1045fb726d48Sopenharmony_ci } 1046fb726d48Sopenharmony_ci }); 1047fb726d48Sopenharmony_ci 1048fb726d48Sopenharmony_ci document.addEventListener('keydown', (e) => { 1049fb726d48Sopenharmony_ci if (!ChartStruct.hoverFuncStruct || !this.isFocusing) { 1050fb726d48Sopenharmony_ci return; 1051fb726d48Sopenharmony_ci } 1052fb726d48Sopenharmony_ci if (e.ctrlKey && e.key.toLocaleLowerCase() === 'c') { 1053fb726d48Sopenharmony_ci let hoverName: string = ChartStruct.hoverFuncStruct!.symbol.split(' (')[0]; 1054fb726d48Sopenharmony_ci navigator.clipboard.writeText(hoverName); 1055fb726d48Sopenharmony_ci } 1056fb726d48Sopenharmony_ci }); 1057fb726d48Sopenharmony_ci this.listenerResize(); 1058fb726d48Sopenharmony_ci } 1059fb726d48Sopenharmony_ci 1060fb726d48Sopenharmony_ci public initHtml(): string { 1061fb726d48Sopenharmony_ci return ` 1062fb726d48Sopenharmony_ci <style> 1063fb726d48Sopenharmony_ci .frame-tip{ 1064fb726d48Sopenharmony_ci position:absolute; 1065fb726d48Sopenharmony_ci left: 0; 1066fb726d48Sopenharmony_ci background-color: white; 1067fb726d48Sopenharmony_ci border: 1px solid #f9f9f9; 1068fb726d48Sopenharmony_ci width: auto; 1069fb726d48Sopenharmony_ci font-size: 12px; 1070fb726d48Sopenharmony_ci color: #50809e; 1071fb726d48Sopenharmony_ci padding: 2px 10px; 1072fb726d48Sopenharmony_ci display: none; 1073fb726d48Sopenharmony_ci max-width:400px; 1074fb726d48Sopenharmony_ci } 1075fb726d48Sopenharmony_ci .bold{ 1076fb726d48Sopenharmony_ci font-weight: bold; 1077fb726d48Sopenharmony_ci } 1078fb726d48Sopenharmony_ci .text{ 1079fb726d48Sopenharmony_ci max-width:350px; 1080fb726d48Sopenharmony_ci word-break: break-all; 1081fb726d48Sopenharmony_ci } 1082fb726d48Sopenharmony_ci :host{ 1083fb726d48Sopenharmony_ci display: flex; 1084fb726d48Sopenharmony_ci padding: 10px 10px; 1085fb726d48Sopenharmony_ci } 1086fb726d48Sopenharmony_ci </style> 1087fb726d48Sopenharmony_ci <canvas id="canvas"></canvas> 1088fb726d48Sopenharmony_ci <div id ="float_hint" class="frame-tip"></div>`; 1089fb726d48Sopenharmony_ci } 1090fb726d48Sopenharmony_ci} 1091