I am using the following code to monitor an output file from another application named TOUGH.
I define a graph component and update its figure using the update_graph_live() call back function. In order for my graphs to remain legible, I adjust the x-axis time units. But although the data appears to update correctly, the x-axis labels remain what they were when the app is initially launched, despite me returning the figure as part of the callback function. I run the script from Spyder IDE in an Anaconda virtual env.
Any help would be most welcome.
Here is what the dash looks like:
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, # Update x-axis labels
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)