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)
问题原因及修复方案
核心问题
- 帧数据引用冲突:直接引用
fig.data创建帧,导致所有帧共享同一组数据对象,后续修改会覆盖之前的帧内容。 - 数据结构冗余混乱:初始化时添加空轨迹,之后又循环添加单个边/节点的轨迹,导致轨迹数量过多,帧无法正确匹配更新。
- 帧与滑块步长不匹配:模拟
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
相关产品推荐
相关产品推荐

