├── course ├── lessons │ ├── README.rst │ ├── lesson1 │ │ ├── README.rst │ │ ├── twelve_xt.png │ │ └── plot_xt.py │ └── lesson2 │ │ ├── README.rst │ │ ├── plot_pitches.py │ │ └── plot_radar.py ├── source │ ├── logo.png │ ├── logo-blue.png │ ├── course_overview.rst │ ├── index.rst │ └── conf.py ├── Makefile └── make.bat ├── README.md ├── environment.yml ├── .readthedocs.yml ├── LICENSE └── .gitignore /course/lessons/README.rst: -------------------------------------------------------------------------------- 1 | Course 2 | ====== 3 | 4 | Welcome to the course. 5 | -------------------------------------------------------------------------------- /course/lessons/lesson1/README.rst: -------------------------------------------------------------------------------- 1 | -------- 2 | Lesson 1 3 | -------- 4 | 5 | Learn about Expected Threat. 6 | -------------------------------------------------------------------------------- /course/source/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewRowlinson/course-template/main/course/source/logo.png -------------------------------------------------------------------------------- /course/source/logo-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewRowlinson/course-template/main/course/source/logo-blue.png -------------------------------------------------------------------------------- /course/lessons/lesson1/twelve_xt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewRowlinson/course-template/main/course/lessons/lesson1/twelve_xt.png -------------------------------------------------------------------------------- /course/lessons/lesson2/README.rst: -------------------------------------------------------------------------------- 1 | -------- 2 | Lesson 2 3 | -------- 4 | 5 | Learning more about visualisations using mplsoccer and Matplotlib. 6 | -------------------------------------------------------------------------------- /course/source/course_overview.rst: -------------------------------------------------------------------------------- 1 | .. image:: logo-blue.png 2 | :width: 157 3 | :align: center 4 | 5 | ----------- 6 | Source code 7 | ----------- 8 | The source code is available `here `_ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # course-template 2 | A template for creating a course with readthedocs. 3 | 4 | # Create the docs locally 5 | To create the docs run the following commands from the course directory: 6 | 7 | ```python 8 | make clean 9 | make html 10 | ``` 11 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: course 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - cmasher 6 | - jupyter 7 | - lxml 8 | - pandas 9 | - pillow 10 | - pyarrow 11 | - requests 12 | - scipy 13 | - seaborn 14 | - sphinx 15 | - sphinx-gallery 16 | - sphinx_rtd_theme 17 | - pip 18 | - pip: 19 | - adjustText 20 | - highlight-text 21 | - kloppy 22 | - mplsoccer 23 | - sphinxcontrib-youtube 24 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | build: 9 | os: "ubuntu-20.04" 10 | tools: 11 | python: "mambaforge-4.10" 12 | 13 | # Build documentation in the docs/ directory with Sphinx 14 | sphinx: 15 | configuration: course/source/conf.py 16 | 17 | # Optionally build your docs in additional formats such as PDF 18 | #formats: 19 | # - pdf 20 | 21 | conda: 22 | environment: environment.yml 23 | -------------------------------------------------------------------------------- /course/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /course/source/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: logo-blue.png 2 | :width: 157 3 | :align: center 4 | 5 | **Welcome to the course.** 6 | 7 | 8 | .. youtube:: MIGQFcVO7-s 9 | :width: 368 10 | :height: 207 11 | 12 | ----------- 13 | Source code 14 | ----------- 15 | The source code is available `here `_ 16 | 17 | Contents 18 | ======== 19 | 20 | .. toctree:: 21 | :maxdepth: 1 22 | :caption: Course intro: 23 | 24 | course_overview 25 | 26 | .. toctree:: 27 | :maxdepth: 1 28 | :caption: Lesson 1: 29 | 30 | gallery/lesson1/plot_xt 31 | 32 | .. toctree:: 33 | :maxdepth: 1 34 | :caption: Lesson 2: 35 | 36 | gallery/lesson2/plot_pitches 37 | gallery/lesson2/plot_radar 38 | -------------------------------------------------------------------------------- /course/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Andrew Rowlinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /course/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | import sphinx_gallery 7 | from sphinx_gallery.sorting import ExplicitOrder 8 | from sphinx_gallery.sorting import ExampleTitleSortKey 9 | 10 | # -- Project information ----------------------------------------------------- 11 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 12 | 13 | project = 'Course Template' 14 | copyright = '2022, Andrew Rowlinson' 15 | author = 'Andrew Rowlinson' 16 | 17 | # -- General configuration --------------------------------------------------- 18 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 19 | 20 | extensions = ['sphinx.ext.mathjax', 21 | 'sphinx_gallery.gen_gallery', 22 | 'sphinxcontrib.youtube', 23 | ] 24 | 25 | templates_path = ['_templates'] 26 | exclude_patterns = [] 27 | 28 | # sphinx gallery 29 | sphinx_gallery_conf = { 30 | 'examples_dirs': ['../lessons'], 31 | 'gallery_dirs': ['gallery'], 32 | 'image_scrapers': ('matplotlib'), 33 | 'matplotlib_animations': True, 34 | 'within_subsection_order': ExampleTitleSortKey, 35 | 'subsection_order': ExplicitOrder(['../lessons/lesson1', 36 | '../lessons/lesson2', 37 | ])} 38 | 39 | # -- Options for HTML output ------------------------------------------------- 40 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 41 | 42 | html_theme = 'sphinx_rtd_theme' 43 | html_static_path = ['_static'] 44 | 45 | # add logo 46 | html_logo = "logo.png" 47 | html_theme_options = {'logo_only': True, 48 | 'display_version': False} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # files for website build 132 | course/build/ 133 | course/source/_static/ 134 | course/source/_templates/ 135 | course/source/gallery/ 136 | -------------------------------------------------------------------------------- /course/lessons/lesson1/plot_xt.py: -------------------------------------------------------------------------------- 1 | """ 2 | Lesson 1: Explaining xT 3 | ======================= 4 | 5 | This is an example lesson, which is copied from this excellent 6 | `blog post `_ by 7 | `David Sumpter (@soccermatics) `_. I have 8 | dispersed some random python code inbetween to demonstrate how Sphinx gallery 9 | can be used to generate examples. 10 | 11 | One of the key questions for everyone interested in football — 12 | from coaches, through scouts to the fans — 13 | is how do we assess the quality of a player using data. 14 | If they score a lot of goals they must be good and, more recently, 15 | we have understood that finding good scoring opportunities 16 | (having high xG) is also good. But what about all those passes, 17 | dribbles, blocks and interceptions. How do we value them? 18 | 19 | Some inline math :math:`s_{x,y}` for the probability of shooting at position 20 | x y and the full formula for expected threat 21 | from `Karun's blog `_ 22 | 23 | .. math:: 24 | 25 | \\texttt{xT}_{x,y} = (s_{x,y} \\times g_{x,y}) + (m_{x,y} \\times \\sum_{z=1}^{16} \\sum_{w=1}^{12} T_{(x,y)\\rightarrow(z,w)}\\texttt{xT}_{z,w}) 26 | 27 | It is with this in mind that 28 | `The Athletic `_ 29 | have started using expected threat when talking about player and team performance. 30 | The idea is to assign a value to every point on the football field based on the probability 31 | that having the ball at that point will lead to a goal. One example of these probability maps is shown below. 32 | 33 | .. image:: ../../../lessons/lesson1/twelve_xt.png 34 | 35 | """ 36 | 37 | import matplotlib.pyplot as plt 38 | 39 | ############################################################################## 40 | # Markov chains 41 | # ------------- 42 | # In order to evaluate actions we look at how an action changes the probability of scoring. 43 | # It is this change in probability of scoring which is the expected threat (xT). 44 | # If a player makes a pass which moves the ball from a place where it is unlikely 45 | # for their team to score, to a place where they are more likely to score, 46 | # then they have increased the xT in favour of their team. In general, 47 | # the nearer you get the ball to the goal the more likely your team is to 48 | # score (although if you look carefully passes back to the goalkeeper are also valuable). 49 | # 50 | # More details of how expected threat is calculated can be found 51 | # in Friends of Tracking in this video. 52 | 53 | ############################################################################## 54 | #.. youtube:: 0VAdzaid8L8 55 | # :width: 375 56 | # :height: 210 57 | 58 | ############################################################################## 59 | # Expected threat history 60 | # ----------------------- 61 | # Expected Threat was invented by Sarah Rudd in 2011. She didn’t call it that. 62 | # In fact, she didn’t call it anything, but she had the mathematical insight, 63 | # using Markov chains, on which it is based. In this video you can see her go 64 | # through all the steps. And, on that basis she was recruited to StatDNA, 65 | # who were very soon after bought up by Arsenal. 66 | # The name xT was first used by Karun Singh, who reproposed it in 67 | # the public sphere in a blog post in 2018. 68 | # 69 | # It is extra important that when we have a clear example of an idea from a 70 | # female scientist in a male-dominated area, which is now used everywhere, 71 | # that we pause to make sure everyone knows where it came from. 72 | # There is a history of womens’ contributions being forgotten in Science. 73 | # It would be embarrassing if we made this same error in the so-called modern 74 | # era, especially in football. 75 | # 76 | # So when we hear about how Liverpool used expected goals added 77 | # (yes, that is expected threat) in recruitment during 2018–19 78 | # or we about how Opta and Statsbomb have there own version of expected 79 | # threat, remember that all this came from the work of one very determined 80 | # young woman, more than ten years ago, who went to as many sports 81 | # analytics conferences as she could and pestered everyone she met 82 | # until she got one of the first ever jobs in football analytics. 83 | # 84 | # (I wrote about Sarah Rudd in Soccermatics 85 | # and I have booked in her in for a Friends of Tracking video during the autumn, 86 | # so there will be a chance to hear more about her story soon) 87 | 88 | fig, ax = plt.subplots() 89 | plt.show() 90 | -------------------------------------------------------------------------------- /course/lessons/lesson2/plot_pitches.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pitch Visualisations 3 | ==================== 4 | 5 | First we import the Pitch classes and matplotlib 6 | """ 7 | import matplotlib.pyplot as plt 8 | 9 | from mplsoccer import Pitch, VerticalPitch 10 | 11 | ############################################################################## 12 | # Draw a pitch on a new axis 13 | # -------------------------- 14 | # Let's plot on a new axis first. 15 | 16 | pitch = Pitch() 17 | # specifying figure size (width, height) 18 | fig, ax = pitch.draw(figsize=(8, 4)) 19 | 20 | ############################################################################## 21 | # Draw on an existing axis 22 | # ------------------------ 23 | # mplsoccer also plays nicely with other matplotlib figures. To draw a pitch on an 24 | # existing matplotlib axis specify an ``ax`` in the ``draw`` method. 25 | 26 | fig, axs = plt.subplots(nrows=1, ncols=2) 27 | pitch = Pitch() 28 | pie = axs[0].pie(x=[5, 15]) 29 | pitch.draw(ax=axs[1]) 30 | 31 | ############################################################################## 32 | # Supported data providers 33 | # ------------------------ 34 | # mplsoccer supports 9 pitch types by specifying the ``pitch_type`` argument: 35 | # 'statsbomb', 'opta', 'tracab', 'wyscout', 'uefa', 'metricasports', 'custom', 36 | # 'skillcorner' and 'secondspectrum'. 37 | # If you are using tracking data or the custom pitch ('metricasports', 'tracab', 38 | # 'skillcorner', 'secondspectrum' or 'custom'), you also need to specify the 39 | # ``pitch_length`` and ``pitch_width``, which are typically 105 and 68 respectively. 40 | 41 | pitch = Pitch(pitch_type='opta') # example plotting an Opta/ Stats Perform pitch 42 | fig, ax = pitch.draw() 43 | 44 | ############################################################################## 45 | 46 | pitch = Pitch(pitch_type='tracab', # example plotting a tracab pitch 47 | pitch_length=105, pitch_width=68, 48 | axis=True, label=True) # showing axis labels is optional 49 | fig, ax = pitch.draw() 50 | 51 | ############################################################################## 52 | # Adjusting the plot layout 53 | # ------------------------- 54 | # mplsoccer also plots on grids by specifying nrows and ncols. 55 | # The default is to use 56 | # tight_layout. See: https://matplotlib.org/stable/tutorials/intermediate/tight_layout_guide.html. 57 | 58 | pitch = Pitch() 59 | fig, axs = pitch.draw(nrows=2, ncols=3) 60 | 61 | ############################################################################## 62 | # But you can also use constrained layout 63 | # by setting ``constrained_layout=True`` and ``tight_layout=False``, which may look better. 64 | # See: https://matplotlib.org/stable/tutorials/intermediate/constrainedlayout_guide.html. 65 | 66 | pitch = Pitch() 67 | fig, axs = pitch.draw(nrows=2, ncols=3, tight_layout=False, constrained_layout=True) 68 | 69 | ############################################################################## 70 | # If you want more control over how pitches are placed 71 | # you can use the grid method. This also works for one pitch (nrows=1 and ncols=1). 72 | # It also plots axes for an endnote and title (see the plot_grid example for more information). 73 | 74 | pitch = Pitch() 75 | fig, axs = pitch.grid(nrows=3, ncols=3, figheight=10, 76 | # the grid takes up 71.5% of the figure height 77 | grid_height=0.715, 78 | # 5% of grid_height is reserved for space between axes 79 | space=0.05, 80 | # centers the grid horizontally / vertically 81 | left=None, bottom=None) 82 | 83 | ############################################################################## 84 | # Pitch orientation 85 | # ----------------- 86 | # There are four basic pitch orientations. 87 | # To get vertical pitches use the VerticalPitch class. 88 | # To get half pitches use the half=True argument. 89 | # 90 | # Horizontal full 91 | 92 | pitch = Pitch(half=False) 93 | fig, ax = pitch.draw() 94 | 95 | ############################################################################## 96 | # Vertical full 97 | 98 | pitch = VerticalPitch(half=False) 99 | fig, ax = pitch.draw() 100 | 101 | ############################################################################## 102 | # Horizontal half 103 | pitch = Pitch(half=True) 104 | fig, ax = pitch.draw() 105 | 106 | ############################################################################## 107 | # Vertical half 108 | pitch = VerticalPitch(half=True) 109 | fig, ax = pitch.draw() 110 | 111 | ############################################################################## 112 | # You can also adjust the pitch orientations with the ``pad_left``, ``pad_right``, 113 | # ``pad_bottom`` and ``pad_top`` arguments to make arbitrary pitch shapes. 114 | 115 | pitch = VerticalPitch(half=True, 116 | pad_left=-10, # bring the left axis in 10 data units (reduce the size) 117 | pad_right=-10, # bring the right axis in 10 data units (reduce the size) 118 | pad_top=10, # extend the top axis 10 data units 119 | pad_bottom=20) # extend the bottom axis 20 data units 120 | fig, ax = pitch.draw() 121 | 122 | ############################################################################## 123 | # Pitch appearance 124 | # ---------------- 125 | # The pitch appearance is adjustable. 126 | # Use ``pitch_color`` and ``line_color``, and ``stripe_color`` (if ``stripe=True``) 127 | # to adjust the colors. 128 | 129 | pitch = Pitch(pitch_color='#aabb97', line_color='white', 130 | stripe_color='#c2d59d', stripe=True) # optional stripes 131 | fig, ax = pitch.draw() 132 | 133 | ############################################################################## 134 | # Line style 135 | # ---------- 136 | # The pitch line style is adjustable. 137 | # Use ``linestyle`` and ``goal_linestyle`` to adjust the colors. 138 | 139 | pitch = Pitch(linestyle='--', linewidth=1, goal_linestyle='-') 140 | fig, ax = pitch.draw() 141 | 142 | ############################################################################## 143 | # Line alpha 144 | # ---------- 145 | # The pitch transparency is adjustable. 146 | # Use ``pitch_alpha`` and ``goal_alpha`` to adjust the colors. 147 | 148 | pitch = Pitch(line_alpha=0.5, goal_alpha=0.3) 149 | fig, ax = pitch.draw() 150 | 151 | ############################################################################## 152 | # Corner arcs 153 | # ----------- 154 | # You can add corner arcs to the pitch by setting ``corner_arcs`` = True 155 | 156 | pitch = VerticalPitch(corner_arcs=True, half=True) 157 | fig, ax = pitch.draw(figsize=(10, 7.727)) 158 | 159 | ############################################################################## 160 | # Juego de Posición 161 | # ----------------- 162 | # You can add the Juego de Posición pitch lines and shade the middle third 163 | 164 | pitch = Pitch(positional=True, shade_middle=True, positional_color='#eadddd', shade_color='#f2f2f2') 165 | fig, ax = pitch.draw() 166 | 167 | ############################################################################## 168 | # mplsoccer can also plot grass pitches by setting ``pitch_color='grass'``. 169 | 170 | pitch = Pitch(pitch_color='grass', line_color='white', 171 | stripe=True) # optional stripes 172 | fig, ax = pitch.draw() 173 | 174 | ############################################################################## 175 | # Three goal types are included ``goal_type='line'``, ``goal_type='box'``, 176 | # and ``goal_type='circle'`` 177 | 178 | fig, axs = plt.subplots(nrows=3, figsize=(10, 18)) 179 | pitch = Pitch(goal_type='box', goal_alpha=1) # you can also adjust the transparency (alpha) 180 | pitch.draw(axs[0]) 181 | pitch = Pitch(goal_type='line') 182 | pitch.draw(axs[1]) 183 | pitch = Pitch(goal_type='circle', linewidth=1) 184 | pitch.draw(axs[2]) 185 | 186 | ############################################################################## 187 | # The line markings and spot size can be adjusted via ``linewidth`` and ``spot_scale``. 188 | # Spot scale also adjusts the size of the circle goal posts. 189 | 190 | pitch = Pitch(linewidth=3, 191 | # the size of the penalty and center spots relative to the pitch_length 192 | spot_scale=0.01) 193 | fig, ax = pitch.draw() 194 | 195 | ############################################################################## 196 | # If you need to lift the pitch markings above other elements of the chart. 197 | # You can do this via ``line_zorder``, ``stripe_zorder``, 198 | # ``positional_zorder``, and ``shade_zorder``. 199 | 200 | pitch = Pitch(line_zorder=2) # e.g. useful if you want to plot pitch lines over heatmaps 201 | fig, ax = pitch.draw() 202 | 203 | ############################################################################## 204 | # Axis 205 | # ---- 206 | # By default mplsoccer turns of the axis (border), ticks, and labels. 207 | # You can use them by setting the ``axis``, ``label`` and ``tick`` arguments. 208 | 209 | pitch = Pitch(axis=True, label=True, tick=True) 210 | fig, ax = pitch.draw() 211 | 212 | ############################################################################## 213 | # xkcd 214 | # ---- 215 | # Finally let's use matplotlib's xkcd theme. 216 | 217 | plt.xkcd() 218 | pitch = Pitch(pitch_color='grass', stripe=True) 219 | fig, ax = pitch.draw(figsize=(8, 4)) 220 | annotation = ax.annotate('Who can resist this?', (60, 10), fontsize=30, ha='center') 221 | 222 | plt.show() # If you are using a Jupyter notebook you do not need this line 223 | -------------------------------------------------------------------------------- /course/lessons/lesson2/plot_radar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Radar Charts 3 | ============ 4 | 5 | * ``mplsoccer``, ``radar_chart`` module helps one to plot radar charts in a few lines of code. 6 | 7 | * The radar-chart inspiration is `StatsBomb `_ 8 | and `Rami Moghadam `_ 9 | 10 | * Here we will show some examples of how to use ``mplsoccer`` to plot radar charts. 11 | 12 | We have re-written the `Soccerplots `_ Radar module 13 | to enable greater customisation of the Radar. You can now set the edge color, decide the number of 14 | concentric circles, and use hatching or path_effects. 15 | """ 16 | 17 | from mplsoccer import Radar, FontManager, grid 18 | import matplotlib.pyplot as plt 19 | 20 | ############################################################################## 21 | # Setting the Radar Boundaries 22 | # ---------------------------- 23 | # One of the most important decisions with Radars is setting the Radar's boundaries. 24 | # StatsBomb popularised the use of Radars for showing player statistics. I recommend checking out 25 | # `understanding football radars for mugs and muggles 26 | # `_. 27 | # StatsBomb's rule of thumb is: "Radar boundaries represent the top 5% and bottom 5% 28 | # of all statistical production by players in that position." 29 | 30 | # parameter names of the statistics we want to show 31 | params = ["npxG", "Non-Penalty Goals", "xA", "Key Passes", "Through Balls", 32 | "Progressive Passes", "Shot-Creating Actions", "Goal-Creating Actions", 33 | "Dribbles Completed", "Pressure Regains", "Touches In Box", "Miscontrol"] 34 | 35 | # The lower and upper boundaries for the statistics 36 | low = [0.08, 0.0, 0.1, 1, 0.6, 4, 3, 0.3, 0.3, 2.0, 2, 0] 37 | high = [0.37, 0.6, 0.6, 4, 1.2, 10, 8, 1.3, 1.5, 5.5, 5, 5] 38 | 39 | # Add anything to this list where having a lower number is better 40 | # this flips the statistic 41 | lower_is_better = ['Miscontrol'] 42 | 43 | ############################################################################## 44 | # Instantiate the Radar Class 45 | # --------------------------- 46 | # We will instantiate a ``Radar`` object with the above parameters so that we can re-use it 47 | # several times. 48 | 49 | radar = Radar(params, low, high, 50 | lower_is_better=lower_is_better, 51 | # whether to round any of the labels to integers instead of decimal places 52 | round_int=[False]*len(params), 53 | num_rings=4, # the number of concentric circles (excluding center circle) 54 | # if the ring_width is more than the center_circle_radius then 55 | # the center circle radius will be wider than the width of the concentric circles 56 | ring_width=1, center_circle_radius=1) 57 | 58 | ############################################################################## 59 | # Load some fonts 60 | # --------------- 61 | # We will use mplsoccer's ``FontManager`` to load some fonts from Google Fonts. 62 | # We borrowed the FontManager from the excellent 63 | # `ridge_map library `_. 64 | URL1 = ('https://github.com/googlefonts/SourceSerifProGFVersion/blob/main/' 65 | 'fonts/SourceSerifPro-Regular.ttf?raw=true') 66 | serif_regular = FontManager(URL1) 67 | URL2 = ('https://github.com/googlefonts/SourceSerifProGFVersion/blob/main/' 68 | 'fonts/SourceSerifPro-ExtraLight.ttf?raw=true') 69 | serif_extra_light = FontManager(URL2) 70 | URL3 = ('https://github.com/google/fonts/blob/main/ofl/rubikmonoone/' 71 | 'RubikMonoOne-Regular.ttf?raw=true') 72 | rubik_regular = FontManager(URL3) 73 | URL4 = 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Thin.ttf?raw=true' 74 | robotto_thin = FontManager(URL4) 75 | URL5 = 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Regular.ttf?raw=true' 76 | robotto_regular = FontManager(URL5) 77 | URL6 = 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Bold.ttf?raw=true' 78 | robotto_bold = FontManager(URL6) 79 | 80 | ############################################################################## 81 | # Player Values 82 | # ------------- 83 | # Here are the player values we are going to plot. The values are taken 84 | # from the excellent `fbref `_ website (supplied by StatsBomb). 85 | 86 | bruno_values = [0.22, 0.25, 0.30, 2.54, 0.43, 5.60, 4.34, 0.29, 0.69, 5.14, 4.97, 1.10] 87 | bruyne_values = [0.25, 0.52, 0.37, 3.59, 0.41, 6.36, 5.68, 0.57, 1.23, 4.00, 4.54, 1.39] 88 | erikson_values = [0.13, 0.10, 0.35, 3.08, 0.29, 6.23, 5.08, 0.43, 0.67, 3.07, 1.34, 1.06] 89 | 90 | ############################################################################## 91 | # Making a Simple Radar Chart 92 | # --------------------------- 93 | # Here we will make a very simple radar chart using the ``radar_chart`` module. 94 | # We will only change the default face and edge colors. 95 | 96 | fig, ax = radar.setup_axis() # format axis as a radar 97 | rings_inner = radar.draw_circles(ax=ax, facecolor='#ffb2b2', edgecolor='#fc5f5f') # draw circles 98 | radar_output = radar.draw_radar(bruno_values, ax=ax, 99 | kwargs_radar={'facecolor': '#aa65b2'}, 100 | kwargs_rings={'facecolor': '#66d8ba'}) # draw the radar 101 | radar_poly, rings_outer, vertices = radar_output 102 | range_labels = radar.draw_range_labels(ax=ax, fontsize=15, 103 | fontproperties=robotto_thin.prop) # draw the range labels 104 | param_labels = radar.draw_param_labels(ax=ax, fontsize=15, 105 | fontproperties=robotto_regular.prop) # draw the param labels 106 | 107 | ############################################################################## 108 | # Adding lines from the center to the edge 109 | # ---------------------------------------- 110 | # Here we add spokes from the radar center to the edge using ``Radar.spoke``. 111 | 112 | fig, ax = radar.setup_axis() # format axis as a radar 113 | rings_inner = radar.draw_circles(ax=ax, facecolor='#ffb2b2', edgecolor='#fc5f5f') # draw circles 114 | radar_output = radar.draw_radar(bruno_values, ax=ax, 115 | kwargs_radar={'facecolor': '#aa65b2'}, 116 | kwargs_rings={'facecolor': '#66d8ba'}) # draw the radar 117 | radar_poly, rings_outer, vertices = radar_output 118 | range_labels = radar.draw_range_labels(ax=ax, fontsize=15, zorder=2.5, 119 | fontproperties=robotto_thin.prop) # draw the range labels 120 | param_labels = radar.draw_param_labels(ax=ax, fontsize=15, 121 | fontproperties=robotto_regular.prop) # draw the param labels 122 | lines = radar.spoke(ax=ax, color='#a6a4a1', linestyle='--', zorder=2) 123 | 124 | ############################################################################## 125 | # Making a Simple Comparison 126 | # -------------------------- 127 | # Here we plot two players on the same axes to compare players. 128 | 129 | # plot radar 130 | fig, ax = radar.setup_axis() 131 | rings_inner = radar.draw_circles(ax=ax, facecolor='#ffb2b2', edgecolor='#fc5f5f') 132 | radar_output = radar.draw_radar_compare(bruno_values, bruyne_values, ax=ax, 133 | kwargs_radar={'facecolor': '#00f2c1', 'alpha': 0.6}, 134 | kwargs_compare={'facecolor': '#d80499', 'alpha': 0.6}) 135 | radar_poly, radar_poly2, vertices1, vertices2 = radar_output 136 | range_labels = radar.draw_range_labels(ax=ax, fontsize=15, 137 | fontproperties=robotto_thin.prop) 138 | param_labels = radar.draw_param_labels(ax=ax, fontsize=15, 139 | fontproperties=robotto_regular.prop) 140 | 141 | ############################################################################## 142 | # Comparing three or more players 143 | # ------------------------------- 144 | # Here we demonstrate comparing three players on the same chart. It's possible to 145 | # add as many as you want by stacking ``Radar.draw_radar_solid`` 146 | 147 | # plot radar 148 | fig, ax = radar.setup_axis() 149 | rings_inner = radar.draw_circles(ax=ax, facecolor='#ffb2b2', edgecolor='#fc5f5f') 150 | 151 | radar1, vertices1 = radar.draw_radar_solid(bruno_values, ax=ax, 152 | kwargs={'facecolor': '#aa65b2', 153 | 'alpha': 0.6, 154 | 'edgecolor': '#216352', 155 | 'lw': 3}) 156 | 157 | radar2, vertices2 = radar.draw_radar_solid(bruyne_values, ax=ax, 158 | kwargs={'facecolor': '#66d8ba', 159 | 'alpha': 0.6, 160 | 'edgecolor': '#216352', 161 | 'lw': 3}) 162 | 163 | radar3, vertices3 = radar.draw_radar_solid(erikson_values, ax=ax, 164 | kwargs={'facecolor': '#697cd4', 165 | 'alpha': 0.6, 166 | 'edgecolor': '#222b54', 167 | 'lw': 3}) 168 | 169 | ax.scatter(vertices1[:, 0], vertices1[:, 1], 170 | c='#aa65b2', edgecolors='#502a54', marker='o', s=150, zorder=2) 171 | ax.scatter(vertices2[:, 0], vertices2[:, 1], 172 | c='#66d8ba', edgecolors='#216352', marker='o', s=150, zorder=2) 173 | ax.scatter(vertices3[:, 0], vertices3[:, 1], 174 | c='#697cd4', edgecolors='#222b54', marker='o', s=150, zorder=2) 175 | 176 | range_labels = radar.draw_range_labels(ax=ax, fontsize=25, fontproperties=robotto_thin.prop) 177 | param_labels = radar.draw_param_labels(ax=ax, fontsize=25, fontproperties=robotto_regular.prop) 178 | 179 | ############################################################################## 180 | # Making a Clean Radar 181 | # -------------------- 182 | # Here we exclude the label lines from the plot. 183 | 184 | fig, ax = radar.setup_axis() # format axis as a radar 185 | rings_inner = radar.draw_circles(ax=ax, facecolor='#ffb2b2', edgecolor='#fc5f5f') # draw circles 186 | radar_output = radar.draw_radar(bruno_values, ax=ax, 187 | kwargs_radar={'facecolor': '#aa65b2'}, 188 | kwargs_rings={'facecolor': '#66d8ba'}) # draw the radar 189 | radar_poly, rings_outer, vertices = radar_output 190 | 191 | ############################################################################## 192 | # Adding a title and endnote 193 | # -------------------------- 194 | # Here we will add an endnote and title to the Radar. We will use the grid function to create 195 | # the figure and pass the axs['radar'] axes to the Radar's methods. 196 | 197 | # creating the figure using the grid function from mplsoccer: 198 | fig, axs = grid(figheight=14, grid_height=0.915, title_height=0.06, endnote_height=0.025, 199 | title_space=0, endnote_space=0, grid_key='radar', axis=False) 200 | 201 | # plot the radar 202 | radar.setup_axis(ax=axs['radar']) 203 | rings_inner = radar.draw_circles(ax=axs['radar'], facecolor='#ffb2b2', edgecolor='#fc5f5f') 204 | radar_output = radar.draw_radar(bruno_values, ax=axs['radar'], 205 | kwargs_radar={'facecolor': '#aa65b2'}, 206 | kwargs_rings={'facecolor': '#66d8ba'}) 207 | radar_poly, rings_outer, vertices = radar_output 208 | range_labels = radar.draw_range_labels(ax=axs['radar'], fontsize=25, 209 | fontproperties=robotto_thin.prop) 210 | param_labels = radar.draw_param_labels(ax=axs['radar'], fontsize=25, 211 | fontproperties=robotto_regular.prop) 212 | 213 | # adding the endnote and title text (these axes range from 0-1, i.e. 0, 0 is the bottom left) 214 | # Note we are slightly offsetting the text from the edges by 0.01 (1%, e.g. 0.99) 215 | endnote_text = axs['endnote'].text(0.99, 0.5, 'Inspired By: StatsBomb / Rami Moghadam', fontsize=15, 216 | fontproperties=robotto_thin.prop, ha='right', va='center') 217 | title1_text = axs['title'].text(0.01, 0.65, 'Bruno Fernandes', fontsize=25, 218 | fontproperties=robotto_bold.prop, ha='left', va='center') 219 | title2_text = axs['title'].text(0.01, 0.25, 'Manchester United', fontsize=20, 220 | fontproperties=robotto_regular.prop, 221 | ha='left', va='center', color='#B6282F') 222 | title3_text = axs['title'].text(0.99, 0.65, 'Radar Chart', fontsize=25, 223 | fontproperties=robotto_bold.prop, ha='right', va='center') 224 | title4_text = axs['title'].text(0.99, 0.25, 'Midfielder', fontsize=20, 225 | fontproperties=robotto_regular.prop, 226 | ha='right', va='center', color='#B6282F') 227 | # sphinx_gallery_thumbnail_path = 'gallery/radar/images/sphx_glr_plot_radar_004.png' 228 | 229 | ############################################################################## 230 | # Comparison Radar with Titles 231 | # ---------------------------- 232 | # Here we will make a very simple radar chart using ``mplsoccer`` module ``radar_chart``. 233 | # We will only change the default facecolors. 234 | 235 | # creating the figure using the grid function from mplsoccer: 236 | fig, axs = grid(figheight=14, grid_height=0.915, title_height=0.06, endnote_height=0.025, 237 | title_space=0, endnote_space=0, grid_key='radar', axis=False) 238 | 239 | # plot radar 240 | radar.setup_axis(ax=axs['radar']) # format axis as a radar 241 | rings_inner = radar.draw_circles(ax=axs['radar'], facecolor='#ffb2b2', edgecolor='#fc5f5f') 242 | radar_output = radar.draw_radar_compare(bruno_values, bruyne_values, ax=axs['radar'], 243 | kwargs_radar={'facecolor': '#00f2c1', 'alpha': 0.6}, 244 | kwargs_compare={'facecolor': '#d80499', 'alpha': 0.6}) 245 | radar_poly, radar_poly2, vertices1, vertices2 = radar_output 246 | range_labels = radar.draw_range_labels(ax=axs['radar'], fontsize=25, 247 | fontproperties=robotto_thin.prop) 248 | param_labels = radar.draw_param_labels(ax=axs['radar'], fontsize=25, 249 | fontproperties=robotto_regular.prop) 250 | axs['radar'].scatter(vertices1[:, 0], vertices1[:, 1], 251 | c='#00f2c1', edgecolors='#6d6c6d', marker='o', s=150, zorder=2) 252 | axs['radar'].scatter(vertices2[:, 0], vertices2[:, 1], 253 | c='#d80499', edgecolors='#6d6c6d', marker='o', s=150, zorder=2) 254 | 255 | # adding the endnote and title text (these axes range from 0-1, i.e. 0, 0 is the bottom left) 256 | # Note we are slightly offsetting the text from the edges by 0.01 (1%, e.g. 0.99) 257 | endnote_text = axs['endnote'].text(0.99, 0.5, 'Inspired By: StatsBomb / Rami Moghadam', fontsize=15, 258 | fontproperties=robotto_thin.prop, ha='right', va='center') 259 | title1_text = axs['title'].text(0.01, 0.65, 'Bruno Fernandes', fontsize=25, color='#01c49d', 260 | fontproperties=robotto_bold.prop, ha='left', va='center') 261 | title2_text = axs['title'].text(0.01, 0.25, 'Manchester United', fontsize=20, 262 | fontproperties=robotto_regular.prop, 263 | ha='left', va='center', color='#01c49d') 264 | title3_text = axs['title'].text(0.99, 0.65, 'Kevin De Bruyne', fontsize=25, 265 | fontproperties=robotto_bold.prop, 266 | ha='right', va='center', color='#d80499') 267 | title4_text = axs['title'].text(0.99, 0.25, 'Manchester City', fontsize=20, 268 | fontproperties=robotto_regular.prop, 269 | ha='right', va='center', color='#d80499') 270 | 271 | ############################################################################## 272 | # Dark Theme 273 | # ---------- 274 | 275 | # creating the figure using the grid function from mplsoccer: 276 | fig, axs = grid(figheight=14, grid_height=0.915, title_height=0.06, endnote_height=0.025, 277 | title_space=0, endnote_space=0, grid_key='radar', axis=False) 278 | 279 | # plot the radar 280 | radar.setup_axis(ax=axs['radar'], facecolor='None') 281 | rings_inner = radar.draw_circles(ax=axs['radar'], facecolor='#28252c', edgecolor='#39353f', lw=1.5) 282 | radar_output = radar.draw_radar(bruno_values, ax=axs['radar'], 283 | kwargs_radar={'facecolor': '#d0667a'}, 284 | kwargs_rings={'facecolor': '#1d537f'}) 285 | radar_poly, rings_outer, vertices = radar_output 286 | range_labels = radar.draw_range_labels(ax=axs['radar'], fontsize=25, color='#fcfcfc', 287 | fontproperties=robotto_thin.prop) 288 | param_labels = radar.draw_param_labels(ax=axs['radar'], fontsize=25, color='#fcfcfc', 289 | fontproperties=robotto_regular.prop) 290 | 291 | # adding the endnote and title text (these axes range from 0-1, i.e. 0, 0 is the bottom left) 292 | # Note we are slightly offsetting the text from the edges by 0.01 (1%, e.g. 0.99) 293 | endnote_text = axs['endnote'].text(0.99, 0.5, 'Inspired By: StatsBomb / Rami Moghadam', 294 | color='#fcfcfc', fontproperties=robotto_thin.prop, 295 | fontsize=15, ha='right', va='center') 296 | title1_text = axs['title'].text(0.01, 0.65, 'Bruno Fernandes', fontsize=25, 297 | fontproperties=robotto_bold.prop, 298 | ha='left', va='center', color='#e4dded') 299 | title2_text = axs['title'].text(0.01, 0.25, 'Manchester United', fontsize=20, 300 | fontproperties=robotto_regular.prop, 301 | ha='left', va='center', color='#cc2a3f') 302 | title3_text = axs['title'].text(0.99, 0.65, 'Radar Chart', fontsize=25, 303 | fontproperties=robotto_bold.prop, 304 | ha='right', va='center', color='#e4dded') 305 | title4_text = axs['title'].text(0.99, 0.25, 'Midfielder', fontsize=20, 306 | fontproperties=robotto_regular.prop, 307 | ha='right', va='center', color='#cc2a3f') 308 | 309 | fig.set_facecolor('#121212') 310 | 311 | ############################################################################## 312 | # Ben Eine Theme 313 | # -------------- 314 | # The theme below is inspired by the artist 315 | # `Ben Eine `_. 316 | 317 | # creating the figure using the grid function from mplsoccer: 318 | fig, axs = grid(figheight=14, grid_height=0.915, title_height=0.06, endnote_height=0.025, 319 | title_space=0, endnote_space=0, grid_key='radar', axis=False) 320 | 321 | # plot the radar 322 | radar.setup_axis(ax=axs['radar'], facecolor='None') 323 | rings_inner = radar.draw_circles(ax=axs['radar'], facecolor='#5bc8ef', edgecolor='#b7ebff', 324 | # you can also vary the linewidths 325 | # here we gradually increase the blue concentric circles 326 | linewidth=[0, 1, 2]) 327 | radar_output = radar.draw_radar(bruno_values, ax=axs['radar'], 328 | kwargs_radar={'facecolor': '#fa4554'}, 329 | kwargs_rings={'facecolor': '#d298c4'}) 330 | radar_poly, rings_outer, vertices = radar_output 331 | range_labels = radar.draw_range_labels(ax=axs['radar'], fontsize=25, color='#f0f6f6', 332 | fontproperties=robotto_thin.prop) 333 | param_labels = radar.draw_param_labels(ax=axs['radar'], fontsize=25, color='#f0f6f6', 334 | fontproperties=robotto_regular.prop) 335 | axs['radar'].scatter(vertices[:, 0], vertices[:, 1], c='#eeb743', edgecolors='#070707', 336 | marker='D', s=220, zorder=2) 337 | 338 | # adding the endnote and title text (these axes range from 0-1, i.e. 0, 0 is the bottom left) 339 | # Note we are slightly offsetting the text from the edges by 0.01 (1%, e.g. 0.99) 340 | endnote_text = axs['endnote'].text(0.99, 0.5, 'The theme is inspired by Ben Eine', fontsize=15, 341 | fontproperties=robotto_thin.prop, ha='right', 342 | va='center', color='#f0f6f6') 343 | title1_text = axs['title'].text(0.01, 0.65, 'Bruno Fernandes', fontsize=25, 344 | fontproperties=robotto_bold.prop, ha='left', 345 | va='center', color='#eeb743') 346 | title2_text = axs['title'].text(0.01, 0.25, 'Manchester United', fontsize=20, 347 | fontproperties=robotto_regular.prop, ha='left', 348 | va='center', color='#f0f6f6') 349 | title3_text = axs['title'].text(0.99, 0.65, 'Radar Chart', fontsize=25, 350 | fontproperties=robotto_bold.prop, ha='right', 351 | va='center', color='#eeb743') 352 | title4_text = axs['title'].text(0.99, 0.25, 'Midfielder', fontsize=20, 353 | fontproperties=robotto_regular.prop, ha='right', 354 | va='center', color='#f0f6f6') 355 | 356 | fig.set_facecolor('#070707') 357 | 358 | ############################################################################## 359 | # Camille Walala theme 360 | # -------------------- 361 | # The theme below is inspired by the London based artist 362 | # `Camille Walala `_. It uses two types of 363 | # hatching to create the radar. 364 | 365 | # creating the figure using the grid function from mplsoccer: 366 | fig, axs = grid(figheight=14, grid_height=0.915, title_height=0.06, endnote_height=0.025, 367 | title_space=0, endnote_space=0, grid_key='radar', axis=False) 368 | 369 | # we are creating a new radar object with more rings, integer rounding, and a larger center circle 370 | radar2 = Radar(params=['Speed', 'Agility', 'Strength', 'Passing', 'Dribbles'], 371 | min_range=[0, 0, 0, 0, 0], 372 | max_range=[5, 5, 5, 5, 5], 373 | # here we make the labels integers instead of floats 374 | round_int=[True, True, True, True, True], 375 | # make the center circle x2 larger than the concentric circles 376 | center_circle_radius=2, 377 | # the number of rings has been chosen to divide the max_range evenly 378 | num_rings=5) 379 | 380 | # plot the radar 381 | radar2.setup_axis(ax=axs['radar'], facecolor='None') 382 | rings_inner = radar2.draw_circles(ax=axs['radar'], facecolor='#f77b83', edgecolor='#fe2837') 383 | radar_output = radar2.draw_radar(values=[5, 2, 4, 3, 1], ax=axs['radar'], 384 | kwargs_radar={'facecolor': '#f9c728', 'hatch': '.', 'alpha': 1}, 385 | kwargs_rings={'facecolor': '#e6dedc', 'edgecolor': '#1a1414', 386 | 'hatch': '/', 'alpha': 1}) 387 | # draw the radar again but without a facecolor ('None') and an edgecolor 388 | # we draw it again so that we can choose a different edgecolor from the radar 389 | radar_output2 = radar2.draw_radar(values=[5, 2, 4, 3, 1], ax=axs['radar'], 390 | kwargs_radar={'facecolor': 'None', 'edgecolor': '#646366'}, 391 | kwargs_rings={'facecolor': 'None'}) 392 | # draw the labels 393 | range_labels = radar2.draw_range_labels(ax=axs['radar'], fontproperties=serif_extra_light.prop, 394 | fontsize=25) 395 | param_labels = radar2.draw_param_labels(ax=axs['radar'], fontproperties=serif_regular.prop, 396 | fontsize=25) 397 | 398 | # adding the endnote and title text (these axes range from 0-1, i.e. 0, 0 is the bottom left) 399 | # Note we are slightly offsetting the text from the edges by 0.01 (1%, e.g. 0.99) 400 | endnote_text = axs['endnote'].text(0.99, 0.5, 'The theme is inspired by Camille Walala', 401 | fontproperties=serif_extra_light.prop, fontsize=15, 402 | ha='right', va='center') 403 | title1_text = axs['title'].text(0.01, 0.65, 'Player name', fontsize=20, 404 | fontproperties=rubik_regular.prop, ha='left', va='center') 405 | title2_text = axs['title'].text(0.01, 0.25, 'Player team', fontsize=15, 406 | fontproperties=rubik_regular.prop, ha='left', 407 | va='center', color='#fa1b38') 408 | title3_text = axs['title'].text(0.99, 0.65, 'Radar Chart', fontsize=20, 409 | fontproperties=rubik_regular.prop, ha='right', va='center') 410 | title4_text = axs['title'].text(0.99, 0.25, 'Position', fontsize=15, 411 | fontproperties=rubik_regular.prop, ha='right', 412 | va='center', color='#fa1b38') 413 | 414 | fig.set_facecolor('#f2dad2') 415 | 416 | plt.show() # If you are using a Jupyter notebook you do not need this line 417 | --------------------------------------------------------------------------------