Browse Source

527适配千节点

Lan 1 tháng trước cách đây
mục cha
commit
7289350b3e

BIN
backend/api/__pycache__/api_graph.cpython-310.pyc


+ 136 - 111
backend/api/api_graph.py

@@ -17,6 +17,54 @@ import json, csv
 
 logger = logging.getLogger("graph")
 
+
+def build_adjacency_lists(nodeJson, edgeJson):
+    """ 构建类型化邻接表 """
+    adj = {n['id']: [] for n in nodeJson}
+    type_map = {n['id']: n['type'] for n in nodeJson}
+    
+    for edge in edgeJson:
+        src, dst = edge['from'], edge['to']
+        src_type = type_map[src]
+        dst_type = type_map[dst]
+        
+        # 根据类型过滤连接
+        if src_type == 'S' and dst_type == 'D':
+            adj[src].append(dst)
+        elif src_type == 'D' and dst_type in ('D', 'I'):
+            adj[src].append(dst)
+        # if edge['meta'][0].get('optimize') != 'new':  # 双向处理
+        #     if dst_type == 'S' and src_type == 'D':
+        #         adj[dst].append(src)
+        #     elif dst_type == 'D' and src_type in ('D', 'I'):
+        #         adj[dst].append(src)
+    return adj, type_map
+
+def parallel_chain_search(s_id, adj, type_map):
+    """ 并行化路径搜索 """
+    from collections import deque
+    chains = []
+    queue = deque([(s_id, 1)])  # (current_id, path_length)
+    visited = {s_id: 1}  # 节点: 到达时的路径长度
+    
+    while queue:
+        nid, length = queue.popleft()
+        if length > 4:  # 限制最大深度为4层(总长度5)
+            continue
+            
+        for neighbor in adj.get(nid, []):
+            n_type = type_map[neighbor]
+            # 剪枝规则
+            if n_type == 'I' and length >= 2:
+                chains.append(length + 1)
+                continue
+            if neighbor not in visited or visited[neighbor] > length + 1:
+                visited[neighbor] = length + 1
+                queue.append((neighbor, length + 1))
+                
+    return chains
+
+
 class ViewGraphByToken(APIView):
     # 通过验证码看图——供VR使用
     authentication_classes = []
@@ -59,122 +107,96 @@ class GenerateGraph(APIView):
         if method == 'chart':
             nodeJson = result.nodeFile.toJson()
             edgeJson = result.edgeFile.toJson()
