使用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 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({
store: {
required: true
@ -194,34 +262,57 @@ const clearEvents = () => {
if (typeof graphVis.unregistEventListener === "function") {
graphVis.unregistEventListener(type, event, handler)
} else {
// GraphVisAPI
console.warn("GraphVis instance does not have unregistEventListener method")
}
})
eventListeners = []
}
}
//线
const highLightAboutNodesOrLinks = (type) => {
// 线使Web Worker
const highLightAboutNodesOrLinks = async (type) => {
graphVis.cancelAllSelected()
const { newNodes, newLinks } = graph.value
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
}
})
} else {
return
try {
let processedItems
if (type == "nodes") {
// 使Web Worker
processedItems = await postWorkerTask("HIGHLIGHT_NODES", {
nodes: graphVis.nodes,
newNodes
})
graphVis.nodes = processedItems
} else if (type == "links") {
// 使Web Worker线
processedItems = await postWorkerTask("HIGHLIGHT_LINKS", {
links: graphVis.links,
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) => {
@ -239,40 +330,55 @@ const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => {
highLightAboutNodesOrLinks("nodes")
}
const handleAnomalousGroup = () => {
// store
const now = props.store.currentUtc
const abnormalData = props.store.graphAbnormalData
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) => now >= config.timeThreshold)
const handleAnomalousGroup = async () => {
try {
// 使Web Worker
const processedNodes = await postWorkerTask("ANOMALOUS_GROUP_PROCESS", {
nodes: graphVis.nodes,
currentUtc: props.store.currentUtc,
abnormalData: props.store.graphAbnormalData
})
// - ID
const abnormalMap = new Map()
activeConfigs.forEach((config) => {
const abnormalIds = abnormalData[config.groupKey]
if (abnormalIds) {
abnormalMap.set(config.groupKey, new Set(abnormalIds))
}
})
graphVis.nodes = processedNodes
graphVis.render()
} catch (error) {
console.error("Error processing anomalous group:", error)
//
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 //
// 使
// store
const now = props.store.currentUtc
const abnormalData = props.store.graphAbnormalData
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) => 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([
@ -286,38 +392,60 @@ const runDiffForceLayout = (layoutConfig, layoutType, isAsync) => {
}
graphVis.excuteLocalLayout(layoutType, layoutConfig, isAsync, handleLayoutSuccess())
}
// cluster
const clusterAnalyze = () => {
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
// cluster使Web Worker
const clusterAnalyze = async () => {
try {
// 使Web Worker
const { nodes: processedNodes, groups } = await postWorkerTask("CLUSTER_ANALYZE", {
nodes: graphVis.nodes,
storeId
})
if (storeId !== "anomalousGroup") {
graphVis.addNodesInGroup(nodes, {
shape: "polygon", //circle|rect|polygon|bubbleset
color: color,
alpha: 0.2
graphVis.removeAllGroup() //
graphVis.nodes = processedNodes
//
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
const updateChart = (newGraphData) => {
const updateChart = async (newGraphData) => {
if (!graphVis) {
initChart()
return
@ -379,8 +507,12 @@ const updateChart = (newGraphData) => {
try {
graphVis.clearAll()
graphVis.addGraph({ ...toRaw(newGraphData) })
// Web Worker
initWorker()
//
clusterAnalyze()
await clusterAnalyze()
runDiffForceLayout({ strength: -300, ajustCluster: true }, "simulation", true)
} catch (error) {
console.error("Error updating chart:", error)
@ -445,6 +577,9 @@ onUnmounted(() => {
forceSimulator = null
}
// Web Worker
terminateWorker()
//
currentSelectNode.value = null
isPlay.value = false

View File

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