使用web worker优化计算

This commit is contained in:
qumeng039@126.com 2025-09-11 11:05:12 +08:00
parent dd56a5b202
commit 346ed68356
4 changed files with 366 additions and 85 deletions

BIN
dist.zip

Binary file not shown.

View File

@ -0,0 +1,147 @@
// 图表计算Web Worker
// 处理力导向布局计算
self.onmessage = function(e) {
const { type, data } = e.data;
let result;
switch(type) {
case 'CLUSTER_ANALYZE':
result = processClusterAnalyze(data);
break;
case 'HIGHLIGHT_NODES':
result = processHighlightNodes(data);
break;
case 'HIGHLIGHT_LINKS':
result = processHighlightLinks(data);
break;
case 'ANOMALOUS_GROUP_PROCESS':
result = processAnomalousGroup(data);
break;
default:
self.postMessage({
success: false,
type,
error: `Unknown task type: ${type}`
});
return;
}
self.postMessage({
success: true,
type,
result
});
};
// 处理聚类分析
function processClusterAnalyze(data) {
const { nodes, storeId } = data;
const clusterNodesMap = new Map();
// 创建cluster到nodes的映射
nodes.forEach(node => {
const cluster = parseInt(node.type);
if (!clusterNodesMap.has(cluster)) {
clusterNodesMap.set(cluster, []);
}
clusterNodesMap.get(cluster).push(node);
});
let colorMap = {
0: "75,241,184", // 绿色
1: "250,222,37", // 黄色
6: "69,192,242" // 蓝色
};
const processedNodes = [];
const groups = [];
// 处理节点颜色和分组
clusterNodesMap.forEach((nodes, cluster) => {
const color = colorMap[cluster];
nodes.forEach(node => {
const processedNode = { ...node };
processedNode.fillColor = color;
processedNode.color = color;
processedNodes.push(processedNode);
});
// 非异常群体模块需要添加分组
if (storeId !== "anomalousGroup") {
groups.push({
nodes: nodes.map(n => n.id),
options: {
shape: "polygon",
color: color,
alpha: 0.2
}
});
}
});
return { nodes: processedNodes, groups };
}
// 处理节点高亮
function processHighlightNodes(data) {
const { nodes, newNodes } = data;
const newNodeIds = new Set(newNodes.map(node => node.name));
return nodes.map(node => ({
...node,
selected: newNodeIds.has(node.id)
}));
}
// 处理连线高亮
function processHighlightLinks(data) {
const { links, newLinks } = data;
const newLinkSet = new Set(newLinks.map(link => `${link.source}-${link.target}`));
return links.map(link => ({
...link,
selected: newLinkSet.has(`${link.source.id}-${link.target.id}`)
}));
}
// 处理异常群体分析
function processAnomalousGroup(data) {
const { nodes, currentUtc, abnormalData } = data;
const abnormalGroupConfigs = [
{ timeThreshold: "2024-06-19T08:57:55Z", groupKey: "groupA", color: "85, 125, 15" }, // 绿色
{ timeThreshold: "2024-06-19T10:58:03Z", groupKey: "groupB", color: "125, 114, 15" }, // 黄色
{ timeThreshold: "2024-06-19T12:58:04Z", groupKey: "groupC", color: "15, 106, 125" } // 蓝色
];
// 计算当前应激活的异常组配置
const activeConfigs = abnormalGroupConfigs.filter(config => currentUtc >= config.timeThreshold);
// 优化异常节点检测 - 预处理异常ID集合
const abnormalMap = new Map();
activeConfigs.forEach(config => {
const abnormalIds = abnormalData[config.groupKey];
if (abnormalIds) {
abnormalMap.set(config.groupKey, new Set(abnormalIds));
}
});
// 单次遍历处理所有异常节点
return nodes.map(node => {
const processedNode = { ...node };
// 检查节点是否属于任何激活的异常组
for (const config of activeConfigs) {
const abnormalIdsSet = abnormalMap.get(config.groupKey);
// 如果节点在异常组中,则设置对应的颜色
if (abnormalIdsSet && abnormalIdsSet.has(node.id)) {
processedNode.fillColor = config.color;
break; // 一旦找到匹配的异常组,就不再检查其他组
}
}
return processedNode;
});
}