-            def isOriginEdge(edge):
-                for meta in edge['meta']:
-                    if meta.get('optimize') == 'new' or meta.get('predict') == 'new':
-                        return False
-                return True
-            # 需要完善图表视图统计数据,根据具体图表类型决定
-            # 通用图数据统计,包括SDI节点数量、边数量,各类型节点度数分布
-            # 平均链长度,聚类系数即D节点间连接紧密程度
-            # 分别统计SDI节点的数量和度数:sNodes,dNodes,iNodes 
-            sDegree = dDegree = iDegree = [0 for i in range(11)] # 度的取值范围0~10,共11个元素
-            sNodes = [node for node in nodeJson if node['type'] == 'S']
-            for s in sNodes:
-                degree = len([edge for edge in edgeJson if edge['from'] == s['id'] or edge['to'] == s['id']])
-                if degree <= 10:
-                    sDegree[degree] += 1
-                else:
-                    sDegree[10] += 1
-            dNodes = [node for node in nodeJson if node['type'] == 'D']
-            for d in dNodes:
-                degree = len([edge for edge in edgeJson if edge['from'] == d['id'] or edge['to'] == d['id']])
-                if degree <= 10:
-                    dDegree[degree] += 1
-                else:
-                    dDegree[10] += 1
-            iNodes = [node for node in nodeJson if node['type'] == 'I']
-            for i in iNodes:
-                degree = len([edge for edge in edgeJson if edge['from'] == i['id'] or edge['to'] == i['id']])
-                if degree <= 10:
-                    iDegree[degree] += 1
-                else:
-                    iDegree[10] += 1
-            # 查找所有OODA链路:chains
-            chains = []
-            for s in sNodes:
-                stack = [(s, [s])]  # (当前节点, 路径, 已访问节点)
-                while stack:
-                    current, path = stack.pop()
-                    # 必须限制链路长度,否则无法结束,除限制长度外还可尝试将不同顺序节点的链路视为同一条,从而减少链路数量
-                    if len(path) > 5:
-                        continue
-                    # 终止条件:到达I节点
-                    if current['type'] == 'I':
-                        chains.append(path)
-                        continue
-                    
-                    # 遍历邻居,注意通用计算只统计原始图
-                    neighbors = [edge['to'] for edge in edgeJson if (isOriginEdge(edge) and edge['from'] == current['id'])] + [edge['from'] for edge in edgeJson if (isOriginEdge(edge) and edge['to'] == current['id'])]
-                    for neighborId in neighbors:
-                        neighbor = [node for node in nodeJson if node.get('id') == neighborId]
-                        if len(neighbor) != 1:
-                            return failed(message="查找OODA链路时出错:不存在的邻居节点")
-                        neighbor = neighbor[0]
-                        if neighbor in path: continue  # 避免环路
-                        # 不能连线的几种情况
-                        if current['type'] == 'S' and neighbor['type'] == 'I':
-                            continue
-                        if current['type'] == 'D' and neighbor['type'] == 'S':
-                            continue
-                        stack.append((neighbor, path + [neighbor]))
-            chainsNum = len(chains)
-            chainsNumByLength = [0 for i in range(5)]
-            for chain in chains:
-                length = len(chain)
-                if length >= 5:
-                    length = 5
-                chainsNumByLength[length-1] += 1
-            averageChainsLength = round(sum([len(chain) for chain in chains]) / chainsNum, 2)
+    
+            # 1. 构建类型化邻接表(O(E)时间复杂度)
+            adj, type_map = build_adjacency_lists(nodeJson, edgeJson)
+            
+            # 2. 并行计算度数分布(O(N)时间复杂度)
+            from joblib import Parallel, delayed
+            s_nodes = [n['id'] for n in nodeJson if n['type'] == 'S']
+            d_nodes = [n['id'] for n in nodeJson if n['type'] == 'D']
+            i_nodes = [n['id'] for n in nodeJson if n['type'] == 'I']
+            
+            # 度数统计
+            degree_bins = lambda deg: min(deg, 10)
+            s_degrees = [degree_bins(len(adj[nid])) for nid in s_nodes]
+            d_degrees = [degree_bins(len(adj[nid])) for nid in d_nodes]
+            i_degrees = [degree_bins(len(adj[nid])) for nid in i_nodes]
+            
+            # 3. 并行路径搜索(使用多线程加速)
+            from concurrent.futures import ThreadPoolExecutor
+            all_chains = []
+            with ThreadPoolExecutor(max_workers=8) as executor:
+                futures = [executor.submit(parallel_chain_search, s_id, adj, type_map) 
+                        for s_id in s_nodes]
+                for future in futures:
+                    all_chains.extend(future.result())
+            
+            # 统计结果(O(L)时间复杂度)
+            chains_num = len(all_chains)
+            len_dist = [0]*5
+            for l in all_chains:
+                len_dist[min(l-1, 4)] += 1  # 1-5层对应索引0-4
+                
+            avg_len = round(sum(all_chains)/chains_num, 2) if chains_num > 0 else 0.0
+            
             data = {
-                'sNodesNum': len(sNodes),
-                'dNodesNum': len(dNodes),
-                'iNodesNum': len(iNodes),
-                'sDegree': sDegree,
-                'dDegree': dDegree,
-                'iDegree': iDegree,
+                'sNodesNum': len(s_nodes),
+                'dNodesNum': len(d_nodes),
+                'iNodesNum': len(i_nodes),
+                'sDegree': [s_degrees.count(i) for i in range(11)],
+                'dDegree': [d_degrees.count(i) for i in range(11)],
+                'iDegree': [i_degrees.count(i) for i in range(11)],
                 'chains': {
-                    'num': chainsNum,
-                    'averageLength': averageChainsLength,
-                    'numByLength': chainsNumByLength,
-                },
+                    'num': chains_num,
+                    'averageLength': avg_len,
+                    'numByLength': len_dist
+                }
             }
             # 根据计算类型的不同分别计算特殊数据
             if result.plan.algorithm.type in ['optimize', 'predict'] :
                 # 拓扑优化算法应该统计原有图的数据与优化后图的数据,优化应该仅优化边
