diff --git a/app.py b/app.py index 1311ea2..db6bbfd 100644 --- a/app.py +++ b/app.py @@ -464,7 +464,9 @@ def render_compound_plot( return go.Figure() included_laps = pd.DataFrame.from_dict(included_laps) - included_laps = included_laps[included_laps["Compound"].isin(compounds)] + included_laps = included_laps[ + (included_laps["Compound"].isin(compounds)) & (included_laps["PctFromLapRep"] <= 10) + ] y = "DeltaToLapRep" if show_seconds else "PctFromLapRep" fig = go.Figure() diff --git a/f1_visualization/plotly_dash/graphs.py b/f1_visualization/plotly_dash/graphs.py index 89ca657..aefb17c 100644 --- a/f1_visualization/plotly_dash/graphs.py +++ b/f1_visualization/plotly_dash/graphs.py @@ -294,14 +294,15 @@ def compounds_lineplot(included_laps: pd.DataFrame, y: str, compounds: list[str] fig = go.Figure() yaxis_title = "Seconds to LRT" if y == "DeltaToLapRep" else "Percent from LRT" - # TODO: remove this hard-coded value - _, palette, marker, _ = _plot_args(2024) + _, palette, marker, _ = _plot_args() + max_stint_length = 0 for compound in compounds: compound_laps = included_laps[included_laps["Compound"] == compound] # clip tyre life range to where there are at least three records tyre_life_range = compound_laps.groupby("TyreLife").size() tyre_life_range = tyre_life_range[tyre_life_range >= 3].index + max_stint_length = max(max_stint_length, tyre_life_range.max()) median_LRT = compound_laps.groupby("TyreLife")[y].median(numeric_only=True) # noqa: N806 median_LRT = median_LRT.loc[tyre_life_range] # noqa: N806 @@ -311,7 +312,6 @@ def compounds_lineplot(included_laps: pd.DataFrame, y: str, compounds: list[str] y=median_LRT, line={"color": palette[compound]}, marker={ - # TODO: tune these parameters "line": {"width": 1, "color": "white"}, "color": palette[compound], "symbol": marker[compound], @@ -324,7 +324,11 @@ def compounds_lineplot(included_laps: pd.DataFrame, y: str, compounds: list[str] fig.update_layout( template="plotly_dark", - xaxis_title="Tyre Age", + xaxis={ + "tickmode": "array", + "tickvals": list(range(5, max_stint_length, 5)), + "title": "Tyre Age", + }, yaxis_title=yaxis_title, showlegend=False, autosize=False, @@ -341,9 +345,53 @@ def compounds_distplot( fig = go.Figure() yaxis_title = "Seconds to LRT" if y == "DeltaToLapRep" else "Percent from LRT" + _, palette, _, _ = _plot_args() + max_stint_length = 0 + + for compound in compounds: + compound_laps = included_laps[included_laps["Compound"] == compound] + # clip tyre life range to where there are at least three records + tyre_life_range = compound_laps.groupby("TyreLife").size() + tyre_life_range = tyre_life_range[tyre_life_range >= 3].index + max_stint_length = max(max_stint_length, tyre_life_range.max()) + + compound_laps = compound_laps[compound_laps["TyreLife"].isin(tyre_life_range)] + + if violin_plot: + fig.add_trace( + go.Violin( + x=compound_laps["TyreLife"], + y=compound_laps[y], + fillcolor=palette[compound], + line={"color": palette[compound]}, + name=compound, + opacity=1, + spanmode="soft", + ) + ) + else: + fig.add_trace( + go.Box( + x=compound_laps["TyreLife"], + y=compound_laps[y], + boxpoints="outliers", + pointpos=0, + fillcolor=palette[compound], + line={"color": "dimgray"}, + name=compound, + showwhiskers=True, + ) + ) + fig.update_layout( template="plotly_dark", - xaxis_title="Tyre Age", + boxmode="group", + violinmode="group", + xaxis={ + "tickmode": "array", + "tickvals": list(range(5, max_stint_length, 5)), + "title": "Tyre Age", + }, yaxis_title=yaxis_title, showlegend=False, autosize=False, diff --git a/f1_visualization/plotly_dash/layout.py b/f1_visualization/plotly_dash/layout.py index 013c323..de0d890 100644 --- a/f1_visualization/plotly_dash/layout.py +++ b/f1_visualization/plotly_dash/layout.py @@ -247,7 +247,7 @@ def lap_numbers_slider(slider_id: str, **kwargs) -> dcc.RangeSlider: [ html.H4("Caveats", className="alert-heading"), html.P( - "The above driver selection does not apply to this plot. " + "The driver selections does not apply to this plot. " "This plot always considers laps driven by all drivers." ), html.Hr(), @@ -256,11 +256,14 @@ def lap_numbers_slider(slider_id: str, **kwargs) -> dcc.RangeSlider: "As the same tyre may have been used in qualifying sessions." ), html.Hr(), - html.P("Only compounds that completed at least one sixth of all laps are shown."), + html.P( + "Only compounds that completed at least one sixth of all laps are shown. " + "Outlier laps are filtered out." + ), html.Hr(), html.P( "For each compound, the range of shown tyre life is limited by " - "the number of drivers who complete a stint of that length. This is to avoid " + "the number of drivers who completed a stint of that length. This is to avoid " "the plot being stretched by one driver doing a very long stint." ), ], @@ -279,7 +282,11 @@ def lap_numbers_slider(slider_id: str, **kwargs) -> dcc.RangeSlider: [ dbc.Col( dcc.Dropdown( - options=["lineplot", "boxplot", "violinplot"], + options=[ + {"label": "Lineplot", "value": "lineplot"}, + {"label": "Boxplot", "value": "boxplot"}, + {"label": "Violin Plot", "value": "violinplot"}, + ], value="lineplot", clearable=False, placeholder="Select a plot type", @@ -290,8 +297,8 @@ def lap_numbers_slider(slider_id: str, **kwargs) -> dcc.RangeSlider: dbc.Col( dcc.Dropdown( options=[ - {"label": "Show as seconds", "value": True}, - {"label": "Show as percentage", "value": False}, + {"label": "Show delta as seconds", "value": True}, + {"label": "Show delta as percentages", "value": False}, ], value=True, clearable=False,