• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseElement, element } from '../../../../../base-ui/BaseElement';
17import { SelectionParam } from '../../../../bean/BoxSelection';
18import { debounce } from '../../../Utils';
19const paddingLeft = 100;
20const paddingBottom = 15;
21const xStep = 50; // x轴间距
22const barWidth = 2; // 柱子宽度
23
24@element('tab-sample-instructions-distrubtions-chart')
25export class TabPaneSampleInstructionDistributions extends BaseElement {
26  private instructionChartEle: HTMLCanvasElement | undefined | null;
27  private ctx: CanvasRenderingContext2D | undefined | null;
28  private onReadableData: Array<unknown> = [];
29  private hintContent = ''; //悬浮框内容
30  private floatHint!: HTMLDivElement | undefined | null; //悬浮框
31  private canvasScrollTop = 0; // tab页上下滚动位置
32  private isUpdateCanvas = false;
33  private cacheData: Array<unknown> = [];
34  private canvasX = -1; // 鼠标当前所在画布x坐标
35  private canvasY = -1; // 鼠标当前所在画布y坐标
36  private startX = 0; // 画布相对于整个界面的x坐标
37  private startY = 0; // 画布相对于整个界面的y坐标
38  private hoverBar: unknown;
39  private isChecked: boolean = false;
40  private xCount = 0; //x轴刻度个数
41  private xMaxValue = 0; //x轴上数据最大值
42  private xSpacing = 50; //x轴间距
43  private xAvg = 0; //根据xMaxValue进行划分 用于x轴上刻度显示
44  private yAvg = 0; //根据yMaxValue进行划分 用于y轴上刻度显示
45
46  initHtml(): string {
47    return `
48      <style>
49        :host {
50          display: flex;
51          position: relative;
52        }
53        .frame-tip {
54          position: absolute;
55          left: 0;
56          background-color: white;
57          border: 1px solid #F9F9F9;
58          width: auto;
59          font-size: 14px;
60          color: #50809e;
61          padding: 2px 10px;
62          box-sizing: border-box;
63          display: none;
64          max-width: 250px;
65        }
66        .title {
67          font-size: 14px;
68          padding: 0 5px;
69        }
70        .bold {
71          font-weight: bold;
72        }
73      </style>
74      <canvas id="instruct-chart-canvas" height="280"></canvas>
75      <div id="float_hint" class="frame-tip"></div>
76    `;
77  }
78
79  initElements(): void {
80    this.instructionChartEle = this.shadowRoot?.querySelector('#instruct-chart-canvas');
81    this.ctx = this.instructionChartEle?.getContext('2d');
82    this.floatHint = this.shadowRoot?.querySelector('#float_hint');
83  }
84
85  set data(SampleParam: SelectionParam) {
86    // @ts-ignore
87    this.onReadableData = SampleParam.sampleData[0].property;
88    this.calInstructionRangeCount(this.isChecked);
89  }
90
91  connectedCallback(): void {
92    super.connectedCallback();
93    this.parentElement!.onscroll = () => {
94      this.canvasScrollTop = this.parentElement!.scrollTop;
95      this.hideTip();
96    };
97    this.instructionChartEle!.onmousemove = (e): void => {
98      if (!this.isUpdateCanvas) {
99        this.updateCanvasCoord();
100      }
101      this.canvasX = e.clientX - this.startX;
102      this.canvasY = e.clientY - this.startY + this.canvasScrollTop;
103      this.onMouseMove();
104    };
105    this.instructionChartEle!.onmouseleave = () => {
106      this.hideTip();
107    };
108    document.addEventListener('sample-popver-change', (e: unknown) => {
109      // @ts-ignore
110      const select = Number(e.detail.select);
111      this.isChecked = Boolean(select);
112      this.calInstructionRangeCount(this.isChecked);
113    });
114    this.listenerResize();
115  }
116
117  /**
118   * 更新canvas坐标
119   */
120  updateCanvasCoord(): void {
121    if (this.instructionChartEle instanceof HTMLCanvasElement) {
122      this.isUpdateCanvas = this.instructionChartEle.clientWidth !== 0;
123      if (this.instructionChartEle.getBoundingClientRect()) {
124        const box = this.instructionChartEle.getBoundingClientRect();
125        const D = this.parentElement!;
126        this.startX = box.left + Math.max(D.scrollLeft, document.body.scrollLeft) - D.clientLeft;
127        this.startY = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop + this.canvasScrollTop;
128      }
129    }
130  }
131
132  /**
133   * 获取鼠标悬停的函数
134   * @param nodes
135   * @param canvasX
136   * @param canvasY
137   * @returns
138   */
139  searchDataByCoord(nodes: unknown, canvasX: number, canvasY: number): void | null {
140    // @ts-ignore
141    for (let i = 0; i < nodes.length; i++) {
142      // @ts-ignore
143      const element = nodes[i];
144      if (this.isContains(element, canvasX, canvasY)) {
145        return element;
146      }
147    }
148    return null;
149  }
150
151  /**
152   * 鼠标移动
153   */
154  onMouseMove(): void {
155    const lastNode = this.hoverBar;
156    //查找鼠标所在的node
157    const searchResult = this.searchDataByCoord(this.cacheData, this.canvasX, this.canvasY);
158    if (searchResult) {
159      this.hoverBar = searchResult;
160      //鼠标悬浮的node未改变则不需重新渲染文字
161      if (searchResult !== lastNode) {
162        this.updateTipContent();
163      }
164      this.showTip();
165    } else {
166      this.hideTip();
167      this.hoverBar = undefined;
168    }
169  }
170
171  /**
172   *  隐藏悬浮框
173   */
174  hideTip(): void {
175    if (this.floatHint) {
176      this.floatHint.style.display = 'none';
177      this.instructionChartEle!.style.cursor = 'default';
178    }
179  }
180
181  /**
182   * 显示悬浮框
183   */
184  showTip(): void {
185    this.floatHint!.innerHTML = this.hintContent;
186    this.floatHint!.style.display = 'block';
187    this.instructionChartEle!.style.cursor = 'pointer';
188    let x = this.canvasX;
189    let y = this.canvasY - this.canvasScrollTop;
190    //右边的函数悬浮框显示在左侧
191    if (this.canvasX + this.floatHint!.clientWidth > (this.instructionChartEle!.clientWidth || 0)) {
192      x -= this.floatHint!.clientWidth - 1;
193    } else {
194      x += 20;
195    }
196    //最下边的函数悬浮框显示在上方
197    y -= this.floatHint!.clientHeight - 1;
198    this.floatHint!.style.transform = `translate(${x}px, ${y}px)`;
199  }
200
201  /**
202   * 更新悬浮框内容
203   * @returns
204   */
205  updateTipContent(): void {
206    const hoverNode = this.hoverBar;
207    if (!hoverNode) {
208      return;
209    }
210    const detail = hoverNode!;
211    this.hintContent = `
212      <span class="blod">${
213      // @ts-ignore
214      detail.instruct}</span></br>
215      <span>${
216      // @ts-ignore
217      parseFloat(detail.heightPer)}</span>
218      `;
219  }
220
221  /**
222   * 判断鼠标当前在那个函数上
223   * @param frame
224   * @param x
225   * @param y
226   * @returns
227   */
228  isContains(point: unknown, x: number, y: number): boolean {
229    // @ts-ignore
230    return x >= point.x && x <= point.x + 2 && point.y <= y && y <= point.y + point.height;
231  }
232
233  /**
234   * 统计onReadable数据各指令的个数
235   * @param isCycles
236   */
237  calInstructionRangeCount(isCycles: boolean) {
238    if (this.onReadableData.length === 0) return;
239    this.cacheData.length = 0;
240    const count = this.onReadableData.length;
241    let instructions = {};
242    if (isCycles) {
243      // @ts-ignore
244      instructions = this.onReadableData.reduce((pre: unknown, current: unknown) => {
245        // @ts-ignore
246        (pre[`${Math.ceil(current.cycles)}`] = pre[`${Math.ceil(current.cycles)}`] || []).push(current);
247        return pre;
248      }, {});
249    } else {
250      // @ts-ignore
251      instructions = this.onReadableData.reduce((pre: unknown, current: unknown) => {
252        // @ts-ignore
253        (pre[`${Math.ceil(current.instructions)}`] = pre[`${Math.ceil(current.instructions)}`] || []).push(current);
254        return pre;
255      }, {});
256    }
257    this.ctx!.clearRect(0, 0, this.instructionChartEle!.width, this.instructionChartEle!.height);
258    this.instructionChartEle!.width = this.clientWidth;
259
260    this.xMaxValue =
261      Object.keys(instructions)
262        .map((i) => Number(i))
263        .reduce((pre, cur) => Math.max(pre, cur), 0) + 10;
264    const yMaxValue = Object.values(instructions).reduce(
265      // @ts-ignore
266      (pre: number, cur: unknown) => Math.max(pre, Number((cur.length / count).toFixed(2))),
267      0
268    );
269    this.yAvg = Number(((yMaxValue / 5) * 1.5).toFixed(2)) || yMaxValue;
270    const height = this.instructionChartEle!.height;
271    const width = this.instructionChartEle!.width;
272    this.drawLineLabelMarkers(width, height, isCycles);
273    this.drawBar(instructions, height, count);
274  }
275
276  /**
277   * 绘制柱状图
278   * @param instructionData
279   * @param height
280   * @param count
281   */
282  drawBar(instructionData: unknown, height: number, count: number): void {
283    const yTotal = Number((this.yAvg * 5).toFixed(2));
284    const interval = Math.floor((height - paddingBottom) / 6);
285    // @ts-ignore
286    for (const x in instructionData) {
287      const xNum = Number(x);
288      const xPosition = xStep + (xNum / (this.xCount * this.xAvg)) * (this.xCount * this.xSpacing) - barWidth / 2;
289      // @ts-ignore
290      const yNum = Number((instructionData[x].length / count).toFixed(3));
291      const percent = Number((yNum / yTotal).toFixed(2));
292      const barHeight = (height - paddingBottom - interval) * percent;
293      this.drawRect(xPosition, height - paddingBottom - barHeight, barWidth, barHeight);
294      // @ts-ignore
295      const existX = this.cacheData.find((i) => i.instruct === x);
296      if (!existX) {
297        this.cacheData.push({
298          instruct: x,
299          x: xPosition,
300          y: height - paddingBottom - barHeight,
301          height: barHeight,
302          heightPer: parseFloat((yNum * 100).toFixed(2)),
303        });
304      } else {
305        // @ts-ignore
306        existX.x = xPosition;
307      }
308    }
309  }
310
311  /**
312   * 绘制x y轴
313   * @param width
314   * @param height
315   * @param isCycles
316   */
317  drawLineLabelMarkers(width: number, height: number, isCycles: boolean) {
318    this.ctx!.font = '12px Arial';
319    this.ctx!.lineWidth = 1;
320    this.ctx!.fillStyle = '#333';
321    this.ctx!.strokeStyle = '#ccc';
322    if (isCycles) {
323      this.ctx!.fillText('cycles数(1e5)', width - paddingLeft + 10, height - paddingBottom);
324    } else {
325      this.ctx!.fillText('指令数(1e5)', width - paddingLeft + 10, height - paddingBottom);
326    }
327
328    //绘制x轴
329    this.drawLine(xStep, height - paddingBottom, width - paddingLeft, height - paddingBottom);
330    //绘制y轴
331    this.drawLine(xStep, 5, xStep, height - paddingBottom);
332    //绘制标记
333    this.drawMarkers(width, height);
334  }
335
336  /**
337   * 绘制横线
338   * @param x
339   * @param y
340   * @param X
341   * @param Y
342   */
343  drawLine(x: number, y: number, X: number, Y: number) {
344    this.ctx!.beginPath;
345    this.ctx!.moveTo(x, y);
346    this.ctx!.lineTo(X, Y);
347    this.ctx!.stroke();
348    this.ctx!.closePath();
349  }
350
351  /**
352   * 绘制x y轴刻度
353   * @param width
354   * @param height
355   */
356  drawMarkers(width: number, height: number) {
357    this.xCount = 0;
358    //绘制x轴锯齿
359    let serrateX = 50;
360    let y = height - paddingBottom;
361    const clientWidth = width - paddingLeft - 50;
362    if (clientWidth > this.xMaxValue) {
363      this.xSpacing = Math.floor(clientWidth / 20);
364      this.xAvg = Math.ceil(this.xMaxValue / 20);
365    } else {
366      this.xSpacing = Math.floor(clientWidth / 10);
367      this.xAvg = Math.ceil(this.xMaxValue / 10);
368    }
369    while (serrateX <= clientWidth) {
370      this.xCount++;
371      serrateX += this.xSpacing;
372      this.drawLine(serrateX, y, serrateX, y + 5);
373    }
374    //绘制x轴刻度
375    this.ctx!.textAlign = 'center';
376    for (let i = 0; i <= this.xCount; i++) {
377      const x = xStep + i * this.xSpacing;
378      this.ctx!.fillText(`${i * this.xAvg}`, x, height);
379    }
380    //绘制y轴刻度
381    this.ctx!.textAlign = 'center';
382    const yPadding = Math.floor((height - paddingBottom) / 6);
383    for (let i = 0; i < 6; i++) {
384      if (i === 0) {
385        this.ctx!.fillText(`${i}%`, 30, y);
386      } else {
387        const y = height - paddingBottom - i * yPadding;
388        this.drawLine(xStep, y, width - paddingLeft, y);
389        this.ctx!.fillText(`${parseFloat((i * this.yAvg).toFixed(2)) * 100}%`, 30, y);
390      }
391    }
392  }
393
394  /**
395   * 监听页面size变化
396   */
397  listenerResize(): void {
398    new ResizeObserver(
399      debounce(() => {
400        if (this.instructionChartEle!.getBoundingClientRect()) {
401          const box = this.instructionChartEle!.getBoundingClientRect();
402          const element = this.parentElement!;
403          this.startX = box.left + Math.max(element.scrollLeft, document.body.scrollLeft) - element.clientLeft;
404          this.startY =
405            box.top + Math.max(element.scrollTop, document.body.scrollTop) - element.clientTop + this.canvasScrollTop;
406          this.calInstructionRangeCount(this.isChecked);
407        }
408      }, 100)
409    ).observe(this.parentElement!);
410  }
411  /**
412   * 绘制方块
413   * @param x
414   * @param y
415   * @param X
416   * @param Y
417   */
418  drawRect(x: number, y: number, X: number, Y: number) {
419    this.ctx!.beginPath();
420    this.ctx!.rect(x, y, X, Y);
421    this.ctx!.fill();
422    this.ctx!.closePath();
423  }
424}
425