-                newChains = []
-                for s in sNodes:
-                    stack = [(s, [s])]  # (当前节点, 路径, 已访问节点)
-                    while stack:
-                        current, path = stack.pop() 
-                        
-                        # 终止条件:到达I节点
-                        if current['type'] == 'I':
-                            newChains.append(path)
-                            continue
-                        
-                        # 遍历邻居,注意优化计算只统计新图
-                        neighbors = [edge['to'] for edge in edgeJson if (not isOriginEdge(edge) and edge['from'] == current['id'])] + [edge['from'] for edge in edgeJson if (not isOriginEdge(edge) and edge['to'] == current['id'])]
-                        for neighborId in neighbors:
-                            neighbor = [node for node in nodeJson if node.get('id') == neighborId]
-                            if len(neighbor) != 1:
-                                return failed(message="查找OODA链路时出错:不存在的邻居节点")
-                            neighbor = neighbor[0]
-                            if neighbor in path: continue  # 避免环路
-                            # 不能连线的几种情况
-                            if current['type'] == 'S' and neighbor['type'] == 'I':
-                                continue
-                            if current['type'] == 'D' and neighbor['type'] == 'S':
-                                continue
-                            stack.append((neighbor, path + [neighbor]))
-                newChainsNum = len(newChains)
-                newAverageChainsLength = 0
-                if newChainsNum != 0:
-                    newAverageChainsLength = round(sum([len(chain) for chain in newChains]) / newChainsNum, 2)
-                data['newChains'] = {
-                        'num': newChainsNum,
-                        'averageLength': newAverageChainsLength,
-                    }                        
+                from concurrent.futures import ThreadPoolExecutor
+
+                def is_new_edge(edge):
+                    """ 判断是否为新增边 """
+                    return any(meta.get('optimize') == 'new' for meta in edge['meta'])
+
+                def build_new_adjacency(nodeJson, edgeJson):
+                    """ 构建优化后的邻接表 """
+                    new_adj = {n['id']: [] for n in nodeJson}
+                    for edge in edgeJson:
+                        if is_new_edge(edge):
+                            src, dst = edge['from'], edge['to']
+                            new_adj[src].append(dst)
+                            new_adj[dst].append(src)  # 无向图处理
+                    return new_adj
+
+                # 在chart处理分支中添加
+                if result.plan.algorithm.type in ['optimize', 'predict']:
+                    # 构建新邻接表
+                    new_adj = build_new_adjacency(nodeJson, edgeJson)
+                    
+                    # 并行计算新链路
+                    with ThreadPoolExecutor(max_workers=8) as executor:
+                        futures = [executor.submit(parallel_chain_search, s_id, new_adj, type_map) 
+                                for s_id in s_nodes]
+                        new_chains = []
+                        for future in futures:
+                            new_chains.extend(future.result())
+                    
+                    # 统计新链路
+                    new_chains_num = len(new_chains)
+                    new_len_dist = [0]*5
+                    for l in new_chains:
+                        new_len_dist[min(l-1,4)] += 1
+                    new_avg_len = round(sum(new_chains)/new_chains_num,2) if new_chains_num else 0
+                    
+                    data['newChains'] = {
+                        'num': new_chains_num,
+                        'averageLength': new_avg_len,
+                        'numByLength': new_len_dist
+                    }                  
             return success(data=data)
 
         # 如果已经生成过图数据,则直接生成新的验证码
