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

Plotly网络感染传播可视化滑块失效问题及解决方案咨询

问题:滑块控制的网络感染传播可视化无法更新

我用以下代码实现网络感染传播的可视化,但拖动滑块推进过程时,可视化效果不更新。需要实现支持滑块控制的分步感染扩散可视化,能查看不同时间步的感染区域。

原代码

import networkx as nx
import plotly.graph_objects as go
import random

def visualize_network(G, Nb_inf_init, HM, N, T):
    """
    Visualize the spread of an infection on a network graph using Plotly.

    Parameters:
        G (networkx.Graph): The network graph.
        Nb_inf_init (int): Initial number of infected nodes.
        HM (float): Infection probability.
        N (int): Total number of nodes in the graph.
        T (int): Number of time steps to simulate.

    """
    # Create initial infected nodes
    infected_nodes = set(random.sample(G.nodes(), Nb_inf_init))

    # Set initial node colors
    node_colors = ['red' if node in infected_nodes else 'blue' for node in G.nodes()]

    # Create initial network graph figure
    fig = go.Figure(data=[go.Scatter(x=[], y=[], mode='lines', line=dict(color='gray', width=1)),
                         go.Scatter(x=[], y=[], mode='markers', marker=dict(color=node_colors, size=10))],
                    layout=go.Layout(showlegend=False, hovermode='closest'))

    # Set initial positions
    pos = nx.spring_layout(G)

    # Update figure with initial node positions
    for edge in G.edges():
        x0, y0 = pos[edge[0]]
        x1, y1 = pos[edge[1]]
        fig.add_trace(go.Scatter(x=[x0, x1, None], y=[y0, y1, None], mode='lines', line=dict(color='gray', width=1)))

    for node in G.nodes():
        x, y = pos[node]
        fig.add_trace(go.Scatter(x=[x], y=[y], mode='markers', marker=dict(color='red' if node in infected_nodes else 'blue', size=10)))

    # Initialize frames list
    frames = []

    # Create frames for each time step
    for t in range(T):
        # Update infected nodes and colors
        node_colors = ['red' if node in infected_nodes else 'blue' for node in G.nodes()]

        # Update figure with new node colors
        fig.data[1].marker.color = node_colors

        # Append the updated figure to the frames list
        frames.append(go.Frame(data=fig.data))

        # Spread the infection
        new_infected_nodes = set()
        for node in infected_nodes:
            for neighbor in G.neighbors(node):
                if neighbor not in infected_nodes and random.random() < HM:
                    new_infected_nodes.add(neighbor)

        # Update infected nodes
        infected_nodes.update(new_infected_nodes)

    # Add frames to the figure
    fig.frames = frames

    # Set up slider steps
    slider_steps = []
    for t in range(T):
        slider_steps.append({'args': [[t], {'frame': {'duration': 500, 'redraw': True}, 'mode': 'immediate'}],
                             'label': t, 'method': 'animate'})

    # Set up slider
    fig.update_layout(updatemenus=[{'buttons': [{'args': [None, {'frame': {'duration': 0, 'redraw': False}, 'fromcurrent': True, 'transition': {'duration': 0}}],
                                                 'label': 'Play',
                                                 'method': 'animate'},
                                                {'args': [[None], {'frame': {'duration': 0, 'redraw': False}, 'mode': 'immediate'}],
                                                 'label': 'Pause',
                                                 'method': 'animate'}],
                                    'direction': 'left',
                                    'pad': {'r': 10, 't': 87},
                                    'showactive': False,
                                    'type': 'buttons',
                                    'x': 0.1,
                                    'xanchor': 'right',
                                    'y': 0,
                                    'yanchor': 'top'}],
                      sliders=[{'active': 0,
                                'currentvalue': {'font': {'size': 12}, 'prefix': 'Time: ', 'visible': True, 'xanchor': 'center'},
                                'transition': {'duration': 0},
                                'pad': {'b': 10, 't': 50},
                                'steps': slider_steps}])

    # Show the figure
    fig.show()

调用示例

# Example usage
N = 500
k = 20
G1 = nx.erdos_renyi_graph(N, k/N)
pos1 = nx.spring_layout(G1)
nx.draw_networkx_nodes(G1, pos1, alpha = 0.6, node_size=[2*i for i in list(dict(G1.degree).values())])
nx.draw_networkx_edges(G1, pos1, alpha=0.5)
plt.title("Erdos-Renyi")
plt.show()

# Set simulation parameters
Nb_inf_init = 10
HM = 0.1
N = len(G1.nodes)
T = 10

# Visualize the network graph and infection spread
visualize_network(G1, Nb_inf_init, HM, N, T)

问题原因及修复方案

核心问题

  1. 帧数据引用冲突:直接引用fig.data创建帧,导致所有帧共享同一组数据对象,后续修改会覆盖之前的帧内容。
  2. 数据结构冗余混乱:初始化时添加空轨迹,之后又循环添加单个边/节点的轨迹,导致轨迹数量过多,帧无法正确匹配更新。
  3. 帧与滑块步长不匹配:模拟T个时间步,但未包含初始状态,滑块无法展示完整的感染过程。

修复后的代码

import networkx as nx
import plotly.graph_objects as go
import random
import matplotlib.pyplot as plt

