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, '&lt;').replace(/>/g, '&gt;').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