@@ -260,10 +282,13 @@ class GenerateGraph(APIView):
                     if not 'from' in edge or not 'to' in edge:
                         return failed(message="边文件存在问题")
                     # 对于演化预测算法,边的属性中应有新旧区分
-                    missingLabel = True
+                    
                     for meta in edge['meta']:
                         if 'predict' in meta and meta['predict'] in ['new', 'old']:
                             missingLabel = False
+                
+                    # 暂时取消标签检测,用于生成图
+                    missingLabel = False
                     if missingLabel:
                         return failed(message="无演化预测标签")
             graph = Graph.objects.createFromResult(result)

BIN
backend/api/models/__pycache__/file.cpython-310.pyc


BIN
backend/api/models/__pycache__/graph.cpython-310.pyc


+ 20 - 19
backend/api/models/file.py

@@ -477,30 +477,31 @@ class File(models.Model):
             nodeFile = csv.reader(open(path2, 'r'))
             # 针对csv文件的检测
             if self.type == 'csv':
-                nodes = []
-                edges = []
+                # 节点用集合存储
+                nodes_set = set()
                 for line in nodeFile:
-                    if not len(line) >= 2:
-                        logger.error("check file illegal failed node len >= 2")
-                        return False
-                    if not line[0].isdigit():
-                        logger.error("check file illegal failed node id wrong")
+                    if len(line) < 2 or not line[0].isdigit():
+                        logger.error("节点格式错误")
                         return False
-                    nodes.append(line[0])
-                for line in edgeFile:
-                    if not len(line) == 2:
-                        logger.error("check file illegal failed edge len =2")
+                    nodes_set.add(line[0])
+                edges_set = set()
+                for line_num, line in enumerate(edgeFile, 1):
+                    if len(line) != 2:
+                        logger.error(f"Line {line_num}: Edge must have 2 nodes")
                         return False
-                    if line[0] not in nodes or line[1] not in nodes:
-                        logger.error("check file illegal failed edge id not exist")
+                    u, v = line[0], line[1]
+                     # 检查节点是否存在
+                    if u not in nodes_set or v not in nodes_set:
+                        logger.error(f"边 {line_num}存在错误: 节点不存在")
                         return False
-                    if [line[0], line[1]] not in edges and [line[1], line[0]] not in edges:
-                        edges.append([line[0], line[1]])
-                    else:
-                        # 将图视为无向图,同一条边的正反算作重复
-                        # 直接去除重复边
-                        logger.error("check file illegal failed edge duplicate edge")
+                     # 无向边标准化:按排序存储元组
+                    edge = (u, v) if u <= v else (v, u)
+                    # 检查重复边
+                    if edge in edges_set:
+                        logger.error(f"边 {line_num}存在错误: 重复边 {u}-{v}")
                         return False
+                    edges_set.add(edge)
+                
                 return True
     
     def toJson(self, request=None):

+ 80 - 45
backend/api/models/graph.py

@@ -314,11 +314,19 @@ class GraphManager(models.Manager):
 
         '''定义函数''' 
         def generate_3d_coordinates(nodes, communities):
