From a1ed97c8256aa28411a3d55d69f3474c48688d05 Mon Sep 17 00:00:00 2001 From: theOehrly <23384863+theOehrly@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:54:21 +0100 Subject: [PATCH] WIP --- docs/changelog/index.rst | 2 +- docs/changelog/previous.rst | 1 + docs/changelog/v3.3.x.rst | 37 + docs/conf.py | 1 + docs/plotting.rst | 27 + examples/plot_driver_styling.py | 104 +++ examples/plot_position_changes.py | 6 +- fastf1/core.py | 3 - fastf1/plotting/__init__.py | 124 ++++ fastf1/plotting/_constants/__init__.py | 33 + fastf1/plotting/_constants/base.py | 25 + fastf1/plotting/_constants/season2023.py | 96 +++ fastf1/plotting/_constants/season2024.py | 96 +++ fastf1/plotting/_drivers.py | 658 ++++++++++++++++++ fastf1/{plotting.py => plotting/_plotting.py} | 265 +------ 15 files changed, 1237 insertions(+), 241 deletions(-) create mode 100644 docs/changelog/v3.3.x.rst create mode 100644 examples/plot_driver_styling.py create mode 100644 fastf1/plotting/__init__.py create mode 100644 fastf1/plotting/_constants/__init__.py create mode 100644 fastf1/plotting/_constants/base.py create mode 100644 fastf1/plotting/_constants/season2023.py create mode 100644 fastf1/plotting/_constants/season2024.py create mode 100644 fastf1/plotting/_drivers.py rename fastf1/{plotting.py => plotting/_plotting.py} (50%) diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index e46919ff2..e5eb155ea 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -3,5 +3,5 @@ Release Notes Looking for :ref:`previous-release-notes`? -.. include:: v3.2.x.rst +.. include:: v3.3.x.rst diff --git a/docs/changelog/previous.rst b/docs/changelog/previous.rst index 5d3932ca9..6e02c04a2 100644 --- a/docs/changelog/previous.rst +++ b/docs/changelog/previous.rst @@ -8,6 +8,7 @@ Release Notes for Older Versions .. toctree:: :maxdepth: 1 + v3.2.x v3.1.x v3.0.x v2.3.2 diff --git a/docs/changelog/v3.3.x.rst b/docs/changelog/v3.3.x.rst new file mode 100644 index 000000000..51bd155fe --- /dev/null +++ b/docs/changelog/v3.3.x.rst @@ -0,0 +1,37 @@ + +What's new in v3.3.0 +-------------------- + +(released dd/mm/yyyy) + + +New Features +^^^^^^^^^^^^ + + + +Bug Fixes +^^^^^^^^^ + + +Deprecations +^^^^^^^^^^^^ + +- The following module level properties of :mod:`fastf1.plotting` have been + deprecated: + :attr:`~fastf1.plotting.COMPOUND_COLORS`, + :attr:`~fastf1.plotting.DRIVER_TRANSLATE`, + :attr:`~fastf1.plotting.TEAM_COLORS`, + :attr:`~fastf1.plotting.TEAM_TRANSLATE`, + :attr:`~fastf1.plotting.COLOR_PALETTE` + + +- The following functions in :mod:`fastf1.plotting` have been deprecated: + :func:`~fastf1.plotting.driver_color`, + :func:`~fastf1.plotting.team_color` + + +Removals +^^^^^^^^ + +- ``fastf1.plotting.lapnumber_axis`` has been removed \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 783f02cb0..a8003be14 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -84,6 +84,7 @@ (r'py:.*', r'pandas\..*'), (r'py:.*', r'pd\..*'), (r'py:.*', r'numpy\..*'), + (r'py:.*', r'matplotlib\..*'), (r'py:mod', r'logging'), (r'py:class', r'logging.Logger'), ] diff --git a/docs/plotting.rst b/docs/plotting.rst index 81c5931aa..7383b5b56 100644 --- a/docs/plotting.rst +++ b/docs/plotting.rst @@ -3,4 +3,31 @@ Plotting - :mod:`fastf1.plotting` .. automodule:: fastf1.plotting :members: + add_sorted_driver_legend, + get_compound_color, + get_driver_abbreviation, + get_driver_abbreviations_by_team, + get_driver_color, + get_driver_name, + get_driver_names_by_team, + get_driver_style, + get_team_color, + get_team_name, + get_team_name_by_driver, + setup_mpl, + driver_color, + team_color + :show-inheritance: + +Deprecated Attributes +--------------------- + +The following module-level attributes are deprecated since version 3.3.0 and +will be removed in a future release. + +.. automodule:: fastf1.plotting + :no-index: + :members: + :exclude-members: + get_compound_color :show-inheritance: diff --git a/examples/plot_driver_styling.py b/examples/plot_driver_styling.py new file mode 100644 index 000000000..c948a4101 --- /dev/null +++ b/examples/plot_driver_styling.py @@ -0,0 +1,104 @@ +"""Driver specific plot styling +=============================== + +Create some plots and show the usage of ``fastf1.plotting.get_driver_style``. +""" + +from matplotlib import pyplot as plt + +import fastf1 +from fastf1 import plotting + + +plotting.setup_mpl() + + +############################################################################### +# Load the race session. + +race = fastf1.get_session(2023, "Azerbaijan", 'R') +race.load() + +############################################################################### +# Basic driver-specific plot styling +# ---------------------------------- +# Plot all the laps for Hamilton, Russel, Perez and Verstappen. +# Filter out slow laps as they distort the graph axis. +# Note: as LapTime is represented by timedelta, calling ``setup_mpl`` earlier +# is required. + +fig, ax = plt.subplots(figsize=(8, 5)) + +for driver in ('HAM', 'PER', 'VER', 'RUS'): + laps = race.laps.pick_driver(driver).pick_quicklaps().reset_index() + style = plotting.get_driver_style(identifier=driver, + style=['color', 'linestyle'], + session=race) + ax.plot(laps['LapTime'], **style, label=driver) + +# add axis labels and a legend +ax.set_xlabel("Lap Number") +ax.set_ylabel("Lap Time") +ax.legend() + +############################################################################### +# Sorting the legend +# ------------------ +# That plot looks pretty good already, but the order of the labels in the +# legend is slightly chaotic. Instead of trying to order the labels manually, +# use :func:`fastf1.plotting.add_sorted_driver_legend`. +# Let's create the exact same plot again, but this time with a sorted legend +# which means, we only change the very last function call. + +fig, ax = plt.subplots(figsize=(8, 5)) + +for driver in ('HAM', 'PER', 'VER', 'RUS'): + laps = race.laps.pick_driver(driver).pick_quicklaps().reset_index() + style = plotting.get_driver_style(identifier=driver, + style=['color', 'linestyle'], + session=race) + ax.plot(laps['LapTime'], **style, label=driver) + +# add axis labels and a legend +ax.set_xlabel("Lap Number") +ax.set_ylabel("Lap Time") +plotting.add_sorted_driver_legend(ax, session=race) + +############################################################################### +# Creating fully custom styles +# ---------------------------- +# If you want to fully customize the plot style, you can define your own +# styling variants. +# +# Note that the value ``'auto'`` is treated as a magic keyword when used in +# combination with a color. It will be replaced with the team color. +# +# We define two styles, one for the first driver and one for the second driver +# in any team. +# +# The plot that is generated here isn't intended to be very readable, but it +# shows how you can customize any plot styling parameter. + +my_styles = [ + # style for each first driver + {'color': 'auto', 'linestyle': 'solid', 'linewidth': 5, 'alpha': 0.3}, + # style for each second driver + {'color': 'auto', 'linestyle': 'solid', 'linewidth': 1, 'alpha': 0.7} +] + +fig, ax = plt.subplots(figsize=(8, 5)) + +for driver in ('HAM', 'PER', 'VER', 'RUS'): + laps = race.laps.pick_driver(driver).pick_quicklaps().reset_index() + + # here, we now use ``style=my_style`` to use the custom styling + style = plotting.get_driver_style(identifier=driver, + style=my_styles, + session=race) + + ax.plot(laps['LapTime'], **style, label=driver) + +# add axis labels and a legend +ax.set_xlabel("Lap Number") +ax.set_ylabel("Lap Time") +plotting.add_sorted_driver_legend(ax, session=race) diff --git a/examples/plot_position_changes.py b/examples/plot_position_changes.py index fff1726a4..a45ca0c10 100644 --- a/examples/plot_position_changes.py +++ b/examples/plot_position_changes.py @@ -28,10 +28,12 @@ drv_laps = session.laps.pick_driver(drv) abb = drv_laps['Driver'].iloc[0] - color = fastf1.plotting.driver_color(abb) + style = fastf1.plotting.get_driver_style(identifier=abb, + style=['color', 'linestyle'], + session=session) ax.plot(drv_laps['LapNumber'], drv_laps['Position'], - label=abb, color=color) + label=abb, **style) # sphinx_gallery_defer_figures ############################################################################## diff --git a/fastf1/core.py b/fastf1/core.py index 3538738b8..6008f5d9c 100644 --- a/fastf1/core.py +++ b/fastf1/core.py @@ -2995,9 +2995,6 @@ def pick_team(self, name: str) -> "Laps": mercedes = session_laps.pick_team('Mercedes') alfa_romeo = session_laps.pick_team('Alfa Romeo') - Have a look to :attr:`fastf1.plotting.TEAM_COLORS` for a quick - reference on team names. - Args: name (str): Team name diff --git a/fastf1/plotting/__init__.py b/fastf1/plotting/__init__.py new file mode 100644 index 000000000..e69e4c74e --- /dev/null +++ b/fastf1/plotting/__init__.py @@ -0,0 +1,124 @@ +import warnings +from functools import cached_property +from typing import ( + Dict, + List +) + +from fastf1.core import Session +from fastf1.plotting._constants import LEGACY_TEAM_TRANSLATE as _LGT +from fastf1.plotting._constants import Constants as _Constants +from fastf1.plotting._constants.base import Colormaps as _Colormaps +from fastf1.plotting._constants.base import Compounds as _Compounds +from fastf1.plotting._drivers import ( # noqa: F401 + _get_driver_team_mapping, + add_sorted_driver_legend, + get_driver_abbreviation, + get_driver_abbreviations_by_team, + get_driver_color, + get_driver_name, + get_driver_names_by_team, + get_driver_style, + get_team_color, + get_team_name, + get_team_name_by_driver +) +from fastf1.plotting._plotting import ( # noqa: F401 + _COLOR_PALETTE, + driver_color, + setup_mpl, + team_color +) + + +def __getattr__(name): + if name in ('COMPOUND_COLORS', 'DRIVER_TRANSLATE', + 'TEAM_COLORS', 'TEAM_TRANSLATE', 'COLOR_PALETTE'): + warnings.warn(f"{name} is deprecated and will be removed in a future" + f"version.", FutureWarning) + return globals()[f"_DEPR_{name}"] + + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +_DEPR_COMPOUND_COLORS: Dict[str, str] = { + str(key.value): val for key, val + in _Constants['2024'].CompoundColors.items() +} +COMPOUND_COLORS: Dict[str, str] +""" +Mapping of tyre compound names to compound colors (hex color codes). +(current season only) + +.. deprecated:: 3.3.0 + The ``COMPOUND_COLORS`` dictionary is deprecated and will be removed in a + future version. Use :func:`~fastf1.plotting.get_compound_color` instead. +""" + +@cached_property +def _DEPR_DRIVER_TRANSLATE() -> Dict[str, str]: + dtm = _get_driver_team_mapping(session=None) + abb_to_name = dtm.abbreviation_to_name + for abb in abb_to_name.keys(): + abb_to_name[abb] = abb_to_name[abb].lower() + return abb_to_name + + +DRIVER_TRANSLATE: Dict[str, str] +""" +Mapping of driver names to theirs respective abbreviations. + +.. deprecated:: 3.3.0 + The ``DRIVER_TRANSLATE`` dictionary is deprecated and will be removed in a + future version. Use :func:`~fastf1.plotting.get_driver_name` instead. +""" + +_DEPR_TEAM_COLORS: Dict[str, str] = { + str(key.value): val for key, val + in _Constants['2024'].Colormaps[_Colormaps.Default].items() +} +TEAM_COLORS: Dict[str, str] +""" +Mapping of team names to team colors (hex color codes). +(current season only) + +.. deprecated:: 3.3.0 + The ``TEAM_COLORS`` dictionary is deprecated and will be removed in a + future version. Use :func:`~fastf1.plotting.get_team_color` instead. +""" + +_DEPR_TEAM_TRANSLATE: Dict[str, str] = { + str(key): val for key, val in _LGT.items() +} +TEAM_TRANSLATE: Dict[str, str] +""" +Mapping of team names to theirs respective abbreviations. + +.. deprecated:: 3.3.0 + The ``TEAM_TRANSLATE`` dictionary is deprecated and will be removed in a + future version. Use :func:`~fastf1.plotting.get_team_name` instead. +""" + +_DEPR_COLOR_PALETTE: List[str] = _COLOR_PALETTE.copy() +COLOR_PALETTE: Dict[str, str] +""" +The default color palette for matplotlib plot lines in fastf1's color scheme. + +.. deprecated:: 3.3.0 + The ``COLOR_PALETTE`` list is deprecated and will be removed in a + future version with no replacement. +""" + + +def get_compound_color(compound: str, *, session: Session) -> str: + """ + Get the compound color as hexadecimal RGB color code for a given compound. + + Args: + compound: The name of the compound + session: the session for which the data should be obtained + + Returns: A hexadecimal RGB color code + """ + year = str(session.event['EventDate'].year) + return _Constants[year].CompoundColors[_Compounds(compound.upper())] diff --git a/fastf1/plotting/_constants/__init__.py b/fastf1/plotting/_constants/__init__.py new file mode 100644 index 000000000..a3a87f84e --- /dev/null +++ b/fastf1/plotting/_constants/__init__.py @@ -0,0 +1,33 @@ +from typing import ( + Dict, + Type +) + +from fastf1.plotting._constants.base import ( + BaseSeason, + Colormaps +) +from fastf1.plotting._constants.season2023 import Season2023 +from fastf1.plotting._constants.season2024 import Season2024 + + +# Deprecated, will be removed for 2025 +LEGACY_TEAM_TRANSLATE: Dict[str, str] = { + 'MER': 'mercedes', + 'FER': 'ferrari', + 'RBR': 'red bull', + 'MCL': 'mclaren', + 'APN': 'alpine', + 'AMR': 'aston martin', + 'SAU': 'sauber', + 'VIS': 'visa', # TODO: update + 'HAA': 'haas', + 'WIL': 'williams' +} + + +Constants: Dict[str, Type[BaseSeason]] = { + '2023': Season2023, + '2024': Season2024 +} + diff --git a/fastf1/plotting/_constants/base.py b/fastf1/plotting/_constants/base.py new file mode 100644 index 000000000..df1d7456b --- /dev/null +++ b/fastf1/plotting/_constants/base.py @@ -0,0 +1,25 @@ +from enum import Enum +from typing import Dict + + +class Colormaps(Enum): + Classic = 'classic' + Default = 'default' + Official = 'official' + + +class Compounds(Enum): + Soft = "SOFT" + Medium = "MEDIUM" + Hard = "HARD" + Intermediate = "INTERMEDIATE" + Wet = "WET" + Unknown = "UNKNOWN" + TestUnknown = "TEST-UNKNOWN" + + +class BaseSeason: + Colormaps: Dict[Colormaps, dict] + CompoundColors: Dict[Compounds, str] + ShortTeamNames: Dict[str, str] + Teams: Enum diff --git a/fastf1/plotting/_constants/season2023.py b/fastf1/plotting/_constants/season2023.py new file mode 100644 index 000000000..9a7a1bfe4 --- /dev/null +++ b/fastf1/plotting/_constants/season2023.py @@ -0,0 +1,96 @@ +from enum import Enum +from typing import ( + Dict, + Sequence +) + +from fastf1.plotting._constants.base import ( + BaseSeason, + Colormaps, + Compounds +) + + +class Teams(Enum): + Mercedes = 'mercedes' + Ferrari = 'ferrari' + RedBull = 'red bull' + McLaren = 'mclaren' + Alpine = 'alpine' + AstonMartin = 'aston martin' + AlfaRomeo = 'alfa romeo' + AlphaTauri = 'alphatauri' + Haas = 'haas' + Williams = 'williams' + + +ShortTeamNames: Dict[Teams, str] = { + Teams.Mercedes: 'Mercedes', + Teams.Ferrari: 'Ferrari', + Teams.RedBull: 'Red Bull', + Teams.McLaren: 'McLaren', + Teams.Alpine: 'Alpine', + Teams.AstonMartin: 'Aston', + Teams.AlfaRomeo: 'Alfa Romeo', + Teams.AlphaTauri: 'AlphaTauri', + Teams.Haas: 'Haas', + Teams.Williams: 'Williams' +} + + +TeamColormaps: Dict[Colormaps, Dict[Teams, Sequence[str]]] = { + Colormaps.Classic: { + Teams.Mercedes: ['#00d2be', ], + Teams.Ferrari: ['#dc0000', ], + Teams.RedBull: ['#0600ef', ], + Teams.McLaren: ['#ff8700', ], + Teams.Alpine: ['#0090ff', ], + Teams.AstonMartin: ['#006f62', ], + Teams.AlfaRomeo: ['#900000', ], + Teams.AlphaTauri: ['#2b4562', ], + Teams.Haas: ['#ffffff', ], + Teams.Williams: ['#005aff', ] + }, + Colormaps.Official: { + Teams.Mercedes: ['#6CD3BF', ], + Teams.Ferrari: ['#F91536', ], + Teams.RedBull: ['#3671C6', ], + Teams.McLaren: ['#F58020', ], + Teams.Alpine: ['#2293D1', ], + Teams.AstonMartin: ['#358C75', ], + Teams.AlfaRomeo: ['#900000', ], + Teams.AlphaTauri: ['#5E8FAA', ], + Teams.Haas: ['#B6BABD', ], + Teams.Williams: ['#37BEDD', ] + }, + Colormaps.Default: { + Teams.Mercedes: ['#00f5d0', '#a8fff2'], + Teams.Ferrari: ['#da291c', '#e84d40'], + Teams.RedBull: ['#fcd700', '#ffec7b'], + Teams.McLaren: ['#ff8000', '#9d4d00'], + Teams.Alpine: ['#fe86bc', '#ff117c'], + Teams.AstonMartin: ['#00665e', '#00413b'], + Teams.AlfaRomeo: ['#900000', '#5f0000'], + Teams.AlphaTauri: ['#2b4562', '#406991'], + Teams.Haas: ['#ffffff', '#a7a7a7'], + Teams.Williams: ['#00a0dd', '#8cc8ff'] + } +} + + +CompoundColors: Dict[Compounds, str] = { + Compounds.Soft: "#da291c", + Compounds.Medium: "#ffd12e", + Compounds.Hard: "#f0f0ec", + Compounds.Intermediate: "#43b02a", + Compounds.Wet: "#0067ad", + Compounds.Unknown: "#00ffff", + Compounds.TestUnknown: "#434649" +} + + +class Season2023(BaseSeason): + Colormaps = TeamColormaps + CompoundColors = CompoundColors + ShortTeamNames = ShortTeamNames + Teams = Teams diff --git a/fastf1/plotting/_constants/season2024.py b/fastf1/plotting/_constants/season2024.py new file mode 100644 index 000000000..232c494bf --- /dev/null +++ b/fastf1/plotting/_constants/season2024.py @@ -0,0 +1,96 @@ +from enum import Enum +from typing import ( + Dict, + Sequence +) + +from fastf1.plotting._constants.base import ( + BaseSeason, + Colormaps, + Compounds +) + + +class Teams(Enum): + Mercedes = 'mercedes' + Ferrari = 'ferrari' + RedBull = 'red bull' + McLaren = 'mclaren' + Alpine = 'alpine' + AstonMartin = 'aston martin' + Sauber = 'sauber' + Visa = 'visa' + Haas = 'haas' + Williams = 'williams' + + +ShortTeamNames: Dict[Teams, str] = { + Teams.Mercedes: 'Mercedes', + Teams.Ferrari: 'Ferrari', + Teams.RedBull: 'Red Bull', + Teams.McLaren: 'McLaren', + Teams.Alpine: 'Alpine', + Teams.AstonMartin: 'Aston', + Teams.Sauber: 'Sauber', + Teams.Visa: 'Visa', + Teams.Haas: 'Haas', + Teams.Williams: 'Williams' +} + + +TeamColormaps: Dict[Colormaps, Dict[Teams, Sequence[str]]] = { + Colormaps.Classic: { + Teams.Mercedes: ['#00d2be', ], + Teams.Ferrari: ['#dc0000', ], + Teams.RedBull: ['#0600ef', ], + Teams.McLaren: ['#ff8700', ], + Teams.Alpine: ['#0090ff', ], + Teams.AstonMartin: ['#006f62', ], + Teams.Sauber: ['#00e701', ], + Teams.Visa: ['#1434CB', ], # TODO: update + Teams.Haas: ['#ffffff', ], + Teams.Williams: ['#005aff', ] + }, + Colormaps.Official: { + Teams.Mercedes: ['#6CD3BF', ], + Teams.Ferrari: ['#F91536', ], + Teams.RedBull: ['#3671C6', ], + Teams.McLaren: ['#F58020', ], + Teams.Alpine: ['#2293D1', ], + Teams.AstonMartin: ['#358C75', ], + Teams.Sauber: ['#00e701', ], + Teams.Visa: ['#5E8FAA', ], # TODO: update + Teams.Haas: ['#B6BABD', ], + Teams.Williams: ['#37BEDD', ] + }, + Colormaps.Default: { + Teams.Mercedes: ['#00f5d0', '#a8fff2'], + Teams.Ferrari: ['#da291c', '#8c1a11'], + Teams.RedBull: ['#fcd700', '#ffec7b'], + Teams.McLaren: ['#ff8700', '#9d4d00'], + Teams.Alpine: ['#fe86bc', '#ff117c'], + Teams.AstonMartin: ['#00665e', '#00413b'], + Teams.Sauber: ['#00e701', '#008d01'], + Teams.Visa: ['#1634cb', '#0c207e'], # TODO: update + Teams.Haas: ['#ffffff', '#a7a7a7'], + Teams.Williams: ['#00a0dd', '#8cc8ff'] + } +} + + +CompoundColors: Dict[Compounds, str] = { + Compounds.Soft: "#da291c", + Compounds.Medium: "#ffd12e", + Compounds.Hard: "#f0f0ec", + Compounds.Intermediate: "#43b02a", + Compounds.Wet: "#0067ad", + Compounds.Unknown: "#00ffff", + Compounds.TestUnknown: "#434649" +} + + +class Season2024(BaseSeason): + Colormaps = TeamColormaps + CompoundColors = CompoundColors + ShortTeamNames = ShortTeamNames + Teams = Teams diff --git a/fastf1/plotting/_drivers.py b/fastf1/plotting/_drivers.py new file mode 100644 index 000000000..9d3b02932 --- /dev/null +++ b/fastf1/plotting/_drivers.py @@ -0,0 +1,658 @@ +import json +import unicodedata +from collections import defaultdict +from typing import ( + Dict, + List, + Optional, + Sequence, + Union +) + +import matplotlib.axes +from thefuzz import fuzz + +from fastf1._api import driver_info +from fastf1.core import Session +from fastf1.plotting._constants import Constants +from fastf1.req import Cache + + +def _get_latest_api_path() -> (str, str): + # find out what the "current" season is; this is the latest season + # for which data is available, therefore before the first session + # of any year, the "current" season is still last year + res_seasons = Cache.requests_get( + 'https://livetiming.formula1.com/static/Index.json' + ) + if res_seasons.status_code != 200: + raise ValueError("Unable to fetch driver list") + year = str(json.loads( + res_seasons.content.decode('utf-8-sig') + )['Years'][-1].get('Year')) + # find the latest session of the current season + res_meetings = Cache.requests_get( + f'https://livetiming.formula1.com/static/{year}/Index.json' + ) + if res_meetings.status_code != 200: + raise ValueError("Unable to fetch driver list") + meeting_info = json.loads(res_meetings.content.decode('utf-8-sig')) + n_meetings = len(meeting_info.get('Meetings', [])) + # iterate over all session of the season in reverse and find the + # latest session that has an api path (not necessarily every + # session has that in case of error or if teh session is testing) + path = None + for i in range(n_meetings): + sessions = meeting_info['Meetings'][-(i + 1)]['Sessions'] + for j in range(len(sessions)): + path = sessions[-(j + 1)].get('Path', None) + if path is not None: + break + if path is not None: + break + + api_path = "/static/" + path + + return api_path, year + + +def _load_drivers_from_f1_livetiming( + *, api_path: str, year: str +) -> Sequence[Dict[str, str]]: + # load the driver information for the determined session + drivers = driver_info(api_path) + + # parse the data into the required format + abb_to_name: Dict[str, str] = dict() + abb_to_full_team_name: Dict[str, str] = dict() + name_normalized_to_abb: Dict[str, str] = dict() + + for num, driver in drivers.items(): + abb = driver.get('Tla') + name = driver.get('FirstName') + ' ' + driver.get('LastName') + team = driver.get('TeamName') + + abb_to_name[abb] = name + abb_to_full_team_name[abb] = team + name_normalized_to_abb[_normalize_name(name)] = abb + + team_name_to_full_team_name: Dict[str, str] = dict() + full_team_name_to_team_name: Dict[str, str] = dict() + for full_team_name in abb_to_full_team_name.values(): + normalized_full_team_name = _normalize_name(full_team_name).lower() + for team in Constants[year].Teams: + if team in normalized_full_team_name: + team_name_to_full_team_name[team] = full_team_name + full_team_name_to_team_name[full_team_name] = team + + abb_to_team: Dict[str, str] = dict() + for abb, full_team_name in abb_to_full_team_name.items(): + abb_to_team[abb] = full_team_name_to_team_name[full_team_name] + + ret_data = (abb_to_name, + abb_to_team, + name_normalized_to_abb, + team_name_to_full_team_name) + + return ret_data + + +_DRIVER_TEAM_MAPPINGS = dict() + + +def _get_driver_team_mapping( + session: Optional[Session] = None +) -> "_DriverTeamMapping": + + if session is None: + api_path, year = _get_latest_api_path() + else: + api_path = session.api_path + year = str(session.event['EventDate'].year) + + if api_path not in _DRIVER_TEAM_MAPPINGS: + datas = _load_drivers_from_f1_livetiming( + api_path=api_path, year=year + ) + mapping = _DriverTeamMapping(year, *datas) + _DRIVER_TEAM_MAPPINGS[api_path] = mapping + + return _DRIVER_TEAM_MAPPINGS[api_path] + + +def _fuzzy_matcher( + identifier: str, + reference: Dict[str, str] +) -> str: + # do fuzzy string matching + key_ratios = list() + for existing_key in reference.keys(): + ratio = fuzz.ratio(identifier, existing_key) + key_ratios.append((ratio, existing_key)) + key_ratios.sort(reverse=True) + if ((key_ratios[0][0] < 35) + or (key_ratios[0][0] / key_ratios[1][0] < 1.2)): + # ensure that the best match has a minimum accuracy (35 out of + # 100) and that it has a minimum confidence (at least 20% + # better than second best) + raise KeyError + best_matched_key = key_ratios[0][1] + team_name = reference[best_matched_key] + return team_name + + +def _normalize_name(name: str) -> str: + # removes accents from a string and returns the closest possible + # ascii representation (https://stackoverflow.com/a/518232) + stripped = ''.join(c for c in unicodedata.normalize('NFD', name) + if unicodedata.category(c) != 'Mn') + return stripped + + +class _DriverTeamMapping: + def __init__( + self, + year: str, + abb_to_name: Dict[str, str], + abb_to_team: Dict[str, str], + name_normalized_to_abb: Dict[str, str], + team_name_to_full_team_name: Dict[str, str] + ): + self.year = year + self.abbreviation_to_name = abb_to_name + self.abbreviation_to_team = abb_to_team + self.name_normalized_to_abbreviation = name_normalized_to_abb + self.team_name_to_full_team_name = team_name_to_full_team_name + + self.team_to_abbreviations: Dict[str, List[str]] = defaultdict(list) + for abb, team in self.abbreviation_to_team.items(): + self.team_to_abbreviations[team].append(abb) + + +def _get_normalized_team_name( + identifier: str, + *, + session: Session +) -> str: + dti = _get_driver_team_mapping(session) + identifier = _normalize_name(identifier).lower() + team_name: Optional[str] = None + # check for an exact team name match + if identifier in dti.team_name_to_full_team_name: + team_name = identifier + # check for exact partial string match + if team_name is None: + for _team_name in dti.team_name_to_full_team_name.keys(): + if identifier in _team_name: + team_name = _team_name + break + # do fuzzy string match + if team_name is None: + team_name = _fuzzy_matcher( + identifier, dti.team_name_to_full_team_name + ) + return team_name + + +def get_team_name( + identifier: str, + *, + session: Session, + short: bool = False +) -> str: + """ + Get a full team name based on a recognizable and identifiable part of + the team name. + + Alternatively, a shortened version of the team name can be returned. The + short version is intended for saving as much space as possible when + annotating plots and may skip parts of the official team name. + + Args: + identifier: a recognizable part of the team name + session: the session for which the data should be obtained + short: if True, a shortened version of the team name will be returned + """ + dti = _get_driver_team_mapping(session) + team_name = _get_normalized_team_name(identifier, session=session) + + if short: + return Constants[dti.year].ShortTeamNames[team_name] + + return dti.team_name_to_full_team_name[team_name] + + +def get_team_name_by_driver( + identifier: str, + *, + session: Session, + short: bool = False +) -> str: + """ + Get a full team name based on a driver's abbreviation or based on a + recognizable and identifiable part of a driver's name. + + Alternatively, a shortened version of the team name can be returned. The + short version is intended for saving as much space as possible when + annotating plots and may skip parts of the official team name. + + Args: + identifier: driver abbreviation or recognizable part of the driver name + session: the session for which the data should be obtained + short: if True, a shortened version of the team name will be returned + """ + dti = _get_driver_team_mapping(session) + abb = get_driver_abbreviation(identifier, session=session) + team = dti.abbreviation_to_team[abb] + if short: + return Constants[dti.year].ShortTeamNames[team] + else: + return dti.team_name_to_full_team_name[team] + + +def _get_team_color( + identifier: str, + *, + session: Session, + colormap: str = 'default', + _variant: int = 0 # internal use only + ): + if (_variant != 0) and (colormap != 'default'): + raise ValueError("Color variants are only supported on the " + "'default' colormap.") + + dti = _get_driver_team_mapping(session) + + team_name = _get_normalized_team_name(identifier, session=session) + + if dti.year not in Constants.keys(): + raise ValueError(f"No team colors for year '{dti.year}'") + + colormaps = Constants[dti.year].Colormaps + if colormap not in colormaps.keys(): + raise ValueError(f"Invalid colormap '{colormap}'") + + if team_name not in colormaps[colormap].keys(): + raise ValueError(f"Invalid team name '{team_name}'") + + return colormaps[colormap][team_name][_variant] + + +def get_team_color( + identifier: str, + *, + session: Session, + colormap: str = 'default', +): + """ + Get a team color based on a recognizable and identifiable part of + the team name. + + The team color is returned as a hexadecimal RGB color code. + + Args: + identifier: a recognizable part of the team name + session: the session for which the data should be obtained + colormap: one of ``'default'`` or ``'official'`` + """ + return _get_team_color(identifier, session=session, colormap=colormap) + + +def get_driver_name(identifier: str, *, session: Session) -> str: + """ + Get a full driver name based on the driver's abbreviation or based on + a recognizable and identifiable part of the driver's name. + + Args: + identifier: driver abbreviation or recognizable part of the driver name + session: the session for which the data should be obtained + """ + dti = _get_driver_team_mapping(session) + abb = get_driver_abbreviation(identifier, session=session) + return dti.abbreviation_to_name[abb] + + +def get_driver_abbreviation(identifier, *, session: Session): + """ + Get a driver's abbreviation based on a recognizable and identifiable + part of the driver's name. + + Note that the driver's abbreviation, if given exactly, is also a valid + identifier. In this case the same value is returned as was given as the + identifier. + + Args: + identifier: recognizable part of the driver's name (or the + driver's abbreviation) + session: the session for which the data should be obtained + """ + identifier = _normalize_name(identifier).lower() + dti = _get_driver_team_mapping(session) + + # try driver abbreviation first + if (abb := identifier.upper()) in dti.abbreviation_to_name: + return abb + + # check for an exact driver name match + if identifier in dti.name_normalized_to_abbreviation: + return dti.name_normalized_to_abbreviation[identifier] + + # check for exact partial string match + for name, abb in dti.name_normalized_to_abbreviation.items(): + if identifier in name: + return abb + + # do fuzzy string matching + return _fuzzy_matcher( + identifier, dti.name_normalized_to_abbreviation + ) + + +def get_driver_names_by_team( + identifier: str, *, session: Session +) -> List[str]: + """ + Get a list of full names of all drivers that drove for a team in a given + session based on a recognizable and identifiable part of the team name. + + Args: + identifier: a recognizable part of the team name + session: the session for which the data should be obtained + """ + dti = _get_driver_team_mapping(session) + team = get_team_name(identifier, session=session) + names = list() + for abb in dti.team_to_abbreviations[team]: + names.append(dti.abbreviation_to_name[abb]) + return names + + +def get_driver_abbreviations_by_team( + identifier: str, *, session: Session +) -> List[str]: + """ + Get a list of abbreviations of all drivers that drove for a team in a given + session based on a recognizable and identifiable part of the team name. + + Args: + identifier: a recognizable part of the team name + session: the session for which the data should be obtained + """ + dti = _get_driver_team_mapping(session) + team = get_team_name(identifier, session=session) + return dti.team_to_abbreviations[team].copy() + + +def _get_driver_color( + identifier: str, + *, + session: Session, + colormap: str = 'default', + _variants: bool = False +): + dti = _get_driver_team_mapping(session) + abb = get_driver_abbreviation(identifier, session=session) + team = dti.abbreviation_to_team[abb] + + if _variants: + idx = dti.team_to_abbreviations[team].index(abb) + if idx > 1: + idx = 1 # we only have two colors, limit to 0 or 1 + return _get_team_color(team, session=session, colormap='default', + _variant=idx) + else: + return _get_team_color(team, session=session, colormap=colormap) + + +def get_driver_color( + identifier: str, + *, + session: Session, + colormap: str = 'default', +): + """ + Get the color that is associated with a driver based on the driver's + abbreviation or based on a recognizable and identifiable part of the + driver's name. + + .. note:: This will simply return the team color of the team that the + driver participated for in this session. Contrary to older versions + of FastF1, there are no separate colors for each driver! + + Args: + identifier: driver abbreviation or recognizable part of the driver name + session: the session for which the data should be obtained + colormap: one of ``'default'`` or ``'official'`` + """ + return _get_driver_color(identifier, session=session, colormap=colormap) + + +def get_driver_style( + identifier: str, + style: Union[str, List[str], List[dict]], + *, + session: Session, + colormap: str = 'default', + additional_color_kws: Union[list, tuple] = () +): + """ + Get a plotting style that is unique for a driver based on the driver's + abbreviation or based on a recognizable and identifiable part of the + driver's name. + + This function simplifies the task of generating unique and easily + distinguishable visual styles for multiple drivers in a plot. + Primarily, the focus is on plotting with Matplotlib, but it is possible + to customize the behaviour for compatibility with other plotting libraries. + + The general idea for creating visual styles is as follows: + + 1. Set the primary color of the style to the color of the team for + which a driver is driving. This may be the line color in a line plot, + the marker color in a scatter plot, and so on. + + 2. Use one or multiple other styling options (line style, markers, ...) + to differentiate drivers in the same team. + + .. note:: It cannot be guaranteed that the styles are consistent throughout + a full season, especially in case of driver changes within a team. + + + **Option 1**: Rely on built-in styling options + + By default, this function supports the following Matplotlib plot arguments: + ``linestyle``, ``marker``, ``color``, ``facecolor``, ``edgecolor`` as well + as almost all other color-related arguments. + + The styling options include one color for each team, up to four different + line styles and marker styles within a team. That means that no more than + four different drivers are supported for a team in a single session. This + should be sufficent in almost all scenarios. + + The following example obtains the driver style for Alonso and Stroll in a + race in the 2023 season. The drivers should be represented using the + ``color`` and ``marker`` arguments, as may be useful in a scatter plot. + Both drivers were driving for the Aston Martin team, therefore, both + automatically get assigned the same color, which is the Aston Martin team + color. But both drivers get assigned a different marker style, so they can + be uniquely identified in the plot. + + Example:: + >>> from fastf1 import get_session + >>> from fastf1.plotting import get_driver_style + >>> session = get_session(2023, 10, 'R') + >>> get_driver_style('ALO', style=['color', 'marker'], session=session) + {'color': '#00665e', 'marker': 'x'} + >>> get_driver_style('STR', style=['color', 'marker'], session=session) + {'color': '#00665e', 'marker': 'o'} + + **Option 2**: Provide a custom list of styling variants + + To allow for almost unlimited styling options, it is possible to specify + custom styling variants. These are not tied to any specific plotting + library. + + In the following example, a list with two custom stlyes is defined that are + then used to generate driver specific styles. Each style is represented by + a dictionary of keywords and values. + + The first driver in a team gets assigned the first style, the second driver + the second style and so on (if there are more than two drivers). It is + necessary to define at least as many styles as there are drivers in a team. + + The following things need to be noted: + + 1. The notion of first or second driver does not refer to any particular + reference and no specific order for drivers within a team is intended or + guranteed. + + 2. Any color-related key can make use of the "magic" ``'auto'`` value as + shown with Alonso in this example. When the color value is set to + ``'auto'`` it will automatically be replaced with the team color for this + driver. All color keywords that are used in Matplotlib should be recognized + automatically. You can define custom arguments as color arguments through + the ``additional_color_kws`` argument. + + 3. Each style dictionary can contain arbitrary keys and value. Therefore, + you are not limited to any particular plotting library. + + Example:: + >>> from fastf1 import get_session + >>> from fastf1.plotting import get_driver_style + >>> session = get_session(2023, 10, 'R') + >>> my_styles = [ \ + {'linestyle': 'solid', 'color': 'auto', 'custom_arg': True}, \ + {'linestyle': 'dotted', 'color': '#FF0060', 'other_arg': 10} \ + ] + >>> get_driver_style('ALO', style=my_styles, session=session) + {'linestyle': 'solid', 'color': '#00665e', 'custom_arg': True} + >>> get_driver_style('STR', style=my_styles, session=session) + {'linestyle': 'dotted', 'color': '#FF0060', 'other_arg': 10} + + .. seealso:: + :ref:`sphx_glr_examples_gallery_plot_driver_styling.py` + + Args: + identifier: driver abbreviation or recognizable part of the driver name + style: list of matplotlib plot arguments that should be used for + styling or a list of custom style dictionaries + session: the session for which the data should be obtained + colormap: one of ``'default'`` or ``'official'`` + additional_color_kws: A list of keys that should additionally be + treated as colors. This is most usefull for making the magic + ``'auto'`` color work with custom styling options. + + Returns: a dictionary of plot style arguments that can be directly passed + to a matplotlib plot function using the ``**`` expansion operator + """ + stylers = { + 'linestyle': ['solid', 'dashed', 'dashdot', 'dotted'], + 'marker': ['x', 'o', '^', 'D'] + } + + # color keyword arguments that are supported by various matplotlib + # functions + color_kwargs = ( + # generic + 'color', 'colors', 'c', + # .plot + 'gapcolor', + 'markeredgecolor', 'mec', + 'markerfacecolor', 'mfc', + 'markerfacecoloralt', 'mfcalt', + # .scatter + 'facecolor', 'facecolors', 'fc', + 'edgecolor', 'edgecolors', 'ec', + # .errorbar + 'ecolor', + # add user defined color keyword arguments + *additional_color_kws + ) + + dti = _get_driver_team_mapping(session) + abb = get_driver_abbreviation(identifier, session=session) + team = dti.abbreviation_to_team[abb] + idx = dti.team_to_abbreviations[team].index(abb) + + if isinstance(style, (list, tuple)) and (len(style) == 0): + raise ValueError + + if isinstance(style, str): + style = [style] + + plot_style = dict() + + if isinstance(style[0], str): + # generate the plot style based on the provided keyword + # arguments + for opt in style: + if opt in color_kwargs: + value = get_team_color(team, colormap=colormap, + session=session) + elif opt in stylers: + value = stylers[opt][idx] + else: + raise ValueError(f"'{opt}' is not a supported styling " + f"option") + plot_style[opt] = value + + elif isinstance(style[0], dict): + # copy the correct user provided style and replace any 'auto' + # colors with the correct color value + plot_style = style[idx].copy() + for kwarg in color_kwargs: + if plot_style.get(kwarg, None) == 'auto': + color = get_team_color(team, colormap=colormap, + session=session) + plot_style[kwarg] = color + + return plot_style + + +def add_sorted_driver_legend(ax: matplotlib.axes.Axes, *, session: Session): + """ + Adds a legend to the axis where drivers are ordered by team and within a + team in the same order that is used for selecting plot styles. + + This function is a drop in replacement for calling Matplotlib's + ``ax.legend()`` method. It can only be used when driver names or driver + abbreviations are used as labels for the legend. + + There is no particular need to use this function except to make the + legend more visually pleasing. + + .. seealso:: + :ref:`sphx_glr_examples_gallery_plot_driver_styling.py` + + Args: + ax: An instance of a Matplotlib ``Axes`` object + session: the session for which the data should be obtained + """ + dti = _get_driver_team_mapping(session) + handles, labels = ax.get_legend_handles_labels() + + teams_list = list(dti.team_to_abbreviations.keys()) + + # create an intermediary list where each element is a tuple that + # contains (team_idx, driver_idx, handle, label). Then sort this list + # based on the team_idx and driver_idx. As a result, drivers from the + # same team will be next to each other and in the same order as their + # styles are cycled. + ref = list() + for hdl, lbl in zip(handles, labels): + abb = get_driver_abbreviation(identifier=lbl, session=session) + team = dti.abbreviation_to_team[abb] + team_idx = teams_list.index(team) + driver_idx = dti.team_to_abbreviations[team].index(abb) + ref.append((team_idx, driver_idx, hdl, lbl)) + + # sort based only on team_idx and driver_idx (i.e. first two entries) + ref.sort(key=lambda e: e[:2]) + + handles_new = list() + labels_new = list() + for elem in ref: + handles_new.append(elem[2]) + labels_new.append(elem[3]) + + ax.legend(handles_new, labels_new) diff --git a/fastf1/plotting.py b/fastf1/plotting/_plotting.py similarity index 50% rename from fastf1/plotting.py rename to fastf1/plotting/_plotting.py index 32d4e8ac3..0f83cb8a7 100644 --- a/fastf1/plotting.py +++ b/fastf1/plotting/_plotting.py @@ -22,10 +22,7 @@ release. """ import warnings -from typing import ( - Dict, - List -) +from typing import List import numpy as np import pandas as pd @@ -45,148 +42,15 @@ "Plotting of timedelta values will be restricted!", UserWarning) -import warnings +from fastf1.plotting._drivers import ( + get_driver_color, + get_team_color +) -with warnings.catch_warnings(): - warnings.filterwarnings('ignore', - message="Using slow pure-python SequenceMatcher") - # suppress that warning, it's confusing at best here, we don't need fast - # sequence matching and the installation (on windows) some effort - from thefuzz import fuzz - - -class __TeamColorsWarnDict(dict): - """Implements userwarning on KeyError in :any:`TEAM_COLORS` after - changing team names.""" - - def get(self, key, default=None): - value = super().get(key, default) - if value is None: - self.warn_change() - return value - - def __getitem__(self, item): - try: - return super().__getitem__(item) - except KeyError as err: - self.warn_change() - raise err - except Exception as err: - raise err - - def warn_change(self): - warnings.warn( - "Team names in `TEAM_COLORS` are now lower-case and only contain " - "the most identifying part of the name. " - "Use function `.team_color` alternatively.", UserWarning - ) - - -TEAM_COLORS = __TeamColorsWarnDict({ - 'mercedes': '#00d2be', 'ferrari': '#dc0000', - 'red bull': '#0600ef', 'mclaren': '#ff8700', - 'alpine': '#0090ff', 'aston martin': '#006f62', - 'alfa romeo': '#900000', 'alphatauri': '#2b4562', - 'haas': '#ffffff', 'williams': '#005aff' -}) -"""Mapping of team names to team colors (hex color codes). -(current season only)""" - -TEAM_TRANSLATE: Dict[str, str] = { - 'MER': 'mercedes', 'FER': 'ferrari', - 'RBR': 'red bull', 'MCL': 'mclaren', - 'APN': 'alpine', 'AMR': 'aston martin', - 'ARR': 'alfa romeo', 'APT': 'alphatauri', - 'HAA': 'haas', 'WIL': 'williams'} -"""Mapping of team names to theirs respective abbreviations.""" - -DRIVER_COLORS: Dict[str, str] = { - "valtteri bottas": "#900000", - "zhou guanyu": "#500000", - "theo pourchaire": "#700000", - - "nyck de vries": "#1e3d61", - "yuki tsunoda": "#356cac", - "daniel ricciardo": "#2b4562", - "liam lawson": "#2b4562", - "isack hadjar": "#1e6176", - - "pierre gasly": "#0090ff", - "esteban ocon": "#70c2ff", - "jack doohan": "#0075c2", - - "fernando alonso": "#006f62", - "lance stroll": "#25a617", - "felipe drugovich": "#2f9b90", - - "charles leclerc": "#dc0000", - "carlos sainz": "#ff8181", - "robert shwartzman": "#9c0000", - "oliver bearman": "#c40000", - - "kevin magnussen": "#ffffff", - "nico hulkenberg": "#cacaca", - - "oscar piastri": "#ff8700", - "lando norris": "#eeb370", - "pato oward": "#ee6d3a", - - "lewis hamilton": "#00d2be", - "george russell": "#24ffff", - "frederik vesti": "#00a6ff", - - "max verstappen": "#0600ef", - "sergio perez": "#716de2", - "jake dennis": "#9f99e2", - - "alexander albon": "#005aff", - "logan sargeant": "#012564", - "zak osullivan": "#1b3d97", -} -"""Mapping of driver names to driver colors (hex color codes). -(current season only)""" - -DRIVER_TRANSLATE: Dict[str, str] = { - 'LEC': 'charles leclerc', 'SAI': 'carlos sainz', - 'SHW': 'robert shwartzman', - 'VER': 'max verstappen', 'PER': 'sergio perez', - 'DEN': 'jake dennis', - 'PIA': 'oscar piastri', 'NOR': 'lando norris', - 'OWA': 'pato oward', - 'GAS': 'pierre gasly', 'OCO': 'esteban ocon', - 'DOO': 'jack doohan', - 'BOT': 'valtteri bottas', 'ZHO': 'zhou guanyu', - 'POU': 'theo pourchaire', - 'DEV': 'nyck de vries', 'TSU': 'yuki tsunoda', - 'RIC': 'daniel ricciardo', 'LAW': 'liam lawson', - 'HAD': 'isack hadjar', - 'MAG': 'kevin magnussen', 'HUL': 'nico hulkenberg', - 'BEA': 'oliver bearman', - 'ALO': 'fernando alonso', 'STR': 'lance stroll', - 'DRU': 'felipe drugovich', - 'HAM': 'lewis hamilton', 'RUS': 'george russell', - 'VES': 'frederik vesti', - 'ALB': 'alexander albon', 'SAR': 'logan sargeant', - 'OSU': 'zak osullivan'} -"""Mapping of driver names to theirs respective abbreviations.""" - -COMPOUND_COLORS: Dict[str, str] = { - "SOFT": "#da291c", - "MEDIUM": "#ffd12e", - "HARD": "#f0f0ec", - "INTERMEDIATE": "#43b02a", - "WET": "#0067ad", - "UNKNOWN": "#00ffff", - "TEST-UNKNOWN": "#434649" -} -"""Mapping of tyre compound names to compound colors (hex color codes). -(current season only)""" - -COLOR_PALETTE: List[str] = ['#FF79C6', '#50FA7B', '#8BE9FD', '#BD93F9', +_COLOR_PALETTE: List[str] = ['#FF79C6', '#50FA7B', '#8BE9FD', '#BD93F9', '#FFB86C', '#FF5555', '#F1FA8C'] -"""The default color palette for matplotlib plot lines in fastf1's color -scheme.""" +# The default color palette for matplotlib plot lines in fastf1's color scheme def setup_mpl( @@ -230,16 +94,17 @@ def setup_mpl( def driver_color(identifier: str) -> str: - """Get a driver's color from a driver name or abbreviation. + """ + Get a driver's color from a driver name or abbreviation. + + .. deprecated:: + This function is deprecated and will be removed in a future version. + Use :func:`~fastf1.plotting.get_driver_color` instead. This function will try to find a matching driver for any identifier string - that is passed to it. This involves case insensitive matching and partial + that is passed to it. This involves case-insensitive matching and partial string matching. - If you want exact string matching, you should use the - :any:`DRIVER_COLORS` dictionary directly, using :any:`DRIVER_TRANSLATE` to - convert abbreviations to team names if necessary. - Example:: >>> driver_color('charles leclerc') @@ -266,49 +131,24 @@ def driver_color(identifier: str) -> str: Returns: str: hex color code """ - - if identifier.upper() in DRIVER_TRANSLATE: - # try short team abbreviations first - return DRIVER_COLORS[DRIVER_TRANSLATE[identifier.upper()]] - else: - identifier = identifier.lower() - - # check for an exact team name match - if identifier in DRIVER_COLORS: - return DRIVER_COLORS[identifier] - - # check for exact partial string match - for team_name, color in DRIVER_COLORS.items(): - if identifier in team_name: - return color - - # do fuzzy string matching - key_ratios = list() - for existing_key in DRIVER_COLORS.keys(): - ratio = fuzz.ratio(identifier, existing_key) - key_ratios.append((ratio, existing_key)) - key_ratios.sort(reverse=True) - if ((key_ratios[0][0] < 35) - or (key_ratios[0][0] / key_ratios[1][0] < 1.2)): - # ensure that the best match has a minimum accuracy (35 out of - # 100) and that it has a minimum confidence (at least 20% better - # than second best) - raise KeyError - best_matched_key = key_ratios[0][1] - return DRIVER_COLORS[best_matched_key] + warnings.warn("The function `driver_color` is deprecated and will be " + "removed in a future version. Use " + "`~fastf1.plotting.get_driver_color` instead.") + return get_driver_color(identifier, session=None, _variants=True) def team_color(identifier: str) -> str: - """Get a team's color from a team name or abbreviation. + """ + Get a team's color from a team name or abbreviation. + + .. deprecated:: + This function is deprecated and will be removed in a future version. + Use :func:`~fastf1.plotting.get_team_color` instead. This function will try to find a matching team for any identifier string - that is passed to it. This involves case insensitive matching and partial + that is passed to it. This involves case-insensitive matching and partial string matching. - If you want exact string matching, you should use the - :any:`TEAM_COLORS` dictionary directly, using :any:`TEAM_TRANSLATE` to - convert abbreviations to team names if necessary. - Example:: >>> team_color('Red Bull') @@ -339,55 +179,10 @@ def team_color(identifier: str) -> str: Returns: str: hex color code """ - if identifier.upper() in TEAM_TRANSLATE: - # try short team abbreviations first - return TEAM_COLORS[TEAM_TRANSLATE[identifier.upper()]] - else: - identifier = identifier.lower() - # remove common non-unique words - for word in ('racing', 'team', 'f1', 'scuderia'): - identifier = identifier.replace(word, "") - - # check for an exact team name match - if identifier in TEAM_COLORS: - return TEAM_COLORS[identifier] - - # check for exact partial string match - for team_name, color in TEAM_COLORS.items(): - if identifier in team_name: - return color - - # do fuzzy string matching - key_ratios = list() - for existing_key in TEAM_COLORS.keys(): - ratio = fuzz.ratio(identifier, existing_key) - key_ratios.append((ratio, existing_key)) - key_ratios.sort(reverse=True) - if ((key_ratios[0][0] < 35) - or (key_ratios[0][0] / key_ratios[1][0] < 1.2)): - # ensure that the best match has a minimum accuracy (35 out of - # 100) and that it has a minimum confidence (at least 20% better - # than second best) - raise KeyError - best_matched_key = key_ratios[0][1] - return TEAM_COLORS[best_matched_key] - - -def lapnumber_axis(ax, axis='xaxis'): - """Set axis to integer ticks only." - - Args: - ax: matplotlib axis - axis: can be 'xaxis' or 'yaxis' - - Returns: - the modified axis instance - - """ - getattr(ax, axis).get_major_locator().set_params(integer=True, - min_n_ticks=0) - - return ax + warnings.warn("The function `team_color` is deprecated and will be " + "removed in a future version. Use " + "`~fastf1.plotting.get_team_color` instead.") + return get_team_color(identifier, session=None) def _enable_timple(): @@ -486,7 +281,7 @@ def _enable_fastf1_color_scheme(): plt.rcParams['axes.titlesize'] = '19' plt.rcParams['axes.titlepad'] = '12' plt.rcParams['axes.titleweight'] = 'light' - plt.rcParams['axes.prop_cycle'] = cycler('color', COLOR_PALETTE) + plt.rcParams['axes.prop_cycle'] = cycler('color', _COLOR_PALETTE) plt.rcParams['legend.fancybox'] = False plt.rcParams['legend.facecolor'] = (0.1, 0.1, 0.1, 0.7) plt.rcParams['legend.edgecolor'] = (0.1, 0.1, 0.1, 0.9)