Creating a Figure Module

This section discusses the elements of a nowcast.figures module. We’ll use nowcast.figures.day_avg_tracer as the basis of the example. The example focuses on the structure of the module and the functions it contains, as well as the interfaces between those and the rest of the GoMSS_Nowcast package.

There are some very strong opinions in this section about what function names to use, and how to break the code that creates a figure up into functions. They are here because we have learned the hard way that figure generation code quickly evolves into hard to read and maintain globs with fragile interconnections. Please follow the methodology in this section, but do feel free to discuss it with the group so that we can try to improve.

The DevelopDayAvgTracer notebook in notebooks/figures/ was used to develop our example figure module’s functions. You can take that approach if you wish, or you can develop directly in a module.

Of course, the ultimate goal is to produce a module. Once you’ve got a code module, you should create a notebook that tests it in the nowcast context. The TestDayAvgTracer notebook in notebooks/figures/ is an example for the nowcast.figures.day_avg_tracer module.

Example Module

First we’ll show the day_avg_tracer module structure as a whole, and then we’ll look at each section in detail.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# Copyright 2016-2019 Doug Latornell (43ravens)
# and the GoMSS Nowcast project contributors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Produce a figure that shows surface values of a tracer field for the full GoMSS
model domain. The values are day averages. They are displayed as filled colour contours.
The axes grid and tick labels are an angled lon/lat grid using a Lambert Conformal Conic
map projection.

Testing notebook for this module is
https://nbviewer.jupyter.org/urls/bitbucket.org/gomss-nowcast/gomss_nowcast/raw/default/notebooks/figures/TestDayAvgTracer.ipynb

