From fe276395739773e2fa558bf98533f310a77fdb97 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 15 May 2024 18:48:18 -0400 Subject: [PATCH 01/61] BLD: bump branch away from tag So the tarballs from GitHub are stable. From 43c95480c6e9ff8787b5ead9fc9daec745b719a5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 15 May 2024 19:54:13 -0400 Subject: [PATCH 02/61] DOC: Add Zenodo DOI for 3.9.0 --- doc/_static/zenodo_cache/11201097.svg | 35 +++++++++++++++++++++++++++ doc/project/citing.rst | 3 +++ tools/cache_zenodo_svg.py | 1 + 3 files changed, 39 insertions(+) create mode 100644 doc/_static/zenodo_cache/11201097.svg diff --git a/doc/_static/zenodo_cache/11201097.svg b/doc/_static/zenodo_cache/11201097.svg new file mode 100644 index 000000000000..70f35a7a659f --- /dev/null +++ b/doc/_static/zenodo_cache/11201097.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + DOI + + + DOI + + + 10.5281/zenodo.11201097 + + + 10.5281/zenodo.11201097 + + + \ No newline at end of file diff --git a/doc/project/citing.rst b/doc/project/citing.rst index 008ee794a41b..fd013231b6c5 100644 --- a/doc/project/citing.rst +++ b/doc/project/citing.rst @@ -32,6 +32,9 @@ By version .. START OF AUTOGENERATED +v3.9.0 + .. image:: ../_static/zenodo_cache/11201097.svg + :target: https://doi.org/10.5281/zenodo.11201097 v3.8.4 .. image:: ../_static/zenodo_cache/10916799.svg :target: https://doi.org/10.5281/zenodo.10916799 diff --git a/tools/cache_zenodo_svg.py b/tools/cache_zenodo_svg.py index fbdbceaf0fbb..600e87efc498 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.9.0": "11201097", "v3.8.4": "10916799", "v3.8.3": "10661079", "v3.8.2": "10150955", From ac17d9fa20166cd1363b9486e868d899a5b3a88e Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 16 May 2024 20:16:05 +0200 Subject: [PATCH 03/61] Backport PR #28231: DOC: we do not need the blit call in on_draw --- galleries/examples/event_handling/path_editor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/galleries/examples/event_handling/path_editor.py b/galleries/examples/event_handling/path_editor.py index d6e84b454008..2af54bad53ed 100644 --- a/galleries/examples/event_handling/path_editor.py +++ b/galleries/examples/event_handling/path_editor.py @@ -94,7 +94,6 @@ def on_draw(self, event): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.pathpatch) self.ax.draw_artist(self.line) - self.canvas.blit(self.ax.bbox) def on_button_press(self, event): """Callback for mouse button presses.""" From 6558e560947bfcfcd2c1bebd0bec788aecbddc0c Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 16 May 2024 13:26:22 -0500 Subject: [PATCH 04/61] Backport PR #28233: CI: Fix font install on macOS/Homebrew --- .github/workflows/tests.yml | 7 +++---- azure-pipelines.yml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 126693beafa7..daa07e62b2e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -160,10 +160,9 @@ jobs: fi ;; macOS) - brew install ccache - brew tap homebrew/cask-fonts - brew install font-noto-sans-cjk ghostscript gobject-introspection gtk4 ninja - brew install --cask inkscape + brew update + brew install ccache ghostscript gobject-introspection gtk4 ninja + brew install --cask font-noto-sans-cjk inkscape ;; esac diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bf055d0eaa16..4c50c543846a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -119,10 +119,10 @@ stages: texlive-xetex ;; Darwin) + brew update brew install --cask xquartz brew install ccache ffmpeg imagemagick mplayer ninja pkg-config - brew tap homebrew/cask-fonts - brew install font-noto-sans-cjk-sc + brew install --cask font-noto-sans-cjk-sc ;; Windows_NT) choco install ninja From 492f4bd95c8d68e60e78523eee2c8c17f2a34bac Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 16 May 2024 16:02:28 -0400 Subject: [PATCH 05/61] DOC: Fix a typo in GitHub stats --- doc/users/github_stats.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index a7f909b30cf5..629d9319fc57 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -655,7 +655,7 @@ Issues (97): * :ghissue:`26778`: [MNT]: Numpy 2.0 support strategy * :ghissue:`28020`: [Bug]: imsave fails on RGBA data when origin is set to lower * :ghissue:`7720`: WXAgg backend not rendering nicely on retina -* :ghissue:`28069`: [Bug]: Cant save with custom toolbar +* :ghissue:`28069`: [Bug]: Can't save with custom toolbar * :ghissue:`28005`: [Doc]: Improve contribute instructions * :ghissue:`22376`: [ENH]: align_titles * :ghissue:`5506`: Confusing status bar values in presence of multiple axes From c35a0e576c172a3a89bfafd63c372e2b6afaa5a5 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 16 May 2024 21:49:04 -0400 Subject: [PATCH 06/61] Backport PR #28219: Bump the actions group with 2 updates --- .github/workflows/cibuildwheel.yml | 10 +++++----- .github/workflows/conflictcheck.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 41d28d864dbe..9c327aba22c2 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -140,7 +140,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -149,7 +149,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -158,7 +158,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -176,7 +176,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for PyPy - uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0 + uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: diff --git a/.github/workflows/conflictcheck.yml b/.github/workflows/conflictcheck.yml index 3eb384fa6585..fc759f52a6b0 100644 --- a/.github/workflows/conflictcheck.yml +++ b/.github/workflows/conflictcheck.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if PRs have merge conflicts - uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0 + uses: eps1lon/actions-label-merge-conflict@6d74047dcef155976a15e4a124dde2c7fe0c5522 # v3.0.1 with: dirtyLabel: "status: needs rebase" repoToken: "${{ secrets.GITHUB_TOKEN }}" From 9f70709254bb3229662c6e568d5fe0f3c016fd0a Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 17 May 2024 10:19:42 +0200 Subject: [PATCH 07/61] Backport PR #28243: DOC: Add more 3D plot types --- galleries/plot_types/3D/bar3d_simple.py | 29 ++++++++++++++++++++ galleries/plot_types/3D/plot3d_simple.py | 27 ++++++++++++++++++ galleries/plot_types/3D/quiver3d_simple.py | 32 ++++++++++++++++++++++ galleries/plot_types/3D/stem3d.py | 27 ++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 galleries/plot_types/3D/bar3d_simple.py create mode 100644 galleries/plot_types/3D/plot3d_simple.py create mode 100644 galleries/plot_types/3D/quiver3d_simple.py create mode 100644 galleries/plot_types/3D/stem3d.py diff --git a/galleries/plot_types/3D/bar3d_simple.py b/galleries/plot_types/3D/bar3d_simple.py new file mode 100644 index 000000000000..aa75560de8f2 --- /dev/null +++ b/galleries/plot_types/3D/bar3d_simple.py @@ -0,0 +1,29 @@ +""" +========================== +bar3d(x, y, z, dx, dy, dz) +========================== + +See `~mpl_toolkits.mplot3d.axes3d.Axes3D.bar3d`. +""" +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# Make data +x = [1, 1, 2, 2] +y = [1, 2, 1, 2] +z = [0, 0, 0, 0] +dx = np.ones_like(x)*0.5 +dy = np.ones_like(x)*0.5 +dz = [2, 3, 1, 4] + +# Plot +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) +ax.bar3d(x, y, z, dx, dy, dz) + +ax.set(xticklabels=[], + yticklabels=[], + zticklabels=[]) + +plt.show() diff --git a/galleries/plot_types/3D/plot3d_simple.py b/galleries/plot_types/3D/plot3d_simple.py new file mode 100644 index 000000000000..108dbecfbd87 --- /dev/null +++ b/galleries/plot_types/3D/plot3d_simple.py @@ -0,0 +1,27 @@ +""" +================ +plot(xs, ys, zs) +================ + +See `~mpl_toolkits.mplot3d.axes3d.Axes3D.plot`. +""" +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# Make data +n = 100 +xs = np.linspace(0, 1, n) +ys = np.sin(xs * 6 * np.pi) +zs = np.cos(xs * 6 * np.pi) + +# Plot +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) +ax.plot(xs, ys, zs) + +ax.set(xticklabels=[], + yticklabels=[], + zticklabels=[]) + +plt.show() diff --git a/galleries/plot_types/3D/quiver3d_simple.py b/galleries/plot_types/3D/quiver3d_simple.py new file mode 100644 index 000000000000..6f4aaa9cad90 --- /dev/null +++ b/galleries/plot_types/3D/quiver3d_simple.py @@ -0,0 +1,32 @@ +""" +======================== +quiver(X, Y, Z, U, V, W) +======================== + +See `~mpl_toolkits.mplot3d.axes3d.Axes3D.quiver`. +""" +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# Make data +n = 4 +x = np.linspace(-1, 1, n) +y = np.linspace(-1, 1, n) +z = np.linspace(-1, 1, n) +X, Y, Z = np.meshgrid(x, y, z) +U = (X + Y)/5 +V = (Y - X)/5 +W = Z*0 + + +# Plot +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) +ax.quiver(X, Y, Z, U, V, W) + +ax.set(xticklabels=[], + yticklabels=[], + zticklabels=[]) + +plt.show() diff --git a/galleries/plot_types/3D/stem3d.py b/galleries/plot_types/3D/stem3d.py new file mode 100644 index 000000000000..50aa80146bdc --- /dev/null +++ b/galleries/plot_types/3D/stem3d.py @@ -0,0 +1,27 @@ +""" +============= +stem(x, y, z) +============= + +See `~mpl_toolkits.mplot3d.axes3d.Axes3D.stem`. +""" +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use('_mpl-gallery') + +# Make data +n = 20 +x = np.sin(np.linspace(0, 2*np.pi, n)) +y = np.cos(np.linspace(0, 2*np.pi, n)) +z = np.linspace(0, 1, n) + +# Plot +fig, ax = plt.subplots(subplot_kw={"projection": "3d"}) +ax.stem(x, y, z) + +ax.set(xticklabels=[], + yticklabels=[], + zticklabels=[]) + +plt.show() From 63351f2f8d9c7566634b87dd94cd4ae730c701c0 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 17 May 2024 11:41:07 +0200 Subject: [PATCH 08/61] Backport PR #28230: Add extra imports to improve typing --- lib/matplotlib/pyplot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index b1354341617d..925322cdd1e5 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -90,6 +90,9 @@ import PIL.Image from numpy.typing import ArrayLike + import matplotlib.axes + import matplotlib.artist + import matplotlib.backend_bases from matplotlib.axis import Tick from matplotlib.axes._base import _AxesBase from matplotlib.backend_bases import RendererBase, Event From fefe7d298f71bda84e2bcc707dc7c6004de14ad9 Mon Sep 17 00:00:00 2001 From: hannah Date: Fri, 17 May 2024 15:16:37 -0400 Subject: [PATCH 09/61] Backport PR #28252: DOC: Flip the imshow plot types example to match the other examples --- galleries/plot_types/arrays/imshow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/plot_types/arrays/imshow.py b/galleries/plot_types/arrays/imshow.py index c28278c6c657..b2920e7fd80c 100644 --- a/galleries/plot_types/arrays/imshow.py +++ b/galleries/plot_types/arrays/imshow.py @@ -19,6 +19,6 @@ # plot fig, ax = plt.subplots() -ax.imshow(Z) +ax.imshow(Z, origin='lower') plt.show() From 82d14c8183f3c4831ee94a27f33aacf676871a53 Mon Sep 17 00:00:00 2001 From: hannah Date: Fri, 17 May 2024 15:50:09 -0400 Subject: [PATCH 10/61] Backport PR #28254: [DOC] plot type heading consistency --- galleries/plot_types/arrays/README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/plot_types/arrays/README.rst b/galleries/plot_types/arrays/README.rst index d9dbfd10ead7..aba457a69940 100644 --- a/galleries/plot_types/arrays/README.rst +++ b/galleries/plot_types/arrays/README.rst @@ -1,7 +1,7 @@ .. _array_plots: -Gridded data: -------------- +Gridded data +------------ Plots of arrays and images :math:`Z_{i, j}` and fields :math:`U_{i, j}, V_{i, j}` on `regular grids `_ and From f3b34c1137a12280c1619183f9c5cdc5b087d7f0 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 20 May 2024 16:19:11 -0500 Subject: [PATCH 11/61] Backport PR #28257: Clean up some Meson-related leftovers --- .../api_changes_3.5.0/development.rst | 6 +++--- doc/install/environment_variables_faq.rst | 7 ------- doc/install/index.rst | 18 ++++++++---------- pyproject.toml | 1 - src/checkdep_freetype2.c | 4 ++-- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/doc/api/prev_api_changes/api_changes_3.5.0/development.rst b/doc/api/prev_api_changes/api_changes_3.5.0/development.rst index 2db21237a699..b42e6eff3423 100644 --- a/doc/api/prev_api_changes/api_changes_3.5.0/development.rst +++ b/doc/api/prev_api_changes/api_changes_3.5.0/development.rst @@ -77,6 +77,6 @@ In order to avoid conflicting with the use of :file:`setup.cfg` by ``setup.cfg`` to ``mplsetup.cfg``. The :file:`setup.cfg.template` has been correspondingly been renamed to :file:`mplsetup.cfg.template`. -Note that the path to this configuration file can still be set via the -:envvar:`MPLSETUPCFG` environment variable, which allows one to keep using the -same file before and after this change. +Note that the path to this configuration file can still be set via the ``MPLSETUPCFG`` +environment variable, which allows one to keep using the same file before and after this +change. diff --git a/doc/install/environment_variables_faq.rst b/doc/install/environment_variables_faq.rst index ba384343cc5a..38e0d0ef0c63 100644 --- a/doc/install/environment_variables_faq.rst +++ b/doc/install/environment_variables_faq.rst @@ -29,13 +29,6 @@ Environment variables used to find a base directory in which the :file:`matplotlib` subdirectory is created. -.. envvar:: MPLSETUPCFG - - This optional variable can be set to the full path of a :file:`mplsetup.cfg` - configuration file used to customize the Matplotlib build. By default, a - :file:`mplsetup.cfg` file in the root of the Matplotlib source tree will be - read. Supported build options are listed in :file:`mplsetup.cfg.template`. - .. envvar:: PATH The list of directories searched to find executable programs. diff --git a/doc/install/index.rst b/doc/install/index.rst index ea8e29d71565..867e4600a77e 100644 --- a/doc/install/index.rst +++ b/doc/install/index.rst @@ -121,22 +121,20 @@ Before trying to install Matplotlib, please install the :ref:`dependencies`. To build from a tarball, download the latest *tar.gz* release file from `the PyPI files page `_. -We provide a `mplsetup.cfg`_ file which you can use to customize the build -process. For example, which default backend to use, whether some of the -optional libraries that Matplotlib ships with are installed, and so on. This -file will be particularly useful to those packaging Matplotlib. - -.. _mplsetup.cfg: https://raw.githubusercontent.com/matplotlib/matplotlib/main/mplsetup.cfg.template - If you are building your own Matplotlib wheels (or sdists) on Windows, note that any DLLs that you copy into the source tree will be packaged too. - Configure build and behavior defaults ===================================== -Aspects of the build and install process and some behaviorial defaults of the -library can be configured via: +We provide a `meson.options`_ file containing options with which you can use to +customize the build process. For example, which default backend to use, whether some of +the optional libraries that Matplotlib ships with are installed, and so on. These +options will be particularly useful to those packaging Matplotlib. + +.. _meson.options: https://github.com/matplotlib/matplotlib/blob/main/meson.options + +Aspects of some behaviorial defaults of the library can be configured via: .. toctree:: :maxdepth: 2 diff --git a/pyproject.toml b/pyproject.toml index fe75b325dc89..a9fb7df68450 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,6 @@ install = ['--tags=data,python-runtime,runtime'] [tool.setuptools_scm] version_scheme = "release-branch-semver" local_scheme = "node-and-date" -write_to = "lib/matplotlib/_version.py" parentdir_prefix_version = "matplotlib-" fallback_version = "0.0+UNKNOWN" diff --git a/src/checkdep_freetype2.c b/src/checkdep_freetype2.c index 8d9d8ca24a07..16e8ac23919e 100644 --- a/src/checkdep_freetype2.c +++ b/src/checkdep_freetype2.c @@ -1,7 +1,7 @@ #ifdef __has_include #if !__has_include() #error "FreeType version 2.3 or higher is required. \ -You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib download it." +You may set the system-freetype Meson build option to false to let Matplotlib download it." #endif #endif @@ -15,5 +15,5 @@ You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib downlo XSTR(FREETYPE_MAJOR) "." XSTR(FREETYPE_MINOR) "." XSTR(FREETYPE_PATCH) ".") #if FREETYPE_MAJOR << 16 + FREETYPE_MINOR << 8 + FREETYPE_PATCH < 0x020300 #error "FreeType version 2.3 or higher is required. \ -You may unset the system_freetype entry in mplsetup.cfg to let Matplotlib download it." +You may set the system-freetype Meson build option to false to let Matplotlib download it." #endif From 8cb17f8b2ae55ab8374650d41cf93bfbf6a3e093 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 21 May 2024 14:15:04 -0400 Subject: [PATCH 12/61] Backport PR #28269: Handle GetForegroundWindow() returning NULL. --- lib/matplotlib/backends/_backend_tk.py | 2 +- src/_c_internal_utils.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 295f6c41372d..df06440a9826 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -44,7 +44,7 @@ def _restore_foreground_window_at_end(): try: yield finally: - if mpl.rcParams['tk.window_focus']: + if foreground and mpl.rcParams['tk.window_focus']: _c_internal_utils.Win32_SetForegroundWindow(foreground) diff --git a/src/_c_internal_utils.cpp b/src/_c_internal_utils.cpp index 464aabcb2e3a..e118183ecc8b 100644 --- a/src/_c_internal_utils.cpp +++ b/src/_c_internal_utils.cpp @@ -111,7 +111,11 @@ static py::object mpl_GetForegroundWindow(void) { #ifdef _WIN32 - return py::capsule(GetForegroundWindow(), "HWND"); + if (HWND hwnd = GetForegroundWindow()) { + return py::capsule(hwnd, "HWND"); + } else { + return py::none(); + } #else return py::none(); #endif From 425b4d3de90f49c6dc84a4396e9ca4c901df1f52 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Wed, 22 May 2024 07:37:36 +0200 Subject: [PATCH 13/61] Backport PR #28274: ci: Remove deprecated codeql option --- .github/workflows/codeql-analysis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 29d2859999bd..203b0eee9ca4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,7 +32,6 @@ jobs: uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - setup-python-dependencies: false - name: Build compiled code if: matrix.language == 'c-cpp' From 7bd803f06f07d0156903c5fafe7f5735b844ed4d Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 22 May 2024 07:59:18 +0200 Subject: [PATCH 14/61] Backport PR #28272: BLD: Move macos builders from 11 to 12 --- .github/workflows/cibuildwheel.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 9c327aba22c2..2fa9569f3fb6 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -105,6 +105,7 @@ jobs: CIBW_SKIP: "*-musllinux_aarch64" CIBW_TEST_COMMAND: >- python {package}/ci/check_version_number.py + MACOSX_DEPLOYMENT_TARGET: "10.12" MPL_DISABLE_FH4: "yes" strategy: matrix: @@ -115,16 +116,10 @@ jobs: cibw_archs: "aarch64" - os: windows-latest cibw_archs: "auto64" - - os: macos-11 + - os: macos-12 cibw_archs: "x86_64" - # NOTE: macos_target can be moved back into global environment after - # meson-python 0.16.0 is released. - macos_target: "10.12" - os: macos-14 cibw_archs: "arm64" - # NOTE: macos_target can be moved back into global environment after - # meson-python 0.16.0 is released. - macos_target: "11.0" steps: - name: Set up QEMU @@ -146,7 +141,6 @@ jobs: env: CIBW_BUILD: "cp312-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.11 uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 @@ -155,7 +149,6 @@ jobs: env: CIBW_BUILD: "cp311-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.10 uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 @@ -164,7 +157,6 @@ jobs: env: CIBW_BUILD: "cp310-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for CPython 3.9 uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 @@ -173,7 +165,6 @@ jobs: env: CIBW_BUILD: "cp39-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" - name: Build wheels for PyPy uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 @@ -182,7 +173,6 @@ jobs: env: CIBW_BUILD: "pp39-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - MACOSX_DEPLOYMENT_TARGET: "${{ matrix.macos_target }}" if: matrix.cibw_archs != 'aarch64' - uses: actions/upload-artifact@v4 From bb1dcc372e5b507e5601ead97eb18c6e206307d8 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 23 May 2024 10:33:21 +0200 Subject: [PATCH 15/61] Backport PR #28280: DOC: Add an example for 2D images in 3D plots --- galleries/examples/mplot3d/imshow3d.py | 88 ++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 galleries/examples/mplot3d/imshow3d.py diff --git a/galleries/examples/mplot3d/imshow3d.py b/galleries/examples/mplot3d/imshow3d.py new file mode 100644 index 000000000000..557d96e1bce5 --- /dev/null +++ b/galleries/examples/mplot3d/imshow3d.py @@ -0,0 +1,88 @@ +""" +=============== +2D images in 3D +=============== + +This example demonstrates how to plot 2D color coded images (similar to +`.Axes.imshow`) as a plane in 3D. + +Matplotlib does not have a native function for this. Below we build one by relying +on `.Axes3D.plot_surface`. For simplicity, there are some differences to +`.Axes.imshow`: This function does not set the aspect of the Axes, hence pixels are +not necessarily square. Also, pixel edges are on integer values rather than pixel +centers. Furthermore, many optional parameters of `.Axes.imshow` are not implemented. + +Multiple calls of ``imshow3d`` use independent norms and thus different color scales +by default. If you want to have a single common color scale, you need to construct +a suitable norm beforehand and pass it to all ``imshow3d`` calls. + +A fundamental limitation of the 3D plotting engine is that intersecting objects cannot +be drawn correctly. One object will always be drawn after the other. Therefore, +multiple image planes can well be used in the background as shown in this example. +But this approach is not suitable if the planes intersect. +""" + +import matplotlib.pyplot as plt +import numpy as np + +from matplotlib.colors import Normalize + + +def imshow3d(ax, array, value_direction='z', pos=0, norm=None, cmap=None): + """ + Display a 2D array as a color-coded 2D image embedded in 3d. + + The image will be in a plane perpendicular to the coordinate axis *value_direction*. + + Parameters + ---------- + ax : Axes3D + The 3D Axes to plot into. + array : 2D numpy array + The image values. + value_direction : {'x', 'y', 'z'} + The axis normal to the image plane. + pos : float + The numeric value on the *value_direction* axis at which the image plane is + located. + norm : `~matplotlib.colors.Normalize`, default: Normalize + The normalization method used to scale scalar data. See `imshow()`. + cmap : str or `~matplotlib.colors.Colormap`, default: :rc:`image.cmap` + The Colormap instance or registered colormap name used to map scalar data + to colors. + """ + if norm is None: + norm = Normalize() + colors = plt.get_cmap(cmap)(norm(array)) + + if value_direction == 'x': + nz, ny = array.shape + zi, yi = np.mgrid[0:nz + 1, 0:ny + 1] + xi = np.full_like(yi, pos) + elif value_direction == 'y': + nx, nz = array.shape + xi, zi = np.mgrid[0:nx + 1, 0:nz + 1] + yi = np.full_like(zi, pos) + elif value_direction == 'z': + ny, nx = array.shape + yi, xi = np.mgrid[0:ny + 1, 0:nx + 1] + zi = np.full_like(xi, pos) + else: + raise ValueError(f"Invalid value_direction: {value_direction!r}") + ax.plot_surface(xi, yi, zi, rstride=1, cstride=1, facecolors=colors, shade=False) + + +fig = plt.figure() +ax = fig.add_subplot(projection='3d') +ax.set(xlabel="x", ylabel="y", zlabel="z") + +nx, ny, nz = 8, 10, 5 +data_xy = np.arange(ny * nx).reshape(ny, nx) + 15 * np.random.random((ny, nx)) +data_yz = np.arange(nz * ny).reshape(nz, ny) + 10 * np.random.random((nz, ny)) +data_zx = np.arange(nx * nz).reshape(nx, nz) + 8 * np.random.random((nx, nz)) + +imshow3d(ax, data_xy) +imshow3d(ax, data_yz, value_direction='x', cmap='magma') +imshow3d(ax, data_zx, value_direction='y', pos=ny, cmap='plasma') + +plt.show() From 042e1bb7f9d5b4295448e7fcf3bbe3b9fbcd9e4f Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Fri, 24 May 2024 23:49:19 +0200 Subject: [PATCH 16/61] Backport PR #28261: Correct roll angle units, issue #28256 --- lib/mpl_toolkits/mplot3d/axes3d.py | 1 + lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d0f5c8d2b23b..677c2668d4e9 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1524,6 +1524,7 @@ def _on_move(self, event): dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll) elev = self.elev + delev azim = self.azim + dazim + roll = self.roll vertical_axis = self._axis_names[self._vertical_axis] self.view_init( elev=elev, diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index ed56e5505d8e..c339e35e903c 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1766,6 +1766,31 @@ def test_shared_axes_retick(): assert ax2.get_zlim() == (-0.5, 2.5) +def test_rotate(): + """Test rotating using the left mouse button.""" + for roll in [0, 30]: + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection='3d') + ax.view_init(0, 0, roll) + ax.figure.canvas.draw() + + # drag mouse horizontally to change azimuth + dx = 0.1 + dy = 0.2 + ax._button_press( + mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0)) + ax._on_move( + mock_event(ax, button=MouseButton.LEFT, + xdata=dx*ax._pseudo_w, ydata=dy*ax._pseudo_h)) + ax.figure.canvas.draw() + roll_radians = np.deg2rad(ax.roll) + cs = np.cos(roll_radians) + sn = np.sin(roll_radians) + assert ax.elev == (-dy*180*cs + dx*180*sn) + assert ax.azim == (-dy*180*sn - dx*180*cs) + assert ax.roll == roll + + def test_pan(): """Test mouse panning using the middle mouse button.""" From a4a0a9571f2a257b9bc8224e6b266253991be02e Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 25 May 2024 20:20:22 +0200 Subject: [PATCH 17/61] Backport PR #28297: Solved #28296 Added missing comma --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b65004b8c272..34c4023a256e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1586,7 +1586,7 @@ def plot(self, *args, scalex=True, scaley=True, data=None, **kwargs): >>> plot(x1, y1, 'bo') >>> plot(x2, y2, 'go') - - If *x* and/or *y* are 2D arrays a separate data set will be drawn + - If *x* and/or *y* are 2D arrays, a separate data set will be drawn for every column. If both *x* and *y* are 2D, they must have the same shape. If only one of them is 2D with shape (N, m) the other must have length N and will be used for every data set m. From 3a55c47f4bf09a247cd420b2cf00f52f9a04629a Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sun, 26 May 2024 18:41:05 +0100 Subject: [PATCH 18/61] Backport PR #28303: Removed drawedges repeated definition from function doc string --- lib/matplotlib/colorbar.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index af61e4671ff4..156ea2ff6497 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -257,10 +257,6 @@ class Colorbar: *location* is None, the ticks will be at the bottom for a horizontal colorbar and at the right for a vertical. - drawedges : bool - Whether to draw lines at color boundaries. - - %(_colormap_kw_doc)s location : None or {'left', 'right', 'top', 'bottom'} From 7462f5e932a7bb99c804c9b19de5fcca48b046bd Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 29 May 2024 14:50:35 -0400 Subject: [PATCH 19/61] Backport PR #28273: CI: Add GitHub artifact attestations to package distribution --- .github/workflows/cibuildwheel.yml | 7 +++++++ .pre-commit-config.yaml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 2fa9569f3fb6..04c70a767ce0 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -189,6 +189,8 @@ jobs: environment: release permissions: id-token: write + attestations: write + contents: read steps: - name: Download packages uses: actions/download-artifact@v4 @@ -200,5 +202,10 @@ jobs: - name: Print out packages run: ls dist + - name: Generate artifact attestation for sdist and wheel + uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + with: + subject-path: dist/matplotlib-* + - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2dc1ca5352c0..14817e95929f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -79,7 +79,7 @@ repos: - id: yamllint args: ["--strict", "--config-file=.yamllint.yml"] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.1 + rev: 0.28.4 hooks: # TODO: Re-enable this when https://github.com/microsoft/azure-pipelines-vscode/issues/567 is fixed. # - id: check-azure-pipelines From ccc61cb2b818bbc2c9e9e2e83ebf11000760a7da Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 29 May 2024 20:08:27 -0500 Subject: [PATCH 20/61] Backport PR #27001: [TYP] Add overload of `pyplot.subplots` --- lib/matplotlib/figure.pyi | 34 +++++++++++++++++-------- lib/matplotlib/gridspec.pyi | 2 +- lib/matplotlib/pyplot.py | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index eae21c2614f0..21de9159d56c 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -1,12 +1,12 @@ from collections.abc import Callable, Hashable, Iterable import os -from typing import Any, IO, Literal, TypeVar, overload +from typing import Any, IO, Literal, Sequence, TypeVar, overload import numpy as np from numpy.typing import ArrayLike from matplotlib.artist import Artist -from matplotlib.axes import Axes, SubplotBase +from matplotlib.axes import Axes from matplotlib.backend_bases import ( FigureCanvasBase, MouseButton, @@ -92,6 +92,20 @@ class FigureBase(Artist): @overload def add_subplot(self, **kwargs) -> Axes: ... @overload + def subplots( + self, + nrows: Literal[1] = ..., + ncols: Literal[1] = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[True] = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + ) -> Axes: ... + @overload def subplots( self, nrows: int = ..., @@ -100,11 +114,11 @@ class FigureBase(Artist): sharex: bool | Literal["none", "all", "row", "col"] = ..., sharey: bool | Literal["none", "all", "row", "col"] = ..., squeeze: Literal[False], - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ... - ) -> np.ndarray: ... + gridspec_kw: dict[str, Any] | None = ..., + ) -> np.ndarray: ... # TODO numpy/numpy#24738 @overload def subplots( self, @@ -114,11 +128,11 @@ class FigureBase(Artist): sharex: bool | Literal["none", "all", "row", "col"] = ..., sharey: bool | Literal["none", "all", "row", "col"] = ..., squeeze: bool = ..., - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ... - ) -> np.ndarray | SubplotBase | Axes: ... + gridspec_kw: dict[str, Any] | None = ..., + ) -> Axes | np.ndarray: ... def delaxes(self, ax: Axes) -> None: ... def clear(self, keep_observers: bool = ...) -> None: ... def clf(self, keep_observers: bool = ...) -> None: ... diff --git a/lib/matplotlib/gridspec.pyi b/lib/matplotlib/gridspec.pyi index 1ac1bb0b40e7..b6732ad8fafa 100644 --- a/lib/matplotlib/gridspec.pyi +++ b/lib/matplotlib/gridspec.pyi @@ -54,7 +54,7 @@ class GridSpecBase: sharey: bool | Literal["all", "row", "col", "none"] = ..., squeeze: Literal[True] = ..., subplot_kw: dict[str, Any] | None = ... - ) -> np.ndarray | SubplotBase | Axes: ... + ) -> np.ndarray | Axes: ... class GridSpec(GridSpecBase): left: float | None diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 925322cdd1e5..a3ce60f01ef5 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1548,6 +1548,57 @@ def subplot(*args, **kwargs) -> Axes: return ax +@overload +def subplots( + nrows: Literal[1] = ..., + ncols: Literal[1] = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[True] = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, Axes]: + ... + + +@overload +def subplots( + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[False], + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, np.ndarray]: # TODO numpy/numpy#24738 + ... + + +@overload +def subplots( + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: bool = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw +) -> tuple[Figure, Axes | np.ndarray]: + ... + + def subplots( nrows: int = 1, ncols: int = 1, *, sharex: bool | Literal["none", "all", "row", "col"] = False, From ecdbc277d3577d1f57401a19151f1a39816472b5 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 2 Jun 2024 11:55:43 +0200 Subject: [PATCH 21/61] Backport PR #28041: [BUG]: Shift box_aspect according to vertical_axis --- lib/mpl_toolkits/mplot3d/axes3d.py | 22 +++++++++++++++---- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 18 +++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 677c2668d4e9..18b823ccccb3 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -383,7 +383,7 @@ def set_box_aspect(self, aspect, *, zoom=1): # of the axes in mpl3.8. aspect *= 1.8294640721620434 * 25/24 * zoom / np.linalg.norm(aspect) - self._box_aspect = aspect + self._box_aspect = self._roll_to_vertical(aspect, reverse=True) self.stale = True def apply_aspect(self, position=None): @@ -1191,9 +1191,23 @@ def set_proj_type(self, proj_type, focal_length=None): f"None for proj_type = {proj_type}") self._focal_length = np.inf - def _roll_to_vertical(self, arr): - """Roll arrays to match the different vertical axis.""" - return np.roll(arr, self._vertical_axis - 2) + def _roll_to_vertical( + self, arr: "np.typing.ArrayLike", reverse: bool = False + ) -> np.ndarray: + """ + Roll arrays to match the different vertical axis. + + Parameters + ---------- + arr : ArrayLike + Array to roll. + reverse : bool, default: False + Reverse the direction of the roll. + """ + if reverse: + return np.roll(arr, (self._vertical_axis - 2) * -1) + else: + return np.roll(arr, (self._vertical_axis - 2)) def get_proj(self): """Create the projection matrix from the current viewing position.""" diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index c339e35e903c..34f10cb9fd63 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -2301,6 +2301,24 @@ def test_on_move_vertical_axis(vertical_axis: str) -> None: ) +@pytest.mark.parametrize( + "vertical_axis, aspect_expected", + [ + ("x", [1.190476, 0.892857, 1.190476]), + ("y", [0.892857, 1.190476, 1.190476]), + ("z", [1.190476, 1.190476, 0.892857]), + ], +) +def test_set_box_aspect_vertical_axis(vertical_axis, aspect_expected): + ax = plt.subplot(1, 1, 1, projection="3d") + ax.view_init(elev=0, azim=0, roll=0, vertical_axis=vertical_axis) + ax.figure.canvas.draw() + + ax.set_box_aspect(None) + + np.testing.assert_allclose(aspect_expected, ax._box_aspect, rtol=1e-6) + + @image_comparison(baseline_images=['arc_pathpatch.png'], remove_text=True, style='mpl20') From 623445641b3a9bbe92c01d8b454b812bdd9f6ccb Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 2 Jun 2024 10:47:35 -0400 Subject: [PATCH 22/61] Backport PR #28292: Resolve MaxNLocator IndexError when no large steps --- lib/matplotlib/tests/test_ticker.py | 8 ++++++++ lib/matplotlib/ticker.py | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 36b83c95b3d3..ac68a5d90b14 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -130,6 +130,14 @@ def test_view_limits_round_numbers_with_offset(self): loc = mticker.MultipleLocator(base=3.147, offset=1.3) assert_almost_equal(loc.view_limits(-4, 4), (-4.994, 4.447)) + def test_view_limits_single_bin(self): + """ + Test that 'round_numbers' works properly with a single bin. + """ + with mpl.rc_context({'axes.autolimit_mode': 'round_numbers'}): + loc = mticker.MaxNLocator(nbins=1) + assert_almost_equal(loc.view_limits(-2.3, 2.3), (-4, 4)) + def test_set_params(self): """ Create multiple locator with 0.7 base, and change it to something else. diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index f042372a7be9..2b00937f9e29 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -2137,7 +2137,10 @@ def _raw_ticks(self, vmin, vmax): large_steps = large_steps & (floored_vmaxs >= _vmax) # Find index of smallest large step - istep = np.nonzero(large_steps)[0][0] + if any(large_steps): + istep = np.nonzero(large_steps)[0][0] + else: + istep = len(steps) - 1 # Start at smallest of the steps greater than the raw step, and check # if it provides enough ticks. If not, work backwards through From a251d42c1eedef63d5d14e731acc4c850e2aa08e Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:03:21 +0200 Subject: [PATCH 23/61] Backport PR #28329: DOC: Add example for 3D intersecting planes --- .../examples/mplot3d/intersecting_planes.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 galleries/examples/mplot3d/intersecting_planes.py diff --git a/galleries/examples/mplot3d/intersecting_planes.py b/galleries/examples/mplot3d/intersecting_planes.py new file mode 100644 index 000000000000..b8aa08fd7e18 --- /dev/null +++ b/galleries/examples/mplot3d/intersecting_planes.py @@ -0,0 +1,89 @@ +""" +=================== +Intersecting planes +=================== + +This examples demonstrates drawing intersecting planes in 3D. It is a generalization +of :doc:`/gallery/mplot3d/imshow3d`. + +Drawing intersecting planes in `.mplot3d` is complicated, because `.mplot3d` is not a +real 3D renderer, but only projects the Artists into 3D and draws them in the right +order. This does not work correctly if Artists overlap each other mutually. In this +example, we lift the problem of mutual overlap by segmenting the planes at their +intersections, making four parts out of each plane. + +This examples only works correctly for planes that cut each other in haves. This +limitation is intentional to keep the code more readable. Cutting at arbitrary +positions would of course be possible but makes the code even more complex. +Thus, this example is more a demonstration of the concept how to work around +limitations of the 3D visualization, it's not a refined solution for drawing +arbitrary intersecting planes, which you can copy-and-paste as is. +""" +import matplotlib.pyplot as plt +import numpy as np + + +def plot_quadrants(ax, array, fixed_coord, cmap): + """For a given 3d *array* plot a plane with *fixed_coord*, using four quadrants.""" + nx, ny, nz = array.shape + index = { + 'x': (nx // 2, slice(None), slice(None)), + 'y': (slice(None), ny // 2, slice(None)), + 'z': (slice(None), slice(None), nz // 2), + }[fixed_coord] + plane_data = array[index] + + n0, n1 = plane_data.shape + quadrants = [ + plane_data[:n0 // 2, :n1 // 2], + plane_data[:n0 // 2, n1 // 2:], + plane_data[n0 // 2:, :n1 // 2], + plane_data[n0 // 2:, n1 // 2:] + ] + + min_val = array.min() + max_val = array.max() + + cmap = plt.get_cmap(cmap) + + for i, quadrant in enumerate(quadrants): + facecolors = cmap((quadrant - min_val) / (max_val - min_val)) + if fixed_coord == 'x': + Y, Z = np.mgrid[0:ny // 2, 0:nz // 2] + X = nx // 2 * np.ones_like(Y) + Y_offset = (i // 2) * ny // 2 + Z_offset = (i % 2) * nz // 2 + ax.plot_surface(X, Y + Y_offset, Z + Z_offset, rstride=1, cstride=1, + facecolors=facecolors, shade=False) + elif fixed_coord == 'y': + X, Z = np.mgrid[0:nx // 2, 0:nz // 2] + Y = ny // 2 * np.ones_like(X) + X_offset = (i // 2) * nx // 2 + Z_offset = (i % 2) * nz // 2 + ax.plot_surface(X + X_offset, Y, Z + Z_offset, rstride=1, cstride=1, + facecolors=facecolors, shade=False) + elif fixed_coord == 'z': + X, Y = np.mgrid[0:nx // 2, 0:ny // 2] + Z = nz // 2 * np.ones_like(X) + X_offset = (i // 2) * nx // 2 + Y_offset = (i % 2) * ny // 2 + ax.plot_surface(X + X_offset, Y + Y_offset, Z, rstride=1, cstride=1, + facecolors=facecolors, shade=False) + + +def figure_3D_array_slices(array, cmap=None): + """Plot a 3d array using three intersecting centered planes.""" + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.set_box_aspect(array.shape) + plot_quadrants(ax, array, 'x', cmap=cmap) + plot_quadrants(ax, array, 'y', cmap=cmap) + plot_quadrants(ax, array, 'z', cmap=cmap) + return fig, ax + + +nx, ny, nz = 70, 100, 50 +r_square = (np.mgrid[-1:1:1j * nx, -1:1:1j * ny, -1:1:1j * nz] ** 2).sum(0) + +figure_3D_array_slices(r_square, cmap='viridis_r') +plt.show() From 75ac1bdaab50c617a151b7a1d76450a4b05b596f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 3 Jun 2024 12:14:13 -0400 Subject: [PATCH 24/61] Backport PR #28332: Call IPython.enable_gui when install repl displayhook --- lib/matplotlib/pyplot.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index a3ce60f01ef5..1a8298212709 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -304,10 +304,12 @@ def install_repl_displayhook() -> None: # This code can be removed when Python 3.12, the latest version supported by # IPython < 8.24, reaches end-of-life in late 2028. from IPython.core.pylabtools import backend2gui - # trigger IPython's eventloop integration, if available ipython_gui_name = backend2gui.get(get_backend()) - if ipython_gui_name: - ip.enable_gui(ipython_gui_name) + else: + _, ipython_gui_name = backend_registry.resolve_backend(get_backend()) + # trigger IPython's eventloop integration, if available + if ipython_gui_name: + ip.enable_gui(ipython_gui_name) def uninstall_repl_displayhook() -> None: From 0fae2283dfe2ba5653166c44fe8a03772254c85b Mon Sep 17 00:00:00 2001 From: hannah Date: Mon, 3 Jun 2024 19:51:19 -0400 Subject: [PATCH 25/61] Backport PR #28336: DOC: Add version warning banner for docs versions different from stable --- doc/_static/switcher.json | 3 ++- doc/conf.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/_static/switcher.json b/doc/_static/switcher.json index 6996d79bc22a..3d712e4ff8e9 100644 --- a/doc/_static/switcher.json +++ b/doc/_static/switcher.json @@ -2,7 +2,8 @@ { "name": "3.9 (stable)", "version": "stable", - "url": "https://matplotlib.org/stable/" + "url": "https://matplotlib.org/stable/", + "preferred": true }, { "name": "3.10 (dev)", diff --git a/doc/conf.py b/doc/conf.py index c9a475aecf9c..92d78f896ca2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -508,6 +508,7 @@ def js_tag_with_cache_busting(js): # this special value indicates the use of the unreleased banner. If we need # an actual announcement, then just place the text here as usual. "announcement": "unreleased" if not is_release_build else "", + "show_version_warning_banner": True, } include_analytics = is_release_build if include_analytics: From 7278380444dd50bfa41302ced547ea779b5d3418 Mon Sep 17 00:00:00 2001 From: Eytan Adler <63426601+eytanadler@users.noreply.github.com> Date: Wed, 5 Jun 2024 17:34:21 -0400 Subject: [PATCH 26/61] Backport PR #28307: DOC: New color line by value example --- .../multicolored_line.py | 205 +++++++++++++++--- 1 file changed, 173 insertions(+), 32 deletions(-) diff --git a/galleries/examples/lines_bars_and_markers/multicolored_line.py b/galleries/examples/lines_bars_and_markers/multicolored_line.py index 5d0727e69181..3d14ecaf8567 100644 --- a/galleries/examples/lines_bars_and_markers/multicolored_line.py +++ b/galleries/examples/lines_bars_and_markers/multicolored_line.py @@ -3,47 +3,188 @@ Multicolored lines ================== -This example shows how to make a multicolored line. In this example, the line -is colored based on its derivative. +The example shows two ways to plot a line with the a varying color defined by +a third value. The first example defines the color at each (x, y) point. +The second example defines the color between pairs of points, so the length +of the color value list is one less than the length of the x and y lists. + +Color values at points +---------------------- + """ +import warnings + import matplotlib.pyplot as plt import numpy as np from matplotlib.collections import LineCollection -from matplotlib.colors import BoundaryNorm, ListedColormap + +def colored_line(x, y, c, ax, **lc_kwargs): + """ + Plot a line with a color specified along the line by a third value. + + It does this by creating a collection of line segments. Each line segment is + made up of two straight lines each connecting the current (x, y) point to the + midpoints of the lines connecting the current point with its two neighbors. + This creates a smooth line with no gaps between the line segments. + + Parameters + ---------- + x, y : array-like + The horizontal and vertical coordinates of the data points. + c : array-like + The color values, which should be the same size as x and y. + ax : Axes + Axis object on which to plot the colored line. + **lc_kwargs + Any additional arguments to pass to matplotlib.collections.LineCollection + constructor. This should not include the array keyword argument because + that is set to the color argument. If provided, it will be overridden. + + Returns + ------- + matplotlib.collections.LineCollection + The generated line collection representing the colored line. + """ + if "array" in lc_kwargs: + warnings.warn('The provided "array" keyword argument will be overridden') + + # Default the capstyle to butt so that the line segments smoothly line up + default_kwargs = {"capstyle": "butt"} + default_kwargs.update(lc_kwargs) + + # Compute the midpoints of the line segments. Include the first and last points + # twice so we don't need any special syntax later to handle them. + x = np.asarray(x) + y = np.asarray(y) + x_midpts = np.hstack((x[0], 0.5 * (x[1:] + x[:-1]), x[-1])) + y_midpts = np.hstack((y[0], 0.5 * (y[1:] + y[:-1]), y[-1])) + + # Determine the start, middle, and end coordinate pair of each line segment. + # Use the reshape to add an extra dimension so each pair of points is in its + # own list. Then concatenate them to create: + # [ + # [(x1_start, y1_start), (x1_mid, y1_mid), (x1_end, y1_end)], + # [(x2_start, y2_start), (x2_mid, y2_mid), (x2_end, y2_end)], + # ... + # ] + coord_start = np.column_stack((x_midpts[:-1], y_midpts[:-1]))[:, np.newaxis, :] + coord_mid = np.column_stack((x, y))[:, np.newaxis, :] + coord_end = np.column_stack((x_midpts[1:], y_midpts[1:]))[:, np.newaxis, :] + segments = np.concatenate((coord_start, coord_mid, coord_end), axis=1) + + lc = LineCollection(segments, **default_kwargs) + lc.set_array(c) # set the colors of each segment + + return ax.add_collection(lc) + + +# -------------- Create and show plot -------------- +# Some arbitrary function that gives x, y, and color values +t = np.linspace(-7.4, -0.5, 200) +x = 0.9 * np.sin(t) +y = 0.9 * np.cos(1.6 * t) +color = np.linspace(0, 2, t.size) + +# Create a figure and plot the line on it +fig1, ax1 = plt.subplots() +lines = colored_line(x, y, color, ax1, linewidth=10, cmap="plasma") +fig1.colorbar(lines) # add a color legend + +# Set the axis limits and tick positions +ax1.set_xlim(-1, 1) +ax1.set_ylim(-1, 1) +ax1.set_xticks((-1, 0, 1)) +ax1.set_yticks((-1, 0, 1)) +ax1.set_title("Color at each point") + +plt.show() + +#################################################################### +# This method is designed to give a smooth impression when distances and color +# differences between adjacent points are not too large. The following example +# does not meet this criteria and by that serves to illustrate the segmentation +# and coloring mechanism. +x = [0, 1, 2, 3, 4] +y = [0, 1, 2, 1, 1] +c = [1, 2, 3, 4, 5] +fig, ax = plt.subplots() +ax.scatter(x, y, c=c, cmap='rainbow') +colored_line(x, y, c=c, ax=ax, cmap='rainbow') + +plt.show() + +#################################################################### +# Color values between points +# --------------------------- +# + + +def colored_line_between_pts(x, y, c, ax, **lc_kwargs): + """ + Plot a line with a color specified between (x, y) points by a third value. + + It does this by creating a collection of line segments between each pair of + neighboring points. The color of each segment is determined by the + made up of two straight lines each connecting the current (x, y) point to the + midpoints of the lines connecting the current point with its two neighbors. + This creates a smooth line with no gaps between the line segments. + + Parameters + ---------- + x, y : array-like + The horizontal and vertical coordinates of the data points. + c : array-like + The color values, which should have a size one less than that of x and y. + ax : Axes + Axis object on which to plot the colored line. + **lc_kwargs + Any additional arguments to pass to matplotlib.collections.LineCollection + constructor. This should not include the array keyword argument because + that is set to the color argument. If provided, it will be overridden. + + Returns + ------- + matplotlib.collections.LineCollection + The generated line collection representing the colored line. + """ + if "array" in lc_kwargs: + warnings.warn('The provided "array" keyword argument will be overridden') + + # Check color array size (LineCollection still works, but values are unused) + if len(c) != len(x) - 1: + warnings.warn( + "The c argument should have a length one less than the length of x and y. " + "If it has the same length, use the colored_line function instead." + ) + + # Create a set of line segments so that we can color them individually + # This creates the points as an N x 1 x 2 array so that we can stack points + # together easily to get the segments. The segments array for line collection + # needs to be (numlines) x (points per line) x 2 (for x and y) + points = np.array([x, y]).T.reshape(-1, 1, 2) + segments = np.concatenate([points[:-1], points[1:]], axis=1) + lc = LineCollection(segments, **lc_kwargs) + + # Set the values used for colormapping + lc.set_array(c) + + return ax.add_collection(lc) + + +# -------------- Create and show plot -------------- x = np.linspace(0, 3 * np.pi, 500) y = np.sin(x) dydx = np.cos(0.5 * (x[:-1] + x[1:])) # first derivative -# Create a set of line segments so that we can color them individually -# This creates the points as an N x 1 x 2 array so that we can stack points -# together easily to get the segments. The segments array for line collection -# needs to be (numlines) x (points per line) x 2 (for x and y) -points = np.array([x, y]).T.reshape(-1, 1, 2) -segments = np.concatenate([points[:-1], points[1:]], axis=1) - -fig, axs = plt.subplots(2, 1, sharex=True, sharey=True) - -# Create a continuous norm to map from data points to colors -norm = plt.Normalize(dydx.min(), dydx.max()) -lc = LineCollection(segments, cmap='viridis', norm=norm) -# Set the values used for colormapping -lc.set_array(dydx) -lc.set_linewidth(2) -line = axs[0].add_collection(lc) -fig.colorbar(line, ax=axs[0]) - -# Use a boundary norm instead -cmap = ListedColormap(['r', 'g', 'b']) -norm = BoundaryNorm([-1, -0.5, 0.5, 1], cmap.N) -lc = LineCollection(segments, cmap=cmap, norm=norm) -lc.set_array(dydx) -lc.set_linewidth(2) -line = axs[1].add_collection(lc) -fig.colorbar(line, ax=axs[1]) - -axs[0].set_xlim(x.min(), x.max()) -axs[0].set_ylim(-1.1, 1.1) +fig2, ax2 = plt.subplots() +line = colored_line_between_pts(x, y, dydx, ax2, linewidth=2, cmap="viridis") +fig2.colorbar(line, ax=ax2, label="dy/dx") + +ax2.set_xlim(x.min(), x.max()) +ax2.set_ylim(-1.1, 1.1) +ax2.set_title("Color between points") + plt.show() From 60197c50f948967e82d16023460d3fb3f13d4698 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sat, 8 Jun 2024 15:14:30 -0600 Subject: [PATCH 27/61] Backport PR #28337: Bump the actions group across 1 directory with 3 updates --- .github/workflows/cibuildwheel.yml | 12 ++++++------ .github/workflows/conflictcheck.yml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 04c70a767ce0..41f5bca65f18 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -135,7 +135,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -143,7 +143,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -151,7 +151,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -159,7 +159,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@711a3d017d0729f3edde18545fee967f03d65f65 # v2.18.0 + uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -203,7 +203,7 @@ jobs: run: ls dist - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 with: subject-path: dist/matplotlib-* diff --git a/.github/workflows/conflictcheck.yml b/.github/workflows/conflictcheck.yml index fc759f52a6b0..3110839e5150 100644 --- a/.github/workflows/conflictcheck.yml +++ b/.github/workflows/conflictcheck.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if PRs have merge conflicts - uses: eps1lon/actions-label-merge-conflict@6d74047dcef155976a15e4a124dde2c7fe0c5522 # v3.0.1 + uses: eps1lon/actions-label-merge-conflict@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2 with: dirtyLabel: "status: needs rebase" repoToken: "${{ secrets.GITHUB_TOKEN }}" From 394b5539dc337b54f8a730ec126cc7fcb4fdf73c Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:39:28 -0600 Subject: [PATCH 28/61] Backport PR #28359: Document that axes unsharing is impossible. --- lib/matplotlib/axes/_base.py | 7 ++++--- lib/matplotlib/figure.py | 2 ++ lib/matplotlib/pyplot.py | 5 +++-- lib/mpl_toolkits/mplot3d/axes3d.py | 14 ++++++++------ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 0164f4e11169..30c4efe80c49 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -597,7 +597,8 @@ def __init__(self, fig, sharex, sharey : `~matplotlib.axes.Axes`, optional The x- or y-`~.matplotlib.axis` is shared with the x- or y-axis in - the input `~.axes.Axes`. + the input `~.axes.Axes`. Note that it is not possible to unshare + axes. frameon : bool, default: True Whether the Axes frame is visible. @@ -1221,7 +1222,7 @@ def sharex(self, other): This is equivalent to passing ``sharex=other`` when constructing the Axes, and cannot be used if the x-axis is already being shared with - another Axes. + another Axes. Note that it is not possible to unshare axes. """ _api.check_isinstance(_AxesBase, other=other) if self._sharex is not None and other is not self._sharex: @@ -1240,7 +1241,7 @@ def sharey(self, other): This is equivalent to passing ``sharey=other`` when constructing the Axes, and cannot be used if the y-axis is already being shared with - another Axes. + another Axes. Note that it is not possible to unshare axes. """ _api.check_isinstance(_AxesBase, other=other) if self._sharey is not None and other is not self._sharey: diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 0a0ff01a2571..e5f4bb9421cf 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -755,6 +755,8 @@ def subplots(self, nrows=1, ncols=1, *, sharex=False, sharey=False, When subplots have a shared axis that has units, calling `.Axis.set_units` will update each axis with the new units. + Note that it is not possible to unshare axes. + squeeze : bool, default: True - If True, extra dimensions are squeezed out from the returned array of Axes: diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 1a8298212709..7f8d0bbc6e7f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1639,8 +1639,9 @@ def subplots( on, use `~matplotlib.axes.Axes.tick_params`. When subplots have a shared axis that has units, calling - `~matplotlib.axis.Axis.set_units` will update each axis with the - new units. + `.Axis.set_units` will update each axis with the new units. + + Note that it is not possible to unshare axes. squeeze : bool, default: True - If True, extra dimensions are squeezed out from the returned diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 18b823ccccb3..91845748880b 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -83,7 +83,8 @@ def __init__( axis. A positive angle spins the camera clockwise, causing the scene to rotate counter-clockwise. sharez : Axes3D, optional - Other Axes to share z-limits with. + Other Axes to share z-limits with. Note that it is not possible to + unshare axes. proj_type : {'persp', 'ortho'} The projection type, default 'persp'. box_aspect : 3-tuple of floats, default: None @@ -107,7 +108,8 @@ def __init__( The focal length can be computed from a desired Field Of View via the equation: focal_length = 1/tan(FOV/2) shareview : Axes3D, optional - Other Axes to share view angles with. + Other Axes to share view angles with. Note that it is not possible + to unshare axes. **kwargs Other optional keyword arguments: @@ -1307,7 +1309,7 @@ def sharez(self, other): This is equivalent to passing ``sharez=other`` when constructing the Axes, and cannot be used if the z-axis is already being shared with - another Axes. + another Axes. Note that it is not possible to unshare axes. """ _api.check_isinstance(Axes3D, other=other) if self._sharez is not None and other is not self._sharez: @@ -1324,9 +1326,9 @@ def shareview(self, other): """ Share the view angles with *other*. - This is equivalent to passing ``shareview=other`` when - constructing the Axes, and cannot be used if the view angles are - already being shared with another Axes. + This is equivalent to passing ``shareview=other`` when constructing the + Axes, and cannot be used if the view angles are already being shared + with another Axes. Note that it is not possible to unshare axes. """ _api.check_isinstance(Axes3D, other=other) if self._shareview is not None and other is not self._shareview: From 35f38de2359963bbb5bb193070547fc059ae15b3 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 12 Jun 2024 16:20:13 -0500 Subject: [PATCH 29/61] Backport PR #28377: DOC: Clarify scope of wrap. --- lib/matplotlib/text.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 40cd8c8cd6f7..7fc19c042a1f 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -606,6 +606,10 @@ def set_wrap(self, wrap): """ Set whether the text can be wrapped. + Wrapping makes sure the text is completely within the figure box, i.e. + it does not extend beyond the drawing area. It does not take into + account any other artists. + Parameters ---------- wrap : bool From 1099476c3ae3b855607f3a6c6d454d0a2c44aaf8 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 13 Jun 2024 15:36:01 +0200 Subject: [PATCH 30/61] Backport PR #28380: Remove outdated docstring section in RendererBase.draw_text. --- lib/matplotlib/backend_bases.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index f4273bc03919..53e5f6b23213 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -513,21 +513,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): If True, use mathtext parser. If "TeX", use tex for rendering. mtext : `~matplotlib.text.Text` The original text object to be rendered. - - Notes - ----- - **Note for backend implementers:** - - When you are trying to determine if you have gotten your bounding box - right (which is what enables the text layout/alignment to work - properly), it helps to change the line in text.py:: - - if 0: bbox_artist(self, renderer) - - to if 1, and then the actual bounding box will be plotted along with - your text. """ - self._draw_text_as_path(gc, x, y, s, prop, angle, ismath) def _get_text_path_transform(self, x, y, s, prop, angle, ismath): From 0ffc8c12352dd59cd08a36710888a945512a37a9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 13 Jun 2024 17:36:34 -0400 Subject: [PATCH 31/61] Backport PR #28388: Allow duplicate (name, value) entry points for backends --- lib/matplotlib/backends/registry.py | 7 +++++-- lib/matplotlib/tests/test_backend_registry.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 19b4cba254ab..47d5f65e350e 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -168,8 +168,11 @@ def backward_compatible_entry_points( def _validate_and_store_entry_points(self, entries): # Validate and store entry points so that they can be used via matplotlib.use() # in the normal manner. Entry point names cannot be of module:// format, cannot - # shadow a built-in backend name, and cannot be duplicated. - for name, module in entries: + # shadow a built-in backend name, and there cannot be multiple entry points + # with the same name but different modules. Multiple entry points with the same + # name and value are permitted (it can sometimes happen outside of our control, + # see https://github.com/matplotlib/matplotlib/issues/28367). + for name, module in set(entries): name = name.lower() if name.startswith("module://"): raise RuntimeError( diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index eaf8417e7a5f..141ffd69c266 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -121,6 +121,17 @@ def test_entry_point_name_duplicate(clear_backend_registry): [('some_name', 'module1'), ('some_name', 'module2')]) +def test_entry_point_identical(clear_backend_registry): + # Issue https://github.com/matplotlib/matplotlib/issues/28367 + # Multiple entry points with the same name and value (value is the module) + # are acceptable. + n = len(backend_registry._name_to_module) + backend_registry._validate_and_store_entry_points( + [('some_name', 'some.module'), ('some_name', 'some.module')]) + assert len(backend_registry._name_to_module) == n+1 + assert backend_registry._name_to_module['some_name'] == 'module://some.module' + + def test_entry_point_name_is_module(clear_backend_registry): with pytest.raises(RuntimeError): backend_registry._validate_and_store_entry_points( From da5c20f637b2c35b157b0da7f2d20bd5d689ddf2 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:17:53 +0100 Subject: [PATCH 32/61] Backport PR #28413: CI: update action that got moved org --- .github/workflows/circleci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index 8f9e3190c5e2..cfdf184b8666 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -10,7 +10,7 @@ jobs: name: Run CircleCI artifacts redirector steps: - name: GitHub Action step - uses: larsoner/circleci-artifacts-redirector-action@master + uses: scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 # v1.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} api-token: ${{ secrets.CIRCLECI_TOKEN }} From 2c59ec6de4a13ac14343fc67aa57643bd9d674b9 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 18 Jun 2024 20:06:17 -0500 Subject: [PATCH 33/61] Backport PR #28414: Clean up obsolete widget code --- lib/matplotlib/widgets.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index eaa35e25440b..ed130e6854f2 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1001,14 +1001,8 @@ class CheckButtons(AxesWidget): ---------- ax : `~matplotlib.axes.Axes` The parent Axes for the widget. - labels : list of `~matplotlib.text.Text` - - rectangles : list of `~matplotlib.patches.Rectangle` - - lines : list of (`.Line2D`, `.Line2D`) pairs - List of lines for the x's in the checkboxes. These lines exist for - each box, but have ``set_visible(False)`` when its box is not checked. + The text label objects of the check buttons. """ def __init__(self, ax, labels, actives=None, *, useblit=True, @@ -1571,8 +1565,6 @@ class RadioButtons(AxesWidget): The color of the selected button. labels : list of `.Text` The button labels. - circles : list of `~.patches.Circle` - The buttons. value_selected : str The label text of the currently selected button. index_selected : int @@ -1751,11 +1743,6 @@ def activecolor(self, activecolor): colors._check_color_like(activecolor=activecolor) self._activecolor = activecolor self.set_radio_props({'facecolor': activecolor}) - # Make sure the deprecated version is updated. - # Remove once circles is removed. - labels = [label.get_text() for label in self.labels] - with cbook._setattr_cm(self, eventson=False): - self.set_active(labels.index(self.value_selected)) def set_active(self, index): """ From f8f82060b0884645dc7b149ae22818160ca94b23 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 19 Jun 2024 11:57:02 +0200 Subject: [PATCH 34/61] Backport PR #28401: FIX: Fix text wrapping --- lib/matplotlib/tests/test_text.py | 9 +++++++-- lib/matplotlib/text.py | 13 ++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index f8837d8a5f1b..8904337f68ba 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -15,6 +15,7 @@ from matplotlib.font_manager import FontProperties import matplotlib.patches as mpatches import matplotlib.pyplot as plt +from matplotlib.gridspec import GridSpec import matplotlib.transforms as mtransforms from matplotlib.testing.decorators import check_figures_equal, image_comparison from matplotlib.testing._markers import needs_usetex @@ -707,9 +708,13 @@ def test_large_subscript_title(): (0.3, 0, 'right'), (0.3, 185, 'left')]) def test_wrap(x, rotation, halign): - fig = plt.figure(figsize=(6, 6)) + fig = plt.figure(figsize=(18, 18)) + gs = GridSpec(nrows=3, ncols=3, figure=fig) + subfig = fig.add_subfigure(gs[1, 1]) + # we only use the central subfigure, which does not align with any + # figure boundary, to ensure only subfigure boundaries are relevant s = 'This is a very long text that should be wrapped multiple times.' - text = fig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign) + text = subfig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign) fig.canvas.draw() assert text._get_wrapped_text() == ('This is a very long\n' 'text that should be\n' diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 7fc19c042a1f..af990ec1bf9f 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -606,9 +606,8 @@ def set_wrap(self, wrap): """ Set whether the text can be wrapped. - Wrapping makes sure the text is completely within the figure box, i.e. - it does not extend beyond the drawing area. It does not take into - account any other artists. + Wrapping makes sure the text is confined to the (sub)figure box. It + does not take into account any other artists. Parameters ---------- @@ -657,16 +656,16 @@ def _get_dist_to_box(self, rotation, x0, y0, figure_box): """ if rotation > 270: quad = rotation - 270 - h1 = y0 / math.cos(math.radians(quad)) + h1 = (y0 - figure_box.y0) / math.cos(math.radians(quad)) h2 = (figure_box.x1 - x0) / math.cos(math.radians(90 - quad)) elif rotation > 180: quad = rotation - 180 - h1 = x0 / math.cos(math.radians(quad)) - h2 = y0 / math.cos(math.radians(90 - quad)) + h1 = (x0 - figure_box.x0) / math.cos(math.radians(quad)) + h2 = (y0 - figure_box.y0) / math.cos(math.radians(90 - quad)) elif rotation > 90: quad = rotation - 90 h1 = (figure_box.y1 - y0) / math.cos(math.radians(quad)) - h2 = x0 / math.cos(math.radians(90 - quad)) + h2 = (x0 - figure_box.x0) / math.cos(math.radians(90 - quad)) else: h1 = (figure_box.x1 - x0) / math.cos(math.radians(rotation)) h2 = (figure_box.y1 - y0) / math.cos(math.radians(90 - rotation)) From f8ff4e1a9efceccf4b3bf14ade86d76851dfc678 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:32:17 +0100 Subject: [PATCH 35/61] Backport PR #28423: Update return type for Axes.axhspan and Axes.axvspan --- lib/matplotlib/axes/_axes.py | 12 ++++++------ lib/matplotlib/axes/_axes.pyi | 4 ++-- lib/matplotlib/pyplot.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 34c4023a256e..fdafc2dcb0bc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1006,14 +1006,14 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): Returns ------- - `~matplotlib.patches.Polygon` + `~matplotlib.patches.Rectangle` Horizontal span (rectangle) from (xmin, ymin) to (xmax, ymax). Other Parameters ---------------- - **kwargs : `~matplotlib.patches.Polygon` properties + **kwargs : `~matplotlib.patches.Rectangle` properties - %(Polygon:kwdoc)s + %(Rectangle:kwdoc)s See Also -------- @@ -1061,14 +1061,14 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): Returns ------- - `~matplotlib.patches.Polygon` + `~matplotlib.patches.Rectangle` Vertical span (rectangle) from (xmin, ymin) to (xmax, ymax). Other Parameters ---------------- - **kwargs : `~matplotlib.patches.Polygon` properties + **kwargs : `~matplotlib.patches.Rectangle` properties - %(Polygon:kwdoc)s + %(Rectangle:kwdoc)s See Also -------- diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index b70d330aa442..76aaee77aff8 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -155,10 +155,10 @@ class Axes(_AxesBase): ) -> AxLine: ... def axhspan( self, ymin: float, ymax: float, xmin: float = ..., xmax: float = ..., **kwargs - ) -> Polygon: ... + ) -> Rectangle: ... def axvspan( self, xmin: float, xmax: float, ymin: float = ..., ymax: float = ..., **kwargs - ) -> Polygon: ... + ) -> Rectangle: ... def hlines( self, y: float | ArrayLike, diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 7f8d0bbc6e7f..8fe8b000bf49 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2870,7 +2870,7 @@ def axhline(y: float = 0, xmin: float = 0, xmax: float = 1, **kwargs) -> Line2D: @_copy_docstring_and_deprecators(Axes.axhspan) def axhspan( ymin: float, ymax: float, xmin: float = 0, xmax: float = 1, **kwargs -) -> Polygon: +) -> Rectangle: return gca().axhspan(ymin, ymax, xmin=xmin, xmax=xmax, **kwargs) @@ -2908,7 +2908,7 @@ def axvline(x: float = 0, ymin: float = 0, ymax: float = 1, **kwargs) -> Line2D: @_copy_docstring_and_deprecators(Axes.axvspan) def axvspan( xmin: float, xmax: float, ymin: float = 0, ymax: float = 1, **kwargs -) -> Polygon: +) -> Rectangle: return gca().axvspan(xmin, xmax, ymin=ymin, ymax=ymax, **kwargs) From 8bf8cf59dca11764f44d14a461879ca34fe2991d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 19 Jun 2024 14:06:20 -0500 Subject: [PATCH 36/61] Backport PR #28425: Fix Circle yaml line length --- .github/workflows/circleci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index cfdf184b8666..4e5733e03466 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -10,7 +10,8 @@ jobs: name: Run CircleCI artifacts redirector steps: - name: GitHub Action step - uses: scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 # v1.0.0 + uses: | #v1.0.0 + scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} api-token: ${{ secrets.CIRCLECI_TOKEN }} From f8d15bdac2c85a55e976278392fbe7022ef521f7 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 20 Jun 2024 10:14:59 +0100 Subject: [PATCH 37/61] Backport PR #28427: Fix circleci yaml --- .github/workflows/circleci.yml | 4 ++-- .yamllint.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index 4e5733e03466..3aead720cf20 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -10,8 +10,8 @@ jobs: name: Run CircleCI artifacts redirector steps: - name: GitHub Action step - uses: | #v1.0.0 - scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 + uses: + scientific-python/circleci-artifacts-redirector-action@4e13a10d89177f4bfc8007a7064bdbeda848d8d1 # v1.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} api-token: ${{ secrets.CIRCLECI_TOKEN }} diff --git a/.yamllint.yml b/.yamllint.yml index 3b30533ececa..2be81b28c7fb 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -3,7 +3,7 @@ extends: default rules: line-length: - max: 111 + max: 120 allow-non-breakable-words: true truthy: check-keys: false From c747ac444db273bdd93f894825b4d32671c998b4 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 24 Jun 2024 07:29:12 -0600 Subject: [PATCH 38/61] Backport PR #28403: FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection --- lib/mpl_toolkits/mplot3d/axes3d.py | 29 +++++++++++++-- .../test_axes3d/voxels-named-colors.png | Bin 59278 -> 54173 bytes lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 34 +++++++++++++++--- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 91845748880b..71cd8f062d40 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2578,7 +2578,7 @@ def tricontourf(self, *args, zdir='z', offset=None, **kwargs): self._auto_scale_contourf(X, Y, Z, zdir, levels, had_data) return cset - def add_collection3d(self, col, zs=0, zdir='z'): + def add_collection3d(self, col, zs=0, zdir='z', autolim=True): """ Add a 3D collection object to the plot. @@ -2590,8 +2590,21 @@ def add_collection3d(self, col, zs=0, zdir='z'): - `.PolyCollection` - `.LineCollection` - - `.PatchCollection` + - `.PatchCollection` (currently not supporting *autolim*) + + Parameters + ---------- + col : `.Collection` + A 2D collection object. + zs : float or array-like, default: 0 + The z-positions to be used for the 2D objects. + zdir : {'x', 'y', 'z'}, default: 'z' + The direction to use for the z-positions. + autolim : bool, default: True + Whether to update the data limits. """ + had_data = self.has_data() + zvals = np.atleast_1d(zs) zsortval = (np.min(zvals) if zvals.size else 0) # FIXME: arbitrary default @@ -2609,6 +2622,18 @@ def add_collection3d(self, col, zs=0, zdir='z'): art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir) col.set_sort_zpos(zsortval) + if autolim: + if isinstance(col, art3d.Line3DCollection): + self.auto_scale_xyz(*np.array(col._segments3d).transpose(), + had_data=had_data) + elif isinstance(col, art3d.Poly3DCollection): + self.auto_scale_xyz(*col._vec[:-1], had_data=had_data) + elif isinstance(col, art3d.Patch3DCollection): + pass + # FIXME: Implement auto-scaling function for Patch3DCollection + # Currently unable to do so due to issues with Patch3DCollection + # See https://github.com/matplotlib/matplotlib/issues/14298 for details + collection = super().add_collection(col) return collection diff --git a/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-named-colors.png b/lib/mpl_toolkits/mplot3d/tests/baseline_images/test_axes3d/voxels-named-colors.png index b71ad19c1608b06b33e51a5ae3ada23fbbc06278..33dfc2f2313ad100797b89136555b8fc9e593a2f 100644 GIT binary patch literal 54173 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 Jzf-jg|3BzpibMba literal 59278 zcmeFYWmuHa*F8K8HFQWxx0G~ugGhsbNQ2VSF!az}ihwjI-6A1fGAaThDIFq6BPsbm z-+t=t{cSw&N=r!d#|5ehK#Ygc!Q0#2OOlVz^?$yc*Ui(OPrs%@4g3fkclF0!5D1Yq z>I1D@uFMev`L*ysS-}vVyL0X5ZTC222`6Mfn7t$H0b}QAwfCdfuO7d8{Q5`Nk7(_k z{FTaz#@rS92NU{g>F?83ziUXb-1loGd#W03lRy^pR=Gx%G^W^f_W6s2VW;M*Jq^aR zMJdLxNSw%&jlBgqdJ9HKq(9%P3Y6nYc4OI)jo4!;iSk$QnthiXdr3MYEFAST;|Fz$ z$rEEJfTzPu9YXr=GZ%sX`|SVD07K#b&fx#g8BClkLKKK2(@B%pAP_PYG+UQEKV@Q! z!^LO}Mg`*Scw%VA(<0LvMGc%031O{>Vh-I$JWP^}kK`>=aWV9+>J+xcJd* zP8qg{Oe2OudZ?=K6dye*;=sjG3}=ibW<*mb9(bsxfiv`O%h3{IVqzlCLlIHxcrpi< zV};>_GxkNHei$D)6Nct^Br!DCYoojd46UDEw`#IS0(&DDgGqr{8Jh#DPOQ8{ zqG)Apjiv|#Yk>6h#}FMZ_oCBEcwz*e?M1oHrRq<8%uXk5G9~z6R(oSR=W9;<%TofM zfWUgGEBItitTZ8PJze168N5n`u)>Pn;Nf52@yvJl-^umbS?XpmuCq|7G3x(4UY7RR zZc8LA30nWdbkeDb7t7w>9u4xtrJ0^dvqXP6ciL^AZs|#;fYCnh>CN_aJ|oPATLGHN z7~92I66rcu?@9x4zdUs{ta=1?Y6FK;-P6uRR92qM`H~{4KKSlZGpE698cfW|G&s|S zpKTZOztX6Qv{~}B zZTA;Di4_Y?c&4_o_ZQl+RrHKteQ@b6RbtZ&%yHrElsMG8-N!`Li7}z7&TCp^?{JRT z;p#`cT-ji178aJ)z_aI1dMVNieFAd5>;&(E2lbv5VPv?NKRqSBe*L<)K0)l+ibI-j6_FAKS8e2%{8`s@Y*08$&+ePB@Qqj1hJ53+-^Q#k+Vl>pE*4amCAzbA#F?)S^c6hL& zL@N_O25z7$w?!X?XvmZh56x}L?e$S=6Z6e{$;S#mpH9WojQ%>5yQ*4JX)r^BTzy`sgLEIOxFmSxN(sl`?giJaMA#=E^VE09 zfZLLmMv|DA2!UvhSL5|vp6$QUuV4h1jyN)jZV?spdbvz{r1ev2fSCGszO3<@a~*1g&aAjR$-PETTel5mIrCUM*%`2Gl@Cbt(@$8MaIdXH8Lsd-;4H9iVp*FSf!3!IFg1OSu0}*_!Y5vonK2raSNcguL zeef>CH&EAW6BiX=!X+NLG#j*vu=zG0JOcv*agS9D-@W;}4xO1KXzyLZZs+W8A^Y3a7|2V@n`3Xxb=n*#(T)cmid|wNEH7SepOoE!|A%OCe^-`#G&_l1T9a5Tt%hi_vfeeUfcwR2}GOlpdN5x6*b~CO7shCuXcV2 zi6=*BvKvrFRv0=ZG4j09X!Tsj1rkd^@ss9iH{U^%h(wcoT9h>Z*jYk?fdm@~Zkz2qUcYN5}=84-jI#h3Gf zkacsip{8$B$Eo^?hJTh1H6MlzP({SSo^N10->5tv?!GM`wiiu)uCpd(A(uEB^poe^ z=jDn&Mo10zm+I^d#czWf}(C4gUAR))$FW*zW`-%B(!+E)ji zw>y3wXDWBUH!}`%G4AM|a=PB>6D%Ci@2p|LcoCnpCe?WUlSb`uDRh&EbnF-Vk51+i z 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

  • G@`d=>P7mn? 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 +sMvbnEJG?Z$bu9Pl6jY?ku%^&PiH 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 ZG`Ib9x_e|QYtjNgQ4!n18$^4){0jl~Hn0Ey diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 34f10cb9fd63..ecb51b724c27 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -961,8 +961,8 @@ def test_poly3dcollection_closed(): facecolor=(0.5, 0.5, 1, 0.5), closed=True) c2 = art3d.Poly3DCollection([poly2], linewidths=3, edgecolor='k', facecolor=(1, 0.5, 0.5, 0.5), closed=False) - ax.add_collection3d(c1) - ax.add_collection3d(c2) + ax.add_collection3d(c1, autolim=False) + ax.add_collection3d(c2, autolim=False) def test_poly_collection_2d_to_3d_empty(): @@ -995,8 +995,8 @@ def test_poly3dcollection_alpha(): c2.set_facecolor((1, 0.5, 0.5)) c2.set_edgecolor('k') c2.set_alpha(0.5) - ax.add_collection3d(c1) - ax.add_collection3d(c2) + ax.add_collection3d(c1, autolim=False) + ax.add_collection3d(c2, autolim=False) @mpl3d_image_comparison(['add_collection3d_zs_array.png'], style='mpl20') @@ -1055,6 +1055,32 @@ def test_add_collection3d_zs_scalar(): ax.set_zlim(0, 2) +def test_line3dCollection_autoscaling(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + + lines = [[(0, 0, 0), (1, 4, 2)], + [(1, 1, 3), (2, 0, 2)], + [(1, 0, 4), (1, 4, 5)]] + + lc = art3d.Line3DCollection(lines) + ax.add_collection3d(lc) + assert np.allclose(ax.get_xlim3d(), (-0.041666666666666664, 2.0416666666666665)) + assert np.allclose(ax.get_ylim3d(), (-0.08333333333333333, 4.083333333333333)) + assert np.allclose(ax.get_zlim3d(), (-0.10416666666666666, 5.104166666666667)) + + +def test_poly3dCollection_autoscaling(): + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + poly = np.array([[0, 0, 0], [1, 1, 3], [1, 0, 4]]) + col = art3d.Poly3DCollection([poly]) + ax.add_collection3d(col) + assert np.allclose(ax.get_xlim3d(), (-0.020833333333333332, 1.0208333333333333)) + assert np.allclose(ax.get_ylim3d(), (-0.020833333333333332, 1.0208333333333333)) + assert np.allclose(ax.get_zlim3d(), (-0.0833333333333333, 4.083333333333333)) + + @mpl3d_image_comparison(['axes3d_labelpad.png'], remove_text=False, style='mpl20') def test_axes3d_labelpad(): From e46044db0fc6f159b461974b8e57c3e3a7f4e366 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:03:22 +0100 Subject: [PATCH 39/61] Backport PR #28441: MNT: Update basic units example to work with numpy 2.0 --- galleries/examples/units/basic_units.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/galleries/examples/units/basic_units.py b/galleries/examples/units/basic_units.py index 0b493ab7216c..f9a94bcf6e37 100644 --- a/galleries/examples/units/basic_units.py +++ b/galleries/examples/units/basic_units.py @@ -146,10 +146,10 @@ def __getattribute__(self, name): return getattr(variable, name) return object.__getattribute__(self, name) - def __array__(self, dtype=object): + def __array__(self, dtype=object, copy=False): return np.asarray(self.value, dtype) - def __array_wrap__(self, array, context): + def __array_wrap__(self, array, context=None, return_scalar=False): return TaggedValue(array, self.unit) def __repr__(self): @@ -222,10 +222,10 @@ def __mul__(self, rhs): def __rmul__(self, lhs): return self*lhs - def __array_wrap__(self, array, context): + def __array_wrap__(self, array, context=None, return_scalar=False): return TaggedValue(array, self) - def __array__(self, t=None, context=None): + def __array__(self, t=None, context=None, copy=False): ret = np.array(1) if t is not None: return ret.astype(t) From b0dba39b9082cf70f5c481775a03000d9b83dea4 Mon Sep 17 00:00:00 2001 From: "Lumberbot (aka Jack)" <39504233+meeseeksmachine@users.noreply.github.com> Date: Mon, 24 Jun 2024 06:31:13 -0700 Subject: [PATCH 40/61] Backport PR #28436: Fix `is_color_like` for 2-tuple of strings and fix `to_rgba` for `(nth_color, alpha)` (#28438) Co-authored-by: hannah --- lib/matplotlib/colors.py | 12 ++++++------ lib/matplotlib/tests/test_colors.py | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index c4e5987fdf92..177557b371a6 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -225,7 +225,7 @@ def is_color_like(c): return True try: to_rgba(c) - except ValueError: + except (TypeError, ValueError): return False else: return True @@ -296,6 +296,11 @@ def to_rgba(c, alpha=None): Tuple of floats ``(r, g, b, a)``, where each channel (red, green, blue, alpha) can assume values between 0 and 1. """ + if isinstance(c, tuple) and len(c) == 2: + if alpha is None: + c, alpha = c + else: + c = c[0] # Special-case nth color syntax because it should not be cached. if _is_nth_color(c): prop_cycler = mpl.rcParams['axes.prop_cycle'] @@ -325,11 +330,6 @@ def _to_rgba_no_colorcycle(c, alpha=None): *alpha* is ignored for the color value ``"none"`` (case-insensitive), which always maps to ``(0, 0, 0, 0)``. """ - if isinstance(c, tuple) and len(c) == 2: - if alpha is None: - c, alpha = c - else: - c = c[0] if alpha is not None and not 0 <= alpha <= 1: raise ValueError("'alpha' must be between 0 and 1, inclusive") orig_c = c diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 63f2d4f00399..4fd9f86c06e3 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -19,7 +19,7 @@ import matplotlib.scale as mscale from matplotlib.rcsetup import cycler from matplotlib.testing.decorators import image_comparison, check_figures_equal -from matplotlib.colors import to_rgba_array +from matplotlib.colors import is_color_like, to_rgba_array @pytest.mark.parametrize('N, result', [ @@ -1697,3 +1697,16 @@ def test_to_rgba_array_none_color_with_alpha_param(): assert_array_equal( to_rgba_array(c, alpha), [[0., 0., 1., 1.], [0., 0., 0., 0.]] ) + + +@pytest.mark.parametrize('input, expected', + [('red', True), + (('red', 0.5), True), + (('red', 2), False), + (['red', 0.5], False), + (('red', 'blue'), False), + (['red', 'blue'], False), + ('C3', True), + (('C3', 0.5), True)]) +def test_is_color_like(input, expected): + assert is_color_like(input) is expected From 9fd68931b5b7d07aa8c74c23cf65e10b6e5e5ef7 Mon Sep 17 00:00:00 2001 From: Greg Lucas Date: Sun, 16 Jun 2024 06:50:16 -0600 Subject: [PATCH 41/61] Merge pull request #28371 from matplotlib/dependabot/github_actions/actions-795b56d292 Bump pypa/cibuildwheel from 2.18.1 to 2.19.0 in the actions group (cherry picked from commit b3d29fb036b0d9de2fb66ee7bcad6887654b3706) --- .github/workflows/cibuildwheel.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 41f5bca65f18..165f496c0b6e 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -135,7 +135,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -143,7 +143,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -151,7 +151,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -159,7 +159,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@ba8be0d98853f5744f24e7f902c8adef7ae2e7f3 # v2.18.1 + uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: From 8f996742b0afd6aaa1eeedf28ba87376acd1da80 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 18 Jun 2024 20:08:00 -0500 Subject: [PATCH 42/61] Merge pull request #28411 from matplotlib/dependabot/github_actions/actions-39ddd2ba80 Bump the actions group with 3 updates (cherry picked from commit 3bd23db8690707ecfb8cfa0ab12ff7258f031ad5) --- .github/workflows/cibuildwheel.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 165f496c0b6e..a4c0c0781813 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -135,7 +135,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -143,7 +143,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -151,7 +151,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.10 - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -159,7 +159,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.9 - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -167,7 +167,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@a8d190a111314a07eb5116036c4b3fb26a4e3162 # v2.19.0 + uses: pypa/cibuildwheel@932529cab190fafca8c735a551657247fa8f8eaf # v2.19.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -203,9 +203,9 @@ jobs: run: ls dist - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 + uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 with: subject-path: dist/matplotlib-* - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # v1.8.14 + uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # v1.9.0 From b14dc233fa189811b7092e1f478cdd7ae3db08a1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 25 Jun 2024 17:41:27 -0400 Subject: [PATCH 43/61] Backport PR #28459: DOC: Document kwargs scope for tick setter functions --- lib/matplotlib/axis.py | 4 +++- lib/matplotlib/pyplot.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index d317f6ec0567..3afc98fac60b 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -2028,7 +2028,9 @@ def set_ticklabels(self, labels, *, minor=False, fontdict=None, **kwargs): .. warning:: - This only sets the properties of the current ticks. + This only sets the properties of the current ticks, which is + only sufficient for static plots. + Ticks are not guaranteed to be persistent. Various operations can create, delete and modify the Tick instances. There is an imminent risk that these settings can get lost if you work on diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 8fe8b000bf49..8b4769342c7d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2170,6 +2170,21 @@ def xticks( **kwargs `.Text` properties can be used to control the appearance of the labels. + .. warning:: + + This only sets the properties of the current ticks, which is + only sufficient if you either pass *ticks*, resulting in a + fixed list of ticks, or if the plot is static. + + Ticks are not guaranteed to be persistent. Various operations + can create, delete and modify the Tick instances. There is an + imminent risk that these settings can get lost if you work on + the figure further (including also panning/zooming on a + displayed figure). + + Use `~.pyplot.tick_params` instead if possible. + + Returns ------- locs @@ -2241,6 +2256,20 @@ def yticks( **kwargs `.Text` properties can be used to control the appearance of the labels. + .. warning:: + + This only sets the properties of the current ticks, which is + only sufficient if you either pass *ticks*, resulting in a + fixed list of ticks, or if the plot is static. + + Ticks are not guaranteed to be persistent. Various operations + can create, delete and modify the Tick instances. There is an + imminent risk that these settings can get lost if you work on + the figure further (including also panning/zooming on a + displayed figure). + + Use `~.pyplot.tick_params` instead if possible. + Returns ------- locs From b8042eed7a106fb1a5bec645f6a830d9f4615c4b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 25 Jun 2024 17:48:32 -0400 Subject: [PATCH 44/61] Backport PR #28458: Correct numpy dtype comparisons in image_resample --- lib/matplotlib/tests/test_image.py | 17 +++++++++++++++++ src/_image_wrapper.cpp | 24 ++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 1602f86716cb..a043d3aec983 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1585,3 +1585,20 @@ def test_non_transdata_image_does_not_touch_aspect(): assert ax.get_aspect() == 1 ax.imshow(im, transform=ax.transAxes, aspect=2) assert ax.get_aspect() == 2 + + +@pytest.mark.parametrize( + 'dtype', + ('float64', 'float32', 'int16', 'uint16', 'int8', 'uint8'), +) +@pytest.mark.parametrize('ndim', (2, 3)) +def test_resample_dtypes(dtype, ndim): + # Issue 28448, incorrect dtype comparisons in C++ image_resample can raise + # ValueError: arrays must be of dtype byte, short, float32 or float64 + rng = np.random.default_rng(4181) + shape = (2, 2) if ndim == 2 else (2, 2, 3) + data = rng.uniform(size=shape).astype(np.dtype(dtype, copy=True)) + fig, ax = plt.subplots() + axes_image = ax.imshow(data) + # Before fix the following raises ValueError for some dtypes. + axes_image.make_image(None)[0] diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 65c8c8324ebc..856dcf4ea3ce 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -173,20 +173,20 @@ image_resample(py::array input_array, if (auto resampler = (ndim == 2) ? ( - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : nullptr) : ( // ndim == 3 - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : - (dtype.is(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : + (dtype.equal(py::dtype::of())) ? resample : nullptr)) { Py_BEGIN_ALLOW_THREADS resampler( From 0caf58a617d2c9c022bb98acd67e730814193a75 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 25 Jun 2024 18:31:41 -0400 Subject: [PATCH 45/61] Backport PR #28440: DOC: Add note about simplification of to_polygons --- lib/matplotlib/path.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index e72eb1a9ca73..94fd97d7b599 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -697,6 +697,9 @@ def to_polygons(self, transform=None, width=0, height=0, closed_only=True): be simplified so that vertices outside of (0, 0), (width, height) will be clipped. + The resulting polygons will be simplified if the + :attr:`Path.should_simplify` attribute of the path is `True`. + If *closed_only* is `True` (default), only closed polygons, with the last point being the same as the first point, will be returned. Any unclosed polylines in the path will be From 14711fac10f959162a054b2b3eb43fa3ef742d49 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 26 Jun 2024 11:43:43 -0400 Subject: [PATCH 46/61] Backport PR #28465: Fix pickling of SubFigures --- lib/matplotlib/figure.py | 5 ++++- lib/matplotlib/figure.pyi | 3 ++- lib/matplotlib/tests/test_pickle.py | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index e5f4bb9421cf..d75ff527a008 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2224,7 +2224,6 @@ def __init__(self, parent, subplotspec, *, self.subplotpars = parent.subplotpars self.dpi_scale_trans = parent.dpi_scale_trans self._axobservers = parent._axobservers - self.canvas = parent.canvas self.transFigure = parent.transFigure self.bbox_relative = Bbox.null() self._redo_transform_rel_fig() @@ -2241,6 +2240,10 @@ def __init__(self, parent, subplotspec, *, self._set_artist_props(self.patch) self.patch.set_antialiased(False) + @property + def canvas(self): + return self._parent.canvas + @property def dpi(self): return self._parent.dpi diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index 21de9159d56c..b079312695c1 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -263,7 +263,6 @@ class SubFigure(FigureBase): figure: Figure subplotpars: SubplotParams dpi_scale_trans: Affine2D - canvas: FigureCanvasBase transFigure: Transform bbox_relative: Bbox figbbox: BboxBase @@ -282,6 +281,8 @@ class SubFigure(FigureBase): **kwargs ) -> None: ... @property + def canvas(self) -> FigureCanvasBase: ... + @property def dpi(self) -> float: ... @dpi.setter def dpi(self, value: float) -> None: ... diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index 7e7ccc14bf8f..0cba4f392035 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -93,6 +93,11 @@ def _generate_complete_test_figure(fig_ref): plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4, label='$-.5 x$') plt.legend(draggable=True) + # Ensure subfigure parenting works. + subfigs = fig_ref.subfigures(2) + subfigs[0].subplots(1, 2) + subfigs[1].subplots(1, 2) + fig_ref.align_ylabels() # Test handling of _align_label_groups Groupers. From 5ad70281f58b6cdda2359359369759e2158b38e5 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 26 Jun 2024 14:18:55 -0500 Subject: [PATCH 47/61] Backport PR #28355: MNT: Re-add matplotlib.cm.get_cmap --- lib/matplotlib/cm.py | 38 ++++++++++++++++++++++++++++++++++++++ lib/matplotlib/cm.pyi | 2 ++ 2 files changed, 40 insertions(+) diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index c14973560ac3..b0cb3e9a7ec1 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -232,6 +232,44 @@ def get_cmap(self, cmap): globals().update(_colormaps) +# This is an exact copy of pyplot.get_cmap(). It was removed in 3.9, but apparently +# caused more user trouble than expected. Re-added for 3.9.1 and extended the +# deprecation period for two additional minor releases. +@_api.deprecated( + '3.7', + removal='3.11', + alternative="``matplotlib.colormaps[name]`` or ``matplotlib.colormaps.get_cmap()``" + " or ``pyplot.get_cmap()``" + ) +def get_cmap(name=None, lut=None): + """ + Get a colormap instance, defaulting to rc values if *name* is None. + + Parameters + ---------- + name : `~matplotlib.colors.Colormap` or str or None, default: None + If a `.Colormap` instance, it will be returned. Otherwise, the name of + a colormap known to Matplotlib, which will be resampled by *lut*. The + default, None, means :rc:`image.cmap`. + lut : int or None, default: None + If *name* is not already a Colormap instance and *lut* is not None, the + colormap will be resampled to have *lut* entries in the lookup table. + + Returns + ------- + Colormap + """ + if name is None: + name = mpl.rcParams['image.cmap'] + if isinstance(name, colors.Colormap): + return name + _api.check_in_list(sorted(_colormaps), name=name) + if lut is None: + return _colormaps[name] + else: + return _colormaps[name].resampled(lut) + + def _auto_norm_from_scale(scale_cls): """ Automatically generate a norm class from *scale_cls*. diff --git a/lib/matplotlib/cm.pyi b/lib/matplotlib/cm.pyi index da78d940ba4a..be8f10b39cb6 100644 --- a/lib/matplotlib/cm.pyi +++ b/lib/matplotlib/cm.pyi @@ -19,6 +19,8 @@ class ColormapRegistry(Mapping[str, colors.Colormap]): _colormaps: ColormapRegistry = ... +def get_cmap(name: str | colors.Colormap | None = ..., lut: int | None = ...) -> colors.Colormap: ... + class ScalarMappable: cmap: colors.Colormap | None colorbar: Colorbar | None From 29637c5b45b345bf443035f1bc47a8194e92b3cb Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 26 Jun 2024 15:24:00 -0400 Subject: [PATCH 48/61] Backport PR #28398: Add GIL Release to flush_events in macosx backend --- src/_macosx.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/_macosx.m b/src/_macosx.m index 656d502fa17c..fda928536ab5 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -77,6 +77,9 @@ static int wait_for_stdin() { // continuously run an event loop until the stdin_received flag is set to exit while (!stdin_received && !stdin_sigint) { + // This loop is similar to the main event loop and flush_events which have + // Py_[BEGIN|END]_ALLOW_THREADS surrounding the loop. + // This should not be necessary here because PyOS_InputHook releases the GIL for us. while (true) { NSEvent *event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: [NSDate distantPast] @@ -380,6 +383,9 @@ static CGFloat _get_device_scale(CGContextRef cr) // to process, breaking out of the loop when no events remain and // displaying the canvas if needed. NSEvent *event; + + Py_BEGIN_ALLOW_THREADS + while (true) { event = [NSApp nextEventMatchingMask: NSEventMaskAny untilDate: [NSDate distantPast] @@ -390,6 +396,9 @@ static CGFloat _get_device_scale(CGContextRef cr) } [NSApp sendEvent:event]; } + + Py_END_ALLOW_THREADS + [self->view displayIfNeeded]; Py_RETURN_NONE; } From bdad968ae24188ab1300b1b387a5f0c69ca1a714 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 26 Jun 2024 15:57:31 -0400 Subject: [PATCH 49/61] Backport PR #28342: DOC: Document the parameter *position* of apply_aspect() as internal --- lib/matplotlib/axes/_base.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 30c4efe80c49..96e497a3316b 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1882,6 +1882,11 @@ def apply_aspect(self, position=None): Parameters ---------- position : None or .Bbox + + .. note:: + This parameter exists for historic reasons and is considered + internal. End users should not use it. + If not ``None``, this defines the position of the Axes within the figure as a Bbox. See `~.Axes.get_position` for further details. @@ -1892,6 +1897,10 @@ def apply_aspect(self, position=None): to call it yourself if you need to update the Axes position and/or view limits before the Figure is drawn. + An alternative with a broader scope is `.Figure.draw_without_rendering`, + which updates all stale components of a figure, not only the positioning / + view limits of a single Axes. + See Also -------- matplotlib.axes.Axes.set_aspect @@ -1900,6 +1909,24 @@ def apply_aspect(self, position=None): Set how the Axes adjusts to achieve the required aspect ratio. matplotlib.axes.Axes.set_anchor Set the position in case of extra space. + matplotlib.figure.Figure.draw_without_rendering + Update all stale components of a figure. + + Examples + -------- + A typical usage example would be the following. `~.Axes.imshow` sets the + aspect to 1, but adapting the Axes position and extent to reflect this is + deferred until rendering for performance reasons. If you want to know the + Axes size before, you need to call `.apply_aspect` to get the correct + values. + + >>> fig, ax = plt.subplots() + >>> ax.imshow(np.zeros((3, 3))) + >>> ax.bbox.width, ax.bbox.height + (496.0, 369.59999999999997) + >>> ax.apply_aspect() + >>> ax.bbox.width, ax.bbox.height + (369.59999999999997, 369.59999999999997) """ if position is None: position = self.get_position(original=True) From 8709791007cf4c8cbc745e314ac6d3d5f6d24ccf Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 26 Jun 2024 14:57:38 -0500 Subject: [PATCH 50/61] Backport PR #28289: Promote mpltype Sphinx role to a public extension --- doc/api/index.rst | 1 + .../next_api_changes/development/28289-ES.rst | 7 + doc/api/sphinxext_roles.rst | 7 + doc/conf.py | 2 +- doc/sphinxext/custom_roles.py | 89 ----------- lib/matplotlib/sphinxext/meson.build | 1 + lib/matplotlib/sphinxext/roles.py | 147 ++++++++++++++++++ pyproject.toml | 4 +- 8 files changed, 166 insertions(+), 92 deletions(-) create mode 100644 doc/api/next_api_changes/development/28289-ES.rst create mode 100644 doc/api/sphinxext_roles.rst delete mode 100644 doc/sphinxext/custom_roles.py create mode 100644 lib/matplotlib/sphinxext/roles.py diff --git a/doc/api/index.rst b/doc/api/index.rst index e55a0ed3c5b2..70c3b5343e7a 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -126,6 +126,7 @@ Alphabetical list of modules: sphinxext_mathmpl_api.rst sphinxext_plot_directive_api.rst sphinxext_figmpl_directive_api.rst + sphinxext_roles.rst spines_api.rst style_api.rst table_api.rst diff --git a/doc/api/next_api_changes/development/28289-ES.rst b/doc/api/next_api_changes/development/28289-ES.rst new file mode 100644 index 000000000000..f891c63a64bf --- /dev/null +++ b/doc/api/next_api_changes/development/28289-ES.rst @@ -0,0 +1,7 @@ +Documentation-specific custom Sphinx roles are now semi-public +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For third-party packages that derive types from Matplotlib, our use of custom roles may +prevent Sphinx from building their docs. These custom Sphinx roles are now public solely +for the purposes of use within projects that derive from Matplotlib types. See +:mod:`matplotlib.sphinxext.roles` for details. diff --git a/doc/api/sphinxext_roles.rst b/doc/api/sphinxext_roles.rst new file mode 100644 index 000000000000..99959ff05d14 --- /dev/null +++ b/doc/api/sphinxext_roles.rst @@ -0,0 +1,7 @@ +============================== +``matplotlib.sphinxext.roles`` +============================== + +.. automodule:: matplotlib.sphinxext.roles + :no-undoc-members: + :private-members: _rcparam_role, _mpltype_role diff --git a/doc/conf.py b/doc/conf.py index 92d78f896ca2..f43806a8b4c0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -116,9 +116,9 @@ def _parse_skip_subdirs_file(): 'sphinx_gallery.gen_gallery', 'matplotlib.sphinxext.mathmpl', 'matplotlib.sphinxext.plot_directive', + 'matplotlib.sphinxext.roles', 'matplotlib.sphinxext.figmpl_directive', 'sphinxcontrib.inkscapeconverter', - 'sphinxext.custom_roles', 'sphinxext.github', 'sphinxext.math_symbol_table', 'sphinxext.missing_references', diff --git a/doc/sphinxext/custom_roles.py b/doc/sphinxext/custom_roles.py deleted file mode 100644 index d76c92709865..000000000000 --- a/doc/sphinxext/custom_roles.py +++ /dev/null @@ -1,89 +0,0 @@ -from urllib.parse import urlsplit, urlunsplit - -from docutils import nodes - -from matplotlib import rcParamsDefault - - -class QueryReference(nodes.Inline, nodes.TextElement): - """ - Wraps a reference or pending reference to add a query string. - - The query string is generated from the attributes added to this node. - - Also equivalent to a `~docutils.nodes.literal` node. - """ - - def to_query_string(self): - """Generate query string from node attributes.""" - return '&'.join(f'{name}={value}' for name, value in self.attlist()) - - -def visit_query_reference_node(self, node): - """ - Resolve *node* into query strings on its ``reference`` children. - - Then act as if this is a `~docutils.nodes.literal`. - """ - query = node.to_query_string() - for refnode in node.findall(nodes.reference): - uri = urlsplit(refnode['refuri'])._replace(query=query) - refnode['refuri'] = urlunsplit(uri) - - self.visit_literal(node) - - -def depart_query_reference_node(self, node): - """ - Act as if this is a `~docutils.nodes.literal`. - """ - self.depart_literal(node) - - -def rcparam_role(name, rawtext, text, lineno, inliner, options={}, content=[]): - # Generate a pending cross-reference so that Sphinx will ensure this link - # isn't broken at some point in the future. - title = f'rcParams["{text}"]' - target = 'matplotlibrc-sample' - ref_nodes, messages = inliner.interpreted(title, f'{title} <{target}>', - 'ref', lineno) - - qr = QueryReference(rawtext, highlight=text) - qr += ref_nodes - node_list = [qr] - - # The default backend would be printed as "agg", but that's not correct (as - # the default is actually determined by fallback). - if text in rcParamsDefault and text != "backend": - node_list.extend([ - nodes.Text(' (default: '), - nodes.literal('', repr(rcParamsDefault[text])), - nodes.Text(')'), - ]) - - return node_list, messages - - -def mpltype_role(name, rawtext, text, lineno, inliner, options={}, content=[]): - mpltype = text - type_to_link_target = { - 'color': 'colors_def', - } - if mpltype not in type_to_link_target: - raise ValueError(f"Unknown mpltype: {mpltype!r}") - - node_list, messages = inliner.interpreted( - mpltype, f'{mpltype} <{type_to_link_target[mpltype]}>', 'ref', lineno) - return node_list, messages - - -def setup(app): - app.add_role("rc", rcparam_role) - app.add_role("mpltype", mpltype_role) - app.add_node( - QueryReference, - html=(visit_query_reference_node, depart_query_reference_node), - latex=(visit_query_reference_node, depart_query_reference_node), - text=(visit_query_reference_node, depart_query_reference_node), - ) - return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/lib/matplotlib/sphinxext/meson.build b/lib/matplotlib/sphinxext/meson.build index 5dc7388384eb..35bb96fecbe1 100644 --- a/lib/matplotlib/sphinxext/meson.build +++ b/lib/matplotlib/sphinxext/meson.build @@ -3,6 +3,7 @@ python_sources = [ 'figmpl_directive.py', 'mathmpl.py', 'plot_directive.py', + 'roles.py', ] typing_sources = [ diff --git a/lib/matplotlib/sphinxext/roles.py b/lib/matplotlib/sphinxext/roles.py new file mode 100644 index 000000000000..301adcd8a5f5 --- /dev/null +++ b/lib/matplotlib/sphinxext/roles.py @@ -0,0 +1,147 @@ +""" +Custom roles for the Matplotlib documentation. + +.. warning:: + + These roles are considered semi-public. They are only intended to be used in + the Matplotlib documentation. + +However, it can happen that downstream packages end up pulling these roles into +their documentation, which will result in documentation build errors. The following +describes the exact mechanism and how to fix the errors. + +There are two ways, Matplotlib docstrings can end up in downstream documentation. +You have to subclass a Matplotlib class and either use the ``:inherited-members:`` +option in your autodoc configuration, or you have to override a method without +specifying a new docstring; the new method will inherit the original docstring and +still render in your autodoc. If the docstring contains one of the custom sphinx +roles, you'll see one of the following error messages: + +.. code-block:: none + + Unknown interpreted text role "mpltype". + Unknown interpreted text role "rc". + +To fix this, you can add this module as extension to your sphinx :file:`conf.py`:: + + extensions = [ + 'matplotlib.sphinxext.roles', + # Other extensions. + ] + +.. warning:: + + Direct use of these roles in other packages is not officially supported. We + reserve the right to modify or remove these roles without prior notification. +""" + +from urllib.parse import urlsplit, urlunsplit + +from docutils import nodes + +import matplotlib +from matplotlib import rcParamsDefault + + +class _QueryReference(nodes.Inline, nodes.TextElement): + """ + Wraps a reference or pending reference to add a query string. + + The query string is generated from the attributes added to this node. + + Also equivalent to a `~docutils.nodes.literal` node. + """ + + def to_query_string(self): + """Generate query string from node attributes.""" + return '&'.join(f'{name}={value}' for name, value in self.attlist()) + + +def _visit_query_reference_node(self, node): + """ + Resolve *node* into query strings on its ``reference`` children. + + Then act as if this is a `~docutils.nodes.literal`. + """ + query = node.to_query_string() + for refnode in node.findall(nodes.reference): + uri = urlsplit(refnode['refuri'])._replace(query=query) + refnode['refuri'] = urlunsplit(uri) + + self.visit_literal(node) + + +def _depart_query_reference_node(self, node): + """ + Act as if this is a `~docutils.nodes.literal`. + """ + self.depart_literal(node) + + +def _rcparam_role(name, rawtext, text, lineno, inliner, options=None, content=None): + """ + Sphinx role ``:rc:`` to highlight and link ``rcParams`` entries. + + Usage: Give the desired ``rcParams`` key as parameter. + + :code:`:rc:`figure.dpi`` will render as: :rc:`figure.dpi` + """ + # Generate a pending cross-reference so that Sphinx will ensure this link + # isn't broken at some point in the future. + title = f'rcParams["{text}"]' + target = 'matplotlibrc-sample' + ref_nodes, messages = inliner.interpreted(title, f'{title} <{target}>', + 'ref', lineno) + + qr = _QueryReference(rawtext, highlight=text) + qr += ref_nodes + node_list = [qr] + + # The default backend would be printed as "agg", but that's not correct (as + # the default is actually determined by fallback). + if text in rcParamsDefault and text != "backend": + node_list.extend([ + nodes.Text(' (default: '), + nodes.literal('', repr(rcParamsDefault[text])), + nodes.Text(')'), + ]) + + return node_list, messages + + +def _mpltype_role(name, rawtext, text, lineno, inliner, options=None, content=None): + """ + Sphinx role ``:mpltype:`` for custom matplotlib types. + + In Matplotlib, there are a number of type-like concepts that do not have a + direct type representation; example: color. This role allows to properly + highlight them in the docs and link to their definition. + + Currently supported values: + + - :code:`:mpltype:`color`` will render as: :mpltype:`color` + + """ + mpltype = text + type_to_link_target = { + 'color': 'colors_def', + } + if mpltype not in type_to_link_target: + raise ValueError(f"Unknown mpltype: {mpltype!r}") + + node_list, messages = inliner.interpreted( + mpltype, f'{mpltype} <{type_to_link_target[mpltype]}>', 'ref', lineno) + return node_list, messages + + +def setup(app): + app.add_role("rc", _rcparam_role) + app.add_role("mpltype", _mpltype_role) + app.add_node( + _QueryReference, + html=(_visit_query_reference_node, _depart_query_reference_node), + latex=(_visit_query_reference_node, _depart_query_reference_node), + text=(_visit_query_reference_node, _depart_query_reference_node), + ) + return {"version": matplotlib.__version__, + "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/pyproject.toml b/pyproject.toml index a9fb7df68450..52bbe308c0f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -283,11 +283,11 @@ ignore_directives = [ "include" ] ignore_roles = [ - # sphinxext.custom_roles - "rc", # matplotlib.sphinxext.mathmpl "mathmpl", "math-stix", + # matplotlib.sphinxext.roles + "rc", # sphinxext.github "ghissue", "ghpull", From bbb0d01bfa333746c77c4490164e9e3682fd56b2 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 26 Jun 2024 17:03:20 -0400 Subject: [PATCH 51/61] Merge pull request #28397 from rcomer/subfigure-stale Backport PR #28397: FIX: stale root Figure when adding/updating subfigures (cherry picked from commit d347c3227f8de8a99aa327390fee619310452a96) --- lib/matplotlib/figure.py | 2 ++ lib/matplotlib/tests/test_figure.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index d75ff527a008..0d939190a0a9 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1633,6 +1633,8 @@ def add_subfigure(self, subplotspec, **kwargs): sf = SubFigure(self, subplotspec, **kwargs) self.subfigs += [sf] sf._remove_method = self.subfigs.remove + sf.stale_callback = _stale_figure_callback + self.stale = True return sf def sca(self, a): diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 58aecd3dea8b..6e6daa77062d 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -1733,3 +1733,27 @@ def test_warn_colorbar_mismatch(): subfig3_1.colorbar(im3_2) # should not warn with pytest.warns(UserWarning, match="different Figure"): subfig3_1.colorbar(im4_1) + + +def test_subfigure_stale_propagation(): + fig = plt.figure() + + fig.draw_without_rendering() + assert not fig.stale + + sfig1 = fig.subfigures() + assert fig.stale + + fig.draw_without_rendering() + assert not fig.stale + assert not sfig1.stale + + sfig2 = sfig1.subfigures() + assert fig.stale + + fig.draw_without_rendering() + assert not fig.stale + assert not sfig2.stale + + sfig2.stale = True + assert fig.stale From a92824c6ee5dee1132df32b970a42d31846ce596 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Thu, 27 Jun 2024 12:04:36 -0500 Subject: [PATCH 52/61] Backport PR #28474: Fix typing and docs for containers --- lib/matplotlib/axes/_axes.py | 6 +++--- lib/matplotlib/container.py | 20 ++++++++++---------- lib/matplotlib/container.pyi | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index fdafc2dcb0bc..ffeecdcbd029 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3527,11 +3527,11 @@ def errorbar(self, x, y, yerr=None, xerr=None, `.ErrorbarContainer` The container contains: - - plotline: `~matplotlib.lines.Line2D` instance of x, y plot markers + - data_line : A `~matplotlib.lines.Line2D` instance of x, y plot markers and/or line. - - caplines: A tuple of `~matplotlib.lines.Line2D` instances of the error + - caplines : A tuple of `~matplotlib.lines.Line2D` instances of the error bar caps. - - barlinecols: A tuple of `.LineCollection` with the horizontal and + - barlinecols : A tuple of `.LineCollection` with the horizontal and vertical error ranges. Other Parameters diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index 0f082e298afc..b6dd43724f34 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -87,12 +87,12 @@ class ErrorbarContainer(Container): lines : tuple Tuple of ``(data_line, caplines, barlinecols)``. - - data_line : :class:`~matplotlib.lines.Line2D` instance of - x, y plot markers and/or line. - - caplines : tuple of :class:`~matplotlib.lines.Line2D` instances of - the error bar caps. - - barlinecols : list of :class:`~matplotlib.collections.LineCollection` - with the horizontal and vertical error ranges. + - data_line : A `~matplotlib.lines.Line2D` instance of x, y plot markers + and/or line. + - caplines : A tuple of `~matplotlib.lines.Line2D` instances of the error + bar caps. + - barlinecols : A tuple of `~matplotlib.collections.LineCollection` with the + horizontal and vertical error ranges. has_xerr, has_yerr : bool ``True`` if the errorbar has x/y errors. @@ -115,13 +115,13 @@ class StemContainer(Container): Attributes ---------- - markerline : :class:`~matplotlib.lines.Line2D` + markerline : `~matplotlib.lines.Line2D` The artist of the markers at the stem heads. - stemlines : list of :class:`~matplotlib.lines.Line2D` + stemlines : `~matplotlib.collections.LineCollection` The artists of the vertical lines for all stems. - baseline : :class:`~matplotlib.lines.Line2D` + baseline : `~matplotlib.lines.Line2D` The artist of the horizontal baseline. """ def __init__(self, markerline_stemlines_baseline, **kwargs): @@ -130,7 +130,7 @@ def __init__(self, markerline_stemlines_baseline, **kwargs): ---------- markerline_stemlines_baseline : tuple Tuple of ``(markerline, stemlines, baseline)``. - ``markerline`` contains the `.LineCollection` of the markers, + ``markerline`` contains the `.Line2D` of the markers, ``stemlines`` is a `.LineCollection` of the main lines, ``baseline`` is the `.Line2D` of the baseline. """ diff --git a/lib/matplotlib/container.pyi b/lib/matplotlib/container.pyi index 9cc2e1ac2acc..c66e7ba4b4c3 100644 --- a/lib/matplotlib/container.pyi +++ b/lib/matplotlib/container.pyi @@ -34,12 +34,12 @@ class BarContainer(Container): ) -> None: ... class ErrorbarContainer(Container): - lines: tuple[Line2D, Line2D, LineCollection] + lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]] has_xerr: bool has_yerr: bool def __init__( self, - lines: tuple[Line2D, Line2D, LineCollection], + lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]], has_xerr: bool = ..., has_yerr: bool = ..., **kwargs From b7423af305df3dd0ad55e56d38e751dbed02db60 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 27 Jun 2024 18:13:23 -0400 Subject: [PATCH 53/61] Backport PR #28473: Do not lowercase module:// backends --- lib/matplotlib/backends/registry.py | 12 +++++++++--- lib/matplotlib/tests/test_backend_registry.py | 9 +++++++++ lib/matplotlib/tests/test_backend_template.py | 11 +++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py index 47d5f65e350e..e08817bb089b 100644 --- a/lib/matplotlib/backends/registry.py +++ b/lib/matplotlib/backends/registry.py @@ -93,6 +93,9 @@ def __init__(self): } def _backend_module_name(self, backend): + if backend.startswith("module://"): + return backend[9:] + # Return name of module containing the specified backend. # Does not check if the backend is valid, use is_valid_backend for that. backend = backend.lower() @@ -224,7 +227,8 @@ def is_valid_backend(self, backend): bool True if backend is valid, False otherwise. """ - backend = backend.lower() + if not backend.startswith("module://"): + backend = backend.lower() # For backward compatibility, convert ipympl and matplotlib-inline long # module:// names to their shortened forms. @@ -342,7 +346,8 @@ def resolve_backend(self, backend): The GUI framework, which will be None for a backend that is non-interactive. """ if isinstance(backend, str): - backend = backend.lower() + if not backend.startswith("module://"): + backend = backend.lower() else: # Might be _auto_backend_sentinel or None # Use whatever is already running... from matplotlib import get_backend @@ -395,7 +400,8 @@ def resolve_gui_or_backend(self, gui_or_backend): framework : str or None The GUI framework, which will be None for a backend that is non-interactive. """ - gui_or_backend = gui_or_backend.lower() + if not gui_or_backend.startswith("module://"): + gui_or_backend = gui_or_backend.lower() # First check if it is a gui loop name. backend = self.backend_for_gui_framework(gui_or_backend) diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py index 141ffd69c266..80c2ce4fc51a 100644 --- a/lib/matplotlib/tests/test_backend_registry.py +++ b/lib/matplotlib/tests/test_backend_registry.py @@ -86,6 +86,15 @@ def test_is_valid_backend(backend, is_valid): assert backend_registry.is_valid_backend(backend) == is_valid +@pytest.mark.parametrize("backend, normalized", [ + ("agg", "matplotlib.backends.backend_agg"), + ("QtAgg", "matplotlib.backends.backend_qtagg"), + ("module://Anything", "Anything"), +]) +def test_backend_normalization(backend, normalized): + assert backend_registry._backend_module_name(backend) == normalized + + def test_deprecated_rcsetup_attributes(): match = "was deprecated in Matplotlib 3.9" with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match): diff --git a/lib/matplotlib/tests/test_backend_template.py b/lib/matplotlib/tests/test_backend_template.py index d7e2a5cd1266..964d15c1559a 100644 --- a/lib/matplotlib/tests/test_backend_template.py +++ b/lib/matplotlib/tests/test_backend_template.py @@ -49,3 +49,14 @@ def test_show_old_global_api(monkeypatch): mpl.use("module://mpl_test_backend") plt.show() mock_show.assert_called_with() + + +def test_load_case_sensitive(monkeypatch): + mpl_test_backend = SimpleNamespace(**vars(backend_template)) + mock_show = MagicMock() + monkeypatch.setattr( + mpl_test_backend.FigureManagerTemplate, "pyplot_show", mock_show) + monkeypatch.setitem(sys.modules, "mpl_Test_Backend", mpl_test_backend) + mpl.use("module://mpl_Test_Backend") + plt.show() + mock_show.assert_called_with() From c7997bed82e43e04cc9d67a97e78bc70646004e7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 27 Jun 2024 18:58:16 -0400 Subject: [PATCH 54/61] Backport PR #28393: Make sticky edges only apply if the sticky edge is the most extreme limit point --- lib/matplotlib/axes/_base.py | 6 ++++++ .../test_axes/sticky_tolerance.png | Bin 0 -> 3941 bytes lib/matplotlib/tests/test_axes.py | 19 ++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 96e497a3316b..f83999436cbb 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2969,9 +2969,15 @@ def handle_single_axis( # Index of largest element < x0 + tol, if any. i0 = stickies.searchsorted(x0 + tol) - 1 x0bound = stickies[i0] if i0 != -1 else None + # Ensure the boundary acts only if the sticky is the extreme value + if x0bound is not None and x0bound > x0: + x0bound = None # Index of smallest element > x1 - tol, if any. i1 = stickies.searchsorted(x1 - tol) x1bound = stickies[i1] if i1 != len(stickies) else None + # Ensure the boundary acts only if the sticky is the extreme value + if x1bound is not None and x1bound < x1: + x1bound = None # Add the margin in figure space and then transform back, to handle # non-linear scales. diff --git a/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png new file mode 100644 index 0000000000000000000000000000000000000000..a3fb13d0716aed8de5596a838828e103d7b23d5d GIT binary patch literal 3941 zcmd^?Yfw{X8pq#+G@?b&I#o(BI3jfwxf2@-0W7v44B*0c!GwDWS4kp-01-&oZNV*< z>^dwgh6R}&p=y&N8W2M+6*Wpk>Sj%(5E9BIg-8g5G$0A=LEEm~wS3W;b!R`EIWzB^ z_ni0nKhN{~Kj#leBZ4n=kKJ=Ty z#u>#SqNDuH^83N>i!9)$2)$m12&u5aVC2YA%(Uzjzs0J9c|b&lH4c8azb0#Un(9yd341y}2||J3A}M;$~s3Z}^~?6J$(nMMXtNf>JwhS?^b0)D-z;b91xA zO=yY|ENgvv7Iv|H+NXdhUQNb)yUf0`p`X~EO0aM>yl0r=`UDK%}^#muXPX1>AFqtvtzq}d8otv)aWuyh14aNRNxFx=r=z^*Fr zB1?I48UP8#u{H-B6Z8Z{toLev)_4Hm`Yqyx5B)@dVJ-@&_$+nzU41bP8y?-8 z2^s-BPC@K)j+g(HBbdcA5ua>hfS67NQG%&y%eF0-Il@N$;K-{Txa{-XPLze}#>Q8# zyPnkgU47hSkw3qw_57$e0Q9PDBCkhO-rw^TQ`5@=LkIxr+ZgE&iCqMHu@r3;r1=v7 z_a?XM-pXR1lYK+80*p$`i&x73sKQ#D4FIzvVvYgf<6pzx8;jVx9yJ{-^2&>vBz^;J za;Z1E5eZbxt&p}e)QPF7^9I{1O-Ihm)ZJ7FIwcei2(!vTf`<>eo^ zFgDj<3&4&O@Rx(>wE%P@{m+&Fe2Z$D%8M_e(Xy~emlAt?Bv`%an8a`%dZq>#WT<^P zU&wG-Y_=F+o_8D}BDZX8E;e+oA>r4PA8R(|7Js!oJ;685C&X>yS$u<=))t1rb?}Pb zmfEOLrLlT%S;2SF#+&p7h_jsK`2gh^1*|JMlQ-tVuDP>xvPG>{cPM8iW^>6g_HWtZ zqCBbhp9QlLcPql>!7C{B{^Dv0)ej{ZCI8CC#)iO8xTnl08sLp#=|a8^?W&9_5aV;< zg=Gd>Jj%fV&peUO>Yt2vWvq^8$8nri7Mj|9M>NJE`aIfo=nd%EzPky4k@S>$;~#Nl z^P{~L1!iIUJo@@j=whiJJ&#&++5&-bU5>ptHGhe~P{$0C!K|CsL<~A z6B}At_<5?_n8bsuYA5T>E;Ak%eoPL*e-Q~R-}z6Nk99H1^d*mQk-CdZ4{7k*RA-** zrudQpQ91gOu>6vI>0b&Lus6h>ezrklp-R1qj(Flcq4NB;i?hoR*k4a709_w~rk{1_ zc99kktAj;9+7IK?OTG;J*ee+78f#re#P{#)T1T3+ zd_NSET@m})sxi7)X@rcUWAM0L1hSzubAP;CVLJJ>mNC>(t2hx3VXox|hHSs}RKInP zpnuc6M52?h*Xc`hQX6(KBZI!AV|H-pS=jP;PzIUuD>=7Latt8Mb0Duly|h3((^@VC zVaZSl-P)*n3nJ~*H7&(ZqNMFff<3ZU$t*=)$Ge6+YsRMR%3BN}BKX6dBKDl+q7_#b zm7Rk{w!2d|kh5RGQ2)Y{S?qb57eKrn(7b{>$O_kZFam#xkc%#=*CAjd>Nk>3}3i5)eP-q~Y+w;enHDA!+m~%HvSC-UKpDAE$mz;v0a}PXx z=xz%pQ{4B5}UkFyCYznA7K!iRhD6lY~(SD(2^Q9r_Rg0|H*@PC@JZ7;}h{>6(&A(ss9_R;pw z=Fwf%D8W7cQi*mzGtAQeS1shF^Yb}ww+inL6?=JNOwQ0xEjaQ)#9_|+Nx%Ci*GT0M literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 0ed5a11c1398..ff4d698fbb6e 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -682,6 +682,25 @@ def test_sticky_shared_axes(fig_test, fig_ref): ax0.pcolormesh(Z) +@image_comparison(['sticky_tolerance.png'], remove_text=True, style="mpl20") +def test_sticky_tolerance(): + fig, axs = plt.subplots(2, 2) + + width = .1 + + axs.flat[0].bar(x=0, height=width, bottom=20000.6) + axs.flat[0].bar(x=1, height=width, bottom=20000.1) + + axs.flat[1].bar(x=0, height=-width, bottom=20000.6) + axs.flat[1].bar(x=1, height=-width, bottom=20000.1) + + axs.flat[2].barh(y=0, width=-width, left=-20000.6) + axs.flat[2].barh(y=1, width=-width, left=-20000.1) + + axs.flat[3].barh(y=0, width=width, left=-20000.6) + axs.flat[3].barh(y=1, width=width, left=-20000.1) + + def test_nargs_stem(): with pytest.raises(TypeError, match='0 were given'): # stem() takes 1-3 arguments. From b7d25c4778e6e87874b8d8cc3a0c1af52992c124 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 29 Jun 2024 14:50:57 -0400 Subject: [PATCH 55/61] Backport PR #28486: Fix CompositeGenericTransform.contains_branch_seperately --- lib/matplotlib/tests/test_transforms.py | 7 +++++++ lib/matplotlib/transforms.py | 11 ++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 959814de82db..3d12b90d5210 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -667,6 +667,13 @@ def test_contains_branch(self): assert not self.stack1.contains_branch(self.tn1 + self.ta2) + blend = mtransforms.BlendedGenericTransform(self.tn2, self.stack2) + x, y = blend.contains_branch_seperately(self.stack2_subset) + stack_blend = self.tn3 + blend + sx, sy = stack_blend.contains_branch_seperately(self.stack2_subset) + assert x is sx is False + assert y is sy is True + def test_affine_simplification(self): # tests that a transform stack only calls as much is absolutely # necessary "non-affine" allowing the best possible optimization with diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 5003e2113930..3575bd1fc14d 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -1423,7 +1423,7 @@ def contains_branch_seperately(self, other_transform): 'transforms with 2 output dimensions') # for a non-blended transform each separate dimension is the same, so # just return the appropriate shape. - return [self.contains_branch(other_transform)] * 2 + return (self.contains_branch(other_transform), ) * 2 def __sub__(self, other): """ @@ -2404,6 +2404,15 @@ def _iter_break_from_left_to_right(self): for left, right in self._b._iter_break_from_left_to_right(): yield self._a + left, right + def contains_branch_seperately(self, other_transform): + # docstring inherited + if self.output_dims != 2: + raise ValueError('contains_branch_seperately only supports ' + 'transforms with 2 output dimensions') + if self == other_transform: + return (True, True) + return self._b.contains_branch_seperately(other_transform) + depth = property(lambda self: self._a.depth + self._b.depth) is_affine = property(lambda self: self._a.is_affine and self._b.is_affine) is_separable = property( From 475d5b70daa5f1b0e66dc89c9cba857d6930ea61 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 29 Jun 2024 14:52:32 -0400 Subject: [PATCH 56/61] Backport PR #28487: Fix autoscaling with axhspan --- lib/matplotlib/axes/_axes.py | 2 +- lib/matplotlib/tests/test_axes.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index ffeecdcbd029..040c5a4ba4e9 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1028,7 +1028,7 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): # For Rectangles and non-separable transforms, add_patch can be buggy # and update the x limits even though it shouldn't do so for an # yaxis_transformed patch, so undo that update. - ix = self.dataLim.intervalx + ix = self.dataLim.intervalx.copy() mx = self.dataLim.minposx self.add_patch(p) self.dataLim.intervalx = ix diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index ff4d698fbb6e..3c0407ee4098 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8195,10 +8195,10 @@ def test_relative_ticklabel_sizes(size): def test_multiplot_autoscale(): fig = plt.figure() ax1, ax2 = fig.subplots(2, 1, sharex='all') - ax1.scatter([1, 2, 3, 4], [2, 3, 2, 3]) + ax1.plot([18000, 18250, 18500, 18750], [2, 3, 2, 3]) ax2.axhspan(-5, 5) xlim = ax1.get_xlim() - assert np.allclose(xlim, [0.5, 4.5]) + assert np.allclose(xlim, [18000, 18800]) def test_sharing_does_not_link_positions(): From 5552302c3fef08f8e29b7f4d1340a723df63962a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 2 Jul 2024 13:14:34 -0400 Subject: [PATCH 57/61] Backport PR #28498: Don't fail if we can't query system fonts on macOS --- lib/matplotlib/font_manager.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 312e8ee97246..813bee6eb623 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -266,8 +266,11 @@ def _get_fontconfig_fonts(): @lru_cache def _get_macos_fonts(): """Cache and list the font paths known to ``system_profiler SPFontsDataType``.""" - d, = plistlib.loads( - subprocess.check_output(["system_profiler", "-xml", "SPFontsDataType"])) + try: + d, = plistlib.loads( + subprocess.check_output(["system_profiler", "-xml", "SPFontsDataType"])) + except (OSError, subprocess.CalledProcessError, plistlib.InvalidFileException): + return [] return [Path(entry["path"]) for entry in d["_items"]] From c43313a77ccb165b7b26de7e9b3cd9b9c1b50700 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 3 Jul 2024 15:08:51 -0500 Subject: [PATCH 58/61] Backport PR #28451: Fix GTK cairo backends --- lib/matplotlib/backends/backend_gtk3cairo.py | 16 ++++++++++------ lib/matplotlib/backends/backend_gtk4.py | 10 ++-------- lib/matplotlib/backends/backend_gtk4cairo.py | 9 ++++----- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3cairo.py b/lib/matplotlib/backends/backend_gtk3cairo.py index 24a26111f062..371b8dc1b31f 100644 --- a/lib/matplotlib/backends/backend_gtk3cairo.py +++ b/lib/matplotlib/backends/backend_gtk3cairo.py @@ -13,15 +13,19 @@ def on_draw_event(self, widget, ctx): with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar else nullcontext()): - self._renderer.set_context(ctx) - scale = self.device_pixel_ratio - # Scale physical drawing to logical size. - ctx.scale(1 / scale, 1 / scale) allocation = self.get_allocation() + # Render the background before scaling, as the allocated size here is in + # logical pixels. Gtk.render_background( self.get_style_context(), ctx, - allocation.x, allocation.y, - allocation.width, allocation.height) + 0, 0, allocation.width, allocation.height) + scale = self.device_pixel_ratio + # Scale physical drawing to logical size. + ctx.scale(1 / scale, 1 / scale) + self._renderer.set_context(ctx) + # Set renderer to physical size so it renders in full resolution. + self._renderer.width = allocation.width * scale + self._renderer.height = allocation.height * scale self._renderer.dpi = self.figure.dpi self.figure.draw(self._renderer) diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 256a8ec9e864..dd86ab628ce7 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -34,7 +34,6 @@ class FigureCanvasGTK4(_FigureCanvasGTK, Gtk.DrawingArea): required_interactive_framework = "gtk4" supports_blit = False manager_class = _api.classproperty(lambda cls: FigureManagerGTK4) - _context_is_scaled = False def __init__(self, figure=None): super().__init__(figure=figure) @@ -228,13 +227,8 @@ def _post_draw(self, widget, ctx): lw = 1 dash = 3 - if not self._context_is_scaled: - x0, y0, w, h = (dim / self.device_pixel_ratio - for dim in self._rubberband_rect) - else: - x0, y0, w, h = self._rubberband_rect - lw *= self.device_pixel_ratio - dash *= self.device_pixel_ratio + x0, y0, w, h = (dim / self.device_pixel_ratio + for dim in self._rubberband_rect) x1 = x0 + w y1 = y0 + h diff --git a/lib/matplotlib/backends/backend_gtk4cairo.py b/lib/matplotlib/backends/backend_gtk4cairo.py index b1d543704351..838ea03fcce6 100644 --- a/lib/matplotlib/backends/backend_gtk4cairo.py +++ b/lib/matplotlib/backends/backend_gtk4cairo.py @@ -5,7 +5,10 @@ class FigureCanvasGTK4Cairo(FigureCanvasCairo, FigureCanvasGTK4): - _context_is_scaled = True + def _set_device_pixel_ratio(self, ratio): + # Cairo in GTK4 always uses logical pixels, so we don't need to do anything for + # changes to the device pixel ratio. + return False def on_draw_event(self, widget, ctx): if self._idle_draw_id: @@ -16,15 +19,11 @@ def on_draw_event(self, widget, ctx): with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar else nullcontext()): self._renderer.set_context(ctx) - scale = self.device_pixel_ratio - # Scale physical drawing to logical size. - ctx.scale(1 / scale, 1 / scale) allocation = self.get_allocation() Gtk.render_background( self.get_style_context(), ctx, allocation.x, allocation.y, allocation.width, allocation.height) - self._renderer.dpi = self.figure.dpi self.figure.draw(self._renderer) From 06189c2b237e9dc4e0de456b80e8ae098cdde4ae Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 3 Jul 2024 20:15:51 -0400 Subject: [PATCH 59/61] Backport PR #28430: Fix pickling of AxesWidgets. --- lib/matplotlib/testing/__init__.py | 21 ++++++++++ lib/matplotlib/testing/__init__.pyi | 1 + .../tests/test_backends_interactive.py | 23 +---------- lib/matplotlib/tests/test_pickle.py | 24 ++++++++++- lib/matplotlib/widgets.py | 40 ++++++------------- lib/matplotlib/widgets.pyi | 12 ++++-- 6 files changed, 67 insertions(+), 54 deletions(-) diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py index 8e60267ed608..19113d399626 100644 --- a/lib/matplotlib/testing/__init__.py +++ b/lib/matplotlib/testing/__init__.py @@ -211,3 +211,24 @@ def ipython_in_subprocess(requested_backend_or_gui_framework, all_expected_backe ) assert proc.stdout.strip().endswith(f"'{expected_backend}'") + + +def is_ci_environment(): + # Common CI variables + ci_environment_variables = [ + 'CI', # Generic CI environment variable + 'CONTINUOUS_INTEGRATION', # Generic CI environment variable + 'TRAVIS', # Travis CI + 'CIRCLECI', # CircleCI + 'JENKINS', # Jenkins + 'GITLAB_CI', # GitLab CI + 'GITHUB_ACTIONS', # GitHub Actions + 'TEAMCITY_VERSION' # TeamCity + # Add other CI environment variables as needed + ] + + for env_var in ci_environment_variables: + if os.getenv(env_var): + return True + + return False diff --git a/lib/matplotlib/testing/__init__.pyi b/lib/matplotlib/testing/__init__.pyi index 1f52a8ccb8ee..6917b6a5a380 100644 --- a/lib/matplotlib/testing/__init__.pyi +++ b/lib/matplotlib/testing/__init__.pyi @@ -51,3 +51,4 @@ def ipython_in_subprocess( requested_backend_or_gui_framework: str, all_expected_backends: dict[tuple[int, int], str], ) -> None: ... +def is_ci_environment() -> bool: ... diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 6830e7d5c845..d624b5db0ac2 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -19,7 +19,7 @@ import matplotlib as mpl from matplotlib import _c_internal_utils from matplotlib.backend_tools import ToolToggleBase -from matplotlib.testing import subprocess_run_helper as _run_helper +from matplotlib.testing import subprocess_run_helper as _run_helper, is_ci_environment class _WaitForStringPopen(subprocess.Popen): @@ -110,27 +110,6 @@ def _get_testable_interactive_backends(): for env, marks in _get_available_interactive_backends()] -def is_ci_environment(): - # Common CI variables - ci_environment_variables = [ - 'CI', # Generic CI environment variable - 'CONTINUOUS_INTEGRATION', # Generic CI environment variable - 'TRAVIS', # Travis CI - 'CIRCLECI', # CircleCI - 'JENKINS', # Jenkins - 'GITLAB_CI', # GitLab CI - 'GITHUB_ACTIONS', # GitHub Actions - 'TEAMCITY_VERSION' # TeamCity - # Add other CI environment variables as needed - ] - - for env_var in ci_environment_variables: - if os.getenv(env_var): - return True - - return False - - # Reasonable safe values for slower CI/Remote and local architectures. _test_timeout = 120 if is_ci_environment() else 20 diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index 0cba4f392035..1474a67d28aa 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -1,5 +1,7 @@ from io import BytesIO import ast +import os +import sys import pickle import pickletools @@ -8,7 +10,7 @@ import matplotlib as mpl from matplotlib import cm -from matplotlib.testing import subprocess_run_helper +from matplotlib.testing import subprocess_run_helper, is_ci_environment from matplotlib.testing.decorators import check_figures_equal from matplotlib.dates import rrulewrapper from matplotlib.lines import VertexSelector @@ -307,3 +309,23 @@ def test_cycler(): ax = pickle.loads(pickle.dumps(ax)) l, = ax.plot([3, 4]) assert l.get_color() == "m" + + +# Run under an interactive backend to test that we don't try to pickle the +# (interactive and non-picklable) canvas. +def _test_axeswidget_interactive(): + ax = plt.figure().add_subplot() + pickle.dumps(mpl.widgets.Button(ax, "button")) + + +@pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649 + ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and + sys.platform == 'darwin' and sys.version_info[:2] < (3, 11), + reason='Tk version mismatch on Azure macOS CI' + ) +def test_axeswidget_interactive(): + subprocess_run_helper( + _test_axeswidget_interactive, + timeout=120 if is_ci_environment() else 20, + extra_env={'MPLBACKEND': 'tkagg'} + ) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index ed130e6854f2..a298f3ae3d6a 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -90,22 +90,6 @@ def ignore(self, event): """ return not self.active - def _changed_canvas(self): - """ - Someone has switched the canvas on us! - - This happens if `savefig` needs to save to a format the previous - backend did not support (e.g. saving a figure using an Agg based - backend saved to a vector format). - - Returns - ------- - bool - True if the canvas has been changed. - - """ - return self.canvas is not self.ax.figure.canvas - class AxesWidget(Widget): """ @@ -131,9 +115,10 @@ class AxesWidget(Widget): def __init__(self, ax): self.ax = ax - self.canvas = ax.figure.canvas self._cids = [] + canvas = property(lambda self: self.ax.figure.canvas) + def connect_event(self, event, callback): """ Connect a callback function with an event. @@ -1100,7 +1085,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, def _clear(self, event): """Internal event handler to clear the buttons.""" - if self.ignore(event) or self._changed_canvas(): + if self.ignore(event) or self.canvas.is_saving(): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._checks) @@ -1677,7 +1662,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, def _clear(self, event): """Internal event handler to clear the buttons.""" - if self.ignore(event) or self._changed_canvas(): + if self.ignore(event) or self.canvas.is_saving(): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self._buttons) @@ -1933,7 +1918,7 @@ def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False, def clear(self, event): """Internal event handler to clear the cursor.""" - if self.ignore(event) or self._changed_canvas(): + if self.ignore(event) or self.canvas.is_saving(): return if self.useblit: self.background = self.canvas.copy_from_bbox(self.ax.bbox) @@ -2573,9 +2558,7 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False, self.drag_from_anywhere = drag_from_anywhere self.ignore_event_outside = ignore_event_outside - # Reset canvas so that `new_axes` connects events. - self.canvas = None - self.new_axes(ax, _props=props) + self.new_axes(ax, _props=props, _init=True) # Setup handles self._handle_props = { @@ -2588,14 +2571,15 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False, self._active_handle = None - def new_axes(self, ax, *, _props=None): + def new_axes(self, ax, *, _props=None, _init=False): """Set SpanSelector to operate on a new Axes.""" - self.ax = ax - if self.canvas is not ax.figure.canvas: + reconnect = False + if _init or self.canvas is not ax.figure.canvas: if self.canvas is not None: self.disconnect_events() - - self.canvas = ax.figure.canvas + reconnect = True + self.ax = ax + if reconnect: self.connect_default_events() # Reset diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index c85ad2158ee7..58adf85aae60 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -33,8 +33,9 @@ class Widget: class AxesWidget(Widget): ax: Axes - canvas: FigureCanvasBase | None def __init__(self, ax: Axes) -> None: ... + @property + def canvas(self) -> FigureCanvasBase | None: ... def connect_event(self, event: Event, callback: Callable) -> None: ... def disconnect_events(self) -> None: ... @@ -310,7 +311,6 @@ class SpanSelector(_SelectorWidget): grab_range: float drag_from_anywhere: bool ignore_event_outside: bool - canvas: FigureCanvasBase | None def __init__( self, ax: Axes, @@ -330,7 +330,13 @@ class SpanSelector(_SelectorWidget): ignore_event_outside: bool = ..., snap_values: ArrayLike | None = ..., ) -> None: ... - def new_axes(self, ax: Axes, *, _props: dict[str, Any] | None = ...) -> None: ... + def new_axes( + self, + ax: Axes, + *, + _props: dict[str, Any] | None = ..., + _init: bool = ..., + ) -> None: ... def connect_default_events(self) -> None: ... @property def direction(self) -> Literal["horizontal", "vertical"]: ... From 53254253095b40b76336afe583820245bfb8a9db Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 4 Jul 2024 01:43:44 -0400 Subject: [PATCH 60/61] DOC: Create release notes for 3.9.1 --- .../api_changes_3.9.1.rst} | 6 + doc/users/github_stats.rst | 876 ++++-------------- .../prev_whats_new/github_stats_3.9.0.rst | 744 +++++++++++++++ doc/users/release_notes.rst | 2 + 4 files changed, 914 insertions(+), 714 deletions(-) rename doc/api/{next_api_changes/development/28289-ES.rst => prev_api_changes/api_changes_3.9.1.rst} (86%) create mode 100644 doc/users/prev_whats_new/github_stats_3.9.0.rst diff --git a/doc/api/next_api_changes/development/28289-ES.rst b/doc/api/prev_api_changes/api_changes_3.9.1.rst similarity index 86% rename from doc/api/next_api_changes/development/28289-ES.rst rename to doc/api/prev_api_changes/api_changes_3.9.1.rst index f891c63a64bf..4a9a1fc6669c 100644 --- a/doc/api/next_api_changes/development/28289-ES.rst +++ b/doc/api/prev_api_changes/api_changes_3.9.1.rst @@ -1,3 +1,9 @@ +API Changes for 3.9.1 +===================== + +Development +----------- + Documentation-specific custom Sphinx roles are now semi-public ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/users/github_stats.rst b/doc/users/github_stats.rst index 629d9319fc57..0c8f29687afb 100644 --- a/doc/users/github_stats.rst +++ b/doc/users/github_stats.rst @@ -1,747 +1,195 @@ .. _github-stats: -GitHub statistics for 3.9.0 (May 15, 2024) +GitHub statistics for 3.9.1 (Jul 04, 2024) ========================================== -GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/05/15 +GitHub statistics for 2024/05/15 (tag: v3.9.0) - 2024/07/04 These lists are automatically generated, and may be incomplete or contain duplicates. -We closed 97 issues and merged 450 pull requests. -The full list can be seen `on GitHub `__ +We closed 30 issues and merged 111 pull requests. +The full list can be seen `on GitHub `__ -The following 175 authors contributed 2584 commits. +The following 29 authors contributed 184 commits. -* 0taj -* Abdul Razak Taha -* Adam J. Stewart -* Adam Turner -* Aditi Gautam -* agautam478 -* Alan Lau -* Albert Y. Shih -* Alec Vercruysse -* Alexander Volkov -* Alice Descoeudres -* Allan Haldane -* Amirreza Aflakparast -* Ananya Devarakonda -* ananya314 -* Anja Beck -* Anjini2004 -* Ant Lockyer * Antony Lee -* Anvi Verma -* Artyom Romanov -* Augusto Borges -* avramid9 -* Ben Root -* bersbersbers -* Binaya Sharma -* Cameron -* Chaoyi Hu -* chaoyihu -* Chiraag Balu -* Christoph Hasse -* ConstableCatnip -* CozyFrog -* Cyril Gadal -* Dale Dai -* Daniel Bergman -* Daniel Hitchcock -* danielcobej -* David Gilbertson -* David Stansby -* ddale1128@gmail.com +* Brigitta Sipőcz +* Christian Mattsson +* dale * dependabot[bot] -* Devilsaint -* dohyun -* Drew Kinneer -* DWesl -* Elisa Heckelmann -* ElisaHeck * Elliott Sales de Andrade -* Eric Firing -* Eric Prestat -* esibinga -* Eva Sibinga -* Evgenii Radchenko -* Faisal Fawad -* Felipe Cybis Pereira -* Garrett Sward -* Gaurav-Kumar-Soni -* Gauri Chaudhari -* Gautam Sagar +* Eytan Adler * Greg Lucas -* Gurudatta Shanbhag +* haaris * hannah -* Haoying Zhang -* Hugues Hoppe -* i-jey -* iamfaham -* Ian Hunt-Isaak * Ian Thomas -* ifEricReturnTrue * Illviljan -* Issam -* Issam Arabi -* Jacob Stevens-Haas -* Jacob Tomlinson -* Jake -* Jake Stevens-Haas -* James Salsman -* Jaroza727 -* Jeremy Farrell -* Jirka -* Jody Klymak -* Jorge Moraleda -* Joshua Stevenson -* jovianw -* João Andrade -* jpgianfaldoni -* jsdodge -* jsjeelshah -* judfs -* Juhan Oskar Hennoste -* Junpei Ota -* Katherine Turk -* katotaisei -* KheshavKumar -* Koustav Ghosh -* Kritika Verma +* K900 * Kyle Sunden -* Linyi Li -* linyilily -* lkkmpn -* Lucia Korpas -* madisonwong210 -* Maggie Liu -* Marc Bresson +* Lumberbot (aka Jack) +* malhar2460 * Matthew Feickert -* Matthew Morrison -* Matthias Bussonnier * Melissa Weber Mendonça -* melissawm -* mliu08 -* Mostafa Noah -* MostafaNouh0011 -* n-aswin -* Nabil -* nbarlowATI -* Nidaa Rabah -* Nivedita Chaudhari +* MischaMegens2 * Oscar Gustafsson -* patel-zeel -* Pavel Liavonau -* Pedro -* Pedro Peçanha -* Peter Talley -* Pradeep Reddy Raamana -* Prajwal Agrawal -* Pranav Raghu -* prateetishah -* pre-commit-ci[bot] -* QuadroTec -* Rafael Tsuha -* Raghuram Sirigiri -* Raphael -* Raphael Quast -* Ratnabali Dutta -* rawwash -* rsp2210 -* Ruoyi -* Ruoyi Xie -* Rushikesh Pandya * Ruth Comer -* samGreer -* Samuel Diebolt -* saranti * Scott Shambaugh -* Sebastian Berg -* Seohyeon Lee -* Sheepfan0828 -* ShivamPathak99 -* Shriya Kalakata -* shriyakalakata -* Stefan -* Steffen Rehberg -* stevezhang1999 -* Sudhanshu Pandey -* Talha Irfan -* thehappycheese +* simond07 +* SjoerdB93 +* Takumasa N +* Takumasa N. +* Takumasa Nakamura * Thomas A Caswell -* Tiago Lubiana * Tim Hoffmann -* tobias -* Tom Sarantis -* trananso -* turnipseason -* tusharkulkarni008 -* UFEddy -* Vashesh08 -* vicky6 -* vigneshvetrivel8 -* wemi3 -* yangyangdotcom -* YiLun Fan -* Zach Champion -* zachjweiner -* zoehcycy GitHub issues and pull requests: -Pull Requests (450): +Pull Requests (111): -* :ghpull:`28206`: Backport PR #28205 on branch v3.9.x (TST: Fix tests with older versions of ipython) -* :ghpull:`28207`: TST: Followup corrections to #28205 -* :ghpull:`28205`: TST: Fix tests with older versions of ipython -* :ghpull:`28203`: Backport PR #28164 on branch v3.9.x (CI: Ensure code coverage is always uploaded) -* :ghpull:`28204`: Backport PR #28195 on branch v3.9.x (TST: Prepare for pytest 9) -* :ghpull:`28191`: DOC: Use released mpl-sphinx-theme on v3.9.x -* :ghpull:`28195`: TST: Prepare for pytest 9 -* :ghpull:`28193`: Backport PR #28185 on branch v3.9.x (DOC: Bump mpl-sphinx-theme to 3.9) -* :ghpull:`28190`: Backport PR #28103 on branch v3.9.x ([DOC]: Fix compatibility with sphinx-gallery 0.16) -* :ghpull:`28164`: CI: Ensure code coverage is always uploaded -* :ghpull:`28194`: Backport PR #28188 on branch v3.9.x ([TST] Bump some tolerances for Macos ARM) -* :ghpull:`28188`: [TST] Bump some tolerances for Macos ARM -* :ghpull:`28185`: DOC: Bump mpl-sphinx-theme to 3.9 -* :ghpull:`28189`: Backport PR #28181 on branch v3.9.x (DOC: Prepare release notes for 3.9) -* :ghpull:`28103`: [DOC]: Fix compatibility with sphinx-gallery 0.16 -* :ghpull:`28181`: DOC: Prepare release notes for 3.9 -* :ghpull:`28184`: Backport PR #28182 on branch v3.9.x (Bump custom hatch deprecation expiration) -* :ghpull:`28182`: Bump custom hatch deprecation expiration -* :ghpull:`28178`: Backport PR #28171 on branch v3.9.x (Support removing absent tools from ToolContainerBase.) -* :ghpull:`28171`: Support removing absent tools from ToolContainerBase. -* :ghpull:`28174`: Backport PR #28169 on branch v3.9.x (Clarify public-ness of some ToolContainerBase APIs.) -* :ghpull:`28169`: Clarify public-ness of some ToolContainerBase APIs. -* :ghpull:`28160`: Backport PR #28039 on branch v3.9.x (Respect vertical_axis when rotating plot interactively) -* :ghpull:`28159`: Backport PR #28157 on branch v3.9.x (Remove call to non-existent method _default_contains in Artist) -* :ghpull:`28162`: Backport PR #27948 on branch v3.9.x (Move IPython backend mapping to Matplotlib and support entry points) -* :ghpull:`28163`: Backport PR #28144 on branch v3.9.x (DOC: Refactor code in the fishbone diagram example) -* :ghpull:`28144`: DOC: Refactor code in the fishbone diagram example -* :ghpull:`27948`: Move IPython backend mapping to Matplotlib and support entry points -* :ghpull:`28039`: Respect vertical_axis when rotating plot interactively -* :ghpull:`28157`: Remove call to non-existent method _default_contains in Artist -* :ghpull:`28141`: Backport PR #27960 on branch v3.9.x (Update AppVeyor config) -* :ghpull:`28138`: Backport PR #28068 on branch v3.9.x ([TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list``) -* :ghpull:`28140`: Backport PR #28136 on branch v3.9.x (Appease pycodestyle.) -* :ghpull:`27960`: Update AppVeyor config -* :ghpull:`28068`: [TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list`` -* :ghpull:`28136`: Appease pycodestyle. -* :ghpull:`28135`: Backport PR #28134 on branch v3.9.x (DOC: Minor improvements on quickstart) -* :ghpull:`28134`: DOC: Minor improvements on quickstart -* :ghpull:`28121`: Backport PR #28085 on branch v3.9.x (Clarify that the pgf backend is never actually used interactively.) -* :ghpull:`28120`: Backport PR #28102 on branch v3.9.x (Fix typo in color mapping documentation in quick_start.py) -* :ghpull:`28109`: Backport PR #28100 on branch v3.9.x (TST: wxcairo sometimes raises OSError on missing cairo libraries) -* :ghpull:`28100`: TST: wxcairo sometimes raises OSError on missing cairo libraries -* :ghpull:`28108`: Backport PR #28107 on branch v3.9.x ([DOC] Fix description in CapStyle example) -* :ghpull:`28107`: [DOC] Fix description in CapStyle example -* :ghpull:`28102`: Fix typo in color mapping documentation in quick_start.py -* :ghpull:`28095`: Backport PR #28094 on branch v3.9.x (DOC: exclude sphinx 7.3.*) -* :ghpull:`28081`: Backport PR #28078 on branch v3.9.x (Clarify that findfont & _find_fonts_by_props return paths.) -* :ghpull:`28080`: Backport PR #28077 on branch v3.9.x (Parent tk StringVar to the canvas widget, not to the toolbar.) -* :ghpull:`28092`: Backport PR #28032 on branch v3.9.x (FIX: ensure images are C order before passing to pillow) -* :ghpull:`28032`: FIX: ensure images are C order before passing to pillow -* :ghpull:`28088`: Backport PR #28087 on branch v3.9.x (Document Qt5 minimal version.) -* :ghpull:`28085`: Clarify that the pgf backend is never actually used interactively. -* :ghpull:`28078`: Clarify that findfont & _find_fonts_by_props return paths. -* :ghpull:`28077`: Parent tk StringVar to the canvas widget, not to the toolbar. -* :ghpull:`28062`: Backport PR #28056 on branch v3.9.x (Strip trailing spaces from log-formatter cursor output.) -* :ghpull:`28063`: Backport PR #28055 on branch v3.9.x (DOC: Improve inverted axis example) -* :ghpull:`28056`: Strip trailing spaces from log-formatter cursor output. -* :ghpull:`28049`: Backport PR #28036 on branch v3.9.x (BLD: Fetch version from setuptools_scm at build time) -* :ghpull:`28036`: BLD: Fetch version from setuptools_scm at build time -* :ghpull:`28038`: Backport PR #28023 on branch v3.9.x (ci: Update merge conflict labeler) -* :ghpull:`28023`: ci: Update merge conflict labeler -* :ghpull:`28035`: Backport PR #28026 on branch v3.9.x ([DOC] reshuffle of contributing) -* :ghpull:`28026`: [DOC] reshuffle of contributing -* :ghpull:`28024`: DOC: Rewrite "Work on an issue" section -* :ghpull:`28011`: DOC: Move bug reports and feature requests to top of contributing index -* :ghpull:`27747`: Move doc/users/installing/ to doc/install/ -* :ghpull:`27952`: ENH: Align titles -* :ghpull:`28017`: Merge up v3.8.4 -* :ghpull:`28014`: Improve timeline example. -* :ghpull:`28019`: DOC: correct path to mpl_toolkits reference images -* :ghpull:`26981`: Fixes Issue #26377 - Auto-escape % Symbol in Latex in pie labels -* :ghpull:`28007`: wx: Fix file extension for toolmanager-style toolbar -* :ghpull:`25556`: Display cursor coordinates for all axes twinned with the current one. -* :ghpull:`23597`: Always use PyQT/PySide6 for GitHub CI -* :ghpull:`28013`: Avoid plt.xticks/plt.yticks in gallery examples. -* :ghpull:`28006`: Fix deprecation warnings in ft2font extension -* :ghpull:`27723`: ci: Enable testing on M1 macOS -* :ghpull:`26375`: Add ``widths``, ``heights`` and ``angles`` setter to ``EllipseCollection`` -* :ghpull:`27999`: Remove documentation that some backends don't support hatching. -* :ghpull:`26710`: Add support for High DPI displays to wxAgg backend -* :ghpull:`27148`: Correctly treat pan/zoom events of overlapping axes. -* :ghpull:`27981`: DOC: Fix label type specification in parameter descriptions -* :ghpull:`27979`: Clarify error message for bad-dimensionality in pcolorfast(). -* :ghpull:`27962`: DOC: Document axes_grid1.Grid attributes -* :ghpull:`27968`: MNT: Remove remaining 3.7 deprecations -* :ghpull:`27965`: DOC: Rewrite the example illustrating bxp() -* :ghpull:`26453`: add documentation for reloading font cache -* :ghpull:`26131`: Tst/restore old tests -* :ghpull:`27730`: Add an rcparam for image.interpolation_stage. -* :ghpull:`27956`: Use PyOS_setsig in macos backend -* :ghpull:`27829`: Simplify color/marker disambiguation logic in _process_plot_format. -* :ghpull:`27840`: Add legend support for boxplots -* :ghpull:`27943`: Support Cn, n>9 in plot() shorthand format. -* :ghpull:`27950`: ci: Fix condition for publishing wheels -* :ghpull:`27909`: Add a note to pyplot docstrings referencing the corresponding object methods -* :ghpull:`27929`: DOC: Add summary lines to plot types -* :ghpull:`27915`: [BUG] Fix redirect-from Sphinx extension -* :ghpull:`27945`: DOC: Explain leading dot in object references -* :ghpull:`27947`: Update docs for ``FancyArrowPatch`` & ``Annotation`` to make it clear that ShrinkA/B parameters are in points and not fractional. -* :ghpull:`27944`: Bump the actions group with 2 updates -* :ghpull:`27932`: Fix pickling of make_axes_area_auto_adjustable'd axes. -* :ghpull:`26500`: closes #26477 ENH: Add interpolation_stage in qt figureoptions -* :ghpull:`27927`: Update docs -* :ghpull:`27916`: Revert renaming labels to tick_labels in boxplot_stats() -* :ghpull:`27931`: Highlight development_setup code snippets as bash, not python. -* :ghpull:`27856`: Support hatching in cairo backends. -* :ghpull:`27922`: Fix cbook style -* :ghpull:`27668`: MNT: prevent merging using labels + branch protection rules -* :ghpull:`27857`: Documentation edit for matshow function -* :ghpull:`27928`: DOC: Fix syntax for ToolBase.image docstring -* :ghpull:`27873`: Simplify the LineCollection example -* :ghpull:`27492`: Fix semantics of MEP22 image names. -* :ghpull:`27918`: Fix new flake8 errors from old merge -* :ghpull:`27874`: Modernize macosx backend a bit -* :ghpull:`25887`: Update ``_unpack_to_numpy`` function to convert JAX and PyTorch arrays to NumPy -* :ghpull:`27685`: Work around pyparsing diagnostic warnings -* :ghpull:`26594`: Added optional props argument to Lasso Widget __init__ to customize Lasso line -* :ghpull:`22761`: Add minor ticks on and off in Axis -* :ghpull:`22407`: Add ``set_XY`` and ``set_data`` to ``Quiver`` -* :ghpull:`27901`: Rename boxplot's tick label parameter -* :ghpull:`27883`: Fix build on older macOS deployment targets -* :ghpull:`27900`: Remove empty user guide tutorials page -* :ghpull:`27885`: Clean up headers in extensions -* :ghpull:`27910`: DOC: Fix dead link in README -* :ghpull:`26567`: Use SVG inheritance diagrams now that linking has been fixed -* :ghpull:`27899`: Merge up 3.8.x into main -* :ghpull:`27905`: Improved error message for malformed colors -* :ghpull:`27906`: Override open_group, close_group methods in PathEffectRenderer -* :ghpull:`27904`: FIX: Restore D213 in flake8 -* :ghpull:`27895`: Remove versions from sidebar in docs -* :ghpull:`27894`: Mark triangulation classes as final -* :ghpull:`27557`: Use :mpltype:``color`` for color types -* :ghpull:`27845`: Make sure custom alpha param does not change 'none' colors in a list of colors -* :ghpull:`27719`: Add BackendRegistry singleton class -* :ghpull:`27890`: DOC: State approximate documentation build time -* :ghpull:`27887`: BLD: Add a fallback URL for FreeType -* :ghpull:`25224`: Allow passing a transformation to secondary_xaxis/_yaxis -* :ghpull:`27886`: Fix devdocs version switcher -* :ghpull:`27884`: FIX: don't copy twice on RGB input -* :ghpull:`27087`: Convert path extension to pybind11 -* :ghpull:`27867`: DOC: Update some animation related topics -* :ghpull:`27848`: FIX: handle nans in RGBA input with ScalarMappables -* :ghpull:`27821`: BLD,Cygwin: Include Python.h first in various C++ files -* :ghpull:`27457`: TST: adding tests of current clear behavior on ticks -* :ghpull:`27872`: doc: add description of ``**kwargs`` usage to collections -* :ghpull:`27868`: Use pybind11 string formatter for exception messages -* :ghpull:`27862`: Add dtype/copy args to internal testing class -* :ghpull:`27658`: Bump pydata-sphinx-theme -* :ghpull:`27303`: FIX: also exclude np.nan in RGB(A) in color mapping -* :ghpull:`27860`: Bump the actions group with 2 updates -* :ghpull:`27869`: Correctly set temporary pdf/pgf backends -* :ghpull:`27850`: Deprecate ``plot_date`` -* :ghpull:`27815`: Add side option to violinplot -* :ghpull:`27836`: DOC: use ... for continuation prompt in docstrings -* :ghpull:`27819`: MNT: remove draw method args and kwargs -* :ghpull:`27813`: DOC: Update violinplot() docs -* :ghpull:`27698`: Add linting and validation of all YAML files -* :ghpull:`27811`: Fix Annulus width check -* :ghpull:`27667`: Change return type of ``ion`` and ``ioff`` to fix unbound variable errors with Pyright -* :ghpull:`27807`: Expand CI pytest reporting config to ignore xfails -* :ghpull:`27806`: Remove self._renderer from AnnotationBbox and ConnectionPatch -* :ghpull:`27799`: Clarify that set_ticks() affects major/minor ticks independently -* :ghpull:`27787`: Improve documentation on boxplot and violinplot -* :ghpull:`27800`: Deactivate sidebar for release notes -* :ghpull:`27798`: Fix sphinx-gallery CSS -* :ghpull:`27462`: DOC: clarify the default value of *radius* in Patch.contains_point -* :ghpull:`27565`: MNT: arghandling subplotspec -* :ghpull:`27796`: Make mypy a bit stricter -* :ghpull:`27767`: Update handling of sequence labels for plot -* :ghpull:`27795`: Add EffVer badge -* :ghpull:`27780`: Partly revert #27711 -* :ghpull:`27768`: MNT: deprecate draw method args and kwargs -* :ghpull:`27783`: Update README.md to fix citation link -* :ghpull:`27726`: TST: always set a (long) timeout for subprocess and always use our wrapper -* :ghpull:`27781`: Simplify example: Box plots with custom fill colors -* :ghpull:`27750`: Bump the actions group with 2 updates -* :ghpull:`27771`: Add marker-only and line+marker visuals to the plot() plot types -* :ghpull:`27764`: Increase size of legend in Legend guide example -* :ghpull:`26800`: Bump minimum NumPy version to 1.23 -* :ghpull:`27752`: Update some Meson internals -* :ghpull:`27702`: GOV: adopt EffVer -* :ghpull:`26965`: Removal of deprecated API cm -* :ghpull:`27758`: [Doc] Remove special casing for removed method -* :ghpull:`25815`: [TST] Make jpl units instantiated with datetimes consistent with mpl converters -* :ghpull:`27729`: DOC: Improve colormap normalization example -* :ghpull:`27732`: TST: Remove memory leak test -* :ghpull:`27733`: ci: Simplify CodeQL setup -* :ghpull:`27692`: Add method to update position of arrow patch -* :ghpull:`27736`: Fix incorrect API reference in docs -* :ghpull:`27731`: DOC: Create explicit rename legend entry section in guide -* :ghpull:`27560`: Moved /users/project to /doc/project -* :ghpull:`27728`: Simplify Figure._suplabels. -* :ghpull:`27715`: Bump the actions group with 3 updates -* :ghpull:`27711`: Fix boxplot legend entries part 2 -* :ghpull:`27696`: DOC: clean up automated tests section of workflow docs -* :ghpull:`27686`: Improve Locator docstrings -* :ghpull:`27704`: ci: Remove prerelease conditions from Azure Pipelines -* :ghpull:`27568`: Fix boxplot legend entries -* :ghpull:`27694`: MNT: fix labeller -* :ghpull:`26953`: MNT: test that table doesn't try to convert unitized data -* :ghpull:`27690`: Remove "Past versions" section from release notes -* :ghpull:`26926`: Closes #22011: Changes to SubFigures so it behaves like a regular artist -* :ghpull:`27469`: Fixed legend with legend location "best" when legend overlaps shaded area and text -* :ghpull:`27684`: Bump the actions group with 1 update -* :ghpull:`27665`: Axes.inset_axes - warning message removed -* :ghpull:`27688`: CI: skip code coverage upload on scheduled tests -* :ghpull:`27689`: ci: Don't include API/what's new notes in general doc labels -* :ghpull:`27640`: Add ``get_cursor_data`` to ``NonUniformImage`` -* :ghpull:`27676`: BLD: Downgrade FreeType to 2.6.1 on Windows ARM -* :ghpull:`27619`: Use GH action to install reviewdog -* :ghpull:`27552`: TST: Use importlib for importing in pytest -* :ghpull:`27650`: DOC: Added call out to API guidelines to contribute + small API guidelines reorg -* :ghpull:`27618`: Add option of running stubtest using tox -* :ghpull:`27656`: Bump the actions group with 1 update -* :ghpull:`27415`: Use class form of data classes -* :ghpull:`27649`: Check for latex binary before building docs -* :ghpull:`27641`: MNT: fix api changes link in PR template -* :ghpull:`27644`: ci: Fix mpl_toolkits label -* :ghpull:`27230`: Query macOS for available system fonts. -* :ghpull:`27643`: ci: Update nightly upload for artifacts v4 -* :ghpull:`27642`: Fix auto-labeler configuration -* :ghpull:`27639`: Doc: typo fix for #22699 -* :ghpull:`26978`: [pre-commit.ci] pre-commit autoupdate -* :ghpull:`27563`: Enable PyPI publishing from GitHub Actions -* :ghpull:`22699`: Proof of concept for adding kwdoc content to properties using a decorator -* :ghpull:`27633`: Auto-label PRs based on changed files -* :ghpull:`27607`: Error on bad input to hexbin extents -* :ghpull:`27629`: Don't run CI twice on dependabot branches -* :ghpull:`27562`: Avoid an extra copy/resample if imshow input has no alpha -* :ghpull:`27628`: Bump the actions group with 2 updates -* :ghpull:`27626`: CI: Group dependabot updates -* :ghpull:`27589`: Don't clip PowerNorm inputs < vmin -* :ghpull:`27613`: Fix marker validator with cycler (allow mix of classes) -* :ghpull:`27615`: MNT: add spaces to PR template -* :ghpull:`27614`: DOC: Updated link in annotation API docs to point to annotation user guide -* :ghpull:`27605`: Ignore masked values in boxplot -* :ghpull:`26884`: Remove deprecated code from _fontconfig_patterns -* :ghpull:`27602`: Let FormatStrFormatter respect axes.unicode_minus. -* :ghpull:`27601`: Clarify dollar_ticks example and FormatStrFormatter docs. -* :ghpull:`24834`: Deprecate apply_theta_transforms=True to PolarTransform -* :ghpull:`27591`: Use macOS instead of OSX in comments/docs -* :ghpull:`27577`: MNT: add the running version to pickle warning message -* :ghpull:`25191`: Deprecate 'prune' kwarg to MaxNLocator -* :ghpull:`27566`: DOC: changed tag ``plot type`` to ``plot-type`` -* :ghpull:`27105`: Use Axes instead of axes core library code -* :ghpull:`27575`: Add quotes round .[dev] in editable install command -* :ghpull:`27104`: Use Axes instead of axes in galleries -* :ghpull:`27373`: Transpose grid_finder tick representation. -* :ghpull:`27363`: ci: Improve coverage for compiled code -* :ghpull:`27200`: DOC: Add role for custom informal types like color -* :ghpull:`27548`: DOC: typo fix in contribute doc -* :ghpull:`27458`: Check if the mappable is in a different Figure than the one fig.color… -* :ghpull:`27546`: MNT: Clean up some style exceptions -* :ghpull:`27514`: Improve check for bbox -* :ghpull:`27265`: DOC: reorganizing contributing docs to clean up toc, better separate topics -* :ghpull:`27517`: Best-legend-location microoptimization -* :ghpull:`27540`: Bump github/codeql-action from 2 to 3 -* :ghpull:`27520`: [Doc] Minor consistency changes and correction of Marker docs -* :ghpull:`27505`: Download Qhull source from Github, not Qhull servers, in meson build -* :ghpull:`27518`: Micro-optimizations related to list handling -* :ghpull:`27495`: Bump actions/stale from 8 to 9 -* :ghpull:`27523`: Changes for stale GHA v9 -* :ghpull:`27519`: [Doc] Improve/correct docs for 3D -* :ghpull:`27447`: TST: Compress some hist geometry tests -* :ghpull:`27513`: Fix docs and add tests for transform and deprecate ``BboxTransformToMaxOnly`` -* :ghpull:`27511`: TST: Add tests for Affine2D -* :ghpull:`27424`: Added Axes.stairs test in test_datetime.py -* :ghpull:`27267`: Fix/restore secondary axis support for Transform-type functions -* :ghpull:`27013`: Add test_contour under test_datetime.py -* :ghpull:`27497`: Clarify that set_axisbelow doesn't move grids below images. -* :ghpull:`27498`: Remove unnecessary del local variables at end of Gcf.destroy. -* :ghpull:`27466`: Add test_eventplot to test_datetime.py -* :ghpull:`25905`: Use annotate coordinate systems to simplify label_subplots. -* :ghpull:`27471`: Doc: visualizing_tests and ``triage_tests`` tools -* :ghpull:`27474`: Added smoke test for Axes.matshow to test_datetime.py -* :ghpull:`27470`: Fix test visualization tool for non-PNG files -* :ghpull:`27426`: DOC: normalizing histograms -* :ghpull:`27452`: Cleanup unit_cube-methods -* :ghpull:`27431`: Added test for Axes.bar_label -* :ghpull:`26962`: Remove backend 3.7-deprecated API -* :ghpull:`27410`: Add test_vlines to test_datetime.py -* :ghpull:`27425`: Added test_fill_betweenx in test_datetime.py -* :ghpull:`27449`: Remove test_quiverkey from test_datetime.py -* :ghpull:`27427`: MNT/TST: remove xcorr and acorr from test_datetime -* :ghpull:`27390`: Add test_bxp in test_datetime.py -* :ghpull:`27428`: Added test for broken_barh to test_datetime.py -* :ghpull:`27222`: [TST] Added test_annotate in test_datetime.py -* :ghpull:`27135`: Added smoke test for Axes.stem -* :ghpull:`27343`: Fix draggable annotations on subfigures. -* :ghpull:`27033`: Add test_bar in test_datetime -* :ghpull:`27423`: Add test for fill_between in test_datetime.py -* :ghpull:`27409`: Fix setting ``_selection_completed`` in ``SpanSelector`` when spanselector is initialised using ``extents`` -* :ghpull:`27440`: Fix get_path for 3d artists -* :ghpull:`27422`: TST: Cache available interactive backends -* :ghpull:`27401`: Add test_fill in test_datetime.py -* :ghpull:`27419`: DOC: Add AsinhScale to list of built-in scales -* :ghpull:`27417`: Switch pytest fixture from tmpdir to tmp_path -* :ghpull:`27172`: ENH: Change logging to warning when creating a legend with no labels -* :ghpull:`27405`: Check that xerr/yerr values are not None in errorbar -* :ghpull:`27392`: Remove test_spy from test_datetime.py -* :ghpull:`27331`: Added smoke test for Axes.barbs in test_datetime.py -* :ghpull:`27393`: MNT: Fix doc makefiles -* :ghpull:`27387`: Revert "MNT: add _version.py to .gitignore" -* :ghpull:`27347`: FIX: scale norm of collections when first array is set -* :ghpull:`27374`: MNT: add _version.py to .gitignore -* :ghpull:`19011`: Simplify tk tooltip setup. -* :ghpull:`27367`: Fix _find_fonts_by_props docstring -* :ghpull:`27359`: Fix build on PyPy -* :ghpull:`27362`: Implement SubFigure.remove. -* :ghpull:`27360`: Fix removal of colorbars on nested subgridspecs. -* :ghpull:`27211`: Add test_hlines to test_datetimes.py -* :ghpull:`27353`: Refactor AxisArtistHelpers -* :ghpull:`27357`: [DOC]: Update 3d axis limits what's new -* :ghpull:`26992`: Convert TkAgg utilities to pybind11 -* :ghpull:`27215`: Add ``@QtCore.Slot()`` decorations to ``NavigationToolbar2QT`` -* :ghpull:`26907`: Removal of deprecations for Contour -* :ghpull:`27285`: Factor out common parts of qt and macos interrupt handling. -* :ghpull:`27306`: Simplify GridSpec setup in make_axes_gridspec. -* :ghpull:`27313`: FIX: allow re-shown Qt windows to be re-destroyed -* :ghpull:`27184`: Use pybind11 for qhull wrapper -* :ghpull:`26794`: Use pybind11 in _c_internal_utils module -* :ghpull:`27300`: Remove idiosyncratic get_tick_iterator API. -* :ghpull:`27275`: MAINT: fix .yml in tag issue template -* :ghpull:`27288`: Use int.from_bytes instead of implementing the conversion ourselves. -* :ghpull:`27286`: Various cleanups -* :ghpull:`27279`: Tweak a few docstrings. -* :ghpull:`27256`: merge up v3.8.1 -* :ghpull:`27254`: Remove redundant axes_grid colorbar examples. -* :ghpull:`27251`: webagg: Don't resize canvas if WebSocket isn't connected -* :ghpull:`27236`: Tagging Example - Tags for multiple figs demo -* :ghpull:`27245`: MNT: be more careful in Qt backend that there is actually a Figure -* :ghpull:`27158`: First attempt for individual hatching styles for stackplot -* :ghpull:`26851`: Establish draft Tag glossary and Tagging guidelines -* :ghpull:`27083`: DOC: Add tags infrastructure for gallery examples -* :ghpull:`27204`: BLD: Use NumPy nightly wheels for non-release builds -* :ghpull:`27208`: Add test_axvline to test_datetime.py -* :ghpull:`26989`: MNT: print fontname in missing glyph warning -* :ghpull:`27177`: Add test_axhline in test_datetime.py -* :ghpull:`27164`: docs: adding explanation for color in ``set_facecolor`` -* :ghpull:`27175`: Deprecate mixing positional and keyword args for legend(handles, labels) -* :ghpull:`27199`: DOC: clean up links under table formatting docs -* :ghpull:`27185`: Added smoke tests for Axes.errorbar in test_datetime.py -* :ghpull:`27091`: Add test_step to test_datetime.py -* :ghpull:`27182`: Add example for plotting a bihistogram -* :ghpull:`27130`: added test_axvspan in test.datetime.py -* :ghpull:`27094`: MNT: move pytest.ini configs to .toml -* :ghpull:`27139`: added test_axhspan in test_datetime.py -* :ghpull:`27058`: DOC: concise dependency heading + small clarifications -* :ghpull:`27053`: Added info for getting compilation output from meson on autorebuild -* :ghpull:`26906`: Fix masking for Axes3D.plot() -* :ghpull:`27142`: Added smoke test for Axes.text in test_datetime.py -* :ghpull:`27024`: Add test_contourf in test_datetime.py -* :ghpull:`22347`: correctly treat pan/zoom events of overlapping axes -* :ghpull:`26900`: #26865 removing deprecations to axislines.py -* :ghpull:`26696`: DOC: Fix colLoc default -* :ghpull:`27064`: Close all plot windows of a blocking show() on Ctrl+C -* :ghpull:`26882`: Add scatter test for datetime units -* :ghpull:`27114`: add test_stackplot in test_datetime.py -* :ghpull:`27084`: Add test_barh to test_datetime.py -* :ghpull:`27110`: DOC: Move figure member sections one level down -* :ghpull:`27127`: BLD: use python3 for shebang consistent with pep-394 -* :ghpull:`27111`: BLD: Fix setting FreeType build type in extension -* :ghpull:`26921`: MNT: clarify path.sketch rcparam format + test validate_sketch -* :ghpull:`27109`: TST: Use importlib for subprocess tests -* :ghpull:`27119`: Update clabel comment. -* :ghpull:`27117`: Remove datetime test for axes.pie -* :ghpull:`27095`: Deprecate nth_coord parameter from FixedAxisArtistHelper.new_fixed_axis. -* :ghpull:`27066`: Tweak array_view to be more like pybind11 -* :ghpull:`27090`: Restore figaspect() API documentation -* :ghpull:`27074`: Issue #26990: Split the histogram image into two for each code block. -* :ghpull:`27086`: Rename py namespace to mpl in extension code -* :ghpull:`27082`: MAINT: Update environment.yml to match requirements files -* :ghpull:`27072`: Remove datetime test stubs for spectral methods/table -* :ghpull:`26830`: Update stix table with Unicode names -* :ghpull:`26969`: DOC: add units to user/explain [ci doc] -* :ghpull:`27028`: Added test_hist in test_datetime.py -* :ghpull:`26876`: issue: 26871 - Remove SimplePath class from patches.py -* :ghpull:`26875`: Fix Deprecation in patches.py -* :ghpull:`26890`: Removing deprecated api from patches -* :ghpull:`27037`: add test_plot_date in test_datetime.py -* :ghpull:`27012`: Bump required C++ standard to c++17 -* :ghpull:`27021`: Add a section to Highlight past winners for JDH plotting contest in docs -* :ghpull:`27004`: Warning if handles and labels have a len mismatch -* :ghpull:`24061`: #24050 No error was thrown even number of handles mismatched labels -* :ghpull:`26754`: DOC: separate and clarify axisartist default tables -* :ghpull:`27020`: CI: Update scientific-python/upload-nightly-action to 0.2.0 -* :ghpull:`26951`: Clarify that explicit ticklabels are used without further formatting. -* :ghpull:`26894`: Deprecate setting the timer interval while starting it. -* :ghpull:`13401`: New clear() method for Radio and Check buttons -* :ghpull:`23829`: Start transitioning to pyproject.toml -* :ghpull:`26621`: Port build system to Meson -* :ghpull:`26928`: [TYP] Add tool for running stubtest -* :ghpull:`26917`: Deprecate ContourLabeler.add_label_clabeltext. -* :ghpull:`26960`: Deprecate backend_ps.get_bbox_header, and split it for internal use. -* :ghpull:`26967`: Minor cleanups. -* :ghpull:`26909`: deprecated api tri -* :ghpull:`26946`: Inline Cursor._update into its sole caller. -* :ghpull:`26915`: DOC: Clarify description and add examples in colors.Normalize -* :ghpull:`26874`: Cleaned up the span_where class method from Polycollections. -* :ghpull:`26586`: Support standard formatters in axisartist. -* :ghpull:`26788`: Fix axh{line,span} on polar axes. -* :ghpull:`26935`: add tomli to rstcheck extras -* :ghpull:`26275`: Use pybind11 in image module -* :ghpull:`26887`: DOC: improve removal for julian dates [ci doc] -* :ghpull:`26929`: DOC: Fix removal doc for Animation attributes -* :ghpull:`26918`: 26865 Removed deprecations from quiver.py -* :ghpull:`26902`: Fixed deprecated APIs in lines.py -* :ghpull:`26903`: Simplify CheckButtons and RadioButtons click handler. -* :ghpull:`26899`: MNT: only account for Artists once in fig.get_tightbbox -* :ghpull:`26861`: QT/NavigationToolbar2: configure subplots dialog should be modal -* :ghpull:`26885`: Removed deprecated code from gridspec.py -* :ghpull:`26880`: Updated offsetbox.py -* :ghpull:`26910`: Removed the deprecated code from offsetbox.py -* :ghpull:`26905`: Add users/explain to default skip subdirs -* :ghpull:`26853`: Widgets: Remove deprecations and make arguments keyword only -* :ghpull:`26877`: Fixes deprecation in lines.py -* :ghpull:`26871`: Removed the deprecated code from ``axis.py`` -* :ghpull:`26872`: Deprecated code removed in animation.py -* :ghpull:`26859`: Add datetime testing skeleton -* :ghpull:`26848`: ci: Don't install recommended packages on Circle -* :ghpull:`26852`: Remove Julian date support -* :ghpull:`26801`: [MNT]: Cleanup ticklabel_format (style=) -* :ghpull:`26840`: Reduce redundant information in _process_plot_var_args. -* :ghpull:`26731`: Explicitly set foreground color to black in svg icons -* :ghpull:`26826`: [MNT] Move NUM_VERTICES from mplutils.h to the only file it is used in -* :ghpull:`26742`: [TYP] Add typing for some private methods and modules -* :ghpull:`26819`: Reorder safe_first_element() and _safe_first_finite() code -* :ghpull:`26813`: Bump docker/setup-qemu-action from 2 to 3 -* :ghpull:`26797`: Remove deprecated draw_gouraud_triangle -* :ghpull:`26815`: Remove plt.Axes from tests -* :ghpull:`26818`: Fix doc build (alternative) -* :ghpull:`26785`: merge up v3.8.0 -* :ghpull:`25272`: Do not add padding to 3D axis limits when limits are manually set -* :ghpull:`26798`: Remove deprecated methods and attributed in Axes3D -* :ghpull:`26744`: Use cbook methods for string checking -* :ghpull:`26802`: specify input range in logs when image data must be clipped -* :ghpull:`26787`: Remove unused Axis private init helpers. -* :ghpull:`26629`: DOC: organize figure API -* :ghpull:`26690`: Make generated pgf code more robust against later changes of tex engine. -* :ghpull:`26577`: Bugfix: data sanitizing for barh -* :ghpull:`26684`: Update PR template doc links -* :ghpull:`26686`: PR template: shorten comment and pull up top -* :ghpull:`26670`: Added sanitize_sequence to kwargs in _preprocess_data -* :ghpull:`26634`: [MNT] Move SubplotParams from figure to gridspec -* :ghpull:`26609`: Cleanup AutoMinorLocator implementation. -* :ghpull:`26293`: Added get_xmargin(), get_ymargin() and get_zmargin() and tests. -* :ghpull:`26516`: Replace reference to %pylab by %matplotlib. -* :ghpull:`26483`: Improve legend(loc='best') warning and test -* :ghpull:`26482`: [DOC]: print pydata sphinx/mpl theme versions -* :ghpull:`23787`: Use pybind11 for C/C++ extensions +* :ghpull:`28507`: Backport PR #28430 on branch v3.9.x (Fix pickling of AxesWidgets.) +* :ghpull:`28506`: Backport PR #28451 on branch v3.9.x (Fix GTK cairo backends) +* :ghpull:`28430`: Fix pickling of AxesWidgets. +* :ghpull:`25861`: Fix Hidpi scaling for GTK4Cairo +* :ghpull:`28451`: Fix GTK cairo backends +* :ghpull:`28499`: Backport PR #28498 on branch v3.9.x (Don't fail if we can't query system fonts on macOS) +* :ghpull:`28498`: Don't fail if we can't query system fonts on macOS +* :ghpull:`28491`: Backport PR #28487 on branch v3.9.x (Fix autoscaling with axhspan) +* :ghpull:`28490`: Backport PR #28486 on branch v3.9.x (Fix CompositeGenericTransform.contains_branch_seperately) +* :ghpull:`28487`: Fix autoscaling with axhspan +* :ghpull:`28486`: Fix CompositeGenericTransform.contains_branch_seperately +* :ghpull:`28483`: Backport PR #28393 on branch v3.9.x (Make sticky edges only apply if the sticky edge is the most extreme limit point) +* :ghpull:`28482`: Backport PR #28473 on branch v3.9.x (Do not lowercase module:// backends) +* :ghpull:`28393`: Make sticky edges only apply if the sticky edge is the most extreme limit point +* :ghpull:`28473`: Do not lowercase module:// backends +* :ghpull:`28480`: Backport PR #28474 on branch v3.9.x (Fix typing and docs for containers) +* :ghpull:`28479`: Backport PR #28397 (FIX: stale root Figure when adding/updating subfigures) +* :ghpull:`28474`: Fix typing and docs for containers +* :ghpull:`28472`: Backport PR #28289 on branch v3.9.x (Promote mpltype Sphinx role to a public extension) +* :ghpull:`28471`: Backport PR #28342 on branch v3.9.x (DOC: Document the parameter *position* of apply_aspect() as internal) +* :ghpull:`28470`: Backport PR #28398 on branch v3.9.x (Add GIL Release to flush_events in macosx backend) +* :ghpull:`28469`: Backport PR #28355 on branch v3.9.x (MNT: Re-add matplotlib.cm.get_cmap) +* :ghpull:`28397`: FIX: stale root Figure when adding/updating subfigures +* :ghpull:`28289`: Promote mpltype Sphinx role to a public extension +* :ghpull:`28342`: DOC: Document the parameter *position* of apply_aspect() as internal +* :ghpull:`28398`: Add GIL Release to flush_events in macosx backend +* :ghpull:`28355`: MNT: Re-add matplotlib.cm.get_cmap +* :ghpull:`28468`: Backport PR #28465 on branch v3.9.x (Fix pickling of SubFigures) +* :ghpull:`28465`: Fix pickling of SubFigures +* :ghpull:`28462`: Backport PR #28440 on branch v3.9.x (DOC: Add note about simplification of to_polygons) +* :ghpull:`28460`: Backport PR #28459 on branch v3.9.x (DOC: Document kwargs scope for tick setter functions) +* :ghpull:`28461`: Backport PR #28458 on branch v3.9.x (Correct numpy dtype comparisons in image_resample) +* :ghpull:`28440`: DOC: Add note about simplification of to_polygons +* :ghpull:`28458`: Correct numpy dtype comparisons in image_resample +* :ghpull:`28459`: DOC: Document kwargs scope for tick setter functions +* :ghpull:`28450`: Backport of 28371 and 28411 +* :ghpull:`28446`: Backport PR #28403 on branch v3.9.x (FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection +* :ghpull:`28445`: Backport PR #28403 on branch v3.9.x (FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection) +* :ghpull:`28438`: Backport PR #28436 on branch v3.9.x (Fix ``is_color_like`` for 2-tuple of strings and fix ``to_rgba`` for ``(nth_color, alpha)``) +* :ghpull:`28403`: FIX: Autoscale support in add_collection3d for Line3DCollection and Poly3DCollection +* :ghpull:`28443`: Backport PR #28441 on branch v3.9.x (MNT: Update basic units example to work with numpy 2.0) +* :ghpull:`28441`: MNT: Update basic units example to work with numpy 2.0 +* :ghpull:`28436`: Fix ``is_color_like`` for 2-tuple of strings and fix ``to_rgba`` for ``(nth_color, alpha)`` +* :ghpull:`28426`: Backport PR #28425 on branch v3.9.x (Fix Circle yaml line length) +* :ghpull:`28427`: Fix circleci yaml +* :ghpull:`28425`: Fix Circle yaml line length +* :ghpull:`28422`: Backport PR #28401 on branch v3.9.x (FIX: Fix text wrapping) +* :ghpull:`28424`: Backport PR #28423 on branch v3.9.x (Update return type for Axes.axhspan and Axes.axvspan) +* :ghpull:`28423`: Update return type for Axes.axhspan and Axes.axvspan +* :ghpull:`28401`: FIX: Fix text wrapping +* :ghpull:`28419`: Backport PR #28414 on branch v3.9.x (Clean up obsolete widget code) +* :ghpull:`28411`: Bump the actions group with 3 updates +* :ghpull:`28414`: Clean up obsolete widget code +* :ghpull:`28415`: Backport PR #28413 on branch v3.9.x (CI: update action that got moved org) +* :ghpull:`28413`: CI: update action that got moved org +* :ghpull:`28392`: Backport PR #28388 on branch v3.9.x (Allow duplicate (name, value) entry points for backends) +* :ghpull:`28362`: Backport PR #28337 on branch v3.9.x (Bump the actions group across 1 directory with 3 updates) +* :ghpull:`28388`: Allow duplicate (name, value) entry points for backends +* :ghpull:`28389`: Backport PR #28380 on branch v3.9.x (Remove outdated docstring section in RendererBase.draw_text.) +* :ghpull:`28380`: Remove outdated docstring section in RendererBase.draw_text. +* :ghpull:`28385`: Backport PR #28377 on branch v3.9.x (DOC: Clarify scope of wrap.) +* :ghpull:`28377`: DOC: Clarify scope of wrap. +* :ghpull:`28368`: Backport PR #28359 on branch v3.9.x (Document that axes unsharing is impossible.) +* :ghpull:`28359`: Document that axes unsharing is impossible. +* :ghpull:`28337`: Bump the actions group across 1 directory with 3 updates +* :ghpull:`28351`: Backport PR #28307 on branch v3.9.x (DOC: New color line by value example) +* :ghpull:`28307`: DOC: New color line by value example +* :ghpull:`28339`: Backport PR #28336 on branch v3.9.x (DOC: Add version warning banner for docs versions different from stable) +* :ghpull:`28336`: DOC: Add version warning banner for docs versions different from stable +* :ghpull:`28334`: Backport PR #28332 on branch v3.9.x (Call IPython.enable_gui when install repl displayhook) +* :ghpull:`28332`: Call IPython.enable_gui when install repl displayhook +* :ghpull:`28331`: Backport PR #28329 on branch v3.9.x (DOC: Add example for 3D intersecting planes) +* :ghpull:`28329`: DOC: Add example for 3D intersecting planes +* :ghpull:`28327`: Backport PR #28292 on branch v3.9.x (Resolve MaxNLocator IndexError when no large steps) +* :ghpull:`28292`: Resolve MaxNLocator IndexError when no large steps +* :ghpull:`28326`: Backport PR #28041 on branch v3.9.x ([BUG]: Shift box_aspect according to vertical_axis) +* :ghpull:`28041`: [BUG]: Shift box_aspect according to vertical_axis +* :ghpull:`28320`: Backport PR #27001 on branch v3.9.x ([TYP] Add overload of ``pyplot.subplots``) +* :ghpull:`27001`: [TYP] Add overload of ``pyplot.subplots`` +* :ghpull:`28318`: Backport PR #28273 on branch v3.9.x (CI: Add GitHub artifact attestations to package distribution) +* :ghpull:`28273`: CI: Add GitHub artifact attestations to package distribution +* :ghpull:`28305`: Backport PR #28303 on branch v3.9.x (Removed drawedges repeated definition from function doc string) +* :ghpull:`28303`: Removed drawedges repeated definition from function doc string +* :ghpull:`28299`: Backport PR #28297 on branch v3.9.x (Solved #28296 Added missing comma) +* :ghpull:`28297`: Solved #28296 Added missing comma +* :ghpull:`28294`: Backport PR #28261 on branch v3.9.x (Correct roll angle units, issue #28256) +* :ghpull:`28261`: Correct roll angle units, issue #28256 +* :ghpull:`28283`: Backport PR #28280 on branch v3.9.x (DOC: Add an example for 2D images in 3D plots) +* :ghpull:`28280`: DOC: Add an example for 2D images in 3D plots +* :ghpull:`28278`: Backport PR #28272 on branch v3.9.x (BLD: Move macos builders from 11 to 12) +* :ghpull:`28277`: Backport PR #28274 on branch v3.9.x (ci: Remove deprecated codeql option) +* :ghpull:`28272`: BLD: Move macos builders from 11 to 12 +* :ghpull:`28274`: ci: Remove deprecated codeql option +* :ghpull:`28270`: Backport PR #28269 on branch v3.9.x (Handle GetForegroundWindow() returning NULL.) +* :ghpull:`28269`: Handle GetForegroundWindow() returning NULL. +* :ghpull:`28266`: Backport PR #28257 on branch v3.9.x (Clean up some Meson-related leftovers) +* :ghpull:`28257`: Clean up some Meson-related leftovers +* :ghpull:`28255`: Backport PR #28254 on branch v3.9.x ([DOC] plot type heading consistency) +* :ghpull:`28254`: [DOC] plot type heading consistency +* :ghpull:`28253`: Backport PR #28252 on branch v3.9.x (DOC: Flip the imshow plot types example to match the other examples) +* :ghpull:`28252`: DOC: Flip the imshow plot types example to match the other examples +* :ghpull:`28247`: Backport PR #28230 on branch v3.9.x (Add extra imports to improve typing) +* :ghpull:`28230`: Add extra imports to improve typing +* :ghpull:`28246`: Backport PR #28243 on branch v3.9.x (DOC: Add more 3D plot types) +* :ghpull:`28243`: DOC: Add more 3D plot types +* :ghpull:`28241`: Backport PR #28219 on branch v3.9.x (Bump the actions group with 2 updates) +* :ghpull:`28219`: Bump the actions group with 2 updates +* :ghpull:`28237`: Backport PR #28233 on branch v3.9.x (CI: Fix font install on macOS/Homebrew) +* :ghpull:`28236`: Backport PR #28231 on branch v3.9.x (DOC: we do not need the blit call in on_draw) +* :ghpull:`28233`: CI: Fix font install on macOS/Homebrew +* :ghpull:`28231`: DOC: we do not need the blit call in on_draw -Issues (97): +Issues (30): -* :ghissue:`28202`: [Bug]: Qt test_ipython fails on older ipython -* :ghissue:`28145`: [TST] Upcoming dependency test failures -* :ghissue:`28034`: [TST] Upcoming dependency test failures -* :ghissue:`28168`: [TST] Upcoming dependency test failures -* :ghissue:`28040`: [Bug]: vertical_axis not respected when rotating plots interactively -* :ghissue:`28146`: [Bug]: Useless recursive group in SVG output when using path_effects -* :ghissue:`28067`: [Bug]: ``LinearSegmentedColormap.from_list`` does not have all type hints for argument ``colors`` -* :ghissue:`26778`: [MNT]: Numpy 2.0 support strategy -* :ghissue:`28020`: [Bug]: imsave fails on RGBA data when origin is set to lower -* :ghissue:`7720`: WXAgg backend not rendering nicely on retina -* :ghissue:`28069`: [Bug]: Can't save with custom toolbar -* :ghissue:`28005`: [Doc]: Improve contribute instructions -* :ghissue:`22376`: [ENH]: align_titles -* :ghissue:`5506`: Confusing status bar values in presence of multiple axes -* :ghissue:`4284`: Twin axis message coordinates -* :ghissue:`18940`: WxAgg backend draws the wrong size when wxpython app is high DPI aware on Windows -* :ghissue:`27792`: [ENH]: Legend entries for boxplot -* :ghissue:`27828`: [Bug]: ".C10" does not work as plot shorthand format spec -* :ghissue:`27911`: redirect not working for updated contribute page -* :ghissue:`21876`: [Doc]: redirect-from directive appears broken? -* :ghissue:`27941`: [Bug]: ShrinkA and ShrinkB are ignored in ax.annotate(arrowprops=...) -* :ghissue:`26477`: [ENH]: Add interpolation_stage selector for images in qt figureoptions -* :ghissue:`363`: Enable hatches for Cairo backend -* :ghissue:`27852`: [Bug]: matplotlib.pyplot.matshow "(first dimension of the array) are displayed horizontally" but are displayed vertically -* :ghissue:`27400`: [Bug]: tk backend confused by presence of file named "move" in current working directory -* :ghissue:`25882`: [Bug]: plt.hist takes significantly more time with torch and jax arrays -* :ghissue:`25204`: [Bug]: Pyparsing warnings emitted in mathtext -* :ghissue:`17707`: getpwuid(): uid not found: 99 -* :ghissue:`27896`: [Doc]: Empty "User guide tutorials page" in docs -* :ghissue:`27824`: [Bug]: polygon from axvspan not correct in polar plot after set_xy -* :ghissue:`27378`: [ENH]: Suggest 'CN' if color is an integer -* :ghissue:`27843`: [Bug]: close_group is not called when using patheffects -* :ghissue:`27839`: [Bug]: PathCollection using alpha ignores 'none' facecolors -* :ghissue:`25119`: [ENH]: secondary_x/yaxis accept transform argument -* :ghissue:`27876`: [Doc]: Fix version switcher in devdocs -* :ghissue:`27301`: [Bug]: ``imshow`` allows RGB(A) images with ``np.nan`` values to pass -* :ghissue:`23839`: [MNT]: Add tests to codify ``ax.clear`` -* :ghissue:`27652`: [Doc]: Low contrast on clicked links in dark mode -* :ghissue:`27865`: [Bug]: Zoom und pan not working after writing pdf pages. -* :ghissue:`25871`: [Bug]: Colorbar cannot be added to another figure -* :ghissue:`8072`: plot_date() ignores timezone in matplotlib version 2.0.0 -* :ghissue:`27812`: [ENH]: Add split feature for violin plots -* :ghissue:`27659`: [MNT]: Improve return type of ``ioff`` and ``ion`` to improve Pyright analysis of bound variables -* :ghissue:`27805`: [Bug]: Saving a figure with indicate_inset_zoom to pdf and then pickling it causes TypeError -* :ghissue:`27701`: [Bug]: axis set_xscale('log') interferes with set_xticks -* :ghissue:`19807`: radius modification in contains_point function when linewidth is specified -* :ghissue:`27762`: [Bug]: Inconsistent treatment of list of labels in ``plot`` when the input is a dataframe -* :ghissue:`27745`: [MNT]: ``_ImageBase.draw`` and ``Axis.draw`` args and kwargs -* :ghissue:`27782`: [Doc]: Link to citation page in read me broken -* :ghissue:`8789`: legend handle size does not automatically scale with linewidth -* :ghissue:`27746`: [Doc]: Citation link in the readme.md points to 404 -* :ghissue:`20853`: Add deprecation for colormaps -* :ghissue:`26865`: [MNT]: Remove 3.7-deprecated API -* :ghissue:`24168`: [Bug]: ``subprocess-exited-with-error`` when trying to build on M1 mac -* :ghissue:`27727`: [Doc]: Text in the colormap normalization gallery doesn't match the code -* :ghissue:`27635`: [Bug]: test_figure_leak_20490 repeatedly failing on CI -* :ghissue:`14217`: [Feature request] Add a way to update the position of the Arrow patch. -* :ghissue:`20512`: Bad boxplot legend entries -* :ghissue:`22011`: [Bug]: subfigures messes up with fig.legend zorder -* :ghissue:`27414`: [Bug]: Legend overlaps shaded area in fill_between with legend location "best" -* :ghissue:`23323`: Legend with "loc=best" does not try to avoid text -* :ghissue:`27648`: [Doc]: ``Axes.inset_axes`` is still experimental -* :ghissue:`27277`: [Doc]: Two license pages in docs -* :ghissue:`24648`: [Doc]: make html fail early if latex not present -* :ghissue:`27554`: [Bug]: Large image draw performance deterioration in recent releases -* :ghissue:`25239`: [Bug]: colors.PowerNorm results in incorrect colorbar -* :ghissue:`13533`: Boxplotting Masked Arrays -* :ghissue:`25967`: [Doc]: dollar_ticks example refers to unused formatter classes -* :ghissue:`24859`: [Doc]: Document color in a consistent way, including link -* :ghissue:`27159`: [Bug]: Meson build fails due to qhull link issue. -* :ghissue:`25691`: [Bug]: Secondary axis does not support Transform as functions -* :ghissue:`25860`: [Bug]: canvas pick events not working when Axes belongs to a subfigure -* :ghissue:`27361`: [Bug]: (Tight) layout engine breaks for 3D patches -* :ghissue:`27145`: [ENH]: Make "No artists with labels found to put in legend" a warning -* :ghissue:`27399`: [Bug]: None in y or yerr arrays leads to TypeError when using errorbar -* :ghissue:`13887`: Accessing default ``norm`` of a Collection removes its colors. -* :ghissue:`26593`: [ENH]: Support SubFigure.remove() -* :ghissue:`27329`: [Bug]: Removing a colorbar for an axes positioned in a subgridspec restores the axes' position to the wrong place. -* :ghissue:`27214`: [Bug]: ``NavigationToolbar2QT`` should use ``@Slot`` annotation -* :ghissue:`27146`: [ENH]: Multi hatching in ``ax.stackplot()`` -* :ghissue:`27168`: [Doc]: Instructions for editable installation on Windows potentially missing a step -* :ghissue:`27174`: [MNT]: Build nightly wheels with NumPy nightly wheels -* :ghissue:`25043`: [ENH]: Plotting masked arrays correctly in 3D line plot -* :ghissue:`26990`: [Doc]: Histogram path example renders poorly in HTML -* :ghissue:`25738`: [MNT]: Improve readability of _mathtext_data.stix_virtual_fonts table -* :ghissue:`11129`: Highlight past winners for JDH plotting contest in docs -* :ghissue:`24050`: No error message in matplotlib.axes.Axes.legend() if there are more labels than handles -* :ghissue:`10922`: ENH: clear() method for widgets.RadioButtons -* :ghissue:`18295`: How to modify ticklabels in axisartist? -* :ghissue:`24996`: [Bug]: for non-rectilinear axes, axvline/axhline should behave as "draw a gridline at that x/y" -* :ghissue:`26841`: [Bug]: Global legend weird behaviors -* :ghissue:`25974`: [MNT]: Cleanup ticklabel_format(..., style=) -* :ghissue:`26786`: Please upload new dev wheel so we pick up 3.9.dev after 3.8 release -* :ghissue:`18052`: the limits of axes are inexact with mplot3d -* :ghissue:`25596`: [MNT]: Consistency on Interface -* :ghissue:`26557`: [ENH]: Nightly Python 3.12 builds -* :ghissue:`26281`: [ENH]: Add get_xmargin, get_ymargin, get_zmargin axes methods +* :ghissue:`22482`: [ENH]: pickle (or save) matplotlib figure with insteractive slider +* :ghissue:`25847`: [Bug]: Graph gets cut off with scaled resolution using gtk4cairo backend +* :ghissue:`28341`: [Bug]: Incorrect X-axis scaling with date values +* :ghissue:`28383`: [Bug]: axvspan no longer participating in limit calculations +* :ghissue:`28223`: [Bug]: Inconsistent Visualization of Intervals in ax.barh for Different Duration Widths +* :ghissue:`28432`: [Bug]: Backend name specified as module gets lowercased since 3.9 +* :ghissue:`28467`: [Bug]: Incorrect type stub for ``ErrorbarContainer``'s ``lines`` attribute. +* :ghissue:`28384`: [Bug]: subfigure artists not drawn interactively +* :ghissue:`28234`: [Bug]: mpltype custom role breaks sphinx build for third-party projects that have intersphinx links to matplotlib +* :ghissue:`28464`: [Bug]: figure with subfigures cannot be pickled +* :ghissue:`28448`: [Bug]: Making an RGB image from pickled data throws error +* :ghissue:`23317`: [Bug]: ``add_collection3d`` does not update view limits +* :ghissue:`17130`: autoscale_view is not working with Line3DCollection +* :ghissue:`28434`: [Bug]: Setting exactly 2 colors with tuple in ``plot`` method gives confusing error +* :ghissue:`28417`: [Doc]: axhspan and axvspan now return Rectangles, not Polygons. +* :ghissue:`28378`: [ENH]: Switch text wrapping boundary to subfigure +* :ghissue:`28404`: [Doc]: matplotlib.widgets.CheckButtons no longer has .rectangles attribute, needs removed. +* :ghissue:`28367`: [Bug]: Backend entry points can be erroneously duplicated +* :ghissue:`28358`: [Bug]: Labels don't get wrapped when set_yticks() is used in subplots +* :ghissue:`28374`: [Bug]: rcParam ``tk.window_focus: True`` is causes crash on Linux in version 3.9.0. +* :ghissue:`28324`: [Bug]: show(block=False) freezes +* :ghissue:`28239`: [Doc]: Gallery example showing 3D slice planes +* :ghissue:`27603`: [Bug]: _raw_ticker() istep +* :ghissue:`24328`: [Bug]: class Axes3D.set_box_aspect() sets wrong aspect ratios when Axes3D.view_init( vertical_axis='y') is enabled. +* :ghissue:`28221`: [Doc]: drawedges attribute described twice in matplotlib.colorbar documentation +* :ghissue:`28296`: [Doc]: Missing comma +* :ghissue:`28256`: [Bug]: axes3d.py's _on_move() converts the roll angle to radians, but then passes it to view_init() as if it were still in degrees +* :ghissue:`28267`: [Bug]: for Python 3.11.9 gor error ValueError: PyCapsule_New called with null pointer +* :ghissue:`28022`: [Bug]: Type of Axes is unknown pyright +* :ghissue:`28002`: Segfault from path editor example with QtAgg Previous GitHub statistics diff --git a/doc/users/prev_whats_new/github_stats_3.9.0.rst b/doc/users/prev_whats_new/github_stats_3.9.0.rst new file mode 100644 index 000000000000..b1d229ffbfa1 --- /dev/null +++ b/doc/users/prev_whats_new/github_stats_3.9.0.rst @@ -0,0 +1,744 @@ +.. _github-stats-3-9.0: + +GitHub statistics for 3.9.0 (May 15, 2024) +========================================== + +GitHub statistics for 2023/09/15 (tag: v3.8.0) - 2024/05/15 + +These lists are automatically generated, and may be incomplete or contain duplicates. + +We closed 97 issues and merged 450 pull requests. +The full list can be seen `on GitHub `__ + +The following 175 authors contributed 2584 commits. + +* 0taj +* Abdul Razak Taha +* Adam J. Stewart +* Adam Turner +* Aditi Gautam +* agautam478 +* Alan Lau +* Albert Y. Shih +* Alec Vercruysse +* Alexander Volkov +* Alice Descoeudres +* Allan Haldane +* Amirreza Aflakparast +* Ananya Devarakonda +* ananya314 +* Anja Beck +* Anjini2004 +* Ant Lockyer +* Antony Lee +* Anvi Verma +* Artyom Romanov +* Augusto Borges +* avramid9 +* Ben Root +* bersbersbers +* Binaya Sharma +* Cameron +* Chaoyi Hu +* chaoyihu +* Chiraag Balu +* Christoph Hasse +* ConstableCatnip +* CozyFrog +* Cyril Gadal +* Dale Dai +* Daniel Bergman +* Daniel Hitchcock +* danielcobej +* David Gilbertson +* David Stansby +* ddale1128@gmail.com +* dependabot[bot] +* Devilsaint +* dohyun +* Drew Kinneer +* DWesl +* Elisa Heckelmann +* ElisaHeck +* Elliott Sales de Andrade +* Eric Firing +* Eric Prestat +* esibinga +* Eva Sibinga +* Evgenii Radchenko +* Faisal Fawad +* Felipe Cybis Pereira +* Garrett Sward +* Gaurav-Kumar-Soni +* Gauri Chaudhari +* Gautam Sagar +* Greg Lucas +* Gurudatta Shanbhag +* hannah +* Haoying Zhang +* Hugues Hoppe +* i-jey +* iamfaham +* Ian Hunt-Isaak +* Ian Thomas +* ifEricReturnTrue +* Illviljan +* Issam +* Issam Arabi +* Jacob Stevens-Haas +* Jacob Tomlinson +* Jake +* Jake Stevens-Haas +* James Salsman +* Jaroza727 +* Jeremy Farrell +* Jirka +* Jody Klymak +* Jorge Moraleda +* Joshua Stevenson +* jovianw +* João Andrade +* jpgianfaldoni +* jsdodge +* jsjeelshah +* judfs +* Juhan Oskar Hennoste +* Junpei Ota +* Katherine Turk +* katotaisei +* KheshavKumar +* Koustav Ghosh +* Kritika Verma +* Kyle Sunden +* Linyi Li +* linyilily +* lkkmpn +* Lucia Korpas +* madisonwong210 +* Maggie Liu +* Marc Bresson +* Matthew Feickert +* Matthew Morrison +* Matthias Bussonnier +* Melissa Weber Mendonça +* melissawm +* mliu08 +* Mostafa Noah +* MostafaNouh0011 +* n-aswin +* Nabil +* nbarlowATI +* Nidaa Rabah +* Nivedita Chaudhari +* Oscar Gustafsson +* patel-zeel +* Pavel Liavonau +* Pedro +* Pedro Peçanha +* Peter Talley +* Pradeep Reddy Raamana +* Prajwal Agrawal +* Pranav Raghu +* prateetishah +* pre-commit-ci[bot] +* QuadroTec +* Rafael Tsuha +* Raghuram Sirigiri +* Raphael +* Raphael Quast +* Ratnabali Dutta +* rawwash +* rsp2210 +* Ruoyi +* Ruoyi Xie +* Rushikesh Pandya +* Ruth Comer +* samGreer +* Samuel Diebolt +* saranti +* Scott Shambaugh +* Sebastian Berg +* Seohyeon Lee +* Sheepfan0828 +* ShivamPathak99 +* Shriya Kalakata +* shriyakalakata +* Stefan +* Steffen Rehberg +* stevezhang1999 +* Sudhanshu Pandey +* Talha Irfan +* thehappycheese +* Thomas A Caswell +* Tiago Lubiana +* Tim Hoffmann +* tobias +* Tom Sarantis +* trananso +* turnipseason +* tusharkulkarni008 +* UFEddy +* Vashesh08 +* vicky6 +* vigneshvetrivel8 +* wemi3 +* yangyangdotcom +* YiLun Fan +* Zach Champion +* zachjweiner +* zoehcycy + +GitHub issues and pull requests: + +Pull Requests (450): + +* :ghpull:`28206`: Backport PR #28205 on branch v3.9.x (TST: Fix tests with older versions of ipython) +* :ghpull:`28207`: TST: Followup corrections to #28205 +* :ghpull:`28205`: TST: Fix tests with older versions of ipython +* :ghpull:`28203`: Backport PR #28164 on branch v3.9.x (CI: Ensure code coverage is always uploaded) +* :ghpull:`28204`: Backport PR #28195 on branch v3.9.x (TST: Prepare for pytest 9) +* :ghpull:`28191`: DOC: Use released mpl-sphinx-theme on v3.9.x +* :ghpull:`28195`: TST: Prepare for pytest 9 +* :ghpull:`28193`: Backport PR #28185 on branch v3.9.x (DOC: Bump mpl-sphinx-theme to 3.9) +* :ghpull:`28190`: Backport PR #28103 on branch v3.9.x ([DOC]: Fix compatibility with sphinx-gallery 0.16) +* :ghpull:`28164`: CI: Ensure code coverage is always uploaded +* :ghpull:`28194`: Backport PR #28188 on branch v3.9.x ([TST] Bump some tolerances for Macos ARM) +* :ghpull:`28188`: [TST] Bump some tolerances for Macos ARM +* :ghpull:`28185`: DOC: Bump mpl-sphinx-theme to 3.9 +* :ghpull:`28189`: Backport PR #28181 on branch v3.9.x (DOC: Prepare release notes for 3.9) +* :ghpull:`28103`: [DOC]: Fix compatibility with sphinx-gallery 0.16 +* :ghpull:`28181`: DOC: Prepare release notes for 3.9 +* :ghpull:`28184`: Backport PR #28182 on branch v3.9.x (Bump custom hatch deprecation expiration) +* :ghpull:`28182`: Bump custom hatch deprecation expiration +* :ghpull:`28178`: Backport PR #28171 on branch v3.9.x (Support removing absent tools from ToolContainerBase.) +* :ghpull:`28171`: Support removing absent tools from ToolContainerBase. +* :ghpull:`28174`: Backport PR #28169 on branch v3.9.x (Clarify public-ness of some ToolContainerBase APIs.) +* :ghpull:`28169`: Clarify public-ness of some ToolContainerBase APIs. +* :ghpull:`28160`: Backport PR #28039 on branch v3.9.x (Respect vertical_axis when rotating plot interactively) +* :ghpull:`28159`: Backport PR #28157 on branch v3.9.x (Remove call to non-existent method _default_contains in Artist) +* :ghpull:`28162`: Backport PR #27948 on branch v3.9.x (Move IPython backend mapping to Matplotlib and support entry points) +* :ghpull:`28163`: Backport PR #28144 on branch v3.9.x (DOC: Refactor code in the fishbone diagram example) +* :ghpull:`28144`: DOC: Refactor code in the fishbone diagram example +* :ghpull:`27948`: Move IPython backend mapping to Matplotlib and support entry points +* :ghpull:`28039`: Respect vertical_axis when rotating plot interactively +* :ghpull:`28157`: Remove call to non-existent method _default_contains in Artist +* :ghpull:`28141`: Backport PR #27960 on branch v3.9.x (Update AppVeyor config) +* :ghpull:`28138`: Backport PR #28068 on branch v3.9.x ([TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list``) +* :ghpull:`28140`: Backport PR #28136 on branch v3.9.x (Appease pycodestyle.) +* :ghpull:`27960`: Update AppVeyor config +* :ghpull:`28068`: [TYP] Add possible type hint to ``colors`` argument in ``LinearSegmentedColormap.from_list`` +* :ghpull:`28136`: Appease pycodestyle. +* :ghpull:`28135`: Backport PR #28134 on branch v3.9.x (DOC: Minor improvements on quickstart) +* :ghpull:`28134`: DOC: Minor improvements on quickstart +* :ghpull:`28121`: Backport PR #28085 on branch v3.9.x (Clarify that the pgf backend is never actually used interactively.) +* :ghpull:`28120`: Backport PR #28102 on branch v3.9.x (Fix typo in color mapping documentation in quick_start.py) +* :ghpull:`28109`: Backport PR #28100 on branch v3.9.x (TST: wxcairo sometimes raises OSError on missing cairo libraries) +* :ghpull:`28100`: TST: wxcairo sometimes raises OSError on missing cairo libraries +* :ghpull:`28108`: Backport PR #28107 on branch v3.9.x ([DOC] Fix description in CapStyle example) +* :ghpull:`28107`: [DOC] Fix description in CapStyle example +* :ghpull:`28102`: Fix typo in color mapping documentation in quick_start.py +* :ghpull:`28095`: Backport PR #28094 on branch v3.9.x (DOC: exclude sphinx 7.3.*) +* :ghpull:`28081`: Backport PR #28078 on branch v3.9.x (Clarify that findfont & _find_fonts_by_props return paths.) +* :ghpull:`28080`: Backport PR #28077 on branch v3.9.x (Parent tk StringVar to the canvas widget, not to the toolbar.) +* :ghpull:`28092`: Backport PR #28032 on branch v3.9.x (FIX: ensure images are C order before passing to pillow) +* :ghpull:`28032`: FIX: ensure images are C order before passing to pillow +* :ghpull:`28088`: Backport PR #28087 on branch v3.9.x (Document Qt5 minimal version.) +* :ghpull:`28085`: Clarify that the pgf backend is never actually used interactively. +* :ghpull:`28078`: Clarify that findfont & _find_fonts_by_props return paths. +* :ghpull:`28077`: Parent tk StringVar to the canvas widget, not to the toolbar. +* :ghpull:`28062`: Backport PR #28056 on branch v3.9.x (Strip trailing spaces from log-formatter cursor output.) +* :ghpull:`28063`: Backport PR #28055 on branch v3.9.x (DOC: Improve inverted axis example) +* :ghpull:`28056`: Strip trailing spaces from log-formatter cursor output. +* :ghpull:`28049`: Backport PR #28036 on branch v3.9.x (BLD: Fetch version from setuptools_scm at build time) +* :ghpull:`28036`: BLD: Fetch version from setuptools_scm at build time +* :ghpull:`28038`: Backport PR #28023 on branch v3.9.x (ci: Update merge conflict labeler) +* :ghpull:`28023`: ci: Update merge conflict labeler +* :ghpull:`28035`: Backport PR #28026 on branch v3.9.x ([DOC] reshuffle of contributing) +* :ghpull:`28026`: [DOC] reshuffle of contributing +* :ghpull:`28024`: DOC: Rewrite "Work on an issue" section +* :ghpull:`28011`: DOC: Move bug reports and feature requests to top of contributing index +* :ghpull:`27747`: Move doc/users/installing/ to doc/install/ +* :ghpull:`27952`: ENH: Align titles +* :ghpull:`28017`: Merge up v3.8.4 +* :ghpull:`28014`: Improve timeline example. +* :ghpull:`28019`: DOC: correct path to mpl_toolkits reference images +* :ghpull:`26981`: Fixes Issue #26377 - Auto-escape % Symbol in Latex in pie labels +* :ghpull:`28007`: wx: Fix file extension for toolmanager-style toolbar +* :ghpull:`25556`: Display cursor coordinates for all axes twinned with the current one. +* :ghpull:`23597`: Always use PyQT/PySide6 for GitHub CI +* :ghpull:`28013`: Avoid plt.xticks/plt.yticks in gallery examples. +* :ghpull:`28006`: Fix deprecation warnings in ft2font extension +* :ghpull:`27723`: ci: Enable testing on M1 macOS +* :ghpull:`26375`: Add ``widths``, ``heights`` and ``angles`` setter to ``EllipseCollection`` +* :ghpull:`27999`: Remove documentation that some backends don't support hatching. +* :ghpull:`26710`: Add support for High DPI displays to wxAgg backend +* :ghpull:`27148`: Correctly treat pan/zoom events of overlapping axes. +* :ghpull:`27981`: DOC: Fix label type specification in parameter descriptions +* :ghpull:`27979`: Clarify error message for bad-dimensionality in pcolorfast(). +* :ghpull:`27962`: DOC: Document axes_grid1.Grid attributes +* :ghpull:`27968`: MNT: Remove remaining 3.7 deprecations +* :ghpull:`27965`: DOC: Rewrite the example illustrating bxp() +* :ghpull:`26453`: add documentation for reloading font cache +* :ghpull:`26131`: Tst/restore old tests +* :ghpull:`27730`: Add an rcparam for image.interpolation_stage. +* :ghpull:`27956`: Use PyOS_setsig in macos backend +* :ghpull:`27829`: Simplify color/marker disambiguation logic in _process_plot_format. +* :ghpull:`27840`: Add legend support for boxplots +* :ghpull:`27943`: Support Cn, n>9 in plot() shorthand format. +* :ghpull:`27950`: ci: Fix condition for publishing wheels +* :ghpull:`27909`: Add a note to pyplot docstrings referencing the corresponding object methods +* :ghpull:`27929`: DOC: Add summary lines to plot types +* :ghpull:`27915`: [BUG] Fix redirect-from Sphinx extension +* :ghpull:`27945`: DOC: Explain leading dot in object references +* :ghpull:`27947`: Update docs for ``FancyArrowPatch`` & ``Annotation`` to make it clear that ShrinkA/B parameters are in points and not fractional. +* :ghpull:`27944`: Bump the actions group with 2 updates +* :ghpull:`27932`: Fix pickling of make_axes_area_auto_adjustable'd axes. +* :ghpull:`26500`: closes #26477 ENH: Add interpolation_stage in qt figureoptions +* :ghpull:`27927`: Update docs +* :ghpull:`27916`: Revert renaming labels to tick_labels in boxplot_stats() +* :ghpull:`27931`: Highlight development_setup code snippets as bash, not python. +* :ghpull:`27856`: Support hatching in cairo backends. +* :ghpull:`27922`: Fix cbook style +* :ghpull:`27668`: MNT: prevent merging using labels + branch protection rules +* :ghpull:`27857`: Documentation edit for matshow function +* :ghpull:`27928`: DOC: Fix syntax for ToolBase.image docstring +* :ghpull:`27873`: Simplify the LineCollection example +* :ghpull:`27492`: Fix semantics of MEP22 image names. +* :ghpull:`27918`: Fix new flake8 errors from old merge +* :ghpull:`27874`: Modernize macosx backend a bit +* :ghpull:`25887`: Update ``_unpack_to_numpy`` function to convert JAX and PyTorch arrays to NumPy +* :ghpull:`27685`: Work around pyparsing diagnostic warnings +* :ghpull:`26594`: Added optional props argument to Lasso Widget __init__ to customize Lasso line +* :ghpull:`22761`: Add minor ticks on and off in Axis +* :ghpull:`22407`: Add ``set_XY`` and ``set_data`` to ``Quiver`` +* :ghpull:`27901`: Rename boxplot's tick label parameter +* :ghpull:`27883`: Fix build on older macOS deployment targets +* :ghpull:`27900`: Remove empty user guide tutorials page +* :ghpull:`27885`: Clean up headers in extensions +* :ghpull:`27910`: DOC: Fix dead link in README +* :ghpull:`26567`: Use SVG inheritance diagrams now that linking has been fixed +* :ghpull:`27899`: Merge up 3.8.x into main +* :ghpull:`27905`: Improved error message for malformed colors +* :ghpull:`27906`: Override open_group, close_group methods in PathEffectRenderer +* :ghpull:`27904`: FIX: Restore D213 in flake8 +* :ghpull:`27895`: Remove versions from sidebar in docs +* :ghpull:`27894`: Mark triangulation classes as final +* :ghpull:`27557`: Use :mpltype:``color`` for color types +* :ghpull:`27845`: Make sure custom alpha param does not change 'none' colors in a list of colors +* :ghpull:`27719`: Add BackendRegistry singleton class +* :ghpull:`27890`: DOC: State approximate documentation build time +* :ghpull:`27887`: BLD: Add a fallback URL for FreeType +* :ghpull:`25224`: Allow passing a transformation to secondary_xaxis/_yaxis +* :ghpull:`27886`: Fix devdocs version switcher +* :ghpull:`27884`: FIX: don't copy twice on RGB input +* :ghpull:`27087`: Convert path extension to pybind11 +* :ghpull:`27867`: DOC: Update some animation related topics +* :ghpull:`27848`: FIX: handle nans in RGBA input with ScalarMappables +* :ghpull:`27821`: BLD,Cygwin: Include Python.h first in various C++ files +* :ghpull:`27457`: TST: adding tests of current clear behavior on ticks +* :ghpull:`27872`: doc: add description of ``**kwargs`` usage to collections +* :ghpull:`27868`: Use pybind11 string formatter for exception messages +* :ghpull:`27862`: Add dtype/copy args to internal testing class +* :ghpull:`27658`: Bump pydata-sphinx-theme +* :ghpull:`27303`: FIX: also exclude np.nan in RGB(A) in color mapping +* :ghpull:`27860`: Bump the actions group with 2 updates +* :ghpull:`27869`: Correctly set temporary pdf/pgf backends +* :ghpull:`27850`: Deprecate ``plot_date`` +* :ghpull:`27815`: Add side option to violinplot +* :ghpull:`27836`: DOC: use ... for continuation prompt in docstrings +* :ghpull:`27819`: MNT: remove draw method args and kwargs +* :ghpull:`27813`: DOC: Update violinplot() docs +* :ghpull:`27698`: Add linting and validation of all YAML files +* :ghpull:`27811`: Fix Annulus width check +* :ghpull:`27667`: Change return type of ``ion`` and ``ioff`` to fix unbound variable errors with Pyright +* :ghpull:`27807`: Expand CI pytest reporting config to ignore xfails +* :ghpull:`27806`: Remove self._renderer from AnnotationBbox and ConnectionPatch +* :ghpull:`27799`: Clarify that set_ticks() affects major/minor ticks independently +* :ghpull:`27787`: Improve documentation on boxplot and violinplot +* :ghpull:`27800`: Deactivate sidebar for release notes +* :ghpull:`27798`: Fix sphinx-gallery CSS +* :ghpull:`27462`: DOC: clarify the default value of *radius* in Patch.contains_point +* :ghpull:`27565`: MNT: arghandling subplotspec +* :ghpull:`27796`: Make mypy a bit stricter +* :ghpull:`27767`: Update handling of sequence labels for plot +* :ghpull:`27795`: Add EffVer badge +* :ghpull:`27780`: Partly revert #27711 +* :ghpull:`27768`: MNT: deprecate draw method args and kwargs +* :ghpull:`27783`: Update README.md to fix citation link +* :ghpull:`27726`: TST: always set a (long) timeout for subprocess and always use our wrapper +* :ghpull:`27781`: Simplify example: Box plots with custom fill colors +* :ghpull:`27750`: Bump the actions group with 2 updates +* :ghpull:`27771`: Add marker-only and line+marker visuals to the plot() plot types +* :ghpull:`27764`: Increase size of legend in Legend guide example +* :ghpull:`26800`: Bump minimum NumPy version to 1.23 +* :ghpull:`27752`: Update some Meson internals +* :ghpull:`27702`: GOV: adopt EffVer +* :ghpull:`26965`: Removal of deprecated API cm +* :ghpull:`27758`: [Doc] Remove special casing for removed method +* :ghpull:`25815`: [TST] Make jpl units instantiated with datetimes consistent with mpl converters +* :ghpull:`27729`: DOC: Improve colormap normalization example +* :ghpull:`27732`: TST: Remove memory leak test +* :ghpull:`27733`: ci: Simplify CodeQL setup +* :ghpull:`27692`: Add method to update position of arrow patch +* :ghpull:`27736`: Fix incorrect API reference in docs +* :ghpull:`27731`: DOC: Create explicit rename legend entry section in guide +* :ghpull:`27560`: Moved /users/project to /doc/project +* :ghpull:`27728`: Simplify Figure._suplabels. +* :ghpull:`27715`: Bump the actions group with 3 updates +* :ghpull:`27711`: Fix boxplot legend entries part 2 +* :ghpull:`27696`: DOC: clean up automated tests section of workflow docs +* :ghpull:`27686`: Improve Locator docstrings +* :ghpull:`27704`: ci: Remove prerelease conditions from Azure Pipelines +* :ghpull:`27568`: Fix boxplot legend entries +* :ghpull:`27694`: MNT: fix labeller +* :ghpull:`26953`: MNT: test that table doesn't try to convert unitized data +* :ghpull:`27690`: Remove "Past versions" section from release notes +* :ghpull:`26926`: Closes #22011: Changes to SubFigures so it behaves like a regular artist +* :ghpull:`27469`: Fixed legend with legend location "best" when legend overlaps shaded area and text +* :ghpull:`27684`: Bump the actions group with 1 update +* :ghpull:`27665`: Axes.inset_axes - warning message removed +* :ghpull:`27688`: CI: skip code coverage upload on scheduled tests +* :ghpull:`27689`: ci: Don't include API/what's new notes in general doc labels +* :ghpull:`27640`: Add ``get_cursor_data`` to ``NonUniformImage`` +* :ghpull:`27676`: BLD: Downgrade FreeType to 2.6.1 on Windows ARM +* :ghpull:`27619`: Use GH action to install reviewdog +* :ghpull:`27552`: TST: Use importlib for importing in pytest +* :ghpull:`27650`: DOC: Added call out to API guidelines to contribute + small API guidelines reorg +* :ghpull:`27618`: Add option of running stubtest using tox +* :ghpull:`27656`: Bump the actions group with 1 update +* :ghpull:`27415`: Use class form of data classes +* :ghpull:`27649`: Check for latex binary before building docs +* :ghpull:`27641`: MNT: fix api changes link in PR template +* :ghpull:`27644`: ci: Fix mpl_toolkits label +* :ghpull:`27230`: Query macOS for available system fonts. +* :ghpull:`27643`: ci: Update nightly upload for artifacts v4 +* :ghpull:`27642`: Fix auto-labeler configuration +* :ghpull:`27639`: Doc: typo fix for #22699 +* :ghpull:`26978`: [pre-commit.ci] pre-commit autoupdate +* :ghpull:`27563`: Enable PyPI publishing from GitHub Actions +* :ghpull:`22699`: Proof of concept for adding kwdoc content to properties using a decorator +* :ghpull:`27633`: Auto-label PRs based on changed files +* :ghpull:`27607`: Error on bad input to hexbin extents +* :ghpull:`27629`: Don't run CI twice on dependabot branches +* :ghpull:`27562`: Avoid an extra copy/resample if imshow input has no alpha +* :ghpull:`27628`: Bump the actions group with 2 updates +* :ghpull:`27626`: CI: Group dependabot updates +* :ghpull:`27589`: Don't clip PowerNorm inputs < vmin +* :ghpull:`27613`: Fix marker validator with cycler (allow mix of classes) +* :ghpull:`27615`: MNT: add spaces to PR template +* :ghpull:`27614`: DOC: Updated link in annotation API docs to point to annotation user guide +* :ghpull:`27605`: Ignore masked values in boxplot +* :ghpull:`26884`: Remove deprecated code from _fontconfig_patterns +* :ghpull:`27602`: Let FormatStrFormatter respect axes.unicode_minus. +* :ghpull:`27601`: Clarify dollar_ticks example and FormatStrFormatter docs. +* :ghpull:`24834`: Deprecate apply_theta_transforms=True to PolarTransform +* :ghpull:`27591`: Use macOS instead of OSX in comments/docs +* :ghpull:`27577`: MNT: add the running version to pickle warning message +* :ghpull:`25191`: Deprecate 'prune' kwarg to MaxNLocator +* :ghpull:`27566`: DOC: changed tag ``plot type`` to ``plot-type`` +* :ghpull:`27105`: Use Axes instead of axes core library code +* :ghpull:`27575`: Add quotes round .[dev] in editable install command +* :ghpull:`27104`: Use Axes instead of axes in galleries +* :ghpull:`27373`: Transpose grid_finder tick representation. +* :ghpull:`27363`: ci: Improve coverage for compiled code +* :ghpull:`27200`: DOC: Add role for custom informal types like color +* :ghpull:`27548`: DOC: typo fix in contribute doc +* :ghpull:`27458`: Check if the mappable is in a different Figure than the one fig.color… +* :ghpull:`27546`: MNT: Clean up some style exceptions +* :ghpull:`27514`: Improve check for bbox +* :ghpull:`27265`: DOC: reorganizing contributing docs to clean up toc, better separate topics +* :ghpull:`27517`: Best-legend-location microoptimization +* :ghpull:`27540`: Bump github/codeql-action from 2 to 3 +* :ghpull:`27520`: [Doc] Minor consistency changes and correction of Marker docs +* :ghpull:`27505`: Download Qhull source from Github, not Qhull servers, in meson build +* :ghpull:`27518`: Micro-optimizations related to list handling +* :ghpull:`27495`: Bump actions/stale from 8 to 9 +* :ghpull:`27523`: Changes for stale GHA v9 +* :ghpull:`27519`: [Doc] Improve/correct docs for 3D +* :ghpull:`27447`: TST: Compress some hist geometry tests +* :ghpull:`27513`: Fix docs and add tests for transform and deprecate ``BboxTransformToMaxOnly`` +* :ghpull:`27511`: TST: Add tests for Affine2D +* :ghpull:`27424`: Added Axes.stairs test in test_datetime.py +* :ghpull:`27267`: Fix/restore secondary axis support for Transform-type functions +* :ghpull:`27013`: Add test_contour under test_datetime.py +* :ghpull:`27497`: Clarify that set_axisbelow doesn't move grids below images. +* :ghpull:`27498`: Remove unnecessary del local variables at end of Gcf.destroy. +* :ghpull:`27466`: Add test_eventplot to test_datetime.py +* :ghpull:`25905`: Use annotate coordinate systems to simplify label_subplots. +* :ghpull:`27471`: Doc: visualizing_tests and ``triage_tests`` tools +* :ghpull:`27474`: Added smoke test for Axes.matshow to test_datetime.py +* :ghpull:`27470`: Fix test visualization tool for non-PNG files +* :ghpull:`27426`: DOC: normalizing histograms +* :ghpull:`27452`: Cleanup unit_cube-methods +* :ghpull:`27431`: Added test for Axes.bar_label +* :ghpull:`26962`: Remove backend 3.7-deprecated API +* :ghpull:`27410`: Add test_vlines to test_datetime.py +* :ghpull:`27425`: Added test_fill_betweenx in test_datetime.py +* :ghpull:`27449`: Remove test_quiverkey from test_datetime.py +* :ghpull:`27427`: MNT/TST: remove xcorr and acorr from test_datetime +* :ghpull:`27390`: Add test_bxp in test_datetime.py +* :ghpull:`27428`: Added test for broken_barh to test_datetime.py +* :ghpull:`27222`: [TST] Added test_annotate in test_datetime.py +* :ghpull:`27135`: Added smoke test for Axes.stem +* :ghpull:`27343`: Fix draggable annotations on subfigures. +* :ghpull:`27033`: Add test_bar in test_datetime +* :ghpull:`27423`: Add test for fill_between in test_datetime.py +* :ghpull:`27409`: Fix setting ``_selection_completed`` in ``SpanSelector`` when spanselector is initialised using ``extents`` +* :ghpull:`27440`: Fix get_path for 3d artists +* :ghpull:`27422`: TST: Cache available interactive backends +* :ghpull:`27401`: Add test_fill in test_datetime.py +* :ghpull:`27419`: DOC: Add AsinhScale to list of built-in scales +* :ghpull:`27417`: Switch pytest fixture from tmpdir to tmp_path +* :ghpull:`27172`: ENH: Change logging to warning when creating a legend with no labels +* :ghpull:`27405`: Check that xerr/yerr values are not None in errorbar +* :ghpull:`27392`: Remove test_spy from test_datetime.py +* :ghpull:`27331`: Added smoke test for Axes.barbs in test_datetime.py +* :ghpull:`27393`: MNT: Fix doc makefiles +* :ghpull:`27387`: Revert "MNT: add _version.py to .gitignore" +* :ghpull:`27347`: FIX: scale norm of collections when first array is set +* :ghpull:`27374`: MNT: add _version.py to .gitignore +* :ghpull:`19011`: Simplify tk tooltip setup. +* :ghpull:`27367`: Fix _find_fonts_by_props docstring +* :ghpull:`27359`: Fix build on PyPy +* :ghpull:`27362`: Implement SubFigure.remove. +* :ghpull:`27360`: Fix removal of colorbars on nested subgridspecs. +* :ghpull:`27211`: Add test_hlines to test_datetimes.py +* :ghpull:`27353`: Refactor AxisArtistHelpers +* :ghpull:`27357`: [DOC]: Update 3d axis limits what's new +* :ghpull:`26992`: Convert TkAgg utilities to pybind11 +* :ghpull:`27215`: Add ``@QtCore.Slot()`` decorations to ``NavigationToolbar2QT`` +* :ghpull:`26907`: Removal of deprecations for Contour +* :ghpull:`27285`: Factor out common parts of qt and macos interrupt handling. +* :ghpull:`27306`: Simplify GridSpec setup in make_axes_gridspec. +* :ghpull:`27313`: FIX: allow re-shown Qt windows to be re-destroyed +* :ghpull:`27184`: Use pybind11 for qhull wrapper +* :ghpull:`26794`: Use pybind11 in _c_internal_utils module +* :ghpull:`27300`: Remove idiosyncratic get_tick_iterator API. +* :ghpull:`27275`: MAINT: fix .yml in tag issue template +* :ghpull:`27288`: Use int.from_bytes instead of implementing the conversion ourselves. +* :ghpull:`27286`: Various cleanups +* :ghpull:`27279`: Tweak a few docstrings. +* :ghpull:`27256`: merge up v3.8.1 +* :ghpull:`27254`: Remove redundant axes_grid colorbar examples. +* :ghpull:`27251`: webagg: Don't resize canvas if WebSocket isn't connected +* :ghpull:`27236`: Tagging Example - Tags for multiple figs demo +* :ghpull:`27245`: MNT: be more careful in Qt backend that there is actually a Figure +* :ghpull:`27158`: First attempt for individual hatching styles for stackplot +* :ghpull:`26851`: Establish draft Tag glossary and Tagging guidelines +* :ghpull:`27083`: DOC: Add tags infrastructure for gallery examples +* :ghpull:`27204`: BLD: Use NumPy nightly wheels for non-release builds +* :ghpull:`27208`: Add test_axvline to test_datetime.py +* :ghpull:`26989`: MNT: print fontname in missing glyph warning +* :ghpull:`27177`: Add test_axhline in test_datetime.py +* :ghpull:`27164`: docs: adding explanation for color in ``set_facecolor`` +* :ghpull:`27175`: Deprecate mixing positional and keyword args for legend(handles, labels) +* :ghpull:`27199`: DOC: clean up links under table formatting docs +* :ghpull:`27185`: Added smoke tests for Axes.errorbar in test_datetime.py +* :ghpull:`27091`: Add test_step to test_datetime.py +* :ghpull:`27182`: Add example for plotting a bihistogram +* :ghpull:`27130`: added test_axvspan in test.datetime.py +* :ghpull:`27094`: MNT: move pytest.ini configs to .toml +* :ghpull:`27139`: added test_axhspan in test_datetime.py +* :ghpull:`27058`: DOC: concise dependency heading + small clarifications +* :ghpull:`27053`: Added info for getting compilation output from meson on autorebuild +* :ghpull:`26906`: Fix masking for Axes3D.plot() +* :ghpull:`27142`: Added smoke test for Axes.text in test_datetime.py +* :ghpull:`27024`: Add test_contourf in test_datetime.py +* :ghpull:`22347`: correctly treat pan/zoom events of overlapping axes +* :ghpull:`26900`: #26865 removing deprecations to axislines.py +* :ghpull:`26696`: DOC: Fix colLoc default +* :ghpull:`27064`: Close all plot windows of a blocking show() on Ctrl+C +* :ghpull:`26882`: Add scatter test for datetime units +* :ghpull:`27114`: add test_stackplot in test_datetime.py +* :ghpull:`27084`: Add test_barh to test_datetime.py +* :ghpull:`27110`: DOC: Move figure member sections one level down +* :ghpull:`27127`: BLD: use python3 for shebang consistent with pep-394 +* :ghpull:`27111`: BLD: Fix setting FreeType build type in extension +* :ghpull:`26921`: MNT: clarify path.sketch rcparam format + test validate_sketch +* :ghpull:`27109`: TST: Use importlib for subprocess tests +* :ghpull:`27119`: Update clabel comment. +* :ghpull:`27117`: Remove datetime test for axes.pie +* :ghpull:`27095`: Deprecate nth_coord parameter from FixedAxisArtistHelper.new_fixed_axis. +* :ghpull:`27066`: Tweak array_view to be more like pybind11 +* :ghpull:`27090`: Restore figaspect() API documentation +* :ghpull:`27074`: Issue #26990: Split the histogram image into two for each code block. +* :ghpull:`27086`: Rename py namespace to mpl in extension code +* :ghpull:`27082`: MAINT: Update environment.yml to match requirements files +* :ghpull:`27072`: Remove datetime test stubs for spectral methods/table +* :ghpull:`26830`: Update stix table with Unicode names +* :ghpull:`26969`: DOC: add units to user/explain [ci doc] +* :ghpull:`27028`: Added test_hist in test_datetime.py +* :ghpull:`26876`: issue: 26871 - Remove SimplePath class from patches.py +* :ghpull:`26875`: Fix Deprecation in patches.py +* :ghpull:`26890`: Removing deprecated api from patches +* :ghpull:`27037`: add test_plot_date in test_datetime.py +* :ghpull:`27012`: Bump required C++ standard to c++17 +* :ghpull:`27021`: Add a section to Highlight past winners for JDH plotting contest in docs +* :ghpull:`27004`: Warning if handles and labels have a len mismatch +* :ghpull:`24061`: #24050 No error was thrown even number of handles mismatched labels +* :ghpull:`26754`: DOC: separate and clarify axisartist default tables +* :ghpull:`27020`: CI: Update scientific-python/upload-nightly-action to 0.2.0 +* :ghpull:`26951`: Clarify that explicit ticklabels are used without further formatting. +* :ghpull:`26894`: Deprecate setting the timer interval while starting it. +* :ghpull:`13401`: New clear() method for Radio and Check buttons +* :ghpull:`23829`: Start transitioning to pyproject.toml +* :ghpull:`26621`: Port build system to Meson +* :ghpull:`26928`: [TYP] Add tool for running stubtest +* :ghpull:`26917`: Deprecate ContourLabeler.add_label_clabeltext. +* :ghpull:`26960`: Deprecate backend_ps.get_bbox_header, and split it for internal use. +* :ghpull:`26967`: Minor cleanups. +* :ghpull:`26909`: deprecated api tri +* :ghpull:`26946`: Inline Cursor._update into its sole caller. +* :ghpull:`26915`: DOC: Clarify description and add examples in colors.Normalize +* :ghpull:`26874`: Cleaned up the span_where class method from Polycollections. +* :ghpull:`26586`: Support standard formatters in axisartist. +* :ghpull:`26788`: Fix axh{line,span} on polar axes. +* :ghpull:`26935`: add tomli to rstcheck extras +* :ghpull:`26275`: Use pybind11 in image module +* :ghpull:`26887`: DOC: improve removal for julian dates [ci doc] +* :ghpull:`26929`: DOC: Fix removal doc for Animation attributes +* :ghpull:`26918`: 26865 Removed deprecations from quiver.py +* :ghpull:`26902`: Fixed deprecated APIs in lines.py +* :ghpull:`26903`: Simplify CheckButtons and RadioButtons click handler. +* :ghpull:`26899`: MNT: only account for Artists once in fig.get_tightbbox +* :ghpull:`26861`: QT/NavigationToolbar2: configure subplots dialog should be modal +* :ghpull:`26885`: Removed deprecated code from gridspec.py +* :ghpull:`26880`: Updated offsetbox.py +* :ghpull:`26910`: Removed the deprecated code from offsetbox.py +* :ghpull:`26905`: Add users/explain to default skip subdirs +* :ghpull:`26853`: Widgets: Remove deprecations and make arguments keyword only +* :ghpull:`26877`: Fixes deprecation in lines.py +* :ghpull:`26871`: Removed the deprecated code from ``axis.py`` +* :ghpull:`26872`: Deprecated code removed in animation.py +* :ghpull:`26859`: Add datetime testing skeleton +* :ghpull:`26848`: ci: Don't install recommended packages on Circle +* :ghpull:`26852`: Remove Julian date support +* :ghpull:`26801`: [MNT]: Cleanup ticklabel_format (style=) +* :ghpull:`26840`: Reduce redundant information in _process_plot_var_args. +* :ghpull:`26731`: Explicitly set foreground color to black in svg icons +* :ghpull:`26826`: [MNT] Move NUM_VERTICES from mplutils.h to the only file it is used in +* :ghpull:`26742`: [TYP] Add typing for some private methods and modules +* :ghpull:`26819`: Reorder safe_first_element() and _safe_first_finite() code +* :ghpull:`26813`: Bump docker/setup-qemu-action from 2 to 3 +* :ghpull:`26797`: Remove deprecated draw_gouraud_triangle +* :ghpull:`26815`: Remove plt.Axes from tests +* :ghpull:`26818`: Fix doc build (alternative) +* :ghpull:`26785`: merge up v3.8.0 +* :ghpull:`25272`: Do not add padding to 3D axis limits when limits are manually set +* :ghpull:`26798`: Remove deprecated methods and attributed in Axes3D +* :ghpull:`26744`: Use cbook methods for string checking +* :ghpull:`26802`: specify input range in logs when image data must be clipped +* :ghpull:`26787`: Remove unused Axis private init helpers. +* :ghpull:`26629`: DOC: organize figure API +* :ghpull:`26690`: Make generated pgf code more robust against later changes of tex engine. +* :ghpull:`26577`: Bugfix: data sanitizing for barh +* :ghpull:`26684`: Update PR template doc links +* :ghpull:`26686`: PR template: shorten comment and pull up top +* :ghpull:`26670`: Added sanitize_sequence to kwargs in _preprocess_data +* :ghpull:`26634`: [MNT] Move SubplotParams from figure to gridspec +* :ghpull:`26609`: Cleanup AutoMinorLocator implementation. +* :ghpull:`26293`: Added get_xmargin(), get_ymargin() and get_zmargin() and tests. +* :ghpull:`26516`: Replace reference to %pylab by %matplotlib. +* :ghpull:`26483`: Improve legend(loc='best') warning and test +* :ghpull:`26482`: [DOC]: print pydata sphinx/mpl theme versions +* :ghpull:`23787`: Use pybind11 for C/C++ extensions + +Issues (97): + +* :ghissue:`28202`: [Bug]: Qt test_ipython fails on older ipython +* :ghissue:`28145`: [TST] Upcoming dependency test failures +* :ghissue:`28034`: [TST] Upcoming dependency test failures +* :ghissue:`28168`: [TST] Upcoming dependency test failures +* :ghissue:`28040`: [Bug]: vertical_axis not respected when rotating plots interactively +* :ghissue:`28146`: [Bug]: Useless recursive group in SVG output when using path_effects +* :ghissue:`28067`: [Bug]: ``LinearSegmentedColormap.from_list`` does not have all type hints for argument ``colors`` +* :ghissue:`26778`: [MNT]: Numpy 2.0 support strategy +* :ghissue:`28020`: [Bug]: imsave fails on RGBA data when origin is set to lower +* :ghissue:`7720`: WXAgg backend not rendering nicely on retina +* :ghissue:`28069`: [Bug]: Can't save with custom toolbar +* :ghissue:`28005`: [Doc]: Improve contribute instructions +* :ghissue:`22376`: [ENH]: align_titles +* :ghissue:`5506`: Confusing status bar values in presence of multiple axes +* :ghissue:`4284`: Twin axis message coordinates +* :ghissue:`18940`: WxAgg backend draws the wrong size when wxpython app is high DPI aware on Windows +* :ghissue:`27792`: [ENH]: Legend entries for boxplot +* :ghissue:`27828`: [Bug]: ".C10" does not work as plot shorthand format spec +* :ghissue:`27911`: redirect not working for updated contribute page +* :ghissue:`21876`: [Doc]: redirect-from directive appears broken? +* :ghissue:`27941`: [Bug]: ShrinkA and ShrinkB are ignored in ax.annotate(arrowprops=...) +* :ghissue:`26477`: [ENH]: Add interpolation_stage selector for images in qt figureoptions +* :ghissue:`363`: Enable hatches for Cairo backend +* :ghissue:`27852`: [Bug]: matplotlib.pyplot.matshow "(first dimension of the array) are displayed horizontally" but are displayed vertically +* :ghissue:`27400`: [Bug]: tk backend confused by presence of file named "move" in current working directory +* :ghissue:`25882`: [Bug]: plt.hist takes significantly more time with torch and jax arrays +* :ghissue:`25204`: [Bug]: Pyparsing warnings emitted in mathtext +* :ghissue:`17707`: getpwuid(): uid not found: 99 +* :ghissue:`27896`: [Doc]: Empty "User guide tutorials page" in docs +* :ghissue:`27824`: [Bug]: polygon from axvspan not correct in polar plot after set_xy +* :ghissue:`27378`: [ENH]: Suggest 'CN' if color is an integer +* :ghissue:`27843`: [Bug]: close_group is not called when using patheffects +* :ghissue:`27839`: [Bug]: PathCollection using alpha ignores 'none' facecolors +* :ghissue:`25119`: [ENH]: secondary_x/yaxis accept transform argument +* :ghissue:`27876`: [Doc]: Fix version switcher in devdocs +* :ghissue:`27301`: [Bug]: ``imshow`` allows RGB(A) images with ``np.nan`` values to pass +* :ghissue:`23839`: [MNT]: Add tests to codify ``ax.clear`` +* :ghissue:`27652`: [Doc]: Low contrast on clicked links in dark mode +* :ghissue:`27865`: [Bug]: Zoom und pan not working after writing pdf pages. +* :ghissue:`25871`: [Bug]: Colorbar cannot be added to another figure +* :ghissue:`8072`: plot_date() ignores timezone in matplotlib version 2.0.0 +* :ghissue:`27812`: [ENH]: Add split feature for violin plots +* :ghissue:`27659`: [MNT]: Improve return type of ``ioff`` and ``ion`` to improve Pyright analysis of bound variables +* :ghissue:`27805`: [Bug]: Saving a figure with indicate_inset_zoom to pdf and then pickling it causes TypeError +* :ghissue:`27701`: [Bug]: axis set_xscale('log') interferes with set_xticks +* :ghissue:`19807`: radius modification in contains_point function when linewidth is specified +* :ghissue:`27762`: [Bug]: Inconsistent treatment of list of labels in ``plot`` when the input is a dataframe +* :ghissue:`27745`: [MNT]: ``_ImageBase.draw`` and ``Axis.draw`` args and kwargs +* :ghissue:`27782`: [Doc]: Link to citation page in read me broken +* :ghissue:`8789`: legend handle size does not automatically scale with linewidth +* :ghissue:`27746`: [Doc]: Citation link in the readme.md points to 404 +* :ghissue:`20853`: Add deprecation for colormaps +* :ghissue:`26865`: [MNT]: Remove 3.7-deprecated API +* :ghissue:`24168`: [Bug]: ``subprocess-exited-with-error`` when trying to build on M1 mac +* :ghissue:`27727`: [Doc]: Text in the colormap normalization gallery doesn't match the code +* :ghissue:`27635`: [Bug]: test_figure_leak_20490 repeatedly failing on CI +* :ghissue:`14217`: [Feature request] Add a way to update the position of the Arrow patch. +* :ghissue:`20512`: Bad boxplot legend entries +* :ghissue:`22011`: [Bug]: subfigures messes up with fig.legend zorder +* :ghissue:`27414`: [Bug]: Legend overlaps shaded area in fill_between with legend location "best" +* :ghissue:`23323`: Legend with "loc=best" does not try to avoid text +* :ghissue:`27648`: [Doc]: ``Axes.inset_axes`` is still experimental +* :ghissue:`27277`: [Doc]: Two license pages in docs +* :ghissue:`24648`: [Doc]: make html fail early if latex not present +* :ghissue:`27554`: [Bug]: Large image draw performance deterioration in recent releases +* :ghissue:`25239`: [Bug]: colors.PowerNorm results in incorrect colorbar +* :ghissue:`13533`: Boxplotting Masked Arrays +* :ghissue:`25967`: [Doc]: dollar_ticks example refers to unused formatter classes +* :ghissue:`24859`: [Doc]: Document color in a consistent way, including link +* :ghissue:`27159`: [Bug]: Meson build fails due to qhull link issue. +* :ghissue:`25691`: [Bug]: Secondary axis does not support Transform as functions +* :ghissue:`25860`: [Bug]: canvas pick events not working when Axes belongs to a subfigure +* :ghissue:`27361`: [Bug]: (Tight) layout engine breaks for 3D patches +* :ghissue:`27145`: [ENH]: Make "No artists with labels found to put in legend" a warning +* :ghissue:`27399`: [Bug]: None in y or yerr arrays leads to TypeError when using errorbar +* :ghissue:`13887`: Accessing default ``norm`` of a Collection removes its colors. +* :ghissue:`26593`: [ENH]: Support SubFigure.remove() +* :ghissue:`27329`: [Bug]: Removing a colorbar for an axes positioned in a subgridspec restores the axes' position to the wrong place. +* :ghissue:`27214`: [Bug]: ``NavigationToolbar2QT`` should use ``@Slot`` annotation +* :ghissue:`27146`: [ENH]: Multi hatching in ``ax.stackplot()`` +* :ghissue:`27168`: [Doc]: Instructions for editable installation on Windows potentially missing a step +* :ghissue:`27174`: [MNT]: Build nightly wheels with NumPy nightly wheels +* :ghissue:`25043`: [ENH]: Plotting masked arrays correctly in 3D line plot +* :ghissue:`26990`: [Doc]: Histogram path example renders poorly in HTML +* :ghissue:`25738`: [MNT]: Improve readability of _mathtext_data.stix_virtual_fonts table +* :ghissue:`11129`: Highlight past winners for JDH plotting contest in docs +* :ghissue:`24050`: No error message in matplotlib.axes.Axes.legend() if there are more labels than handles +* :ghissue:`10922`: ENH: clear() method for widgets.RadioButtons +* :ghissue:`18295`: How to modify ticklabels in axisartist? +* :ghissue:`24996`: [Bug]: for non-rectilinear axes, axvline/axhline should behave as "draw a gridline at that x/y" +* :ghissue:`26841`: [Bug]: Global legend weird behaviors +* :ghissue:`25974`: [MNT]: Cleanup ticklabel_format(..., style=) +* :ghissue:`26786`: Please upload new dev wheel so we pick up 3.9.dev after 3.8 release +* :ghissue:`18052`: the limits of axes are inexact with mplot3d +* :ghissue:`25596`: [MNT]: Consistency on Interface +* :ghissue:`26557`: [ENH]: Nightly Python 3.12 builds +* :ghissue:`26281`: [ENH]: Add get_xmargin, get_ymargin, get_zmargin axes methods diff --git a/doc/users/release_notes.rst b/doc/users/release_notes.rst index 3befbeee5b77..1204450f6c05 100644 --- a/doc/users/release_notes.rst +++ b/doc/users/release_notes.rst @@ -19,8 +19,10 @@ Version 3.9 :maxdepth: 1 prev_whats_new/whats_new_3.9.0.rst + ../api/prev_api_changes/api_changes_3.9.1.rst ../api/prev_api_changes/api_changes_3.9.0.rst github_stats.rst + prev_whats_new/github_stats_3.9.0.rst Version 3.8 ^^^^^^^^^^^ From 44be14cc3a866e28a9ad22f36abfe8c0623cc6ac Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 4 Jul 2024 01:45:22 -0400 Subject: [PATCH 61/61] REL: 3.9.1 This is the first bugfix release of the 3.9.x series. This release contains several bug-fixes and adjustments: - Add GitHub artifact attestations for sdist and wheels - Re-add `matplotlib.cm.get_cmap`; note this function will still be removed at a later date - Allow duplicate backend entry points - Fix `Axes` autoscaling of thin bars at large locations - Fix `Axes` autoscaling with `axhspan` / `axvspan` - Fix `Axes3D` autoscaling of `Line3DCollection` / `Poly3DCollection` - Fix `Axes3D` mouse interactivity with non-default roll angle - Fix box aspect ratios in `Axes3D` with alternate vertical axis - Fix case handling of backends specified as `module://...` - Fix crash with TkAgg on Windows with `tk.window_focus: True` - Fix interactive update of SubFigures - Fix interactivity when using the IPython console - Fix pickling of AxesWidgets and SubFigures - Fix scaling on GTK3Cairo / GTK4Cairo backends - Fix text wrapping within SubFigures - Promote `mpltype` Sphinx role to a public extension; note this is only intended for development reasons