-            """ 计算三维空间坐标布局 """
-            # 参数设置
-            D_LAYER_RADIUS = 1.0    # D类型节点分布半径
-            I_S_LAYER_RADIUS = 3.0  # I/S类型节点分布半径, 因为D类靠中心,I/S类靠外围
-            COMMUNITY_SPACING = 8.0 # 社团间距
+            """ 计算三维空间坐标布局(动态调整版) """
+            # 动态参数计算
+            total_nodes = len(nodes)
+            num_communities = len(communities)
+            
+            # 基础参数
+            BASE_NODE_SPACE = 0.5  # 单个节点基础空间需求
+            D_LAYER_MULTIPLIER = 1.2  # D层紧凑系数
+            I_S_LAYER_MULTIPLIER = 2  # I/S层扩展系数
+            
+            # 动态调整参数
+            community_spacing = 8.0 * (num_communities**0.5)  # 社区间距与社区数量正相关
+            density_factor = (total_nodes / 1000)**0.5  # 密度调整系数
             
             # 初始化坐标字典
             coords = {}
@@ -326,75 +334,102 @@ class GraphManager(models.Manager):
             # 为每个社团分配空间区域
             comm_centers = {}
             for i, (comm_id, members) in enumerate(communities.items()):
-                # 在三维空间分配不同象限
-                angle = i * 2*np.pi / len(communities)
+                # 计算社区半径(基于社区规模)
+                comm_radius = 1.2 * (len(members)**0.33) * density_factor
+                
+                # 三维空间分布
+                theta = i * 2*np.pi / num_communities
+                phi = np.pi * (i % num_communities) / num_communities
+                
                 comm_centers[comm_id] = (
-                    COMMUNITY_SPACING * np.cos(angle),
-                    COMMUNITY_SPACING * np.sin(angle),
-                    COMMUNITY_SPACING * (i % 2)  # Z轴分层
+                    community_spacing * np.cos(theta) * (1 + 0.2*np.sin(phi)),
+                    community_spacing * np.sin(theta) * (1 + 0.2*np.cos(phi)),
+                    community_spacing * (i % 2) * 0.5 * density_factor  # Z轴分层
                 )
-            
+
             # 为每个节点生成坐标
             for node in nodes:
                 node_id = node['id']
                 comm_id = partition[node_id]
+                members = communities[comm_id]
                 base_x, base_y, base_z = comm_centers[comm_id]
                 
-                # 根据节点类型确定分布层
+                # 根据社区规模调整分布参数
+                comm_density = len(members) / total_nodes
+                node_space = BASE_NODE_SPACE / (comm_density + 0.1)
+                
                 if node['type'] == 'D':
-                    # D类型节点紧密分布在社团中心附近
-                    r = D_LAYER_RADIUS * np.random.rand()
-                    theta = np.random.uniform(0, 2*np.pi)
-                    phi = np.random.uniform(0, np.pi)
-                    
-                    x = base_x + r * np.sin(phi) * np.cos(theta)
-                    y = base_y + r * np.sin(phi) * np.sin(theta)
-                    z = base_z + r * np.cos(phi)
+                    # D类型节点使用高斯分布
+                    r = D_LAYER_MULTIPLIER * node_space * np.random.randn()
+                    theta = np.random.vonmises(0, 2)
+                    z_offset = np.random.normal(0, 0.3)
                 else:
-                    # I/S类型节点分布在更大半径的球面上
-                    r = I_S_LAYER_RADIUS
+                    # I/S类型使用均匀球面分布
+                    r = I_S_LAYER_MULTIPLIER * node_space * (1 + 0.5*np.random.rand())
                     theta = np.random.uniform(0, 2*np.pi)
-                    phi = np.random.uniform(0, np.pi)
-                    
-                    x = base_x + r * np.sin(phi) * np.cos(theta)
-                    y = base_y + r * np.sin(phi) * np.sin(theta)
-                    z = base_z + r * np.cos(phi)
+                    z_offset = np.random.uniform(-1, 1)
+                
+                # 球坐标转笛卡尔坐标
+                phi = np.arccos(2*np.random.rand() - 1)
+                x = base_x + r * np.sin(phi) * np.cos(theta)
+                y = base_y + r * np.sin(phi) * np.sin(theta)
+                z = base_z + 0.5 * z_offset * density_factor
                 
                 coords[node_id] = (x, y, z)
             
             return coords
         
         def optimize_overlap(coords, iterations=100):
