基于Python的动态节点网络可视化:随边权调整节点位置
问题
我有一个循环迭代的网络,每次迭代时边的权重会更新:部分边权重增大以强化节点连接,部分边权重减小以弱化关联。边权重存储为G[node1][node2]["weight"]。
想要实现这样的可视化:每次迭代中,权重相较于上一次增大的边,两端节点相互靠近;权重变弱的边,两端节点相互远离。要求节点位置默认保持静态,只有关联边权重变化时才移动,最终将所有帧合成GIF展示动态过程。
目前用圆形布局设置了静态节点位置,边会随权重变色,但无法实现节点的动态靠近/远离效果。尝试过修改布局函数,但节点出现无规则分散,未达预期。
现有静态绘图代码:
def draw_graph_to_file(G: nx.Graph, iteration: int): cleaned_labels = {node: node.split('_')[1] for node in G.nodes()} node_colors = ['pink' if 'S++' in node else 'lightblue' for node in G.nodes()] edge_weights = nx.get_edge_attributes(G, 'weight') edge_widths = [0.1 * G[node1][node2]['weight'] for node1, node2 in G.edges()] edge_colors = ['red' if weight < 10 else 'green' if weight > 10 else 'blue' for weight in edge_weights.values()] nodePos = nx.get_node_attributes(G, 'pos') nx.draw(G, pos=nodePos, with_labels=True, node_color=node_colors,labels=cleaned_labels, edge_color=edge_colors, width=edge_widths) plt.title(f"Random Network - {iteration}") # 保存图片到目录 create_folder_if_not_exists(f"generatedImages/Network{networkNum}") plt.savefig(f"generatedImages/Network{networkNum}/Random Network - {iteration}.png") plt.clf() return
尝试过的无效修改(节点无规则分散):
def draw_graph_to_file(G: nx.Graph, iteration: int): cleaned_labels = {node: node.split('_')[1] for node in G.nodes()} node_colors = ['pink' if 'S++' in node else 'lightblue' for node in G.nodes()] edge_weights = nx.get_edge_attributes(G, 'weight') edge_widths = [0.1 * G[node1][node2]['weight'] for node1, node2 in G.edges()] edge_colors = ['red' if weight < 10 else 'green' if weight > 10 else 'blue' for weight in edge_weights.values()] positions = nx.spring_layout(G) adjust_node_positions(G, positions) nx.draw(G, pos=positions, with_labels=True, node_color=node_colors, labels=cleaned_labels, edge_color=edge_colors, width=edge_widths, node_size=2000) plt.title(f"Random Network - {iteration}") create_folder_if_not_exists(f"generatedImages/Network{networkNum}") plt.savefig(f"generatedImages/Network{networkNum}/Random Network - {iteration}.png") plt.clf() return def adjust_node_positions(G, positions, initial_positions): new_positions = positions.copy() for edge in G.edges(): u, v = edge weight = G[u][v].get('weight', 10) delta = initial_positions[v] - initial_positions[u] displacement = delta / np.linalg.norm(delta) * weight new_positions[u] += displacement new_positions[v] -= displacement return new_positions
解决方案
核心思路是:基于初始位置,根据边权重的变化量而非绝对权重调整节点位置,同时累计每次迭代的位移,确保节点只在关联边权重变化时移动。
关键步骤
- 保存初始位置与历史权重:迭代开始前,为节点存初始圆形布局位置,为每条边存初始权重,后续迭代记录权重变化。
- 计算权重变化量:对每条边,计算当前权重与上一次的差值,差值正表示权重增大(节点靠近),差值负表示权重减小(节点远离)。
- 累计位移调整位置:根据权重变化量计算位移,基于上一次的节点位置累计调整,而非重置到初始位置。
修改后的完整代码
import networkx as nx import matplotlib.pyplot as plt import numpy as np import os import imageio # 初始化图的初始位置与历史权重 def init_graph(G): # 设置初始圆形布局位置 initial_pos = nx.circular_layout(G) G.graph['initial_pos'] = initial_pos # 保存当前位置(初始等于初始位置) G.graph['current_pos'] = {node: pos.copy() for node, pos in initial_pos.items()} # 保存初始权重作为上一次迭代的权重 for u, v in G.edges(): G[u][v]['prev_weight'] = G[u][v]['weight'] # 根据权重变化调整节点位置 def adjust_node_positions(G): current_pos = G.graph['current_pos'] initial_pos = G.graph['initial_pos'] scale_factor = 0.01 # 控制位移幅度,避免移动过度 # 复制当前位置,避免遍历边时直接修改影响计算 new_pos = {node: pos.copy() for node, pos in current_pos.items()} for u, v in G.edges(): current_weight = G[u][v]['weight'] prev_weight = G[u][v]['prev_weight'] delta_weight = current_weight - prev_weight if delta_weight == 0: continue # 权重无变化,跳过调整 # 获取初始位置的方向向量 pos_u_initial = initial_pos[u] pos_v_initial = initial_pos[v] direction = pos_v_initial - pos_u_initial norm = np.linalg.norm(direction) if norm == 0: continue # 避免除以0 unit_direction = direction / norm # 计算位移:权重增大则节点相互靠近,减小则相互远离 displacement = unit_direction * delta_weight * scale_factor new_pos[u] += displacement new_pos[v] -= displacement # 更新当前边的历史权重 G[u][v]['prev_weight'] = current_weight # 更新图的当前位置 G.graph['current_pos'] = new_pos return new_pos # 绘图并保存帧 def draw_graph_to_file(G: nx.Graph, iteration: int): cleaned_labels = {node: node.split('_')[1] for node in G.nodes()} node_colors = ['pink' if 'S++' in node else 'lightblue' for node in G.nodes()] edge_weights = nx.get_edge_attributes(G, 'weight') edge_widths = [0.1 * G[node1][node2]['weight'] for node1, node2 in G.edges()] edge_colors = ['red' if weight < 10 else 'green' if weight > 10 else 'blue' for weight in edge_weights.values()] # 获取调整后的位置 positions = adjust_node_positions(G) nx.draw(G, pos=positions, with_labels=True, node_color=node_colors, labels=cleaned_labels, edge_color=edge_colors, width=edge_widths) plt.title(f"Random Network - {iteration}") # 创建目录并保存图片 img_dir = f"generatedImages/Network{networkNum}" os.makedirs(img_dir, exist_ok=True) plt.savefig(f"{img_dir}/Random Network - {iteration}.png") plt.clf() # 合成GIF def create_gif(image_dir, output_gif): images = [] # 按迭代顺序读取图片 for filename in sorted(os.listdir(image_dir), key=lambda x: int(x.split('-')[1].strip().split('.')[0])): if filename.endswith('.png'): images.append(imageio.imread(os.path.join(image_dir, filename))) imageio.mimsave(output_gif, images, duration=0.5) # duration控制帧切换间隔
注意事项
- 缩放系数调整:
scale_factor需根据网络规模和权重变化范围修改,确保节点移动幅度适中,不会跑出画布。 - 边界控制:若担心节点超出画布,可在调整位置后添加坐标限制,比如将节点坐标约束在
[0,1]区间内。 - 迭代流程:在循环迭代前需先调用
init_graph(G)初始化位置和权重,之后每次迭代调用draw_graph_to_file生成帧,最后用create_gif合成动态图。
内容的提问来源于stack exchange,提问作者Paco
相关产品推荐
相关产品推荐

