diff --git a/doc/_static/zenodo_cache/17595503.svg b/doc/_static/zenodo_cache/17595503.svg new file mode 100644 index 000000000000..891bd118d125 --- /dev/null +++ b/doc/_static/zenodo_cache/17595503.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.17595503 + + + 10.5281/zenodo.17595503 + + + \ No newline at end of file diff --git a/doc/conf.py b/doc/conf.py index 96738492b68b..292aff3e1983 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -200,8 +200,6 @@ def _check_dependencies(): else: sg_matplotlib_animations = True -# The following import is only necessary to monkey patch the signature later on -from sphinx_gallery import gen_rst # Prevent plt.show() from emitting a non-GUI backend warning. warnings.filterwarnings('ignore', category=UserWarning, @@ -299,7 +297,7 @@ def autodoc_process_bases(app, name, obj, options, bases): 'reference_url': {'matplotlib': None, 'mpl_toolkits': None}, 'prefer_full_module': {r'mpl_toolkits\.'}, 'remove_config_comments': True, - 'reset_modules': ('matplotlib', clear_basic_units), + 'reset_modules': ('matplotlib', clear_basic_units, 'sphinxext.util.patch_header'), 'subsection_order': gallery_order_sectionorder, 'thumbnail_size': (320, 224), 'within_subsection_order': gallery_order_subsectionorder, @@ -347,31 +345,6 @@ def gallery_image_warning_filter(record): mathmpl_fontsize = 11.0 mathmpl_srcset = ['2x'] -# Monkey-patching gallery header to include search keywords -gen_rst.EXAMPLE_HEADER = """ -.. DO NOT EDIT. -.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. -.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: -.. "{0}" -.. LINE NUMBERS ARE GIVEN BELOW. - -.. only:: html - - .. meta:: - :keywords: codex - - .. note:: - :class: sphx-glr-download-link-note - - :ref:`Go to the end ` - to download the full example code.{2} - -.. rst-class:: sphx-glr-example-title - -.. _sphx_glr_{1}: - -""" - # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/project/citing.rst b/doc/project/citing.rst index c5e56e6f12d4..1f8c6509e9e6 100644 --- a/doc/project/citing.rst +++ b/doc/project/citing.rst @@ -32,6 +32,9 @@ By version .. START OF AUTOGENERATED +v3.10.8 + .. image:: ../_static/zenodo_cache/17595503.svg + :target: https://doi.org/10.5281/zenodo.17595503 v3.10.7 .. image:: ../_static/zenodo_cache/17298696.svg :target: https://doi.org/10.5281/zenodo.17298696 diff --git a/doc/sphinxext/util.py b/doc/sphinxext/util.py index 14097ba9396a..c0f336eaea18 100644 --- a/doc/sphinxext/util.py +++ b/doc/sphinxext/util.py @@ -1,5 +1,7 @@ import sys +from sphinx_gallery import gen_rst + def matplotlib_reduced_latex_scraper(block, block_vars, gallery_conf, **kwargs): @@ -19,3 +21,33 @@ def matplotlib_reduced_latex_scraper(block, block_vars, gallery_conf, # Clear basic_units module to re-register with unit registry on import. def clear_basic_units(gallery_conf, fname): return sys.modules.pop('basic_units', None) + + +# Monkey-patching gallery header to include search keywords +EXAMPLE_HEADER = """ +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "{0}" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. meta:: + :keywords: codex + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code{2} + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_{1}: + +""" + + +def patch_header(gallery_conf, fname): + gen_rst.EXAMPLE_HEADER = EXAMPLE_HEADER diff --git a/galleries/examples/animation/double_pendulum.py b/galleries/examples/animation/double_pendulum.py index 7a42a6d989ba..76076341d5c2 100644 --- a/galleries/examples/animation/double_pendulum.py +++ b/galleries/examples/animation/double_pendulum.py @@ -51,7 +51,7 @@ def derivs(t, state): return dydx -# create a time array from 0..t_stop sampled at 0.02 second steps +# create a time array from 0..t_stop sampled at 0.01 second steps dt = 0.01 t = np.arange(0, t_stop, dt) diff --git a/galleries/examples/lines_bars_and_markers/barh.py b/galleries/examples/lines_bars_and_markers/barh.py index 5493c7456c75..c8834e252cb6 100644 --- a/galleries/examples/lines_bars_and_markers/barh.py +++ b/galleries/examples/lines_bars_and_markers/barh.py @@ -15,12 +15,10 @@ # Example data people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim') -y_pos = np.arange(len(people)) performance = 3 + 10 * np.random.rand(len(people)) error = np.random.rand(len(people)) -ax.barh(y_pos, performance, xerr=error, align='center') -ax.set_yticks(y_pos, labels=people) +ax.barh(people, performance, xerr=error, align='center') ax.invert_yaxis() # labels read top-to-bottom ax.set_xlabel('Performance') ax.set_title('How fast do you want to go today?') diff --git a/galleries/examples/ticks/align_ticklabels.py b/galleries/examples/ticks/align_ticklabels.py new file mode 100644 index 000000000000..ec36e0db4d07 --- /dev/null +++ b/galleries/examples/ticks/align_ticklabels.py @@ -0,0 +1,32 @@ +""" +================= +Align tick labels +================= + +By default, tick labels are aligned towards the axis. This means the set of +*y* tick labels appear right-aligned. Because the alignment reference point +is on the axis, left-aligned tick labels would overlap the plotting area. +To achieve a good-looking left-alignment, you have to additionally increase +the padding. +""" +import matplotlib.pyplot as plt + +population = { + "Sydney": 5.2, + "Mexico City": 8.8, + "São Paulo": 12.2, + "Istanbul": 15.9, + "Lagos": 15.9, + "Shanghai": 21.9, +} + +fig, ax = plt.subplots(layout="constrained") +ax.barh(population.keys(), population.values()) +ax.set_xlabel('Population (in millions)') + +# left-align all ticklabels +for ticklabel in ax.get_yticklabels(): + ticklabel.set_horizontalalignment("left") + +# increase padding +ax.tick_params("y", pad=70) diff --git a/galleries/tutorials/coding_shortcuts.py b/galleries/tutorials/coding_shortcuts.py new file mode 100644 index 000000000000..46868482598f --- /dev/null +++ b/galleries/tutorials/coding_shortcuts.py @@ -0,0 +1,172 @@ +""" +================ +Coding shortcuts +================ + +Matplotlib's primary and universal API is the :ref:`Axes interface `. +While it is clearly structured and powerful, it can sometimes feel overly verbose and +thus cumbersome to write. This page collects patterns for condensing the code +of the Axes-based API and achieving the same results with less typing for many simpler +plots. + +.. note:: + + The :ref:`pyplot interface ` is an alternative more compact + interface, and was historically modeled to be similar to MATLAB. It remains a + valid approach for those who want to use it. However, it has the disadvantage that + it achieves its brevity through implicit assumptions that you have to understand. + + Since it follows a different paradigm, switching between the Axes interface and + the pyplot interface requires a shift of the mental model, and some code rewrite, + if the code develops to a point at which pyplot no longer provides enough + flexibility. + +This tutorial goes the other way round, starting from the standard verbose Axes +interface and using its capabilities for shortcuts when you don't need all the +generality. + +Let's assume we want to make a plot of the number of daylight hours per day over the +year in London. + +The standard approach with the Axes interface looks like this. +""" + +import matplotlib.pyplot as plt +import numpy as np + +day = np.arange(365) +hours = 4.276 * np.sin(2 * np.pi * (day - 80)/365) + 12.203 + +fig, ax = plt.subplots() +ax.plot(day, hours, color="orange") +ax.set_xlabel("day") +ax.set_ylabel("daylight hours") +ax.set_title("London") +plt.show() + +# %% +# Note that we've included ``plt.show()`` here. This is needed to show the plot window +# when running from a command line or in a Python script. If you run a Jupyter notebook, +# this command is automatically executed at the end of each cell. +# +# For the rest of the tutorial, we'll assume that we are in a notebook and leave this +# out for brevity. Depending on your context you may still need it. +# +# If you instead want to save to a file, use ``fig.savefig("daylight.png")``. +# +# +# Collect Axes properties into a single ``set()`` call +# ==================================================== +# +# The properties of Matplotlib Artists can be modified through their respective +# ``set_*()`` methods. Artists additionally have a generic ``set()`` method, that takes +# keyword arguments and is equivalent to calling all the respective ``set_*()`` methods. +# :: +# +# ax.set_xlabel("day") +# ax.set_ylabel("daylight hours") +# +# can also be written as :: +# +# ax.set(xlabel="day", ylabel="daylight hours") +# +# This is the most simple and effective reduction you can do. With that we can shorten +# the above plot to + +fig, ax = plt.subplots() +ax.plot(day, hours, color="orange") +ax.set(xlabel="day", ylabel="daylight hours", title="London") + +# %% +# +# This works as long as you only need to pass one parameter to the ``set_*()`` function. +# The individual functions are still necessary if you want more control, e.g. +# ``set_title("London", fontsize=16)``. +# +# +# Not storing a reference to the figure +# ===================================== +# Another nuisance of ``fig, ax = plt.subplots()`` is that you always create a ``fig`` +# variable, even if you don't use it. A slightly shorter version would be using the +# standard variable for unused value in Python (``_``): ``_, ax = plt.subplots()``. +# However, that's only marginally better. +# +# You can work around this by separating figure and Axes creation and chaining them :: +# +# ax = plt.figure().add_subplot() +# +# This is a bit cleaner logically and has the slight advantage that you could set +# figure properties inline as well; e.g. ``plt.figure(facecolor="lightgoldenrod")``. +# But it has the disadvantage that it's longer than ``fig, ax = plt.subplots()``. +# +# You can still obtain the figure from the Axes if needed, e.g. :: +# +# ax.figure.savefig("daylight_hours.png") +# +# The example code now looks like this: + +ax = plt.figure().add_subplot() +ax.plot(day, hours, color="orange") +ax.set(xlabel="day", ylabel="daylight hours", title="London") + +# %% +# Define Axes properties during axes creation +# =========================================== +# The ``set_*`` methods as well as ``set`` modify existing objects. You can +# alternatively define them right at creation. Since you typically don't instantiate +# classes yourself in Matplotlib, but rather call some factory function that creates +# the object and wires it up correctly with the plot, this may seem less obvious. But +# in fact you just pass the desired properties to the factory functions. You are likely +# doing this already in some places without realizing. Consider the function to create +# a line :: +# +# ax.plot(x, y, color="orange") +# +# This is equivalent to :: +# +# line, = ax.plot(x, y) +# line.set_color("orange") +# +# The same can be done with functions that create Axes. + +ax = plt.figure().add_subplot(xlabel="day", ylabel="daylight hours", title="London") +ax.plot(day, hours, color="orange") + +# %% +# .. important:: +# The Axes properties are only accepted as keyword arguments by +# `.Figure.add_subplot`, which creates a single Axes. +# +# For `.Figure.subplots` and `.pyplot.subplots`, you'd have to pass the properties +# as a dict via the keyword argument ``subplot_kw``. The limitation here is that +# such parameters are given to all Axes. For example, if you need two subplots +# (``fig, (ax1, ax2) = plt.subplots(1, 2)``) with different labels, you have to +# set them individually. +# +# Defining Axes properties during creation is best used for single subplots or when +# all subplots share the same properties. +# +# +# Using implicit figure creation +# ============================== +# You can go even further by tapping into the pyplot logic and use `.pyplot.axes` to +# create the axes: + +ax = plt.axes(xlabel="day", ylabel="daylight hours", title="London") +ax.plot(day, hours, color="orange") + +# %% +# .. warning:: +# When using this, you have to be aware of the implicit figure semantics of pyplot. +# ``plt.axes()`` will only create a new figure if no figure exists. Otherwise, it +# will add the Axes to the current existing figure, which is likely not what you +# want. +# +# Not storing a reference to the Axes +# =================================== +# If you only need to visualize one dataset, you can append the plot command +# directly to the Axes creation. This may be useful e.g. in notebooks, +# where you want to create a plot with some configuration, but as little distracting +# code as possible: + +plt.axes(xlabel="day", ylabel="daylight hours").plot(day, hours, color="orange") diff --git a/galleries/tutorials/index.rst b/galleries/tutorials/index.rst index 48187a862a2e..76c0037dca11 100644 --- a/galleries/tutorials/index.rst +++ b/galleries/tutorials/index.rst @@ -32,6 +32,23 @@ a :ref:`FAQ ` in our :ref:`user guide `. +.. raw:: html + +
+ +.. only:: html + + .. image:: /tutorials/images/thumb/sphx_glr_coding_shortcuts_thumb.png + :alt: Coding shortcuts + + :ref:`sphx_glr_tutorials_coding_shortcuts.py` + +.. raw:: html + +
Coding shortcuts
+
+ + .. raw:: html
@@ -92,6 +109,7 @@ a :ref:`FAQ ` in our :ref:`user guide `. :hidden: /tutorials/pyplot + /tutorials/coding_shortcuts /tutorials/images /tutorials/lifecycle /tutorials/artists diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index a87f00201124..a4eb80ad1b34 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -951,9 +951,21 @@ def save(self, filename, writer=None, fps=None, dpi=None, codec=None, filename : str The output filename, e.g., :file:`mymovie.mp4`. - writer : `MovieWriter` or str, default: :rc:`animation.writer` - A `MovieWriter` instance to use or a key that identifies a - class to use, such as 'ffmpeg'. + writer : `AbstractMovieWriter` subclass or str, default: :rc:`animation.writer` + The writer used to grab the frames and create the movie file. + This can be an instance of an `AbstractMovieWriter` subclass or a + string. The builtin writers are + + ================== ============================== + str class + ================== ============================== + 'ffmpeg' `.FFMpegWriter` + 'ffmpeg_file' `.FFMpegFileWriter` + 'imagemagick' `.ImageMagickWriter` + 'imagemagick_file' `.ImageMagickFileWriter` + 'pillow' `.PillowWriter` + 'html' `.HTMLWriter` + ================== ============================== fps : int, optional Movie frame rate (per second). If not set, the frame rate from the diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index d0aded5fff63..dd642ba838af 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -55,7 +55,7 @@ ("Key_F8", "f8"), ("Key_F9", "f9"), ("Key_F10", "f10"), - ("Key_F10", "f11"), + ("Key_F11", "f11"), ("Key_F12", "f12"), ("Key_Super_L", "super"), ("Key_Super_R", "super"), diff --git a/lib/matplotlib/tests/test_doc.py b/lib/matplotlib/tests/test_doc.py index 3e28fd1b8eb7..f3d6d6e3fd5d 100644 --- a/lib/matplotlib/tests/test_doc.py +++ b/lib/matplotlib/tests/test_doc.py @@ -7,9 +7,9 @@ def test_sphinx_gallery_example_header(): This test monitors that the version we have copied is still the same as the EXAMPLE_HEADER in sphinx-gallery. If sphinx-gallery changes its EXAMPLE_HEADER, this test will start to fail. In that case, please update - the monkey-patching of EXAMPLE_HEADER in conf.py. + the monkey-patching of EXAMPLE_HEADER in sphinxext/util.py. """ - pytest.importorskip('sphinx_gallery', minversion='0.16.0') + pytest.importorskip('sphinx_gallery', minversion='0.20.0') from sphinx_gallery import gen_rst EXAMPLE_HEADER = """ @@ -25,7 +25,7 @@ def test_sphinx_gallery_example_header(): :class: sphx-glr-download-link-note :ref:`Go to the end ` - to download the full example code.{2} + to download the full example code{2} .. rst-class:: sphx-glr-example-title diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py index 07b67a3e04ee..2ee72c6a89fa 100644 --- a/tools/cache_zenodo_svg.py +++ b/tools/cache_zenodo_svg.py @@ -63,6 +63,7 @@ def _get_xdg_cache_dir(): if __name__ == "__main__": data = { + "v3.10.8": "17595503", "v3.10.7": "17298696", "v3.10.6": "16999430", "v3.10.5": "16644850",