dashboard-test / app.py
mijtsma3's picture
Added data of B747 scenario
3828253
import pandas as pd
import dash
from dash import dcc, html
import webbrowser
from src.viz import Visualizer as viz
import plotly.graph_objects as go
import os
import datetime
import plotly.express as px
import json
start_time = 0#240
end_time = 1600#600
start_date = datetime.datetime(2024, 6, 7, 12, 0, 0)
end_date = start_date + datetime.timedelta(seconds=end_time)
# Initialize the Dash app
app = dash.Dash(__name__, suppress_callback_exceptions=True)
# Global dictionary to store data
data_store = {}
def parse_action_trace(path, exclude = []):
"""
Parses action data from CSV files into a dictionary.
This function reads CSV files specified in a dictionary where each key-value pair
corresponds to an condition ID and its associated file. It extracts columns related to
time, action name, agent name, action end time, and attributes from each file and
stores them in a nested dictionary structure.
Args:
files_dict (dict): A dictionary where keys are action IDs and values are file paths.
Returns:
dict: A dictionary where each key is an condition ID and the value is another dictionary
containing parsed data from the corresponding CSV file.
"""
df = pd.read_csv(path, engine = "python")
# Convert to events list for timeline plotting
events = []
for i in range(len(df['time'])):
if not any(excl in df['actionName'][i] for excl in exclude):
event = {
'agent': df['agentName'][i],
'name': df['actionName'][i],
'start': start_date + datetime.timedelta(seconds=df['time'][i]),
'end': start_date + datetime.timedelta(seconds=df['time'][i] + max(df['actionEndTime'][i], 2)),
'duration': max(df['actionEndTime'][i], 2),
'attributes': df['attributes'][i]
}
events.append(event)
return events
def parse_all_data(path):
# Get the list of subfolders in the 'data' folder
subfolders = [f.name for f in os.scandir(path) if f.is_dir()]
# Load all data at the start of the program
# Load the CSV files into pandas DataFrames from the selected subfolder
data = {}
exclude = ['Detect_crash','Detect_conflict','Flight_Dynamics','Reroute_flight','Show_radar','Direct_to_waypoint','Change_heading','fly']
for subfolder in subfolders:
data[subfolder] = {
'wnd_action_trace': pd.read_csv(f'{path}/{subfolder}/actionTrace_Agent_PIC Blaze.csv'),
'all_action_trace': parse_action_trace(f'{path}/{subfolder}/actionTrace.csv', exclude),
}
data[subfolder]['aircraft_data'] = {}
for file in os.listdir(f'{path}/{subfolder}'):
if file.endswith('_acstate.csv'):
aircraft_id = file.split('_')[0]
data[subfolder]['aircraft_data'][aircraft_id] = pd.read_csv(f'{path}/{subfolder}/{file}')
subfolder_labels = [{'label': i, 'value': i} for i in subfolders]
return subfolder_labels, data
# Create the layout for the app
app.layout = html.Div([
html.Div([
html.Div("Input the relative path to your results folder, then click 'Submit':", id='text1'),
dcc.Input(
id='path-input',
value='data',
type='text',
placeholder='Type your path... For example, ../wmc5.1/Scenario/AAMv2/Results',
style={'width': '90%'} # Make the input wider
),
html.Button('Submit', id='submit-button', n_clicks=0),
# Create a dropdown menu with the subfolders as options
html.Div("Once all subfolders are loaded (representing the different conditions you ran in WMC), you can select which one to plot:", id='text2'),
dcc.Dropdown(
id='subfolder-dropdown',
value=None #subfolders[0] # Default value
)
], style={'width': '50%', 'border': '1px solid gray'}),
html.Div([
html.Div([
dcc.Graph(id='map'), # Second graph
dcc.Dropdown(
id='variable-dropdown',
# options=[{'label': i, 'value': i} for i in subfolders],
value=None #subfolders[0] # Default value
),
dcc.Graph(id='altitude-series') # Third graph
], style={'width': '39%', 'display': 'inline-block', 'border': '1px solid gray'}),
html.Div([
dcc.Tabs(id='tabs-example-1', value='tab-1', children=[
dcc.Tab(label='Traditional Action Trace', value='tab-1'),
dcc.Tab(label='What`s Next Diagram', value='tab-2'),
]),
html.Div(id='tabs-example-content-1'),
html.Div([dcc.Graph(id='timeline')], id='tab-1-content', style={'display': 'none'}), # Fourth graph
html.Div([dcc.Graph(id='time-series')], id='tab-2-content', style={'display': 'none'}), # First graph
], style={'width': '59%', 'display': 'inline-block', 'border': '1px solid gray'})
], style={'display': 'flex'}),
html.Div(
dcc.Slider(
id='time-slider',
min=start_time,
max=end_time,
value=start_time+100,
marks={str(time): str(time) for time in range(start_time, end_time+1, 50)},
updatemode='drag'
),
style={'width': '100%'} # Set the width of the div containing the slider to 80% of the container
)
])
@app.callback(
dash.dependencies.Output('tab-1-content', 'style'),
dash.dependencies.Output('tab-2-content', 'style'),
dash.dependencies.Input('tabs-example-1', 'value')
)
def render_content(tab):
if tab == 'tab-1':
return {'display': 'block'}, {'display': 'none'}
elif tab == 'tab-2':
return {'display': 'none'}, {'display': 'block'}
# Load data when the app starts and store it in the hidden div
@app.callback(
dash.dependencies.Output('subfolder-dropdown', 'options'),
dash.dependencies.Output('variable-dropdown', 'options'),
[dash.dependencies.Input('submit-button', 'n_clicks')],
[dash.dependencies.Input('path-input', 'value')],
)
def load_data(n_clicks, selected_path):
print("selected path is",selected_path)
global data_store
if n_clicks > 0:
if selected_path is None or not os.path.isdir(selected_path):
return [], []
subfolder_labels, data = parse_all_data(selected_path)
print('subfolders',subfolder_labels)
if data is None:
data = {}
# Store data in the global dictionary
data_store = data
labels = list(data[list(data.keys())[0]]['aircraft_data'][list(data[list(data.keys())[0]]['aircraft_data'].keys())[0]].columns.tolist()) # Extracting column names for the first aircraft-specific dataframe
return subfolder_labels, labels
else:
return [], []
# Define the callback to update the graphs based on the slider value
@app.callback(
dash.dependencies.Output('time-series', 'figure'),
dash.dependencies.Output('map', 'figure'),
dash.dependencies.Output('altitude-series', 'figure'),
dash.dependencies.Output('timeline', 'figure'),
# dash.dependencies.Input('path-input', 'value'),
[dash.dependencies.Input('subfolder-dropdown', 'value'),
dash.dependencies.Input('time-slider', 'value'),
dash.dependencies.Input('variable-dropdown','value')]
)
# def update_graph(selected_subfolder, selected_time):
# # Load the CSV files into pandas DataFrames from the selected subfolder
# df1 = pd.read_csv(f'data/{selected_subfolder}/actionTrace_Agent_PIC Blaze.csv')
# df2 = pd.read_csv(f'data/{selected_subfolder}/SNLBlaze_acstate.csv')
# df3 = pd.read_csv(f'data/{selected_subfolder}/GCCRaven_acstate.csv')
# exclude = ['Detect_crash','Detect_conflict','Flight_Dynamics','Reroute_flight','Show_radar','Direct_to_waypoint','Change_heading']
# events = parse_action_trace(f'data/{selected_subfolder}/actionTrace.csv', exclude)
def update_graph(selected_subfolder, selected_time, selected_variable):
if selected_subfolder not in data_store:
return go.Figure(), go.Figure(), go.Figure(layout=dict(autosize=True, width=None, height=300)), go.Figure()
wnd_action_trace = data_store[selected_subfolder].get('wnd_action_trace', pd.DataFrame())
all_action_trace = data_store[selected_subfolder].get('all_action_trace', [])
aircraft_data = data_store[selected_subfolder].get('aircraft_data', {})
filtered_wnd_action_trace = wnd_action_trace[(wnd_action_trace['time'] >= start_time) & (wnd_action_trace['time'] <= selected_time)]
filtered_all_action_trace = []
for event in all_action_trace:
if (event['start'] - start_date).total_seconds() <= selected_time:
event_copy = event.copy() # Create a local copy of the event
if (event_copy['end'] - start_date).total_seconds() > selected_time:
event_copy['end'] = start_date + datetime.timedelta(seconds=selected_time)
filtered_all_action_trace.append(event_copy)
ac_data = {}
for aircraft_id, df in aircraft_data.items():
filtered_df = df[(df['time'] >= start_time) & (df['time'] <= selected_time)]
ac_data[aircraft_id] = filtered_df
fig1 = go.Figure(layout=dict(autosize=True, width=None, height=500))
fig1['layout']['uirevision'] = 'Hello world!'
fig1 = viz.plot_trajectory(fig1, ac_data)
fig2 = go.Figure(layout=dict(autosize=True, width=700, height=700))
fig2 = viz.wnd_visualization(fig2, filtered_wnd_action_trace, selected_time, start_time, end_time)
fig2['layout']['uirevision'] = 'Hello world!'
if selected_variable is not None:
fig3 = go.Figure(layout=dict(autosize=True, width=None, height=300))
for aircraft_id, df in ac_data.items():
fig3.add_trace(go.Scatter(x=df['time'], y=df[selected_variable], mode='lines', name=aircraft_id))
fig3.update_layout(xaxis_title='Real Time', yaxis_title=selected_variable, showlegend=True, xaxis=dict(range=[start_time, end_time]))#, yaxis=dict(range=[0, 1200])
fig3['layout']['uirevision'] = 'Hello world!'
else:
fig3 = go.Figure(layout=dict(autosize=True, width=None, height=300))
fig4 = go.Figure(layout=dict(autosize=True, width=None, height=700))
fig4 = viz.timeline(fig4, filtered_all_action_trace, selected_time, start_date, end_date)
fig4['layout']['uirevision'] = 'Hello world!'
return fig2, fig1, fig3, fig4
# Run the app
if __name__ == '__main__':
# webbrowser.open_new("http://127.0.0.1:8050/")
app.run_server(debug=False, host='0.0.0.0', port=7860)