Skip to content

Commit

Permalink
Reorganize callbacks
Browse files Browse the repository at this point in the history
Separate loading session info and laps after the load session button is clicked into two functions.

Driver dropdowns are now set when session info changes.

Y-axis option dropdowns are set when lap data changes.
  • Loading branch information
Casper-Guo committed Aug 20, 2024
1 parent 144bb15 commit de4da3c
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 19 deletions.
106 changes: 91 additions & 15 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@

import f1_visualization.plotly_dash.graphs as pg
from f1_visualization._consts import CURRENT_SEASON, SPRINT_FORMATS
from f1_visualization.plotly_dash.layout import (
app_layout,
)
from f1_visualization.plotly_dash.layout import app_layout, line_y_options, scatter_y_options
from f1_visualization.visualization import get_session_info, load_laps

Session_info: TypeAlias = tuple[int, str, list[str]]
Expand Down Expand Up @@ -151,11 +149,7 @@ def enable_load_session(season: int | None, event: str | None, session: str | No


@callback(
Output("drivers", "options"),
Output("drivers", "value"),
Output("drivers", "disabled"),
Output("session-info", "data"),
Output("laps", "data"),
Input("load-session", "n_clicks"),
State("season", "value"),
State("event", "value"),
Expand All @@ -169,29 +163,111 @@ def get_session_metadata(
event: str,
session: str,
teammate_comp: bool,
) -> tuple[list[str], list, bool, Session_info, dict]:
) -> tuple[list[str], list, bool, Session_info]:
"""
Store session metadata and populate driver dropdown.
Store round number, event name, and the list of drivers into browser cache.
Can assume that season, event, and session are all set (not None).
"""
round_number, event_name, drivers = get_session_info(
season, event, session, teammate_comp=teammate_comp
)

return (round_number, event_name, drivers)


@callback(
Output("laps", "data"),
Input("load-session", "n_clicks"),
State("season", "value"),
State("event", "value"),
State("session", "value"),
prevent_initial_call=True,
)
def get_session_laps(
_: int, # ignores actual_value of n_clicks
season: int,
event: str,
session: str,
) -> dict:
"""
Save the laps of the selected session into browser cache.
Can assume that season, event, and session are all set (not None).
"""
included_laps = DF_DICT[season][session]
included_laps = included_laps[included_laps["RoundNumber"] == round_number]
included_laps = included_laps[included_laps["EventName"] == event]
included_laps = df_convert_timedelta(included_laps)

return included_laps.to_dict()


@callback(
Output("drivers", "options"),
Output("drivers", "value"),
Output("drivers", "disabled"),
Output("gap-drivers", "options"),
Output("gap-drivers", "value"),
Output("gap-drivers", "disabled"),
Input("session-info", "data"),
prevent_initial_call=True,
)
def set_driver_dropdowns(session_info: Session_info):
"""Configure driver dropdowns."""
drivers = session_info[2]
return drivers, drivers, False, drivers, None, False


@callback(
Output("scatter-y", "options"),
Output("line-y", "options"),
Output("scatter-y", "value"),
Output("line-y", "value"),
Input("laps", "data"),
prevent_initial_call=True,
)
def set_y_axis_dropdowns(
data: dict,
) -> tuple[list[dict[str, str]], list[dict[str, str]], str, str]:
"""Update y axis options based on the columns in the laps dataframe."""

def readable_gap_col_name(col: str) -> str:
"""Convert Pandas GapTox column names to the more readable Gap to x."""
return f"Gap to {col[-3:]} (s)"

gap_cols = filter(lambda x: x.startswith("Gap"), data.keys())
gap_col_options = [{"label": readable_gap_col_name(col), "value": col} for col in gap_cols]
return (
drivers,
drivers,
False,
(round_number, event_name, drivers),
included_laps.to_dict(),
scatter_y_options + gap_col_options,
line_y_options + gap_col_options,
"LapTime",
"LapTime",
)


@callback(
Output("laps", "data", allow_duplicate=True),
Input("add-gap", "n_clicks"),
State("gap-drivers", "value"),
State("laps", "data"),
running=[
(Output("gap-drivers", "disabled"), True, False),
(Output("add-gap", "disabled"), True, False),
(Output("add-gap", "children"), "Calculating...", "Add Gap"),
(Output("add-gap", "color"), "warning", "success"),
],
prevent_initial_call=True,
)
def add_gap_to_driver(_: int, drivers: list[str], data: dict) -> dict:
"""Amend the dataframe in cache and add driver gap columns."""
laps = pd.DataFrame.from_dict(data)
for driver in drivers:
if f"GapTo{driver}" not in laps.columns:
laps = add_gap(driver, laps)

return laps.to_dict()


@callback(
Output("lap-numbers-scatter", "max"),
Output("lap-numbers-scatter", "value"),
Expand Down
4 changes: 1 addition & 3 deletions f1_visualization/plotly_dash/graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,6 @@ def stats_lineplot(
included_laps: pd.DataFrame, drivers: list[str], y: str, upper_bound: int
) -> go.Figure:
"""Make lineplots showing a statistic."""
# TODO: incorporate the add_gap functionality

# Identify SC and VSC laps before filtering for upper bound
sc_laps, vsc_laps = _find_sc_laps(included_laps)

Expand Down Expand Up @@ -230,7 +228,7 @@ def stats_lineplot(
)

fig = shade_sc_periods(fig, sc_laps, vsc_laps)
if y == "Position":
if y == "Position" or y.startswith("Gap"):
fig.update_yaxes(autorange="reversed")

num_laps = included_laps["LapNumber"].max()
Expand Down
2 changes: 1 addition & 1 deletion f1_visualization/plotly_dash/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def lap_numbers_slider(slider_id: str, **kwargs) -> dcc.RangeSlider:
add_gap_row = dbc.Row(
dbc.Card(
[
dbc.CardHeader("Calculate gaps to drivers"),
dbc.CardHeader("Calculate gaps between drivers"),
dbc.CardBody(
[
dbc.Row(
Expand Down

0 comments on commit de4da3c

Please sign in to comment.