Plotly Dashboard中X轴标签无法实时更新的问题求助
问题:Dash实时图表X轴标签无法更新
我用以下代码监控TOUGH程序的输出文件,通过update_graph_live回调实时更新图表。为了让图表可读性更好,我会根据数据中的最大时间自动调整X轴的时间单位。现在数据能正常更新,但X轴标签始终停留在程序启动时的初始状态,哪怕我在回调里返回了新的图表对象。
我在Anaconda虚拟环境的Spyder里运行脚本,Dash调试器的计数器正常递增,说明回调在执行,但X轴标签就是不更新,这让我很困惑。
仪表盘效果:
调试器状态:
代码实现
import os import time import pandas as pd import numpy as np import plotly.graph_objs as go from dash import Dash, dcc, html from dash.dependencies import Input, Output, State from plotly.subplots import make_subplots # Create Dash app app = Dash(__name__) # Layout of the app app.layout = html.Div([ dcc.Input( id='file-path', type='text', placeholder='Enter the file path...', style={'width': '100%'} ), dcc.Graph(id='live-graph', animate=True), dcc.Interval( id='graph-update', interval=1000, # in milliseconds (1 seconds) n_intervals=0 ) ]) def load_data(file_path): try: data = pd.read_csv(file_path, header=0, skiprows=0, index_col=False) new_headers = [s.strip() for s in data.columns.values] # remove whitespaces data.columns = new_headers # Adjust the times in a legible unit max_time = data["TIME(S)"].max() if max_time > 2 * 365 * 24 * 3600: data["TIME(Years)"] = data["TIME(S)"] / (365 * 24 * 3600) time_col = "TIME(Years)" time_label = "Time (Years)" elif max_time > 2 * 30 * 24 * 3600: data["TIME(Months)"] = data["TIME(S)"] / (30 * 24 * 3600) time_col = "TIME(Months)" time_label = "Time (Months)" elif max_time > 2 * 7 * 24 * 3600: data["TIME(Weeks)"] = data["TIME(S)"] / (7 * 24 * 3600) time_col = "TIME(Weeks)" time_label = "Time (Weeks)" elif max_time > 1 * 24 * 3600: data["TIME(Days)"] = data["TIME(S)"] / (24 * 3600) time_col = "TIME(Days)" time_label = "Time (Days)" elif max_time > 0.5 * 3600: data["TIME(Hours)"] = data["TIME(S)"] / 3600 time_col = "TIME(Hours)" time_label = "Time (Hours)" elif max_time > 24: data["TIME(Minutes)"] = data["TIME(S)"] / 60 time_col = "TIME(Minutes)" time_label = "Time (Minutes)" else: time_col = "TIME(S)" time_label = "Time (Seconds)" print(time_label) data.iloc[:, 1:] = data.iloc[:, 1:].apply(pd.to_numeric) return data, time_col, time_label except Exception as e: print(f"Error loading data: {e}") return None, None, None @app.callback( Output('live-graph', 'figure'), [Input('graph-update', 'n_intervals')], [State('file-path', 'value')] ) def update_graph_live(n_intervals, file_path): if not file_path or not os.path.exists(file_path): return go.Figure() data, time_col, time_label = load_data(file_path) if data is None: return go.Figure() time = data[time_col] pressure = data["PRES"] temperature = data["TEMP"] saturation_gas = data["SAT_Gas"] saturation_aqu = data["SAT_Aqu"] time_diff = data["TIME(S)"].diff().dropna() fig = make_subplots(rows=2, cols=3, subplot_titles=("Pressure vs Time", "Molar Fractions vs Time", "Time Difference vs Time", "Temperature vs Time", "Saturation vs Time")) fig.add_trace(go.Scatter(x=time, y=pressure, mode='lines', name='Pressure (Pa)', line=dict(color='blue')), row=1, col=1) fig.add_trace(go.Scatter(x=time, y=temperature, mode='lines', name='Temperature (°C)', line=dict(color='red')), row=2, col=1) fig.add_trace(go.Scatter(x=time, y=saturation_gas, mode='lines', name='Gas Phase Saturation', line=dict(color='green')), row=2, col=2) fig.add_trace(go.Scatter(x=time, y=saturation_aqu, mode='lines', name='Aqueous Phase Saturation', line=dict(color='blue')), row=2, col=2) # just sorting out some colouring and styling for legibility for col in data.columns[6:]: color = 'black' linestyle = 'solid' if 'H2' in col: color = 'green' elif 'CH4' in col: color = 'lightblue' elif 'water' in col: color = 'blue' if 'Gas' in col: linestyle = 'solid' elif 'Aqu' in col: linestyle = 'dash' if 'TIME' in col: continue fig.add_trace(go.Scatter(x=time, y=data[col], mode='lines', name=col, line=dict(color=color, dash=linestyle)), row=1, col=2) fig.add_trace(go.Scatter(x=time.iloc[1:len(time_diff)+1], y=time_diff, mode='lines', name='Time Step Size', line=dict(color='purple')), row=1, col=3) # # PREVIOUS ATTEMPT # fig.update_layout( # title='Real-Time TOUGH Simulation Data', # showlegend=True, # yaxis3_type='log' # Setting the y-axis of the third subplot to log scale # ) # PREVIOUS ATTEMPT # # Update x-axis labels individually # fig.update_xaxes(title_text=time_label, row=1, col=1) # fig.update_xaxes(title_text=time_label, row=1, col=2) # fig.update_xaxes(title_text=time_label, row=1, col=3) # fig.update_xaxes(title_text=time_label, row=2, col=1) # fig.update_xaxes(title_text=time_label, row=2, col=2) # CURRENT ATTEMPT fig.update_layout( title='Real-Time TOUGH Simulation Data', showlegend=True, yaxis3_type='log', # Setting the y-axis of the third subplot to log scale xaxis_title_text=time_label+str(n_intervals), # Update x-axis # labels. I add the interval counter for debugging xaxis2_title_text=time_label, xaxis3_title_text=time_label, xaxis4_title_text=time_label, xaxis5_title_text=time_label ) print('Update> ',time_label) # just a sanity check which demonstrates that the time # conversion and label change is being read correctly - which it is fig.update_yaxes(title_text='Pressure (Pa)', row=1, col=1) fig.update_yaxes(title_text='Molar Fraction (-)', row=1, col=2) fig.update_yaxes(title_text='Timestep (s)', row=1, col=3) fig.update_yaxes(title_text='Temperature (°C)', row=2, col=1) fig.update_yaxes(title_text= 'Saturation (-)', row=2, col=2) return fig if __name__ == '__main__': app.run_server(debug=True, port=8050)
解决方案
问题根源
dcc.Graph组件设置的animate=True是核心问题。启用动画时,Dash会采用Plotly的过渡动画机制,优先复用旧图表的布局配置(如轴标签、刻度),仅更新数据部分,导致新生成的X轴标签无法覆盖旧值。
修复步骤
- 移除动画属性:将
dcc.Graph(id='live-graph', animate=True)改为dcc.Graph(id='live-graph'),关闭自动动画,让Dash每次完全替换新的图表对象。 - 可靠更新X轴标签:改用
update_xaxes按子图的行列位置逐个设置标签,这种方式比在update_layout中通过轴ID设置更不易出错。
修改后的关键代码
布局部分
dcc.Graph(id='live-graph'), # 移除animate=True
回调中的布局更新部分
替换原update_layout中的X轴标签设置,改用以下代码:
fig.update_layout( title='Real-Time TOUGH Simulation Data', showlegend=True, yaxis3_type='log' # 保留对数轴设置 ) # 逐个更新所有子图的X轴标签 fig.update_xaxes(title_text=time_label, row=1, col=1) fig.update_xaxes(title_text=time_label, row=1, col=2) fig.update_xaxes(title_text=time_label, row=1, col=3) fig.update_xaxes(title_text=time_label, row=2, col=1) fig.update_xaxes(title_text=time_label, row=2, col=2)
验证效果
修改后重启程序,当TOUGH输出文件的时间范围跨越单位阈值时,X轴标签会自动更新为对应的时间单位,同时数据也能正常实时刷新。
内容的提问来源于stack exchange,提问作者Sorade
相关产品推荐
相关产品推荐

