Improvements to the visualization
Browse files- app.py +72 -64
- bash_scopy.sh +36 -0
- src/viz.py +5 -4
app.py
CHANGED
|
@@ -68,7 +68,7 @@ def parse_all_data(path):
|
|
| 68 |
for subfolder in subfolders:
|
| 69 |
data[subfolder] = {
|
| 70 |
'wnd_action_trace': pd.read_csv(f'{path}/{subfolder}/actionTrace_Agent_PIC Blaze.csv'),
|
| 71 |
-
'all_action_trace': parse_action_trace(f'{path}/{subfolder}/actionTrace.csv', exclude),
|
| 72 |
}
|
| 73 |
data[subfolder]['aircraft_data'] = {}
|
| 74 |
for file in os.listdir(f'{path}/{subfolder}'):
|
|
@@ -84,22 +84,22 @@ def parse_all_data(path):
|
|
| 84 |
app.layout = html.Div([
|
| 85 |
html.Div([
|
| 86 |
html.Div([
|
| 87 |
-
html.Div("Select the desired input conditions
|
| 88 |
# html.Label("Contingency Time: "),
|
| 89 |
# dcc.Input( id='cont-time', type='text', placeholder='Time at which the lost link event occurs (set to "ALONGTIMEAWAY" (no quotes) if no lost link event needs to occur)', style={'width': '80%'}),
|
| 90 |
# html.Br(),
|
| 91 |
-
html.Label("
|
| 92 |
dcc.RadioItems(
|
| 93 |
id='backgroundDivert',
|
| 94 |
options=[
|
| 95 |
-
{'label': 'Divert', 'value': 'True'},
|
| 96 |
-
{'label': 'Continue', 'value': 'False'}
|
| 97 |
],
|
| 98 |
value='True',
|
| 99 |
labelStyle={'display': 'inline-block'}
|
| 100 |
),
|
| 101 |
html.Br(),
|
| 102 |
-
html.Label("Required certainty: A value that represents how
|
| 103 |
html.Br(),
|
| 104 |
dcc.Slider(
|
| 105 |
id='IFthreshold',
|
|
@@ -113,7 +113,7 @@ app.layout = html.Div([
|
|
| 113 |
# style={'width': '100%'}, # Set the width of the div containing the slider to 80% of the container
|
| 114 |
# dcc.Input( id='IFthreshold', type='text', placeholder='A `double` which represents the interaction factor at which Blaze will make a decision.', style={'width': '80%'}),
|
| 115 |
# html.Br(),
|
| 116 |
-
html.Label("Time buffer:
|
| 117 |
dcc.Slider(
|
| 118 |
id='TAthreshold',
|
| 119 |
min=10,
|
|
@@ -125,7 +125,7 @@ app.layout = html.Div([
|
|
| 125 |
),
|
| 126 |
# dcc.Input( id='TAthreshold', type='text', placeholder='An `integer` which represents how much time needs to be left before Blaze makes a decision.', style={'width': '80%'}),
|
| 127 |
# html.Br(),
|
| 128 |
-
html.Label("Wait interval: How
|
| 129 |
dcc.Slider(
|
| 130 |
id='updateTime',
|
| 131 |
min=20,
|
|
@@ -140,23 +140,38 @@ app.layout = html.Div([
|
|
| 140 |
# html.Button('Simulate!', id='button', n_clicks=0),
|
| 141 |
], style={'width': '50%', 'border': '1px solid gray'}),
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
html.Div([
|
| 144 |
-
html.
|
| 145 |
-
dcc.
|
| 146 |
-
id='
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
| 151 |
),
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
# id='subfolder-dropdown',
|
| 157 |
-
# value=None #subfolders[0] # Default value
|
| 158 |
-
# )
|
| 159 |
-
], style={'width': '50%', 'border': '1px solid gray'}),
|
| 160 |
], style={'display': 'flex'}),
|
| 161 |
|
| 162 |
html.Div([
|
|
@@ -165,7 +180,7 @@ app.layout = html.Div([
|
|
| 165 |
dcc.Dropdown(
|
| 166 |
id='variable-dropdown',
|
| 167 |
# options=[{'label': i, 'value': i} for i in subfolders],
|
| 168 |
-
value=
|
| 169 |
),
|
| 170 |
dcc.Graph(id='altitude-series') # Third graph
|
| 171 |
], style={'width': '39%', 'display': 'inline-block', 'border': '1px solid gray'}),
|
|
@@ -174,18 +189,6 @@ app.layout = html.Div([
|
|
| 174 |
], style={'width': '59%', 'display': 'inline-block', 'border': '1px solid gray'})
|
| 175 |
], style={'display': 'flex'}),
|
| 176 |
|
| 177 |
-
html.Div(
|
| 178 |
-
dcc.Slider(
|
| 179 |
-
id='time-slider',
|
| 180 |
-
min=start_time,
|
| 181 |
-
max=end_time,
|
| 182 |
-
value=start_time+100,
|
| 183 |
-
marks={str(time): str(time) for time in range(start_time, end_time+1, 50)},
|
| 184 |
-
updatemode='drag'
|
| 185 |
-
),
|
| 186 |
-
style={'width': '100%'} # Set the width of the div containing the slider to 80% of the container
|
| 187 |
-
)
|
| 188 |
-
|
| 189 |
])
|
| 190 |
|
| 191 |
# @app.callback(
|
|
@@ -215,31 +218,31 @@ app.layout = html.Div([
|
|
| 215 |
# return f"error!"
|
| 216 |
|
| 217 |
# Load data when the app starts and store it in the hidden div
|
| 218 |
-
@app.callback(
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
)
|
| 225 |
-
def load_data(n_clicks, selected_path):
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
|
| 244 |
|
| 245 |
# Define the callback to update the graphs based on the slider value
|
|
@@ -247,6 +250,7 @@ def load_data(n_clicks, selected_path):
|
|
| 247 |
dash.dependencies.Output('time-series', 'figure'),
|
| 248 |
dash.dependencies.Output('map', 'figure'),
|
| 249 |
dash.dependencies.Output('altitude-series', 'figure'),
|
|
|
|
| 250 |
# dash.dependencies.Output('timeline', 'figure'),
|
| 251 |
# dash.dependencies.Input('path-input', 'value'),
|
| 252 |
# [dash.dependencies.Input('subfolder-dropdown', 'value'),
|
|
@@ -269,9 +273,10 @@ def load_data(n_clicks, selected_path):
|
|
| 269 |
def update_graph(selected_time, selected_variable, backgroundDivert, RequiredCertainty, TimeBuffer, WaitInterval): #selected_subfolder,
|
| 270 |
|
| 271 |
selected_subfolder = "exampleTime=200"+backgroundDivert+"_"+str(RequiredCertainty)+"_"+str(TimeBuffer)+"_"+str(WaitInterval)
|
| 272 |
-
|
| 273 |
if selected_subfolder not in data_store:
|
| 274 |
-
|
|
|
|
| 275 |
|
| 276 |
wnd_action_trace = data_store[selected_subfolder].get('wnd_action_trace', pd.DataFrame())
|
| 277 |
all_action_trace = data_store[selected_subfolder].get('all_action_trace', [])
|
|
@@ -314,11 +319,14 @@ def update_graph(selected_time, selected_variable, backgroundDivert, RequiredCer
|
|
| 314 |
# fig4 = viz.timeline(fig4, filtered_all_action_trace, selected_time, start_date, end_date)
|
| 315 |
# fig4['layout']['uirevision'] = 'Hello world!'
|
| 316 |
|
| 317 |
-
return fig2, fig1, fig3#, fig4
|
| 318 |
|
| 319 |
# Run the app
|
| 320 |
if __name__ == '__main__':
|
| 321 |
|
|
|
|
|
|
|
|
|
|
| 322 |
# webbrowser.open_new("http://127.0.0.1:8050/")
|
| 323 |
# app.run_server(debug=True)
|
| 324 |
app.run_server(debug=True, host='0.0.0.0', port=7860)
|
|
|
|
| 68 |
for subfolder in subfolders:
|
| 69 |
data[subfolder] = {
|
| 70 |
'wnd_action_trace': pd.read_csv(f'{path}/{subfolder}/actionTrace_Agent_PIC Blaze.csv'),
|
| 71 |
+
# 'all_action_trace': parse_action_trace(f'{path}/{subfolder}/actionTrace.csv', exclude),
|
| 72 |
}
|
| 73 |
data[subfolder]['aircraft_data'] = {}
|
| 74 |
for file in os.listdir(f'{path}/{subfolder}'):
|
|
|
|
| 84 |
app.layout = html.Div([
|
| 85 |
html.Div([
|
| 86 |
html.Div([
|
| 87 |
+
html.Div("Select the desired input conditions and see how the strategy on the What's Next Diagram changes:", id='text1'),
|
| 88 |
# html.Label("Contingency Time: "),
|
| 89 |
# dcc.Input( id='cont-time', type='text', placeholder='Time at which the lost link event occurs (set to "ALONGTIMEAWAY" (no quotes) if no lost link event needs to occur)', style={'width': '80%'}),
|
| 90 |
# html.Br(),
|
| 91 |
+
html.Label("What is the behavior of Unmanned Aerial Vehicle when a lost link is detected?"),
|
| 92 |
dcc.RadioItems(
|
| 93 |
id='backgroundDivert',
|
| 94 |
options=[
|
| 95 |
+
{'label': 'Divert to nearest vertiport', 'value': 'True'},
|
| 96 |
+
{'label': 'Continue to fly along its approved route', 'value': 'False'}
|
| 97 |
],
|
| 98 |
value='True',
|
| 99 |
labelStyle={'display': 'inline-block'}
|
| 100 |
),
|
| 101 |
html.Br(),
|
| 102 |
+
html.Label("Required certainty: A value that represents how certain the pilot of Blaze needs to be about what's next before they make a decision."),
|
| 103 |
html.Br(),
|
| 104 |
dcc.Slider(
|
| 105 |
id='IFthreshold',
|
|
|
|
| 113 |
# style={'width': '100%'}, # Set the width of the div containing the slider to 80% of the container
|
| 114 |
# dcc.Input( id='IFthreshold', type='text', placeholder='A `double` which represents the interaction factor at which Blaze will make a decision.', style={'width': '80%'}),
|
| 115 |
# html.Br(),
|
| 116 |
+
html.Label("Time buffer: How many seconds before the Blazes `point of no return` should Blaze make decision, even if not quite certain about what's next?"),
|
| 117 |
dcc.Slider(
|
| 118 |
id='TAthreshold',
|
| 119 |
min=10,
|
|
|
|
| 125 |
),
|
| 126 |
# dcc.Input( id='TAthreshold', type='text', placeholder='An `integer` which represents how much time needs to be left before Blaze makes a decision.', style={'width': '80%'}),
|
| 127 |
# html.Br(),
|
| 128 |
+
html.Label("Wait interval: How often should Blaze receive new information and revise projections?"),
|
| 129 |
dcc.Slider(
|
| 130 |
id='updateTime',
|
| 131 |
min=20,
|
|
|
|
| 140 |
# html.Button('Simulate!', id='button', n_clicks=0),
|
| 141 |
], style={'width': '50%', 'border': '1px solid gray'}),
|
| 142 |
|
| 143 |
+
# html.Div([
|
| 144 |
+
# html.Div("Next, input the relative path to your results folder, then click 'Submit'. This will load the data:", id='text2'),
|
| 145 |
+
# dcc.Input(
|
| 146 |
+
# id='path-input',
|
| 147 |
+
# # value='data',
|
| 148 |
+
# type='text',
|
| 149 |
+
# placeholder='Type your path... For example, ../wmc5.1/Scenario/AAMv2/Results',
|
| 150 |
+
# style={'width': '70%'} # Make the input wider
|
| 151 |
+
# ),
|
| 152 |
+
# html.Button('Submit', id='submit-button', n_clicks=0),
|
| 153 |
+
# # Create a dropdown menu with the subfolders as options
|
| 154 |
+
# # html.Div("Once all subfolders are loaded (representing the different conditions you ran in WMC), you can select which one to plot:", id='text2'),
|
| 155 |
+
# # dcc.Dropdown(
|
| 156 |
+
# # id='subfolder-dropdown',
|
| 157 |
+
# # value=None #subfolders[0] # Default value
|
| 158 |
+
# # )
|
| 159 |
+
# ], style={'width': '50%', 'border': '1px solid gray'}),
|
| 160 |
+
|
| 161 |
html.Div([
|
| 162 |
+
html.Label("Use the slider to move through time."),
|
| 163 |
+
dcc.Slider(
|
| 164 |
+
id='time-slider',
|
| 165 |
+
min=start_time,
|
| 166 |
+
max=end_time,
|
| 167 |
+
value=start_time+100,
|
| 168 |
+
marks={str(time): str(time) for time in range(start_time, end_time+1, 50)},
|
| 169 |
+
updatemode='drag'
|
| 170 |
),
|
| 171 |
+
# style={'width': '100%'} # Set the width of the div containing the slider to 80% of the container
|
| 172 |
+
], style={'width': '50%', 'border': '1px solid gray'}
|
| 173 |
+
)
|
| 174 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
], style={'display': 'flex'}),
|
| 176 |
|
| 177 |
html.Div([
|
|
|
|
| 180 |
dcc.Dropdown(
|
| 181 |
id='variable-dropdown',
|
| 182 |
# options=[{'label': i, 'value': i} for i in subfolders],
|
| 183 |
+
value='altitude_ft' #subfolders[0] # Default value
|
| 184 |
),
|
| 185 |
dcc.Graph(id='altitude-series') # Third graph
|
| 186 |
], style={'width': '39%', 'display': 'inline-block', 'border': '1px solid gray'}),
|
|
|
|
| 189 |
], style={'width': '59%', 'display': 'inline-block', 'border': '1px solid gray'})
|
| 190 |
], style={'display': 'flex'}),
|
| 191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
])
|
| 193 |
|
| 194 |
# @app.callback(
|
|
|
|
| 218 |
# return f"error!"
|
| 219 |
|
| 220 |
# Load data when the app starts and store it in the hidden div
|
| 221 |
+
# @app.callback(
|
| 222 |
+
# # dash.dependencies.Output('subfolder-dropdown', 'options'),
|
| 223 |
+
# # dash.dependencies.Output('variable-dropdown', 'options'),
|
| 224 |
+
# dash.dependencies.Output('submit-button', 'n_clicks'), # Add this output to reset submit button clicks
|
| 225 |
+
# [dash.dependencies.Input('submit-button', 'n_clicks')],
|
| 226 |
+
# [dash.dependencies.Input('path-input', 'value')],
|
| 227 |
+
# )
|
| 228 |
+
# def load_data(n_clicks, selected_path):
|
| 229 |
+
# print("selected path is",selected_path)
|
| 230 |
+
# global data_store
|
| 231 |
+
|
| 232 |
+
# if n_clicks > 0:
|
| 233 |
+
# if selected_path is None or not os.path.isdir(selected_path):
|
| 234 |
+
# return [], []
|
| 235 |
+
# subfolder_labels, data = parse_all_data(selected_path)
|
| 236 |
+
# print('subfolders',subfolder_labels)
|
| 237 |
+
# if data is None:
|
| 238 |
+
# data = {}
|
| 239 |
+
# # Store data in the global dictionary
|
| 240 |
+
# data_store = data
|
| 241 |
+
# 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
|
| 242 |
+
# return 0
|
| 243 |
+
# else:
|
| 244 |
+
# # return [], [], 0
|
| 245 |
+
# return 0
|
| 246 |
|
| 247 |
|
| 248 |
# Define the callback to update the graphs based on the slider value
|
|
|
|
| 250 |
dash.dependencies.Output('time-series', 'figure'),
|
| 251 |
dash.dependencies.Output('map', 'figure'),
|
| 252 |
dash.dependencies.Output('altitude-series', 'figure'),
|
| 253 |
+
dash.dependencies.Output('variable-dropdown', 'options'),
|
| 254 |
# dash.dependencies.Output('timeline', 'figure'),
|
| 255 |
# dash.dependencies.Input('path-input', 'value'),
|
| 256 |
# [dash.dependencies.Input('subfolder-dropdown', 'value'),
|
|
|
|
| 273 |
def update_graph(selected_time, selected_variable, backgroundDivert, RequiredCertainty, TimeBuffer, WaitInterval): #selected_subfolder,
|
| 274 |
|
| 275 |
selected_subfolder = "exampleTime=200"+backgroundDivert+"_"+str(RequiredCertainty)+"_"+str(TimeBuffer)+"_"+str(WaitInterval)
|
| 276 |
+
print(selected_subfolder)
|
| 277 |
if selected_subfolder not in data_store:
|
| 278 |
+
print("Not found")
|
| 279 |
+
return go.Figure(), go.Figure(), go.Figure(layout=dict(autosize=True, width=None, height=300)), labels#, go.Figure()
|
| 280 |
|
| 281 |
wnd_action_trace = data_store[selected_subfolder].get('wnd_action_trace', pd.DataFrame())
|
| 282 |
all_action_trace = data_store[selected_subfolder].get('all_action_trace', [])
|
|
|
|
| 319 |
# fig4 = viz.timeline(fig4, filtered_all_action_trace, selected_time, start_date, end_date)
|
| 320 |
# fig4['layout']['uirevision'] = 'Hello world!'
|
| 321 |
|
| 322 |
+
return fig2, fig1, fig3, labels#, fig4
|
| 323 |
|
| 324 |
# Run the app
|
| 325 |
if __name__ == '__main__':
|
| 326 |
|
| 327 |
+
subfolder_labels, data_store = parse_all_data("Results")
|
| 328 |
+
labels = list(data_store[list(data_store.keys())[0]]['aircraft_data'][list(data_store[list(data_store.keys())[0]]['aircraft_data'].keys())[0]].columns.tolist()) # Extracting column names for the first aircraft-specific dataframe
|
| 329 |
+
print(subfolder_labels)
|
| 330 |
# webbrowser.open_new("http://127.0.0.1:8050/")
|
| 331 |
# app.run_server(debug=True)
|
| 332 |
app.run_server(debug=True, host='0.0.0.0', port=7860)
|
bash_scopy.sh
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Source folder (replace with your actual source folder path)
|
| 4 |
+
SOURCE_FOLDER="Results/"
|
| 5 |
+
|
| 6 |
+
# Destination folder (replace with your actual destination folder path)
|
| 7 |
+
DEST_FOLDER="Simple_results"
|
| 8 |
+
|
| 9 |
+
# List of specific file names to copy (replace these with the actual file names)
|
| 10 |
+
FILES_TO_COPY=("actionTrace_Agent_PIC Blaze.csv" "GCCRaven_acstate.csv" "SNLBlaze_acstate.csv")
|
| 11 |
+
|
| 12 |
+
# Create the destination folder if it doesn't exist
|
| 13 |
+
mkdir -p "$DEST_FOLDER"
|
| 14 |
+
|
| 15 |
+
# Loop through each subfolder in the source folder
|
| 16 |
+
for SUBFOLDER in "$SOURCE_FOLDER"/*; do
|
| 17 |
+
if [ -d "$SUBFOLDER" ]; then
|
| 18 |
+
# Get the name of the subfolder
|
| 19 |
+
SUBFOLDER_NAME=$(basename "$SUBFOLDER")
|
| 20 |
+
|
| 21 |
+
# Create a corresponding subfolder in the destination folder
|
| 22 |
+
mkdir -p "$DEST_FOLDER/$SUBFOLDER_NAME"
|
| 23 |
+
|
| 24 |
+
# Copy the specific files if they exist in the subfolder
|
| 25 |
+
for FILENAME in "${FILES_TO_COPY[@]}"; do
|
| 26 |
+
SOURCE_FILE="$SUBFOLDER/$FILENAME"
|
| 27 |
+
if [ -f "$SOURCE_FILE" ]; then
|
| 28 |
+
cp "$SOURCE_FILE" "$DEST_FOLDER/$SUBFOLDER_NAME"
|
| 29 |
+
else
|
| 30 |
+
echo "File $FILENAME not found in $SUBFOLDER"
|
| 31 |
+
fi
|
| 32 |
+
done
|
| 33 |
+
fi
|
| 34 |
+
done
|
| 35 |
+
|
| 36 |
+
echo "Done! Specified files have been copied to the destination folder."
|
src/viz.py
CHANGED
|
@@ -11,10 +11,10 @@ class Visualizer:
|
|
| 11 |
|
| 12 |
def wnd_visualization(figure_handle, dataframe, current_time, start_time = 0, end_time = 10^6):
|
| 13 |
# Diagonal line y=x
|
| 14 |
-
figure_handle.add_trace(go.Scatter(x=[start_time, end_time], y=[start_time, end_time], mode='lines', line=dict(color='black', dash='dash')))
|
| 15 |
|
| 16 |
# Dotted lines from the point to axes
|
| 17 |
-
figure_handle.add_trace(go.Scatter(x=[current_time, current_time, start_time], y=[start_time, current_time, current_time], mode='lines', line=dict(color='black', dash='dot')))
|
| 18 |
|
| 19 |
# Adding scatter points with hover text
|
| 20 |
hover_text = [f"Time: {time}<br>Action: {name}" for time, name in dataframe[['time', 'actionName']].values]
|
|
@@ -24,7 +24,8 @@ class Visualizer:
|
|
| 24 |
mode='markers',
|
| 25 |
marker=dict(color='black', size=15),
|
| 26 |
text=hover_text,
|
| 27 |
-
hoverinfo='text'
|
|
|
|
| 28 |
))
|
| 29 |
|
| 30 |
timestamps_and_ids_dict = {}
|
|
@@ -210,7 +211,7 @@ class Visualizer:
|
|
| 210 |
lon=filtered_xdata,
|
| 211 |
lat=filtered_ydata,
|
| 212 |
line=dict(width=2, color=colors[color_index]),
|
| 213 |
-
name=ac_name
|
| 214 |
))
|
| 215 |
|
| 216 |
if len(filtered_xdata) > 0 and len(filtered_ydata) > 0:
|
|
|
|
| 11 |
|
| 12 |
def wnd_visualization(figure_handle, dataframe, current_time, start_time = 0, end_time = 10^6):
|
| 13 |
# Diagonal line y=x
|
| 14 |
+
figure_handle.add_trace(go.Scatter(x=[start_time, end_time], y=[start_time, end_time], mode='lines', line=dict(color='black', dash='dash'), showlegend=False))
|
| 15 |
|
| 16 |
# Dotted lines from the point to axes
|
| 17 |
+
figure_handle.add_trace(go.Scatter(x=[current_time, current_time, start_time], y=[start_time, current_time, current_time], mode='lines', line=dict(color='black', dash='dot'), showlegend=False))
|
| 18 |
|
| 19 |
# Adding scatter points with hover text
|
| 20 |
hover_text = [f"Time: {time}<br>Action: {name}" for time, name in dataframe[['time', 'actionName']].values]
|
|
|
|
| 24 |
mode='markers',
|
| 25 |
marker=dict(color='black', size=15),
|
| 26 |
text=hover_text,
|
| 27 |
+
hoverinfo='text',
|
| 28 |
+
name='Action'
|
| 29 |
))
|
| 30 |
|
| 31 |
timestamps_and_ids_dict = {}
|
|
|
|
| 211 |
lon=filtered_xdata,
|
| 212 |
lat=filtered_ydata,
|
| 213 |
line=dict(width=2, color=colors[color_index]),
|
| 214 |
+
name=ac_name + " Flight Path"
|
| 215 |
))
|
| 216 |
|
| 217 |
if len(filtered_xdata) > 0 and len(filtered_ydata) > 0:
|