View File

@ -41,6 +41,74 @@ import group2Leg from "@/assets/images/groupEvolution/legends-group2.png"
import group3Leg from "@/assets/images/groupEvolution/legends-group3.png" import group3Leg from "@/assets/images/groupEvolution/legends-group3.png"
import abnormalLeg from "@/assets/images/groupEvolution/legends-abnormal-group.png" import abnormalLeg from "@/assets/images/groupEvolution/legends-abnormal-group.png"
// Web Worker
let graphWorker = null
let workerCallbacks = new Map()
let workerTaskId = 0
const initWorker = () => {
if (!graphWorker) {
// Web Worker
graphWorker = new Worker(new URL("./graphWorker.js", import.meta.url))
// Worker
graphWorker.onmessage = (e) => {
const { success, type, result, error, taskId } = e.data
//
const callback = workerCallbacks.get(taskId)
if (callback) {
if (success) {
callback(null, result)
} else {
callback(new Error(`Worker error: ${error}`))
}
workerCallbacks.delete(taskId)
}
}
// Worker
graphWorker.onerror = (error) => {
console.error("Graph Worker error:", error)
}
}
}
// Worker
const postWorkerTask = (type, data) => {
return new Promise((resolve, reject) => {
if (!graphWorker) {
initWorker()
// Worker使
if (!graphWorker) {
reject(new Error("Web Worker not available"))
return
}
}
const taskId = ++workerTaskId
workerCallbacks.set(taskId, (error, result) => {
if (error) {
reject(error)
} else {
resolve(result)
}
})
graphWorker.postMessage({ type, data, taskId })
})
}
// Worker
const terminateWorker = () => {
if (graphWorker) {
graphWorker.terminate()
graphWorker = null
workerCallbacks.clear()
workerTaskId = 0
}
}
const props = defineProps({ const props = defineProps({
store: { store: {
required: true required: true
@ -194,34 +262,57 @@ const clearEvents = () => {
if (typeof graphVis.unregistEventListener === "function") { if (typeof graphVis.unregistEventListener === "function") {
graphVis.unregistEventListener(type, event, handler) graphVis.unregistEventListener(type, event, handler)
} else { } else {
// GraphVisAPI
console.warn("GraphVis instance does not have unregistEventListener method")
} }
}) })
eventListeners = [] eventListeners = []
} }
} }
//线 // 线使Web Worker
const highLightAboutNodesOrLinks = (type) => { const highLightAboutNodesOrLinks = async (type) => {
graphVis.cancelAllSelected() graphVis.cancelAllSelected()
const { newNodes, newLinks } = graph.value const { newNodes, newLinks } = graph.value
if (type == "nodes") {
const newNodeIds = new Set(newNodes.map((node) => node.name)) try {
graphVis.nodes.forEach((node) => { let processedItems
if (newNodeIds.has(node.id)) {
node.selected = true if (type == "nodes") {
} // 使Web Worker
}) processedItems = await postWorkerTask("HIGHLIGHT_NODES", {
} else if (type == "links") { nodes: graphVis.nodes,
const newLinkSet = new Set(newLinks.map((link) => `${link.source}-${link.target}`)) newNodes
graphVis.links.forEach((link) => { })
if (newLinkSet.has(`${link.source.id}-${link.target.id}`)) { graphVis.nodes = processedItems
link.selected = true } else if (type == "links") {
} // 使Web Worker线
}) processedItems = await postWorkerTask("HIGHLIGHT_LINKS", {
} else { links: graphVis.links,
return newLinks
})
graphVis.links = processedItems
} else {
return
}
//
graphVis.render()
} catch (error) {
// 使
if (type == "nodes") {
const newNodeIds = new Set(newNodes.map((node) => node.name))
graphVis.nodes.forEach((node) => {
if (newNodeIds.has(node.id)) {
node.selected = true
}
})
} else if (type == "links") {
const newLinkSet = new Set(newLinks.map((link) => `${link.source}-${link.target}`))
graphVis.links.forEach((link) => {
if (newLinkSet.has(`${link.source.id}-${link.target.id}`)) {
link.selected = true
}
})
}
} }
} }
const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => { const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => {
@ -239,40 +330,55 @@ const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => {
highLightAboutNodesOrLinks("nodes") highLightAboutNodesOrLinks("nodes")
} }
const handleAnomalousGroup = () => { const handleAnomalousGroup = async () => {
// store try {
const now = props.store.currentUtc // 使Web Worker
const abnormalData = props.store.graphAbnormalData const processedNodes = await postWorkerTask("ANOMALOUS_GROUP_PROCESS", {
const abnormalGroupConfigs = [ nodes: graphVis.nodes,
{ timeThreshold: "2024-06-19T08:57:55Z", groupKey: "groupA", color: "85, 125, 15" }, // 绿 currentUtc: props.store.currentUtc,
{ timeThreshold: "2024-06-19T10:58:03Z", groupKey: "groupB", color: "125, 114, 15" }, // abnormalData: props.store.graphAbnormalData
{ timeThreshold: "2024-06-19T12:58:04Z", groupKey: "groupC", color: "15, 106, 125" } // })
]
//
const activeConfigs = abnormalGroupConfigs.filter((config) => now >= config.timeThreshold)
// - ID graphVis.nodes = processedNodes
const abnormalMap = new Map() graphVis.render()
activeConfigs.forEach((config) => { } catch (error) {
const abnormalIds = abnormalData[config.groupKey] console.error("Error processing anomalous group:", error)
if (abnormalIds) {
abnormalMap.set(config.groupKey, new Set(abnormalIds))
}
})
// // 使
graphVis.nodes = graphVis.nodes.map((node) => { // store
// const now = props.store.currentUtc
for (const config of activeConfigs) { const abnormalData = props.store.graphAbnormalData
const abnormalIdsSet = abnormalMap.get(config.groupKey) const abnormalGroupConfigs = [
// { timeThreshold: "2024-06-19T08:57:55Z", groupKey: "groupA", color: "85, 125, 15" }, // 绿
if (abnormalIdsSet && abnormalIdsSet.has(node.id)) { { timeThreshold: "2024-06-19T10:58:03Z", groupKey: "groupB", color: "125, 114, 15" }, //
node.fillColor = config.color { timeThreshold: "2024-06-19T12:58:04Z", groupKey: "groupC", color: "15, 106, 125" } //
break // ]
//
const activeConfigs = abnormalGroupConfigs.filter((config) => now >= config.timeThreshold)
// - ID
const abnormalMap = new Map()
activeConfigs.forEach((config) => {
const abnormalIds = abnormalData[config.groupKey]
if (abnormalIds) {
abnormalMap.set(config.groupKey, new Set(abnormalIds))
} }
} })
return node
}) //
graphVis.nodes = graphVis.nodes.map((node) => {
//
for (const config of activeConfigs) {
const abnormalIdsSet = abnormalMap.get(config.groupKey)
//
if (abnormalIdsSet && abnormalIdsSet.has(node.id)) {
node.fillColor = config.color
break //
}
}
return node
})
}
} }
new Map([ new Map([
@ -286,38 +392,60 @@ const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => {
} }
graphVis.excuteLocalLayout(layoutType, layoutConfig, isAsync, handleLayoutSuccess()) graphVis.excuteLocalLayout(layoutType, layoutConfig, isAsync, handleLayoutSuccess())
} }
// cluster // cluster使Web Worker
const clusterAnalyze = () => { const clusterAnalyze = async () => {
graphVis.removeAllGroup() // try {
// clusternodes // 使Web Worker
const clusterNodesMap = new Map() const { nodes: processedNodes, groups } = await postWorkerTask("CLUSTER_ANALYZE", {
graphVis.nodes.forEach((node) => { nodes: graphVis.nodes,
const cluster = parseInt(node.type) storeId
if (!clusterNodesMap.has(cluster)) {
clusterNodesMap.set(cluster, [])
}
clusterNodesMap.get(cluster).push(node)
})
let colorMap = {
0: "75,241,184", // 绿
1: "250,222,37", //
6: "69,192,242" //
}
clusterNodesMap.forEach((nodes, cluster) => {
const color = colorMap[cluster]
nodes.forEach((node) => {
node.fillColor = color
node.color = color
}) })
if (storeId !== "anomalousGroup") {
graphVis.addNodesInGroup(nodes, { graphVis.removeAllGroup() //
shape: "polygon", //circle|rect|polygon|bubbleset graphVis.nodes = processedNodes
color: color, //
alpha: 0.2 if (storeId !== "anomalousGroup" && groups && groups.length) {
groups.forEach((group) => {
const groupNodes = processedNodes.filter((node) => group.nodes.includes(node.id))
if (groupNodes.length) {
graphVis.addNodesInGroup(groupNodes, group.options)
}
}) })
} }
})
// graphVis.autoGroupLayout(graphVis.nodes) graphVis.render()
} catch (error) {
// 使
graphVis.removeAllGroup() //
// clusternodes
const clusterNodesMap = new Map()
graphVis.nodes.forEach((node) => {
const cluster = parseInt(node.type)
if (!clusterNodesMap.has(cluster)) {
clusterNodesMap.set(cluster, [])
}
clusterNodesMap.get(cluster).push(node)
})
let colorMap = {
0: "75,241,184", // 绿
1: "250,222,37", //
6: "69,192,242" //
}
clusterNodesMap.forEach((nodes, cluster) => {
const color = colorMap[cluster]
nodes.forEach((node) => {
node.fillColor = color
node.color = color
})
if (storeId !== "anomalousGroup") {
graphVis.addNodesInGroup(nodes, {
shape: "polygon", //circle|rect|polygon|bubbleset
color: color,
alpha: 0.2
})
}
})
}
} }
// //
@ -370,7 +498,7 @@ onMounted(() => {
}) })
// 使requestAnimationFrame // 使requestAnimationFrame
const updateChart = (newGraphData) => { const updateChart = async (newGraphData) => {
if (!graphVis) { if (!graphVis) {
initChart() initChart()
return return
@ -379,8 +507,12 @@ const updateChart = (newGraphData) => {
try { try {
graphVis.clearAll() graphVis.clearAll()
graphVis.addGraph({ ...toRaw(newGraphData) }) graphVis.addGraph({ ...toRaw(newGraphData) })
// Web Worker
initWorker()
// //
clusterAnalyze() await clusterAnalyze()
runDiffForceLayout({ strength: -300, ajustCluster: true }, "simulation", true) runDiffForceLayout({ strength: -300, ajustCluster: true }, "simulation", true)
} catch (error) { } catch (error) {
console.error("Error updating chart:", error) console.error("Error updating chart:", error)
@ -445,6 +577,9 @@ onUnmounted(() => {
forceSimulator = null forceSimulator = null
} }
// Web Worker
terminateWorker()
// //
currentSelectNode.value = null currentSelectNode.value = null
isPlay.value = false isPlay.value = false

View File

@ -101,10 +101,9 @@ const startAutomaticPlay = () => {
isAutoPlaying.value = true isAutoPlaying.value = true
// 300ms2%
animationTimer = setInterval(() => { animationTimer = setInterval(() => {
// //
const newPosition = clickedPosition.value + 2 const newPosition = clickedPosition.value + 0.1
// //
if (newPosition >= 100) { if (newPosition >= 100) {
@ -118,7 +117,7 @@ const startAutomaticPlay = () => {
// activeTimePoint // activeTimePoint
updateActiveTimePointByPosition(newPosition) updateActiveTimePointByPosition(newPosition)
}, 1000) }, 100)
} }
// activeTimePoint // activeTimePoint