diff --git a/README.md b/README.md index 56583d11..cf296bca 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ geometry.write_shapefile(dataset, './gbr4.shp') ## Examples +The `examples/` directory contains examples of making many different plots. +These are included in the documentation under [Examples][emsarray-examples]. + Examples of using `emsarray` are available in the [emsarray-notebooks][emsarray-notebooks] repository. You can [explore these notebooks online][emsarray-binder] with Binder. @@ -98,6 +101,7 @@ You can the view the docs at [emsarray-binder]: https://mybinder.org/v2/gh/csiro-coasts/emsarray-notebooks/HEAD [emsarray-conda-forge]: https://anaconda.org/conda-forge/emsarray/ [emsarray-documentation]: https://emsarray.readthedocs.io +[emsarray-examples]: https://emsarray.readthedocs.io/en/stable/examples/ [emsarray-notebooks]: https://github.com/csiro-coasts/emsarray-notebooks [emsarray-pypi]: https://pypi.org/project/emsarray/ [emsarray-source-code]: https://github.com/csiro-coasts/emsarray diff --git a/continuous-integration/docs.yaml b/continuous-integration/docs.yaml index 3d9555f5..f960bf62 100644 --- a/continuous-integration/docs.yaml +++ b/continuous-integration/docs.yaml @@ -3,7 +3,9 @@ channels: dependencies: - geos ~=3.12.2 + - udunits2 >=2.2.25 - python =3.14 + - ffmpeg - wheel - pip: - -r ./requirements-3.14.txt diff --git a/docs/.gitignore b/docs/.gitignore index e35d8850..9a7fdf7e 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ -_build +_build/ +examples/ diff --git a/docs/_static/images/animated-plot.mp4 b/docs/_static/images/animated-plot.mp4 deleted file mode 100644 index 73e1fbf4..00000000 Binary files a/docs/_static/images/animated-plot.mp4 and /dev/null differ diff --git a/docs/_static/images/animated-plot.png b/docs/_static/images/animated-plot.png deleted file mode 100644 index ba7a0c91..00000000 Binary files a/docs/_static/images/animated-plot.png and /dev/null differ diff --git a/docs/_static/images/kgari-path.png b/docs/_static/images/kgari-path.png deleted file mode 100644 index 15123101..00000000 Binary files a/docs/_static/images/kgari-path.png and /dev/null differ diff --git a/docs/_static/images/kgari-transect.png b/docs/_static/images/kgari-transect.png deleted file mode 100644 index c5693ccc..00000000 Binary files a/docs/_static/images/kgari-transect.png and /dev/null differ diff --git a/docs/_static/images/plot-set-extent.png b/docs/_static/images/plot-set-extent.png deleted file mode 100644 index 00618494..00000000 Binary files a/docs/_static/images/plot-set-extent.png and /dev/null differ diff --git a/docs/_static/images/plot-with-clim.png b/docs/_static/images/plot-with-clim.png deleted file mode 100644 index e5bc2b7a..00000000 Binary files a/docs/_static/images/plot-with-clim.png and /dev/null differ diff --git a/docs/api/plot.rst b/docs/api/plot.rst index 821d4634..6d341e01 100644 --- a/docs/api/plot.rst +++ b/docs/api/plot.rst @@ -11,11 +11,10 @@ The :func:`plot_on_figure` and :func:`animate_on_figure` functions will generate a simple plot of any supported variables. These functions are intended as quick and simple ways of exploring a dataset and have limited customisation options. -Consult the :ref:`examples gallery ` -for demonstrations on making more customised plots. -The :ref:`examples ` section contains many worked examples on how to generate plots. -:ref:`example-plot-with-clim` is a good place to start. +The :ref:`examples ` section contains many worked examples +on how to generate plots with more customisations. +:ref:`sphx_glr_examples_plot-with-clim.py` is a good place to start. Shortcuts ========= diff --git a/docs/api/transect.rst b/docs/api/transect.rst index 8db5f436..a4c58cf9 100644 --- a/docs/api/transect.rst +++ b/docs/api/transect.rst @@ -12,13 +12,7 @@ Transects are vertical slices along some path through your dataset. Examples -------- -.. list-table:: - - * - :ref:`example-kgari-transect` - - .. image:: /_static/images/kgari-transect.png - :width: 200 - :class: no-scaled-link +.. minigallery:: ../examples/plot-kgari-transect.py .. autofunction:: plot diff --git a/docs/conf.py b/docs/conf.py index 16a0a952..5d8d49d2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,6 +40,7 @@ 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinxcontrib.video', + 'sphinx_gallery.gen_gallery', 'roles' ] @@ -65,9 +66,13 @@ html_static_path = ['_static'] -add_module_names = False +def setup(app): + app.add_css_file("hide_links.css") +# Options for generated documentation +add_module_names = True + napoleon_google_docstring = False napoleon_numpy_docstring = True napoleon_use_param = False @@ -99,3 +104,13 @@ 'shapely': ('https://shapely.readthedocs.io/en/stable/', None), 'xarray': ('https://docs.xarray.dev/en/stable/', None), } + + +# Sphinx gallery configuration +sphinx_gallery_conf = { + 'examples_dirs': '../examples', + 'gallery_dirs': './examples', + 'filename_pattern': '/plot-', + 'matplotlib_animations': True, + 'backreferences_dir': './examples/backreferences', +} diff --git a/docs/examples/animated-plot.rst b/docs/examples/animated-plot.rst deleted file mode 100644 index 85699d91..00000000 --- a/docs/examples/animated-plot.rst +++ /dev/null @@ -1,35 +0,0 @@ -.. _example-animated-plot: - -============= -Animated plot -============= - -Animated plots are possible with emsarray by using :class:`matplotlib.animation.FuncAnimation` -and calling :meth:`.GridArtist.set_data_array()` each frame. -A :class:`~matplotlib.animation.FuncAnimation` will call a function every frame where you can update the plot. -Each artist returned by :meth:`.Convention.make_artist()` has a :meth:`~.GridArtist.set_data_array()` method -which can be used to update the data in the plot. -In combination this makes animations in emsarray about as straight forward as making a static plot: - -.. video:: /_static/images/animated-plot.mp4 - :poster: /_static/images/animated-plot.png - :alt: A video of surface water currents around K'gari. - :loop: - :muted: - :width: 100% - -Code -==== - -Saving a video to a file requires `ffmpeg `_ -which can be installed using conda: - -.. code-block:: shell - - $ conda install ffmpeg - -:download:`Download animated-plot.py example `. - -.. literalinclude:: animated-plot.py - :language: python - diff --git a/docs/examples/index.rst b/docs/examples/index.rst deleted file mode 100644 index 079aaf09..00000000 --- a/docs/examples/index.rst +++ /dev/null @@ -1,51 +0,0 @@ -.. _examples: - -======== -Examples -======== - -.. image:: https://mybinder.org/badge_logo.svg - :target: https://mybinder.org/v2/gh/csiro-coasts/emsarray-notebooks/HEAD - -A collection of Jupyter notebooks demonstrating many features of ``emsarray`` are available. -To explore these notebooks interactively on the web you can launch a -`Jupyter Lab environment on Binder `_. - -If you would prefer to download the examples and explore them locally, -they are available on Github at -`csiro-coasts/emsarray-notebooks `_. - -Gallery -======= - -.. toctree:: - :glob: - :hidden: - - * - -.. list-table:: - - * - :ref:`example-animated-plot` - - .. image:: /_static/images/animated-plot.png - :width: 200 - :class: no-scaled-link - - * - :ref:`example-plot-set-extent` - - .. image:: /_static/images/plot-set-extent.png - :width: 200 - :class: no-scaled-link - - * - :ref:`example-plot-with-clim` - - .. image:: /_static/images/plot-with-clim.png - :width: 200 - :class: no-scaled-link - - * - :ref:`example-kgari-transect` - - .. image:: /_static/images/kgari-transect.png - :width: 200 - :class: no-scaled-link diff --git a/docs/examples/kgari-transect.rst b/docs/examples/kgari-transect.rst deleted file mode 100644 index 317b8349..00000000 --- a/docs/examples/kgari-transect.rst +++ /dev/null @@ -1,29 +0,0 @@ -.. _example-kgari-transect: - -==================== -K'gari transect plot -==================== - -The following is a :mod:`transect ` path -starting in the Great Sandy Strait near K'gari, -heading roughly North out to deeper waters: - -.. image:: /_static/images/kgari-path.png - :alt: Transect of GBR1 dataset along Great Sandy Strait and past K'gari - showing the water temperature. - :align: center - -The temperature along this transect can be plotted: - -.. image:: /_static/images/kgari-transect.png - :alt: Transect of GBR1 dataset along Great Sandy Strait and past K'gari - showing the water temperature. - -Code -==== - -:download:`Download kgari-transect.py example `. - -.. literalinclude:: kgari-transect.py - :language: python - diff --git a/docs/examples/plot-set-extent.rst b/docs/examples/plot-set-extent.rst deleted file mode 100644 index b989c8ea..00000000 --- a/docs/examples/plot-set-extent.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. _example-plot-set-extent: - -================================ -Plot a small area with landmarks -================================ - -You can use :meth:`.Axes.set_extent()` to display a region of interest on a plot. -Combined with :func:`add_landmarks()` to highlight regional landmarks. - -.. image:: /_static/images/plot-set-extent.png - :alt: A plot of sea surface temperature from the GBR 4km dataset. - The plot shows the area around Mackay on the Queensland coast. - -Code -==== - -:download:`Download plot-set-extent.py example `. - -.. literalinclude:: plot-set-extent.py - :language: python - - diff --git a/docs/examples/plot-with-clim.rst b/docs/examples/plot-with-clim.rst deleted file mode 100644 index 0e8bc6c3..00000000 --- a/docs/examples/plot-with-clim.rst +++ /dev/null @@ -1,29 +0,0 @@ -.. _example-plot-with-clim: - -=============================== -Plot with specified data limits -=============================== - -:meth:`.Convention.make_artist()` method will create an appropriate -:class:`~matplotlib.artist.Artist` to plot a variable. -The artist can be customised by passing kwargs to :meth:`~.Convention.make_artist()`. -The :meth:`~.Convention.make_artist()` documentation for each convention -will describe what artists are created for what kinds of variables. - -Typically a scalar variable defined on a polygon grid -will be drawn using a :class:`~matplotlib.collections.PolyCollection`. -This class accepts options such as `clim` and `cmap` -which can be used to customise the appearance of the plot. - -.. image:: /_static/images/plot-with-clim.png - :alt: A plot of sea surface height deviation from the Australia wide AUSTEn dataset. - The limits of the colour bar have been constrianed to (-3, 3). - -Code -==== - -:download:`Download plot-with-clim.py example `. - -.. literalinclude:: plot-with-clim.py - :language: python - diff --git a/docs/live.py b/docs/live.py index 7430ef84..546d93a3 100755 --- a/docs/live.py +++ b/docs/live.py @@ -10,6 +10,7 @@ def main() -> None: server.watch('**/*.py', command, delay=0.1) server.watch('**/*.ipynb', command, delay=0.1) server.watch('../src', command, delay=0.1) + server.watch('../examples', command, delay=0.1) server.serve(root='./_build/dirhtml') diff --git a/docs/releases/0.10.0.rst b/docs/releases/0.10.0.rst index 2cec5e97..d3dd2e28 100644 --- a/docs/releases/0.10.0.rst +++ b/docs/releases/0.10.0.rst @@ -8,7 +8,7 @@ Released on 2025-09-08 where some variables are lacking a `standard_name` (:issue:`178`, :pr:`180`). * Updated the tests to be compatible with the latest xarray versions (:pr:`182`). * Added an example to the documentation showing - :ref:`how to set the clim parameter in plots ` + :ref:`how to set the clim parameter in plots ` (:pr:`179`). * Bumped pinned dependencies (:pr:`183`). * Fixed :func:`emsarray.utils.datetime_from_np_time` diff --git a/docs/releases/development.rst b/docs/releases/development.rst index 130bf543..f8ce2615 100644 --- a/docs/releases/development.rst +++ b/docs/releases/development.rst @@ -2,4 +2,5 @@ Next release (in development) ============================= -* ... \ No newline at end of file +* Add `sphinx-gallery `_ to the docs + to render the example gallery (:pr:`210`). diff --git a/examples/GALLERY_HEADER.rst b/examples/GALLERY_HEADER.rst new file mode 100644 index 00000000..d3817e63 --- /dev/null +++ b/examples/GALLERY_HEADER.rst @@ -0,0 +1,5 @@ +.. _examples: + +======== +Examples +======== diff --git a/docs/examples/animated-plot.py b/examples/plot-animation.py similarity index 77% rename from docs/examples/animated-plot.py rename to examples/plot-animation.py index 6e0325e9..81e18aa0 100644 --- a/docs/examples/animated-plot.py +++ b/examples/plot-animation.py @@ -1,3 +1,15 @@ +""" +============= +Animated plot +============= + +Animated plots are possible with emsarray by using :class:`matplotlib.animation.FuncAnimation` +and calling :meth:`.GridArtist.set_data_array()` each frame. +A :class:`~matplotlib.animation.FuncAnimation` will call a function every frame where you can update the plot. +Each artist returned by :meth:`.Convention.make_artist()` has a :meth:`~.GridArtist.set_data_array()` method +which can be used to update the data in the plot. +In combination this makes animations in emsarray about as straight forward as making a static plot: +""" import cartopy.crs import datetime import emsarray @@ -24,7 +36,7 @@ # Make a figure -figure = pyplot.figure(figsize=(8, 8), layout='constrained') +figure = pyplot.figure(figsize=(8, 8)) axes = figure.add_subplot(projection=cartopy.crs.PlateCarree()) axes.set_aspect('equal', adjustable='datalim') coast = GSHHSFeature(scale='intermediate') @@ -47,16 +59,10 @@ axes, (u.isel(time=0), v.isel(time=0)), scale=40) - -# Finish setting up the plot -axes.autoscale() - - def update_plot(frame: int) -> list[Artist]: # This function is called every frame and should update the plot with new data. - # Disable the matplotlib layout engine after the first frame - # else the plot has a tendency to jiggle around on later frames. + # Disable the layout engine after the first frame, else the plot can shift slightly if frame > 0: figure.set_layout_engine('none') @@ -72,19 +78,14 @@ def update_plot(frame: int) -> list[Artist]: # Return every artist that has been updated this frame return [axes.title, magnitude_artist, uv_artist] +# Render the first frame +update_plot(0) + +# Finish setting up the plot +axes.autoscale() animation = FuncAnimation( figure, # The figure to animate update_plot, # The function to call to update the plot data frames=ds.sizes['time'], # How many frames of animation to render ) - -# Draw and save the first frame of the animation for a thumbnail -update_plot(0) -figure.savefig('animated-plot.png') - -# Save the animation -ffmpeg_writer = FFMpegWriter(fps=5, bitrate=1800) -animation.save('animated-plot.mp4', writer=ffmpeg_writer) - -pyplot.show(block=True) diff --git a/docs/examples/kgari-transect.py b/examples/plot-kgari-transect.py similarity index 80% rename from docs/examples/kgari-transect.py rename to examples/plot-kgari-transect.py index 904042ac..136597ce 100644 --- a/docs/examples/kgari-transect.py +++ b/examples/plot-kgari-transect.py @@ -1,3 +1,9 @@ +""" +==================== +K'gari transect plot +==================== +""" + import shapely from matplotlib import pyplot @@ -8,6 +14,10 @@ dataset = emsarray.open_dataset(dataset_url).isel(time=-1) dataset = dataset.ems.select_variables(['botz', 'temp']) +# %% +# The following is a :mod:`transect ` path +# starting in the Great Sandy Strait near K'gari, +# heading roughly North out to deeper waters: line = shapely.LineString([ [152.9768944, -25.4827962], [152.9701996, -25.4420345], @@ -29,7 +39,9 @@ ('Lady Elliot Island', shapely.Point(152.7145958, -24.1129146)), ] -# Plot the transect +# %% +# Plot a transect showing temperature along this path. + figure = transect.plot( dataset, line, dataset['temp'], figsize=(7.9, 3), @@ -37,7 +49,10 @@ landmarks=landmarks, title="Temperature", cmap='Oranges_r') -figure.savefig('kgari-transect.png') +pyplot.show() + +# %% +# The path of the transect can be plotted using matplotlib. # Plot the path of the transect figure = pyplot.figure(figsize=(5, 5), dpi=100) @@ -46,7 +61,7 @@ axes.set_title('Transect path') dataset.ems.make_artist( axes, 'botz', cmap='Blues', clim=(0, 2000), edgecolor='face', - linewidth=0.5, zorder=0)) + linewidth=0.5, zorder=0) axes = figure.axes[0] axes.set_extent(plot.bounds_to_extent(line.envelope.buffer(0.2).bounds)) axes.plot(*line.coords.xy, zorder=2, c='orange', linewidth=4) @@ -54,6 +69,5 @@ plot.add_coast(axes, zorder=1) plot.add_gridlines(axes) plot.add_landmarks(axes, landmarks) -figure.savefig('kgari-path.png') -pyplot.show(block=True) +pyplot.show() diff --git a/docs/examples/plot-set-extent.py b/examples/plot-set-extent.py similarity index 74% rename from docs/examples/plot-set-extent.py rename to examples/plot-set-extent.py index e125f82a..46dcd361 100644 --- a/docs/examples/plot-set-extent.py +++ b/examples/plot-set-extent.py @@ -1,10 +1,18 @@ +""" +================================ +Plot a small area with landmarks +================================ + +You can use :meth:`.Axes.set_extent()` to display a region of interest on a plot. +Combined with :func:`add_landmarks()` to highlight regional landmarks. +""" import emsarray.plot import shapely from matplotlib import pyplot dataset = emsarray.tutorial.open_dataset('gbr4') -# set up the figure +# Set up the figure figure = pyplot.figure() axes = figure.add_subplot(projection=dataset.ems.data_crs) axes.set_title("Sea surface temperature around Mackay") @@ -26,5 +34,4 @@ ('Mackay', shapely.Point(149.192671, -21.146719)), ]) -figure.savefig("plot-set-extent.png") -pyplot.show(block=True) +pyplot.show() diff --git a/examples/plot-two-subplots.py b/examples/plot-two-subplots.py new file mode 100644 index 00000000..dcb9f65d --- /dev/null +++ b/examples/plot-two-subplots.py @@ -0,0 +1,42 @@ +""" +============================ +Multiple plots in one figure +============================ +""" + +from matplotlib import pyplot +import emsarray +from emsarray.utils import datetime_from_np_time +from emsarray.plot import add_coast + +ds = emsarray.tutorial.open_dataset('austen') +upper_frame = ds.isel(record=0) +lower_frame = ds.isel(record=23) + +figure = pyplot.figure(figsize=(7, 10), layout='constrained') +figure.suptitle("Sea surface height in the Timor Sea") +upper_axes, lower_axes = figure.subplots(2, 1, subplot_kw=dict(projection=ds.ems.data_crs)) + +upper_artist = ds.ems.make_artist( + upper_axes, upper_frame['eta'], clim=(-3, 3), cmap='BrBG', add_colorbar=False) +lower_artist = ds.ems.make_artist( + lower_axes, lower_frame['eta'], clim=(-3, 3), cmap='BrBG', add_colorbar=False) + +# This roughly encompases the Timor Sea +extent = (124.38, 135.15, -17.0, -8.0) + +add_coast(upper_axes) +upper_time = datetime_from_np_time(upper_frame['t'].values) +upper_axes.set_title(upper_time.strftime("%Y-%m-%d %H:%M +10:00")) +upper_axes.set_extent(extent) + +add_coast(lower_axes) +lower_time = datetime_from_np_time(lower_frame['t'].values) +lower_axes.set_title(lower_time.strftime("%Y-%m-%d %H:%M +10:00")) +lower_axes.set_extent(extent) + +figure.colorbar( + lower_artist, ax=[upper_axes, lower_axes], + location='right', fraction=0.05, label='meters') + +pyplot.show() diff --git a/docs/examples/plot-with-clim.py b/examples/plot-with-clim.py similarity index 56% rename from docs/examples/plot-with-clim.py rename to examples/plot-with-clim.py index 7438690e..38e527fa 100644 --- a/docs/examples/plot-with-clim.py +++ b/examples/plot-with-clim.py @@ -1,3 +1,19 @@ +""" +=============================== +Plot with specified data limits +=============================== + +:meth:`.Convention.make_artist()` method will create an appropriate +:class:`~matplotlib.artist.Artist` to plot a variable. +The artist can be customised by passing kwargs to :meth:`~.Convention.make_artist()`. +The :meth:`~.Convention.make_artist()` documentation for each convention +will describe what artists are created for what kinds of variables. + +Typically a scalar variable defined on a polygon grid +will be drawn using a :class:`~matplotlib.collections.PolyCollection`. +This class accepts options such as `clim` and `cmap` +which can be used to customise the appearance of the plot. +""" import cartopy.crs import emsarray import emsarray.plot @@ -29,6 +45,4 @@ axes.set_title("Sea surface height deviation") axes.set_facecolor('aliceblue') -figure.savefig('plot-with-clim.png') - -pyplot.show(block=True) +pyplot.show() diff --git a/pyproject.toml b/pyproject.toml index 74368dd0..9fdec59d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,7 @@ docs = [ "sphinx ~=8.2.3", "sphinx_book_theme ~=1.1.4", "sphinxcontrib-video ~=0.4.1", + "sphinx-gallery ~=0.20.0", "livereload~=2.7.1", ] diff --git a/src/emsarray/conventions/_base.py b/src/emsarray/conventions/_base.py index 28313b19..24f01fbf 100644 --- a/src/emsarray/conventions/_base.py +++ b/src/emsarray/conventions/_base.py @@ -1134,9 +1134,8 @@ def animate_on_figure( This method is a shortcut for quickly generating simple animations. It is not intended to be fully featured. - See the :ref:`examples ` for more comprehensive plotting examples. - - For real world examples, refer to the ``examples/animation.ipynb`` notebook. + See the :ref:`examples ` for more comprehensive plotting examples, + and specifically :ref:`sphx_glr_examples_plot-animation.py` for an animated example. Parameters ---------- diff --git a/src/emsarray/plot/shortcuts.py b/src/emsarray/plot/shortcuts.py index c2dc92cd..a3cf2a0d 100644 --- a/src/emsarray/plot/shortcuts.py +++ b/src/emsarray/plot/shortcuts.py @@ -96,7 +96,7 @@ def add_landmarks( Examples -------- - :ref:`example-plot-set-extent` + .. minigallery:: ../examples/plot-set-extent.py """ outline = patheffects.withStroke( linewidth=outline_width, foreground=outline_color)