You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 01:57:01