如何更新 Plotly Dashboard 中的轴标签? - python - SO中文参考 (2024)

我正在使用以下代码来监视另一个名为 TOUGH 的应用程序的输出文件。

我定义了一个图形组件并使用 update_graph_live() 回调函数更新其图形。为了使我的图表保持清晰,我调整了 x 轴时间单位。但是,尽管数据似乎更新正确,但 x 轴标签仍保持应用程序最初启动时的状态,尽管我将数字作为回调函数的一部分返回。我在 Anaconda 虚拟环境中从 Spyder IDE 运行脚本。

当我实际检查 Dash 调试器并看到我的调试计数器实际上在仪表板上很好地递增时,我又摸不着头脑了,但不知何故该数字没有更新。

如何更新 Plotly Dashboard 中的轴标签? - python - SO中文参考 (1)

非常欢迎任何帮助。

破折号如下所示:如何更新 Plotly Dashboard 中的轴标签? - python - SO中文参考 (2)

import osimport timeimport pandas as pdimport numpy as npimport plotly.graph_objs as gofrom dash import Dash, dcc, htmlfrom dash.dependencies import Input, Output, Statefrom plotly.subplots import make_subplots# Create Dash appapp = Dash(__name__)# Layout of the appapp.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 figif __name__ == '__main__': app.run_server(debug=True, port=8050)

我过去也遇到过类似的问题,这是由于在图形组件上设置

animate=True

造成的:更新图形时,数据更新已正确应用,但由于某种原因,布局更新未正确应用。顺便说一下,

animate

选项仍处于测试阶段:

animate
(布尔值;默认
False
):Beta - 如果
True
,则在之间设置动画使用plotly.js 的 animate 函数进行更新。

解决此问题的一种方法是使用 javascript/Plotly.js “手动”应用缺少的布局更新:我们可以使用

MutationObserver

在将绘图 div 加载到 DOM 后立即获取它(有没有 Dash/Plotly 事件来轻松完成此操作),一旦元素准备就绪,我们就可以在其上注册一个

plotly_animated

处理程序,以便能够响应图形更新,即通过使用

Plotly.relayout

应用缺少的属性功能。

在您的

assets

文件夹中创建一个.js文件(@参见添加静态资源),使用以下代码:

window.addEventListener('load', () => { // Root node to be observed for mutations const root = document.getElementById('_dash-app-content'); // Observe root's children as they are added to the DOM new MutationObserver((mutationRecords, observer) => { for (const record of mutationRecords) { const nodes = record.addedNodes; if (!nodes.length) continue; if ([...nodes].some(node => node.className === 'plot-container plotly')) { // `gd` is the Plotly graph div (which is now fully loaded) const gd = record.target; // Register the plotly handler that will apply the missing layout updates // after each animation. Note the `gd.layout` object is updated properly // but the updates are not applied to the plot until we call `Plotly.relayout` gd.on('plotly_animated', () => { Plotly.relayout(gd, { 'xaxis.title.text': gd.layout.xaxis.title.text, 'xaxis2.title.text': gd.layout.xaxis2.title.text, 'xaxis3.title.text': gd.layout.xaxis3.title.text, 'xaxis4.title.text': gd.layout.xaxis4.title.text, 'xaxis5.title.text': gd.layout.xaxis5.title.text }); }); observer.disconnect(); break; } } }).observe(root, { childList: true, subtree: true });});
如何更新 Plotly Dashboard 中的轴标签? - python - SO中文参考 (2024)
Top Articles
Latest Posts
Article information

Author: Nicola Considine CPA

Last Updated:

Views: 5911

Rating: 4.9 / 5 (49 voted)

Reviews: 88% of readers found this page helpful

Author information

Name: Nicola Considine CPA

Birthday: 1993-02-26

Address: 3809 Clinton Inlet, East Aleisha, UT 46318-2392

Phone: +2681424145499

Job: Government Technician

Hobby: Calligraphy, Lego building, Worldbuilding, Shooting, Bird watching, Shopping, Cooking

Introduction: My name is Nicola Considine CPA, I am a determined, witty, powerful, brainy, open, smiling, proud person who loves writing and wants to share my knowledge and understanding with you.