-            """ 使用斥力优化减少节点重叠 """
+            """ 改进的斥力优化算法 """
             nodes = list(coords.keys())
             positions = np.array(list(coords.values()))
+            total_nodes = len(nodes)
             
-            # 设置优化参数
-            repulsion = 0.1  # 斥力强度
-            min_dist = 0.5   # 最小间距
+            # 动态参数
+            min_dist = 1 * (total_nodes/1000)**(-0.3)  # 最小间距随密度调整
+            repulsion = 0.2 * (total_nodes/500)  # 斥力强度随规模调整
+            cooling_factor = 0.95  # 模拟退火冷却系数
             
-            for _ in range(iterations):
-                # 计算所有节点间距
-                diffs = positions[:, np.newaxis, :] - positions[np.newaxis, :, :]
-                dists = np.linalg.norm(diffs, axis=2)
+            # 添加随机扰动
+            positions += np.random.normal(0, 0.1, positions.shape)
+            
+            for i in range(iterations):
+                # 计算节点间距
+                deltas = positions[:, np.newaxis, :] - positions[np.newaxis, :, :]
+                dists = np.linalg.norm(deltas, axis=2)
                 
                 # 避免自比较
                 np.fill_diagonal(dists, np.inf)
                 
-                # 计算斥力
-                with np.errstate(divide='ignore'):
-                    forces = repulsion / (dists**2)
-                forces[dists > min_dist] = 0
+                # 动态调整参数
+                current_repulsion = repulsion * (cooling_factor)**(i)
+                current_min_dist = min_dist * (1 + 0.1*i/iterations)  # 逐步收紧间距
+                
+                # 计算斥力矩阵
+                with np.errstate(divide='ignore', invalid='ignore'):
+                    forces = np.where(
+                        dists < current_min_dist,
+                        current_repulsion / (dists**2 + 1e-9),
+                        0
+                    )
+                
+                # 计算合力
+                force_vectors = (deltas / (dists[:, :, np.newaxis] + 1e-9)) * forces[:, :, np.newaxis]
+                movement = np.sum(force_vectors, axis=1)
+                
+                # 应用移动限制
+                max_move = 0.5 * (1 + i/iterations)  # 逐步减小最大移动步长
+                movement = np.clip(movement, -max_move, max_move)
                 
-                # 更新位置
-                movement = np.sum(forces[:, :, np.newaxis] * diffs, axis=1)
                 positions += movement
                 
-            # 归一化到0-10范围
-            scaler = MinMaxScaler(feature_range=(0, 10))
-            positions = scaler.fit_transform(positions)
+            # 自适应归一化
+            pos_min = np.min(positions, axis=0)
+            pos_max = np.max(positions, axis=0)
+            range_size = pos_max - pos_min
+            scale = 10 / np.max(range_size)  # 保持比例缩放
             
-            return {node: tuple(pos) for node, pos in zip(nodes, positions)}
+            return {node: tuple((pos - pos_min) * scale) for node, pos in zip(nodes, positions)}
         '''结束定义'''
         # 生成初始坐标
         initial_coords = generate_3d_coordinates(nodeJson, communities)

BIN
backend/db.sqlite3


BIN
scheduler/algo1Folder/测试输出/边集(教师模型测试).xlsx


BIN
scheduler/algo1Folder/测试输出/边集(教师测试删除).xlsx


BIN
scheduler/algo1Folder/测试输出/边集(教师预测).xlsx


BIN
scheduler/algo3Folder/测试输出/边集(测试).xlsx


BIN
scheduler/algo3Folder/测试输出/边集(预测).xlsx


+ 21 - 10
viewer/src/views/dashoard/threeDView.vue