def visualize_network(G, Nb_inf_init, HM, T):
    """
    可视化网络上的感染传播过程,支持滑块分步查看
    参数:
        G (networkx.Graph): 网络图
        Nb_inf_init (int): 初始感染节点数
        HM (float): 感染概率
        T (int): 模拟时间步数
    """
    # 提前固定节点位置,避免帧切换时布局跳动
    pos = nx.spring_layout(G)
    node_list = list(G.nodes())
    
    # 初始化感染节点集合
    infected_nodes = set(random.sample(node_list, Nb_inf_init))
    
    # 生成节点颜色的工具函数
    def get_node_colors(infected_set):
        return ['red' if node in infected_set else 'blue' for node in node_list]
    
    # 统一生成所有边的坐标数据
    edge_x = []
    edge_y = []
    for edge in G.edges():
        x0, y0 = pos[edge[0]]
        x1, y1 = pos[edge[1]]
        edge_x.extend([x0, x1, None])
        edge_y.extend([y0, y1, None])
    
    # 创建边的轨迹(全程不变)
    edge_trace = go.Scatter(
        x=edge_x, y=edge_y,
        line=dict(width=1, color='gray'),
        hoverinfo='none',
        mode='lines')
    
    # 创建初始节点轨迹
    node_x = [pos[node][0] for node in node_list]
    node_y = [pos[node][1] for node in node_list]
    initial_colors = get_node_colors(infected_nodes)
    
    node_trace = go.Scatter(
        x=node_x, y=node_y,
        mode='markers',
        marker=dict(color=initial_colors, size=10),
        hoverinfo='text')
    
    # 初始化图表
    fig = go.Figure(
        data=[edge_trace, node_trace],
        layout=go.Layout(
            showlegend=False,
            hovermode='closest',
            margin=dict(b=20, l=5, r=5, t=40)
        )
    )
    
    # 生成所有时间步的帧(包含初始状态)
    frames = []
    frames.append(go.Frame(data=[edge_trace, go.Scatter(x=node_x, y=node_y, mode='markers', marker=dict(color=initial_colors, size=10))]))
    
    # 模拟感染传播,生成每一步的帧
    for t in range(T):
        # 计算新感染节点
        new_infected = set()
        for node in infected_nodes:
            for neighbor in G.neighbors(node):
                if neighbor not in infected_nodes and random.random() < HM:
                    new_infected.add(neighbor)
        infected_nodes.update(new_infected)
        
        # 创建当前步的节点轨迹(独立对象,避免引用冲突)
        current_colors = get_node_colors(infected_nodes)
        current_node_trace = go.Scatter(
            x=node_x, y=node_y,
            mode='markers',
            marker=dict(color=current_colors, size=10),
            hoverinfo='text')
        
        frames.append(go.Frame(data=[edge_trace, current_node_trace]))
    
    # 配置帧与滑块
    fig.frames = frames
    slider_steps = []
    for i in range(len(frames)):
        slider_steps.append({
            'args': [[i], {'frame': {'duration': 500, 'redraw': True}, 'mode': 'immediate'}],
            'label': f'Time {i}',
            'method': 'animate'
        })
    
    # 配置播放控件和滑块样式
    fig.update_layout(
        updatemenus=[{
            'buttons': [
                {
                    'args': [None, {'frame': {'duration': 500, 'redraw': True}, 'fromcurrent': True}],
                    'label': '播放',
                    'method': 'animate'
                },
                {
                    'args': [[None], {'frame': {'duration': 0, 'redraw': False}, 'mode': 'immediate'}],
                    'label': '暂停',
                    'method': 'animate'
                }
            ],
            'direction': 'left',
            'pad': {'r': 10, 't': 87},
            'showactive': False,
            'type': 'buttons',
            'x': 0.1,
            'xanchor': 'right',
            'y': 0,
            'yanchor': 'top'
        }],
        sliders=[{
            'active': 0,
            'currentvalue': {'font': {'size': 12}, 'prefix': '当前时间步: ', 'visible': True, 'xanchor': 'center'},
            'transition': {'duration': 0},
            'pad': {'b': 10, 't': 50},
            'steps': slider_steps
        }])
    
    fig.show()

# 调用示例
N = 500
k = 20
G1 = nx.erdos_renyi_graph(N, k/N)

# 展示静态网络
pos1 = nx.spring_layout(G1)
nx.draw_networkx_nodes(G1, pos1, alpha=0.6, node_size=[2*i for i in list(dict(G1.degree).values())])
nx.draw_networkx_edges(G1, pos1, alpha=0.5)
plt.title("Erdos-Renyi 网络")
plt.show()

# 模拟参数
Nb_inf_init = 10
HM = 0.1
T = 10

# 启动交互式可视化
visualize_network(G1, Nb_inf_init, HM, T)

修复要点说明

  • 固定节点位置:提前计算并复用节点坐标,避免帧切换时布局跳动。
  • 独立帧数据:每个帧创建新的节点轨迹对象,确保各帧数据互不干扰。
  • 完整状态覆盖:包含初始状态在内共T+1个帧,对应从时间步0到T的所有感染状态。
  • 简化数据结构:将所有边合并为单个轨迹,所有节点合并为单个轨迹,减少冗余并提升更新效率。
  • 滑块步长匹配:滑块步骤数与帧数量完全对应,拖动滑块可精准切换到目标时间步。

内容的提问来源于stack exchange,提问作者MartyMcFly

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 06:12:05