Development notebook for this module is
https://nbviewer.jupyter.org/urls/bitbucket.org/gomss-nowcast/gomss_nowcast/raw/default/notebooks/figures/DevelopDayAvgTracer.ipynb
"""
from types import SimpleNamespace

import matplotlib.pyplot as plt
import numpy
import xarray

import nowcast.figures.website_theme
from nowcast.figures import shared


def make_figure(
    results_archive,
    run_date,
    var,
    cmap,
    bathy,
    figsize=(16, 9),
    theme=nowcast.figures.website_theme,
):
    """Plot colour contours of day averaged tracer variable surface values for the
    full GoMSS model domain on an angled lon/lat grid using a Lambert Conformal Conic
    map projection.

    :param results_archive: Path of directory tree in which NEMO model results are stored.
    :type results_archive: :py:class:`pathlib.Path`

    :param run_date: Run date to produce the figure for.
    :type run_date: :py:class:`arrow.Arrow`

    :param str var: Name of NEMO results variable to display in figure.

    :param cmap: Colour map to use for filled contours in figure.
    :type cmap:  :py:class:`matplotlib.colors.ListedColormap`

    :param bathy: GoMSS NEMO model bathymetry.
    :type bathy: :py:class:`xarray.Dataset`

    :param 2-tuple figsize: Figure size (width, height) in inches.

    :param theme: Module-like object that defines the style elements for the
                 figure. See :py:mod:`nowcast.figures.website_theme` for an
                 example.

    :return: :py:class:`matplotlib.figure.Figure`
    """
    plot_data = _prep_plot_data(results_archive, run_date, bathy)
    fig, ax_surface, x, y = _prep_fig_axes(figsize, plot_data, theme)
    contour_set, isobath = _plot_tracer_surface(
        ax_surface, x, y, plot_data, var, cmap, theme
    )
    _surface_axes_labels(
        ax_surface, plot_data, var, contour_set, isobath, theme
    )
    return fig


def _prep_plot_data(results_archive, run_date, bathy):
    """
    :param :py:class:`pathlib.Path` results_archive:
    :param :py:class:`arrow.Arrow` run_date:
    :param :py:class:`xarray.Dataset` bathy:

    :return: :py:class:`types.SimpleNamespace`
    """
    ddmmmyy = run_date.format('DDMMMYY').lower()
    yyyymmdd = run_date.format('YYYYMMDD')
    dataset_path = (
        results_archive / ddmmmyy /
        f'GoMSS_NOWCAST_1d_{yyyymmdd}_{yyyymmdd}_grid_T.nc'
    )
    day_avg_tracers = xarray.open_dataset(dataset_path)
    shared.localize_time(day_avg_tracers)
    return SimpleNamespace(
        day_avg_tracers=day_avg_tracers,
        bathy=bathy,
        tz_name=day_avg_tracers.attrs['tz_name'],
    )


def _prep_fig_axes(figsize, plot_data, theme):
    """
    :param 2-tuple figsize: Figure size (width, height) in inches.
    :param :py:class:`types.SimpleNamespace` plot_data:
    :param :py:mod:`nowcast.figures.website_theme` theme:

    :return: :py:class:`matplotlib.figure.Figure`, :py:class:`matplotlib.axes.Axes`,
             :py:class:`numpy.ndarray`, :py:class:`numpy.ndarray`
     """
    fig, ax_surface = plt.subplots(
        figsize=figsize, facecolor=theme.COLOURS['figure']['facecolor']
    )
    x, y = shared.projected_lon_lat_axes(
        ax_surface, shared.MAP_PARAMS['full domain'], plot_data.bathy, theme
    )
    return fig, ax_surface, x, y


def _plot_tracer_surface(ax, x, y, plot_data, var, cmap, theme):
    """
    :param :py:class:`matplotlib.axes.Axes` ax:
    :param :py:class:`numpy.ndarray` x:
    :param :py:class:`numpy.ndarray` y:
    :param :py:class:`types.SimpleNamespace` plot_data:
    :param str var:
    :param :py:class:`matplotlib.colors.ListedColormap` cmap:
    :param :py:mod:`nowcast.figures.website_theme` theme:

    :return: :py:class:`matplotlib.contour.QuadContourSet`,
             :py:class:`matplotlib.contour.QuadContourSet`
    """
    tracer = (
        plot_data.day_avg_tracers.data_vars[var]
        .isel(time_counter=0, deptht=0)
    )
    # tracer as filled contours
    contour_set = ax.contourf(
        x,
        y,
        tracer,
        cmap=cmap,
        levels=numpy.linspace(
            numpy.floor(tracer.where(tracer > 0).min()),
            numpy.floor(tracer.where(tracer > 0).max()), 20
        ),
        extend='max',
    )
    # land
    ax.contourf(
        x,
        y,
        plot_data.bathy.Bathymetry,
        levels=(-0.01, 0.01),
        colors=theme.COLOURS['land'],
    )
    # coastline
    ax.contour(
        x,
        y,
        plot_data.bathy.Bathymetry,
        levels=(-0.01, 0.01),
        colors=theme.COLOURS['coastline'],
    )
    # 1000m isobath
    isobath = ax.contour(
        x,
        y,
        plot_data.bathy.Bathymetry,
        levels=(1000,),
        colors=theme.COLOURS['contour lines']['1000m isobath'],
    )
    return contour_set, isobath


def _surface_axes_labels(ax, plot_data, var, contour_set, isobath, theme):
    """
    :param :py:class:`matplotlib.axes.Axes` ax:
    :param :py:class:`types.SimpleNamespace` plot_data:
    :param str var:
    :param :py:class:`matplotlib.contour.QuadContourSet` contour_set:
    :param :py:class:`matplotlib.contour.QuadContourSet` isobath:
    :param :py:mod:`nowcast.figures.website_theme` theme:

    :return: None
    """
    # Colour bar labels
    cbar = plt.colorbar(contour_set, ax=ax)
    cbar.ax.axes.tick_params(
        labelcolor=theme.COLOURS['cbar']['tick labels'],
        labelsize=theme.FONTS['cbar']['tick labels'].get_size(),
    )
    long_name = plot_data.day_avg_tracers.data_vars[var].long_name
    units = plot_data.day_avg_tracers.data_vars[var].units
    cbar.set_label(
        f'Surface {long_name.title()} [{units}]',
        color=theme.COLOURS['cbar']['label'],
        fontproperties=theme.FONTS['cbar']['label'],
    )
    plt.clabel(isobath, fmt={isobath.levels[0]: f'{isobath.levels[0]:.0f} m'})
    # Axes title
    time = plot_data.day_avg_tracers.temp.time_counter
    year = time.dt.year.values[0]
    month = time.dt.month.values[0]
    day = time.dt.day.values[0]
    ax.set_title(
        f'{year}-{month:02d}-{day:02d}\n\n',
        color=theme.COLOURS['text']['figure title'],
        fontproperties=theme.FONTS['figure title'],
        fontsize=theme.FONTS['figure title'].get_size(),
    )
    # Axes aspect ratio from latitude
    ax.set_aspect(
        1 /
        numpy.cos(plot_data.day_avg_tracers.nav_lat.median() * numpy.pi / 180),
    )
    # Axes element colours
    theme.set_axis_colors(ax)

Summary of Functions in a Figure Module

The function that the nowcast.workers.make_plots worker will call is named make_figure(). More details in make_figure() Function section.

make_figure() starts by calling 2 other functions:

  1. _prep_plot_data() to do all of the extraction and preparatory processing of the data that will be plotted in the figure’s axes objects.

    All of the slicing of the plot data from the dataset objects passed into the :make_figure(), or loaded within _prep_plot_data(), and any calculations that are required, should be done in _prep_plot_data() so that the variables it returns are ready to be passed into plotting methods. More details in the _prep_plot_data() Function section.

  2. _prep_fig_axes() creates the figure and axes objects that the variables will be plotted on. If the variables will be plotted on a projected lon/lat axes (as is the case in day_avg_tracer), The x and y arrays of longitudes and latitudes transformed to axes coordinates are also calculated in _prep_fig_axes(). More details in the _prep_fig_axes() Function section.

make_figure() then calls a function whose name starts with _plot_() for each of the axes objects returned by _prep_fig_axes().

If the processing in the _prep_plot_data(), _prep_fig_axes(), or _plot_*() functions is long or complicated, it may be broken up into additional functions that those functions call. An example is:

  • Axis labelling and prettifying code like nowcast.figures.day_avg_tracer._surface_axes_labels()

The following sub-sections go through the example module above section by section to discuss its details.

Module Docstring

The module docstring will appear at top of the automatically generated module documentation (nowcast.figures.day_avg_tracer in this case).

15
16
17
18
19
20
21
22
23
24
25
 """Produce a figure that shows surface values of a tracer field for the full GoMSS
 model domain. The values are day averages. They are displayed as filled colour contours.
 The axes grid and tick labels are an angled lon/lat grid using a Lambert Conformal Conic
 map projection.

 Testing notebook for this module is
 https://nbviewer.jupyter.org/urls/bitbucket.org/gomss-nowcast/gomss_nowcast/raw/default/notebooks/figures/TestDayAvgTracer.ipynb

 Development notebook for this module is
 https://nbviewer.jupyter.org/urls/bitbucket.org/gomss-nowcast/gomss_nowcast/raw/default/notebooks/figures/DevelopDayAvgTracer.ipynb
 """

Imports

Next come the imports:

26
27
28
29
30
31
32
33
 from types import SimpleNamespace

 import matplotlib.pyplot as plt
 import numpy
 import xarray

 import nowcast.figures.website_theme
 from nowcast.figures import shared

The Python standard library imports, and those from 3rd party libraries like matplotlib, numpy, etc., will vary from one figure module to another. However, the

32
 import nowcast.figures.website_theme

import must be present in every figure module. nowcast.figures.website_theme provides the definition of colours and fonts that figure modules must use in order to ensure consistency from one to the next, and with the gomss.ocean.dal.ca site styling.

See nowcast.figures.website_theme Module for more details about the website_theme module.

Notes About Imports

  • Only import things that you are actually using in your module. Installing a static analysis tool like flake8 and enabling your editor to use it to highlight problem code will help you to write well-styled code, including identifying unused imports for you.

  • Never use:

    from something import *
    
  • Imports should be grouped:

    • Python standard library

    • Other installed libraries

    • Other GoMSS project libraries

    • The library that the module is part of

    The groups should be separated by an empty line, and the imports should be sorted alphabetically within the groups.

make_figure() Function

The first function in the module is the function that will be called by the nowcast.workers.make_plots worker to return a matplotlib.figure.Figure object. This function is always named make_figure(). It is also the module’s only public function.

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
 def make_figure(
     results_archive,
     run_date,
     var,
     cmap,
     bathy,
     figsize=(16, 9),
     theme=nowcast.figures.website_theme,
 ):
     """Plot colour contours of day averaged tracer variable surface values for the
     full GoMSS model domain on an angled lon/lat grid using a Lambert Conformal Conic
     map projection.

     :param results_archive: Path of directory tree in which NEMO model results are stored.
     :type results_archive: :py:class:`pathlib.Path`

     :param run_date: Run date to produce the figure for.
     :type run_date: :py:class:`arrow.Arrow`

     :param str var: Name of NEMO results variable to display in figure.

     :param cmap: Colour map to use for filled contours in figure.
     :type cmap:  :py:class:`matplotlib.colors.ListedColormap`

     :param bathy: GoMSS NEMO model bathymetry.
     :type bathy: :py:class:`xarray.Dataset`

     :param 2-tuple figsize: Figure size (width, height) in inches.

     :param theme: Module-like object that defines the style elements for the
                  figure. See :py:mod:`nowcast.figures.website_theme` for an
                  example.

     :return: :py:class:`matplotlib.figure.Figure`
     """
     plot_data = _prep_plot_data(results_archive, run_date, bathy)
     fig, ax_surface, x, y = _prep_fig_axes(figsize, plot_data, theme)
     contour_set, isobath = _plot_tracer_surface(
         ax_surface, x, y, plot_data, var, cmap, theme
     )
     _surface_axes_labels(
         ax_surface, plot_data, var, contour_set, isobath, theme
     )
     return fig

Function Signature

The function signature

36
37
38
39
40
41
42
43
44
 def make_figure(
     results_archive,
     run_date,
     var,
     cmap,
     bathy,
     figsize=(16, 9),
     theme=nowcast.figures.website_theme,
 ):

includes all of the code objects that figure module needs from the nowcast.workers.make_plots worker. In this case:

  • results_archive: the path of the directory tree where the NEMO model results are stored

  • run_date: the NEMO run date to produce the figure for

  • var: the NEMO results variable to display in the figure

  • cmap: the colour map to use for the filled contours in the figure

  • bathy the model bathymetry dataset

The signature ends with the default-values keyword arguments figsize and theme.

The figsize 2-tuple give the width and height of the figure, but more importantly its aspect ratio. Choose values that are appropriate to the information presented in the figure. If you don’t have a good reason to choose something else, use figsize=(16, 9) because that matches the aspect ration of wide displays that most people use to view web sites (even phones in landscape orientation).

The theme should be defaulted to nowcast.figures.website_theme, a module that provides colours and font specifications that fit with the gomss.ocean.dal.ca site styling and provide consistency among the figures.

Function Docstring

The function docstring

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
 """Plot colour contours of day averaged tracer variable surface values for the
 full GoMSS model domain on an angled lon/lat grid using a Lambert Conformal Conic
 map projection.

 :param results_archive: Path of directory tree in which NEMO model results are stored.
 :type results_archive: :py:class:`pathlib.Path`

 :param run_date: Run date to produce the figure for.
 :type run_date: :py:class:`arrow.Arrow`

 :param str var: Name of NEMO results variable to display in figure.

 :param cmap: Colour map to use for filled contours in figure.
 :type cmap:  :py:class:`matplotlib.colors.ListedColormap`

 :param bathy: GoMSS NEMO model bathymetry.
 :type bathy: :py:class:`xarray.Dataset`

 :param 2-tuple figsize: Figure size (width, height) in inches.

 :param theme: Module-like object that defines the style elements for the
              figure. See :py:mod:`nowcast.figures.website_theme` for an
              example.

 :return: :py:class:`matplotlib.figure.Figure`
 """

includes description and type information for each of the function arguments. Those are written using Sphinx Info Field List markup so that they render nicely in the automatically generated module documentation.

Simple, 1-word type information can be included in the :arg ...: role, for example:

:param str var: Name of NEMO results variable to display in figure.

More complicated type information should go in a separate :type ...: role like:

:param results_archive: Path of directory tree in which NEMO model results are stored.
:type results_archive: :py:class:`pathlib.Path`

Function Code

The function code does 4 things:

  1. Call a module-private function _prep_plot_data() to prepare the collection of objects that contain the data that will be plotted in the figure:

    71
     plot_data = _prep_plot_data(results_archive, run_date, bathy)
    
  2. Call a module-private function _prep_fig_axes():

    72
     fig, ax_surface, x, y = _prep_fig_axes(figsize, plot_data, theme)
    

    That function returns:

    The _prep_fig_axes() function accept arguments named figsize and theme. figsize provides the size and shape of the figure area. theme provides the nowcast.figures.website_theme WebsiteTheme module which defines things like the figure and axes background colours.

    The axes object(s) returned by _prep_fig_axes() should be given meaningful name(s) as shown above rather than:

    fig, (ax1, ax2, ax2, ax4) = _prep_fig_axes(figsize, theme)
    
  3. For each axes object returned by _prep_fig_axes(), call a module-private function whose name starts with _plot_() is called to draw all the things on the axes:

    73
    74
    75
    76
    77
    78
     contour_set, isobath = _plot_tracer_surface(
         ax_surface, x, y, plot_data, var, cmap, theme
     )
     _surface_axes_labels(
         ax_surface, plot_data, var, contour_set, isobath, theme
     )
    

    In day_avg_tracer we have separated the axes labeling and prettifying code into a separate function, _surface_axes_labels().

  4. Return the matplotlib.figure.Figure object to the nowcast.workers.make_plots worker:

    79
     return fig
    

_prep_plot_data() Function

The _prep_plot_data() function is responsible for all of the extraction and preparatory processing of the data that will be plotted in the figure’s axes object(s). All of the slicing of the plot data from the dataset objects passed into the make_figure() Function, and any calculations that are required should be done in _prep_plot_data() so that the variables it returns are ready to be passed into plotting methods.

 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
 def _prep_plot_data(results_archive, run_date, bathy):
     """
     :param :py:class:`pathlib.Path` results_archive:
     :param :py:class:`arrow.Arrow` run_date:
     :param :py:class:`xarray.Dataset` bathy:

     :return: :py:class:`types.SimpleNamespace`
     """
     ddmmmyy = run_date.format('DDMMMYY').lower()
     yyyymmdd = run_date.format('YYYYMMDD')
     dataset_path = (
         results_archive / ddmmmyy /
         f'GoMSS_NOWCAST_1d_{yyyymmdd}_{yyyymmdd}_grid_T.nc'
     )
     day_avg_tracers = xarray.open_dataset(dataset_path)
     shared.localize_time(day_avg_tracers)
     return SimpleNamespace(
         day_avg_tracers=day_avg_tracers,
         bathy=bathy,
         tz_name=day_avg_tracers.attrs['tz_name'],
     )

The type definitions in the function docstring are optional, but can be very helpful to your future self and other people reading your code, especially if they are using an IDE (Integrated Development Environment) like PyCharm, or an editor like Visual Studio Code, that understands Python type annotations.

_prep_plot_data() should return a types.SimpleNamespace so that the various data objects to be plotted can be easily accessed using dotted notation; e.g. plot_data.day_avg_tracers.

Notes About Returning SimpleNamespace from Functions

If you are writing a function that returns more than one value, consider returning the collection of values as a SimpleNamespace. If your function returns more than 3 values, definitely return them as a SimpleNamespace.

SimpleNamespace objects that have fields accessible by attribute lookup (dotted notation). They also have a helpful string representation which lists the namespace contents in a name=value format.

>>> p = SimpleNamespace(x=11, y=22)
>>> p.x + p.y               # fields accessible by name
33
>>> p                       # readable string representation with a name=value style
namespace(x=11, y=22)

Using the _prep_plot_data() Function function code as an example:

 98
 99
100
101
102
return SimpleNamespace(
    day_avg_tracers=day_avg_tracers,
    bathy=bathy,
    tz_name=day_avg_tracers.attrs['tz_name'],
)

Returning a SimpleNamespace lets us call _prep_plot_data() like:

plot_data = _prep_plot_data(results_archive, run_date, bathy)

and we can access the name of the time zone to which the model results have been localized as:

plot_data.tz_name

This makes for compact and easy to understand code that our future selves and others will appreciate when they read our code.

_prep_fig_axes() Function

The _prep_fig_axes() function accepts arguments named figsize, plot_data, and theme. figsize provides the size and shape of the figure area. :plot_data provides access to the model bathymetry that is needed to set up the projected longitude/latitude axes object and transformed x and y coordinates to use for plotting. theme provides the nowcast.figures.website_theme WebsiteTheme module which defines things like the figure and axes background colours.

105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
 def _prep_fig_axes(figsize, plot_data, theme):
     """
     :param 2-tuple figsize: Figure size (width, height) in inches.
     :param :py:class:`types.SimpleNamespace` plot_data:
     :param :py:mod:`nowcast.figures.website_theme` theme:

     :return: :py:class:`matplotlib.figure.Figure`, :py:class:`matplotlib.axes.Axes`,
              :py:class:`numpy.ndarray`, :py:class:`numpy.ndarray`
      """
     fig, ax_surface = plt.subplots(
         figsize=figsize, facecolor=theme.COLOURS['figure']['facecolor']
     )
     x, y = shared.projected_lon_lat_axes(
         ax_surface, shared.MAP_PARAMS['full domain'], plot_data.bathy, theme
     )
     return fig, ax_surface, x, y

As in the _prep_plot_data() Function, the type definitions in the function docstring are optional.

plt.subplots() creates the matplotlib.figure.Figure object for us, and the matplotlib.axes.Axes object within it that we will plot our model tracer field on. The figsize parameter specifies the figure size and aspect ratio. A colour to match the web page background colour is used as the figure facecolor: theme.COLOURS['figure']['facecolor'].

nowcast.figures.shared.projected_lon_lat_axes() uses Basemap to calculate transformed coordinates for the axes object according to the projection and other parameters in shared.MAP_PARAMS['full domain']. The (x, y) arrays that are returned are the transformed coordinates to use for plotting on the axes. The axes will be labelled with longitudes and latitudes and it will show a longitude/latitude grid.

The function returns:

Axes Plotting Functions

After preparing the plot data, and setting up the figure and axes objects, our example make_figure() Function calls the _plot_tracer_surface() Function.

Plotting functions generally accept:

They may accept other arguments as necessary.

The job of the _plot_*() functions is to act on the matplotlib.axes.Axes object (ax) so they may or may not return anything.

_plot_tracer_surface() Function

The _plot_tracer_surface() function in our example plots colour contours of the surface values of a tracer for the full domain.

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
 def _plot_tracer_surface(ax, x, y, plot_data, var, cmap, theme):
 """
 :param :py:class:`matplotlib.axes.Axes` ax:
 :param :py:class:`numpy.ndarray` x:
 :param :py:class:`numpy.ndarray` y:
 :param :py:class:`types.SimpleNamespace` plot_data:
 :param str var:
 :param :py:class:`matplotlib.colors.ListedColormap` cmap:
 :param :py:mod:`nowcast.figures.website_theme` theme:

 :return: :py:class:`matplotlib.contour.QuadContourSet`,
          :py:class:`matplotlib.contour.QuadContourSet`
 """
 tracer = (
     plot_data.day_avg_tracers.data_vars[var]
     .isel(time_counter=0, deptht=0)
 )
 # tracer as filled contours
 contour_set = ax.contourf(
     x,
     y,
     tracer,
     cmap=cmap,
     levels=numpy.linspace(
         numpy.floor(tracer.where(tracer > 0).min()),
         numpy.floor(tracer.where(tracer > 0).max()), 20
     ),
     extend='max',
 )
 # land
 ax.contourf(
     x,
     y,
     plot_data.bathy.Bathymetry,
     levels=(-0.01, 0.01),
     colors=theme.COLOURS['land'],
 )
 # coastline
 ax.contour(
     x,
     y,
     plot_data.bathy.Bathymetry,
     levels=(-0.01, 0.01),
     colors=theme.COLOURS['coastline'],
 )
 # 1000m isobath
 isobath = ax.contour(
     x,
     y,
     plot_data.bathy.Bathymetry,
     levels=(1000,),
     colors=theme.COLOURS['contour lines']['1000m isobath'],
 )
 return contour_set, isobath

As in the _prep_plot_data() Function, the type definitions in the function docstring are optional.

It returns two matplotlib.contour.QuadContourSet objects:

  • the surface tracer field filled contours so that they can be used to construct a colour bar for the figure

  • the 1000m isobath contour line so that it can be labelled

for the separate _surface_axes_labels() to use to “make the axes pretty”:

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
 def _surface_axes_labels(ax, plot_data, var, contour_set, isobath, theme):
 """
 :param :py:class:`matplotlib.axes.Axes` ax:
 :param :py:class:`types.SimpleNamespace` plot_data:
 :param str var:
 :param :py:class:`matplotlib.contour.QuadContourSet` contour_set:
 :param :py:class:`matplotlib.contour.QuadContourSet` isobath:
 :param :py:mod:`nowcast.figures.website_theme` theme:

 :return: None
 """
 # Colour bar labels
 cbar = plt.colorbar(contour_set, ax=ax)
 cbar.ax.axes.tick_params(
     labelcolor=theme.COLOURS['cbar']['tick labels'],
     labelsize=theme.FONTS['cbar']['tick labels'].get_size(),
 )
 long_name = plot_data.day_avg_tracers.data_vars[var].long_name
 units = plot_data.day_avg_tracers.data_vars[var].units
 cbar.set_label(
     f'Surface {long_name.title()} [{units}]',
     color=theme.COLOURS['cbar']['label'],
     fontproperties=theme.FONTS['cbar']['label'],
 )
 plt.clabel(isobath, fmt={isobath.levels[0]: f'{isobath.levels[0]:.0f} m'})
 # Axes title
 time = plot_data.day_avg_tracers.temp.time_counter
 year = time.dt.year.values[0]
 month = time.dt.month.values[0]
 day = time.dt.day.values[0]
 ax.set_title(
     f'{year}-{month:02d}-{day:02d}\n\n',
     color=theme.COLOURS['text']['figure title'],
     fontproperties=theme.FONTS['figure title'],
     fontsize=theme.FONTS['figure title'].get_size(),
 )
 # Axes aspect ratio from latitude
 ax.set_aspect(
     1 /
     numpy.cos(plot_data.day_avg_tracers.nav_lat.median() * numpy.pi / 180),
 )
 # Axes element colours
 theme.set_axis_colors(ax)

As in the _prep_plot_data() Function, the type definitions in the function docstring are optional.

This function shows:

  • how text colours and fonts are obtained from theme

  • how date components are extracted from the xarray.DataArray.dt attribute of a model results time coordinate

  • how those components are formatted into a string for the axes title

  • how to fine tune the aspect ratio of the axes according to the median latitude of the field that is being displayed

It finishes with a call to the theme.set_axis_colors() convenience function to set the colours of axis labels, ticks, and spines so that they are consistent with the web site theme.

Automatic Module Documentation Generation

When you create a new figure module don’t forget to add it to the GoMSS_Nowcast/docs/workers.rst file so that documentation will be generated for it. For our example, the content added to GoMSS_Nowcast/docs/workers.rst is:

.. _nowcast.figures.day_avg_tracer:

:py:mod:`nowcast.figures.day_avg_tracer` Module
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. automodule:: nowcast.figures.day_avg_tracer
    :members:

Automatic Code Formatting

The GoMSS_Nowcast package uses the yapf code formatting tool to maintain a coding style that is very close to PEP 8.

yapf is installed as part of the Nowcast Figures Development Environment setup.

Before each commit of your figure module please run yapf to automatically format your code. For our example day_avg_tracer module the command would be:

$ yapf --in-place nowcast/figures/day_avg_tracer.py