@@ -138,15 +138,17 @@ const TYPE_COLORS = {
 }
 // 图例数据
 const legendItems = reactive([
-  { type: 'S', label: '侦察节点', color: '#FF6666' },
-  { type: 'D', label: '决策节点', color: '#FFFF00' },
-  { type: 'I', label: '打击节点', color: '#0066CC' }
+    { type: 'S', label: '侦察节点', color: '#FF6666' },
+    { type: 'D', label: '决策节点', color: '#FFFF00' },
+    { type: 'I', label: '打击节点', color: '#0066CC' }
 ])
 
 // 用于透明化的样式
 const STYLE = {
+    HIGHLIGHT_NODE_OPACITY: 1.0,
+    HIGHLIGHT_EDGE_OPACITY: 0.1,
     HIGHLIGHT_OPACITY: 1.0,
-    DIM_OPACITY: 0.05,
+    DIM_OPACITY: 0.02,
     NODE_COLOR: 0x0000FF,
     EDGE_COLOR: 0x800080,
     HOVER_NODE_COLOR: 0x00FF00,
@@ -161,15 +163,22 @@ const createEdges = () => {
         nodesGroup.value.group.children = [];
     }
 
+    // 动态计算节点尺寸
+    const nodeCount = nodes.value.length;
+    // 使用对数缩放保证数量级差异时的可视性
+    const baseRadius = 0.5;  // 基础半径(节点数=1时的尺寸)
+    const minRadius = 0.03;  // 最小半径限制
+    const radius = Math.max(baseRadius / (1 + Math.log(nodeCount + 10)), minRadius);
+
     // 创建节点
     nodes.value.forEach(node => {
-        const geometry = new THREE.SphereGeometry(0.2)
+        const geometry = new THREE.SphereGeometry(radius)
         // 根据类型获取颜色
         const typeColor = TYPE_COLORS[node.type] || STYLE.NODE_COLOR
         const material = new THREE.MeshBasicMaterial({
             color: typeColor,  // 使用类型颜色
             transparent: true,
-            opacity: STYLE.HIGHLIGHT_OPACITY,
+            opacity: STYLE.HIGHLIGHT_NODE_OPACITY,
         })
         const sphere = new THREE.Mesh(geometry, material)
         sphere.position.set(...Object.values(node.coordinates))
@@ -184,14 +193,16 @@ const createEdges = () => {
     // 创建边(添加鼠标事件支持)
     lines.value.forEach(line => scene.value.scene.remove(line))
     lines.value = []
+    const opacity = Math.min(1, Math.max(0.05, 1 - nodeCount/500))
     edges.value.forEach(edge => {
         const points = edge.map(p => new THREE.Vector3(...Object.values(p.coordinates)))
         const geometry = new THREE.BufferGeometry().setFromPoints(points)
         const material = new THREE.LineBasicMaterial({
             color: STYLE.EDGE_COLOR,
-            linewidth: 2,
+            linewidth: Math.max(1, 2 - nodeCount/100),
             transparent: true,
-            opacity: STYLE.HIGHLIGHT_OPACITY
+            opacity: STYLE.HIGHLIGHT_EDGE_OPACITY,
+            depthTest: false
         })
         const line = new THREE.Line(geometry, material)
         line.userData = {
@@ -223,11 +234,11 @@ const handleHover = (event) => {
         if (!selectedNodes.value.has(child.userData.id)) {
             child.material.color.set(child.userData.originalColor) // 恢复原始类型颜色
         }
-        child.material.opacity = STYLE.HIGHLIGHT_OPACITY
+        child.material.opacity = STYLE.HIGHLIGHT_NODE_OPACITY
     })
     lines.value.forEach(line => {
         line.material.color.set(STYLE.EDGE_COLOR)
-        line.material.opacity = STYLE.HIGHLIGHT_OPACITY
+        line.material.opacity = STYLE.HIGHLIGHT_EDGE_OPACITY
     })
 
     // 计算标准化设备坐标