/* * Copyright (C) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { TraceRow } from '../trace/base/TraceRow'; import { renders } from '../../database/ui-worker/ProcedureWorker'; import { JankRender, JankStruct } from '../../database/ui-worker/ProcedureWorkerJank'; import { SpSystemTrace } from '../SpSystemTrace'; import { JanksStruct } from '../../bean/JanksStruct'; import { ns2xByTimeShaft, type PairPoint } from '../../database/ui-worker/ProcedureWorkerCommon'; import { FrameDynamicRender, FrameDynamicStruct } from '../../database/ui-worker/ProcedureWorkerFrameDynamic'; import { FrameAnimationRender, FrameAnimationStruct } from '../../database/ui-worker/ProcedureWorkerFrameAnimation'; import { type BaseStruct } from '../../bean/BaseStruct'; import { FrameSpacingRender, FrameSpacingStruct } from '../../database/ui-worker/ProcedureWorkerFrameSpacing'; import { FlagsConfig, type Params } from '../SpFlags'; import { type AnimationRanges, type DeviceStruct } from '../../bean/FrameComponentBean'; import { type EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU'; import { TreeItemData } from '../../../base-ui/tree/LitTree'; import { QueryEnum } from '../../database/data-trafic/utils/QueryEnum'; import { frameAnimationSender, frameDynamicSender, frameSpacingSender, } from '../../database/data-trafic/FrameDynamicEffectSender'; import { frameJanksSender } from '../../database/data-trafic/FrameJanksSender'; import { queryAnimationIdAndNameData, queryAnimationTimeRangeData, queryDynamicIdAndNameData, queryFrameApp, queryFrameTimeData, queryPhysicalData, querySourceTypen, } from '../../database/sql/SqlLite.sql'; import { queryAllProcessNames } from '../../database/sql/ProcessThread.sql'; export class SpFrameTimeChart { private trace: SpSystemTrace; private flagConfig: Params | undefined; private pidToProcessNameMap: Map = new Map(); private idToProcessNameMap: Map = new Map(); constructor(trace: SpSystemTrace) { this.trace = trace; } async init(): Promise { let frameTimeData = await queryFrameTimeData(); this.pidToProcessNameMap.clear(); this.idToProcessNameMap.clear(); if (frameTimeData.length > 0) { let processNamesArray = await queryAllProcessNames(); processNamesArray.forEach((it) => { //@ts-ignore this.pidToProcessNameMap.set(it.pid, it.name); //@ts-ignore this.idToProcessNameMap.set(it.id, it.name); }); let frameTimeLineRow: TraceRow = await this.initFrameTimeLine(); await this.initExpectedChart(frameTimeLineRow); await this.initActualChart(frameTimeLineRow); } } async initFrameTimeLine(): Promise> { let frameTimeLineRow: TraceRow = TraceRow.skeleton(); frameTimeLineRow.rowId = 'frameTime'; frameTimeLineRow.rowType = TraceRow.ROW_TYPE_JANK; frameTimeLineRow.rowParentId = ''; frameTimeLineRow.style.width = '100%'; frameTimeLineRow.style.height = '40px'; frameTimeLineRow.folder = true; frameTimeLineRow.name = 'FrameTimeline'; frameTimeLineRow.setAttribute('children', ''); frameTimeLineRow.supplier = (): Promise => new Promise((resolve) => { resolve([]); }); frameTimeLineRow.addTemplateTypes('AppStartup'); frameTimeLineRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; frameTimeLineRow.selectChangeHandler = this.trace.selectChangeHandler; frameTimeLineRow.onThreadHandler = (useCache: boolean): void => { let context: CanvasRenderingContext2D; if (frameTimeLineRow.currentContext) { context = frameTimeLineRow.currentContext; } else { context = frameTimeLineRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; } frameTimeLineRow!.canvasSave(context); (renders.jank as JankRender).renderMainThread( { context: context, useCache: useCache, type: 'expected_frame_timeline_slice', }, frameTimeLineRow! ); frameTimeLineRow!.canvasRestore(context, this.trace); }; this.trace.rowsEL?.appendChild(frameTimeLineRow); return frameTimeLineRow; } private expectedChartSupplierFrame(expectedTimeLineRow: TraceRow): void { expectedTimeLineRow.supplierFrame = async (): Promise => { const res = await frameJanksSender(QueryEnum.FrameExpectedData, expectedTimeLineRow); let maxDepth: number = 1; let unitHeight: number = 20; res.forEach((item) => { if (item.depth! >= maxDepth) { maxDepth = item.depth! + 1; } item.frameType = 'frameTime'; item.cmdline = this.pidToProcessNameMap.get(item.pid!); item.rs_name = this.idToProcessNameMap.get(Number(item.rs_name)!); }); if (expectedTimeLineRow && !expectedTimeLineRow.isComplete && res.length > 0) { let maxHeight: number = maxDepth * unitHeight; expectedTimeLineRow.style.height = `${maxHeight}px`; expectedTimeLineRow.setAttribute('height', `${maxHeight}`); } return res; }; } async initExpectedChart(frameTimeLineRow: TraceRow): Promise { let expectedTimeLineRow = TraceRow.skeleton(); expectedTimeLineRow.rowId = 'expected frameTime'; expectedTimeLineRow.rowType = TraceRow.ROW_TYPE_JANK; expectedTimeLineRow.rowHidden = !frameTimeLineRow.expansion; expectedTimeLineRow.rowParentId = 'frameTime'; expectedTimeLineRow.style.width = '100%'; expectedTimeLineRow.name = 'Expected Timeline'; expectedTimeLineRow.addTemplateTypes('FrameTimeline'); expectedTimeLineRow.setAttribute('children', ''); this.expectedChartSupplierFrame(expectedTimeLineRow); expectedTimeLineRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; expectedTimeLineRow.selectChangeHandler = this.trace.selectChangeHandler; expectedTimeLineRow.onThreadHandler = (useCache: boolean): void => { let context: CanvasRenderingContext2D; if (expectedTimeLineRow.currentContext) { context = expectedTimeLineRow.currentContext; } else { context = expectedTimeLineRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; } expectedTimeLineRow!.canvasSave(context); (renders.jank as JankRender).renderMainThread( { context: context, useCache: useCache, type: 'expected_frame_timeline_slice', }, expectedTimeLineRow! ); expectedTimeLineRow!.canvasRestore(context, this.trace); }; frameTimeLineRow.addChildTraceRow(expectedTimeLineRow); } private actualChartSupplierFrame(row: TraceRow): void { row.supplierFrame = async (): Promise => { const res = await frameJanksSender(QueryEnum.FrameActualData, row); let maxDepth: number = 1; let unitHeight: number = 20; res.forEach((item) => { if (item.depth! >= maxDepth) { maxDepth = item.depth! + 1; } item.frameType = 'frameTime'; item.cmdline = this.pidToProcessNameMap.get(item.pid!); item.rs_name = this.idToProcessNameMap.get(Number(item.rs_name)!); item.type = '0'; }); if (row && !row.isComplete && res.length > 0) { let maxHeight: number = maxDepth * unitHeight; row.style.height = `${maxHeight}px`; row.setAttribute('height', `${maxHeight}`); } return res; }; } async initActualChart(frameTimeLineRow: TraceRow): Promise { let actualTimeLineRow = TraceRow.skeleton(); actualTimeLineRow.rowId = 'actual frameTime'; actualTimeLineRow.rowType = TraceRow.ROW_TYPE_JANK; actualTimeLineRow.rowHidden = !frameTimeLineRow.expansion; actualTimeLineRow.rowParentId = 'frameTime'; actualTimeLineRow.style.width = '100%'; actualTimeLineRow.name = 'Actual Timeline'; actualTimeLineRow.addTemplateTypes('FrameTimeline'); actualTimeLineRow.setAttribute('children', ''); this.actualChartSupplierFrame(actualTimeLineRow); actualTimeLineRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; actualTimeLineRow.selectChangeHandler = this.trace.selectChangeHandler; actualTimeLineRow.onThreadHandler = (useCache: boolean): void => { let context: CanvasRenderingContext2D; if (actualTimeLineRow.currentContext) { context = actualTimeLineRow.currentContext; } else { context = actualTimeLineRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; } actualTimeLineRow!.canvasSave(context); (renders.jank as JankRender).renderMainThread( { context: context, useCache: useCache, type: 'expected_frame_timeline_slice', }, actualTimeLineRow! ); actualTimeLineRow!.canvasRestore(context, this.trace); }; frameTimeLineRow.addChildTraceRow(actualTimeLineRow); let offsetYTimeOut: number = 0; frameTimeLineRow.addEventListener('expansion-change', (customEventInit: CustomEventInit) => { JankStruct.delJankLineFlag = false; if (offsetYTimeOut) { clearTimeout(offsetYTimeOut); } if (customEventInit.detail?.expansion) { offsetYTimeOut = this.frameExpandTimeOut(customEventInit, actualTimeLineRow); } else { offsetYTimeOut = this.frameNoExpandTimeOut(customEventInit, frameTimeLineRow); } }); } async initAnimatedScenesChart( processRow: TraceRow, process: { pid: number | null; processName: string | null }, firstRow: TraceRow, secondRow: TraceRow ): Promise { let sourceTypeName = await querySourceTypen(); this.flagConfig = FlagsConfig.getFlagsConfig('AnimationAnalysis'); let appNameMap: Map = new Map(); //@ts-ignore if (this.flagConfig?.AnimationAnalysis === 'Enabled' && sourceTypeName[0].value !== 'txt-based-trace') { if (process.processName?.startsWith('render_service')) { let targetRowList = processRow.childrenList.filter( (childRow) => childRow.rowType === 'thread' && childRow.name.startsWith('render_service') ); let nameArr: { name: string }[] = await queryFrameApp(); if (nameArr && nameArr.length > 0) { let currentName = nameArr[0].name; let frameChart = await this.initFrameChart(processRow, nameArr); if (firstRow !== null) { processRow.addChildTraceRowBefore(frameChart, firstRow); } else if (secondRow !== null) { processRow.addChildTraceRowBefore(frameChart, secondRow); } else { // @ts-ignore processRow.addChildTraceRowBefore(frameChart, targetRowList[0]); } let appNameList = await queryDynamicIdAndNameData(); appNameList.forEach((item) => { appNameMap.set(item.id, item.appName); }); let animationRanges = await this.initAnimationChart(processRow); await this.initDynamicCurveChart(appNameMap, frameChart, currentName, animationRanges); await this.initFrameSpacing(appNameMap, frameChart, currentName, animationRanges); } } } } private async initFrameChart( processRow: TraceRow, nameArr: { name: string }[] ): Promise> { let frameChart: TraceRow = TraceRow.skeleton(); let labelName = frameChart.shadowRoot?.querySelector('.name') as HTMLLabelElement; labelName.style.marginRight = '77px'; this.addSystemConfigButton(frameChart, nameArr, 'model-name', true); frameChart.rowId = 'frame'; frameChart.rowType = TraceRow.ROW_TYPE_FRAME; frameChart.rowHidden = !processRow.expansion; frameChart.rowParentId = processRow.rowId; frameChart.style.width = '100%'; frameChart.style.height = '40px'; frameChart.folder = true; frameChart.name = nameArr[0].name; frameChart.setAttribute('children', ''); frameChart.supplier = (): Promise => new Promise((resolve) => { resolve([]); }); frameChart.favoriteChangeHandler = this.trace.favoriteChangeHandler; frameChart.selectChangeHandler = this.trace.selectChangeHandler; frameChart.onThreadHandler = (useCache: boolean): void => { let context: CanvasRenderingContext2D = frameChart!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; frameChart!.canvasSave(context); (renders.empty as EmptyRender).renderMainThread( { context: context, useCache: useCache, type: 'frame', }, frameChart! ); frameChart!.canvasRestore(context, this.trace); }; this.trace.rowsEL?.appendChild(frameChart); return frameChart; } private animationChartSupplierFrame( row: TraceRow, animationIdNameMap: Map, animationIdInfoMap: Map ): void { const unitIndex: number = 1; const unitHeight: number = 20; row.supplierFrame = async (): Promise => { const result = await frameAnimationSender(row); let maxDepth = 0; result.forEach((item) => { if (`${item.status}` === '1') { item.status = 'Completion delay'; } else if (`${item.status}` === '0') { item.status = 'Response delay'; } if (item.depth > maxDepth) { maxDepth = item.depth; } if (animationIdNameMap.has(item.animationId!)) { item.name = animationIdNameMap.get(item.animationId!); item.frameInfo = item.status === 'Completion delay' ? animationIdInfoMap.get(item.animationId!) : '0'; } }); let maxHeight: number = (maxDepth + unitIndex) * unitHeight; row.style.height = `${maxHeight}px`; row.setAttribute('height', `${maxHeight}`); return result; }; } private animationThreadHandler(row: TraceRow): void { row.onThreadHandler = (useCache): void => { let context: CanvasRenderingContext2D = row!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; row!.canvasSave(context); (renders.frameAnimation as FrameAnimationRender).renderMainThread( { context: context, useCache: useCache, type: 'frameAnimation', }, row! ); row!.canvasRestore(context, this.trace); }; } async initAnimationChart(processRow: TraceRow): Promise { let animationRanges: AnimationRanges[] = []; let frameAnimationRow = TraceRow.skeleton(); frameAnimationRow.rowId = 'Animation'; frameAnimationRow.rowType = TraceRow.ROW_TYPE_FRAME_ANIMATION; frameAnimationRow.rowHidden = !processRow.expansion; frameAnimationRow.rowParentId = processRow.rowId; frameAnimationRow.style.width = '100%'; frameAnimationRow.name = 'Animation'; frameAnimationRow.addTemplateTypes('AnimationEffect'); frameAnimationRow.setAttribute('children', ''); let timeRangeData = await queryAnimationTimeRangeData(); timeRangeData.forEach((rangeTime) => { if (rangeTime.status === 'Completion delay') { animationRanges.push({ start: rangeTime.startTs, end: rangeTime.endTs, }); } }); let animationIdNameMap: Map = new Map(); let animationIdInfoMap: Map = new Map(); let animationNameData = await queryAnimationIdAndNameData(); animationNameData.forEach((item) => { animationIdNameMap.set(item.id, item.name); animationIdInfoMap.set(item.id, item.info); }); this.animationChartSupplierFrame(frameAnimationRow, animationIdNameMap, animationIdInfoMap); frameAnimationRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; frameAnimationRow.selectChangeHandler = this.trace.selectChangeHandler; this.animationThreadHandler(frameAnimationRow); processRow.addChildTraceRowSpecifyLocation(frameAnimationRow, 0); return animationRanges; } private dynamicCurveChartThreadHandler( dynamicCurveRow: TraceRow, animationRanges: AnimationRanges[] ): void { dynamicCurveRow.onThreadHandler = (useCache: boolean): void => { let context: CanvasRenderingContext2D = dynamicCurveRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; dynamicCurveRow!.canvasSave(context); (renders.frameDynamicCurve as FrameDynamicRender).renderMainThread( { context: context, useCache: useCache, type: 'dynamicEffectCurve', animationRanges: animationRanges, }, dynamicCurveRow! ); dynamicCurveRow!.canvasRestore(context, this.trace); }; } async initDynamicCurveChart( appNameMap: Map, frameChart: TraceRow, name: string, animationRanges: AnimationRanges[] ): Promise { let systemConfigList: { name: string; }[] = [{ name: 'x' }, { name: 'y' }, { name: 'width' }, { name: 'height' }, { name: 'alpha' }]; let dynamicCurveRow: TraceRow = TraceRow.skeleton(); this.addSystemConfigButton(dynamicCurveRow, systemConfigList, 'model-type'); dynamicCurveRow.setAttribute('model-type', systemConfigList[0].name); dynamicCurveRow.rowId = 'animation-Effect-Curve'; dynamicCurveRow.rowType = TraceRow.ROW_TYPE_FRAME_DYNAMIC; dynamicCurveRow.rowHidden = !frameChart.expansion; dynamicCurveRow.rowParentId = frameChart.rowId; dynamicCurveRow.style.width = '100%'; dynamicCurveRow.style.height = '40px'; dynamicCurveRow.style.height = '100px'; let labelName = dynamicCurveRow.shadowRoot?.querySelector('.name') as HTMLLabelElement; labelName.style.marginRight = '77px'; dynamicCurveRow.name = 'Animation Effect Curve'; dynamicCurveRow.addTemplateTypes('AnimationEffect'); dynamicCurveRow.setAttribute('height', '100px'); dynamicCurveRow.setAttribute('children', ''); dynamicCurveRow.setAttribute('model-type', systemConfigList[0].name); dynamicCurveRow.setAttribute('model-name', name); dynamicCurveRow.supplierFrame = async (): Promise => { const result = await frameDynamicSender(dynamicCurveRow); result.forEach((dataItem) => { if (appNameMap.has(dataItem.id!)) { dataItem.appName = appNameMap.get(dataItem.id!); } }); return result; }; dynamicCurveRow.selectChangeHandler = this.trace.selectChangeHandler; this.dynamicCurveChartThreadHandler(dynamicCurveRow, animationRanges); frameChart.addChildTraceRow(dynamicCurveRow); } private FrameSpacingThreadHandler( frameSpacingRow: TraceRow, animationRanges: AnimationRanges[], rate: number ): void { frameSpacingRow.onThreadHandler = (useCache: boolean): void => { let context = frameSpacingRow!.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!; frameSpacingRow!.canvasSave(context); (renders.frameSpacing as FrameSpacingRender).renderMainThread( { context: context, useCache: useCache, type: 'frame_spacing_slice', frameRate: rate, animationRanges: animationRanges, }, frameSpacingRow! ); frameSpacingRow!.canvasRestore(context, this.trace); }; } async initFrameSpacing( appNameMap: Map, frameChart: TraceRow, name: string, animationRanges: AnimationRanges[] ): Promise { let deviceStructArray = await queryPhysicalData(); let deviceStruct: DeviceStruct = deviceStructArray[0]; let frameSpacingRow = TraceRow.skeleton(); frameSpacingRow.rowId = 'frame spacing'; frameSpacingRow.rowType = TraceRow.ROW_TYPE_FRAME_SPACING; frameSpacingRow.rowHidden = !frameChart.expansion; frameSpacingRow.rowParentId = frameChart.rowId; frameSpacingRow.style.width = '100%'; frameSpacingRow.style.height = '140px'; frameSpacingRow.name = 'Frame spacing'; frameSpacingRow.addTemplateTypes('AnimationEffect'); frameSpacingRow.setAttribute('height', '140'); frameSpacingRow.setAttribute('children', ''); frameSpacingRow.setAttribute('model-name', name); let physicalConfigWidth = Number(this.flagConfig!.physicalWidth); let physicalConfigHeight = Number(this.flagConfig!.physicalHeight); let physicalWidth = physicalConfigWidth !== 0 ? physicalConfigWidth : deviceStruct.physicalWidth; let physicalHeight = physicalConfigHeight !== 0 ? physicalConfigHeight : deviceStruct.physicalHeight; frameSpacingRow.supplierFrame = async (): Promise => { const result = await frameSpacingSender(physicalWidth, physicalHeight, frameSpacingRow); result.forEach((dataItem) => { if (appNameMap.has(dataItem.id!)) { dataItem.nameId = appNameMap.get(dataItem.id!); } dataItem.physicalWidth = physicalWidth; dataItem.physicalHeight = physicalHeight; }); return result; }; frameSpacingRow.favoriteChangeHandler = this.trace.favoriteChangeHandler; frameSpacingRow.selectChangeHandler = this.trace.selectChangeHandler; this.FrameSpacingThreadHandler(frameSpacingRow, animationRanges, deviceStruct.physicalFrameRate); frameChart.addChildTraceRow(frameSpacingRow); } addSystemConfigButton( systemTraceRow: TraceRow, systemConfigList: { name: string }[], attributeKey: string, allowChangeName: boolean = false ): void { let componentList: Array = []; for (let index = 0; index < systemConfigList.length; index++) { let componentName = systemConfigList[index].name; componentList.push({ key: `${componentName}`, title: `${componentName}`, checked: index === 0, }); } systemTraceRow.addRowSettingPop(); systemTraceRow.rowSetting = 'enable'; systemTraceRow.rowSettingPopoverDirection = 'bottomLeft'; systemTraceRow.rowSettingList = componentList; systemTraceRow.onRowSettingChangeHandler = (value: string[]): void => { if (allowChangeName) { systemTraceRow.name = value[0]; } systemTraceRow.setAttribute(attributeKey, `${value[0]}`); systemTraceRow.childrenList.forEach((row): void => { row.setAttribute(attributeKey, `${value[0]}`); }); this.trace.refreshCanvas(false); }; } private frameNoExpandTimeOut(event: CustomEventInit, frameTimeLineRow: TraceRow): number { if (JankStruct!.selectJankStruct) { JankStruct.selectJankStructList?.push(JankStruct!.selectJankStruct); } let topPadding: number = 195; let halfNumber: number = 2; let offsetYTime: number = 300; let refreshTime: number = 360; let offsetYTimeOut: number = window.setTimeout(() => { this.trace.linkNodes.forEach((linkNode: PairPoint[]) => { if (linkNode[0].rowEL.collect) { linkNode[0].rowEL.translateY = linkNode[0].rowEL.getBoundingClientRect().top - topPadding; } else { linkNode[0].rowEL.translateY = linkNode[0].rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop; } linkNode[0].y = linkNode[0].rowEL!.translateY! + linkNode[0].offsetY; if (linkNode[1].rowEL.collect) { linkNode[1].rowEL.translateY = linkNode[1].rowEL.getBoundingClientRect().top - topPadding; } else { linkNode[1].rowEL.translateY = linkNode[1].rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop; } linkNode[1].y = linkNode[1].rowEL!.translateY! + linkNode[1].offsetY; //@ts-ignore if (linkNode[0].rowEL.rowParentId === event.detail?.rowId) { if (!linkNode[0].rowEL.collect) { linkNode[0].x = ns2xByTimeShaft(linkNode[0].ns, this.trace.timerShaftEL!); linkNode[0].y = frameTimeLineRow!.translateY! + linkNode[0].offsetY / halfNumber; linkNode[0].offsetY = linkNode[0].offsetY / halfNumber; //@ts-ignore linkNode[0].rowEL = frameTimeLineRow; } //@ts-ignore } else if (linkNode[1].rowEL.rowParentId === event.detail?.rowId) { if (!linkNode[1].rowEL.collect) { linkNode[1].x = ns2xByTimeShaft(linkNode[1].ns, this.trace.timerShaftEL!); linkNode[1].y = frameTimeLineRow!.translateY! + linkNode[1].offsetY / halfNumber; linkNode[1].offsetY = linkNode[1].offsetY / halfNumber; //@ts-ignore linkNode[1].rowEL = frameTimeLineRow!; } } }); }, offsetYTime); let refreshTimeOut: number = window.setTimeout(() => { this.trace.refreshCanvas(true); clearTimeout(refreshTimeOut); }, refreshTime); return offsetYTimeOut; } private frameExpandTimeOut( event: CustomEventInit<{ expansion: boolean; rowType: string; rowId: string; rowParentId: string }>, actualTimeLineRow: TraceRow ): number { let topPadding: number = 195; let halfNumber: number = 2; let offsetYTime: number = 300; let refreshTime: number = 360; let offsetYTimeOut: number = window.setTimeout(() => { this.trace.linkNodes.forEach((linkFrameNode: PairPoint[]) => { JankStruct.selectJankStructList?.forEach((dat: JankStruct) => { if (event.detail?.rowId === dat.pid) { JankStruct.selectJankStruct = dat; JankStruct.hoverJankStruct = dat; } }); if (linkFrameNode[0].rowEL.collect) { linkFrameNode[0].rowEL.translateY = linkFrameNode[0].rowEL.getBoundingClientRect().top - topPadding; } else { linkFrameNode[0].rowEL.translateY = linkFrameNode[0].rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop; } linkFrameNode[0].y = linkFrameNode[0].rowEL!.translateY! + linkFrameNode[0].offsetY; if (linkFrameNode[1].rowEL.collect) { linkFrameNode[1].rowEL.translateY = linkFrameNode[1].rowEL.getBoundingClientRect().top - topPadding; } else { linkFrameNode[1].rowEL.translateY = linkFrameNode[1].rowEL.offsetTop - this.trace.rowsPaneEL!.scrollTop; } linkFrameNode[1].y = linkFrameNode[1].rowEL!.translateY! + linkFrameNode[1].offsetY; if (linkFrameNode[0].rowEL.rowId === event.detail?.rowId) { linkFrameNode[0].x = ns2xByTimeShaft(linkFrameNode[0].ns, this.trace.timerShaftEL!); linkFrameNode[0].y = actualTimeLineRow!.translateY! + linkFrameNode[0].offsetY * halfNumber; linkFrameNode[0].offsetY = linkFrameNode[0].offsetY * halfNumber; //@ts-ignore linkFrameNode[0].rowEL = actualTimeLineRow; } else if (linkFrameNode[1].rowEL.rowId === event.detail?.rowId) { linkFrameNode[1].x = ns2xByTimeShaft(linkFrameNode[1].ns, this.trace.timerShaftEL!); linkFrameNode[1].y = actualTimeLineRow!.translateY! + linkFrameNode[1].offsetY * halfNumber; linkFrameNode[1].offsetY = linkFrameNode[1].offsetY * halfNumber; //@ts-ignore linkFrameNode[1].rowEL = actualTimeLineRow!; } }); }, offsetYTime); let refreshTimeOut: number = window.setTimeout(() => { this.trace.refreshCanvas(true); clearTimeout(refreshTimeOut); }, refreshTime); return offsetYTimeOut; } }