From bbff84c0df2bcfa59bacd8f38723e6be0de0fd84 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 20 Jun 2025 19:17:22 -0400 Subject: [PATCH 01/57] Replace facepair_t with std::optional This type seems to cover the intent more clearly than `std::pair`. --- src/_backend_agg.h | 47 ++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 6eb54e485e86..1ac3d4c06b13 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "agg_alpha_mask_u8.h" @@ -123,9 +124,6 @@ class RendererAgg typedef agg::renderer_base renderer_base_alpha_mask_type; typedef agg::renderer_scanline_aa_solid renderer_alpha_mask_type; - /* TODO: Remove facepair_t */ - typedef std::pair facepair_t; - RendererAgg(unsigned int width, unsigned int height, double dpi); virtual ~RendererAgg(); @@ -248,7 +246,7 @@ class RendererAgg bool render_clippath(mpl::PathIterator &clippath, const agg::trans_affine &clippath_trans, e_snap_mode snap_mode); template - void _draw_path(PathIteratorType &path, bool has_clippath, const facepair_t &face, GCAgg &gc); + void _draw_path(PathIteratorType &path, bool has_clippath, const std::optional &face, GCAgg &gc); template inline void -RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, GCAgg &gc) +RendererAgg::_draw_path(path_t &path, bool has_clippath, const std::optional &face, GCAgg &gc) { typedef agg::conv_stroke stroke_t; typedef agg::conv_dash dash_t; @@ -306,7 +304,7 @@ RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, typedef agg::renderer_scanline_bin_solid amask_bin_renderer_type; // Render face - if (face.first) { + if (face) { theRasterizer.add_path(path); if (gc.isaa) { @@ -314,10 +312,10 @@ RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, pixfmt_amask_type pfa(pixFmt, alphaMask); amask_ren_type r(pfa); amask_aa_renderer_type ren(r); - ren.color(face.second); + ren.color(*face); agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); } else { - rendererAA.color(face.second); + rendererAA.color(*face); agg::render_scanlines(theRasterizer, slineP8, rendererAA); } } else { @@ -325,10 +323,10 @@ RendererAgg::_draw_path(path_t &path, bool has_clippath, const facepair_t &face, pixfmt_amask_type pfa(pixFmt, alphaMask); amask_ren_type r(pfa); amask_bin_renderer_type ren(r); - ren.color(face.second); + ren.color(*face); agg::render_scanlines(theRasterizer, scanlineAlphaMask, ren); } else { - rendererBin.color(face.second); + rendererBin.color(*face); agg::render_scanlines(theRasterizer, slineP8, rendererBin); } } @@ -458,7 +456,10 @@ RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, typedef agg::conv_curve curve_t; typedef Sketch sketch_t; - facepair_t face(color.a != 0.0, color); + std::optional face; + if (color.a != 0.0) { + face = color; + } theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); @@ -467,7 +468,7 @@ RendererAgg::draw_path(GCAgg &gc, PathIterator &path, agg::trans_affine &trans, trans *= agg::trans_affine_scaling(1.0, -1.0); trans *= agg::trans_affine_translation(0.0, (double)height); - bool clip = !face.first && !gc.has_hatchpath(); + bool clip = !face && !gc.has_hatchpath(); bool simplify = path.should_simplify() && clip; double snapping_linewidth = points_to_pixels(gc.linewidth); if (gc.color.a == 0.0) { @@ -529,7 +530,10 @@ inline void RendererAgg::draw_markers(GCAgg &gc, curve_t path_curve(path_snapped); path_curve.rewind(0); - facepair_t face(color.a != 0.0, color); + std::optional face; + if (color.a != 0.0) { + face = color; + } // maxim's suggestions for cached scanlines agg::scanline_storage_aa8 scanlines; @@ -541,7 +545,7 @@ inline void RendererAgg::draw_markers(GCAgg &gc, try { std::vector fillBuffer; - if (face.first) { + if (face) { theRasterizer.add_path(marker_path_curve); agg::render_scanlines(theRasterizer, slineP8, scanlines); fillBuffer.resize(scanlines.byte_size()); @@ -605,8 +609,8 @@ inline void RendererAgg::draw_markers(GCAgg &gc, amask_ren_type r(pfa); amask_aa_renderer_type ren(r); - if (face.first) { - ren.color(face.second); + if (face) { + ren.color(*face); sa.init(fillBuffer.data(), fillBuffer.size(), x, y); agg::render_scanlines(sa, sl, ren); } @@ -633,8 +637,8 @@ inline void RendererAgg::draw_markers(GCAgg &gc, continue; } - if (face.first) { - rendererAA.color(face.second); + if (face) { + rendererAA.color(*face); sa.init(fillBuffer.data(), fillBuffer.size(), x, y); agg::render_scanlines(sa, sl, rendererAA); } @@ -936,10 +940,9 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, // Set some defaults, assuming no face or edge gc.linewidth = 0.0; - facepair_t face; - face.first = Nfacecolors != 0; + std::optional face; agg::trans_affine trans; - bool do_clip = !face.first && !gc.has_hatchpath(); + bool do_clip = Nfacecolors == 0 && !gc.has_hatchpath(); for (int i = 0; i < (int)N; ++i) { typename PathGenerator::path_iterator path = path_generator(i); @@ -970,7 +973,7 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, if (Nfacecolors) { int ic = i % Nfacecolors; - face.second = agg::rgba(facecolors(ic, 0), facecolors(ic, 1), facecolors(ic, 2), facecolors(ic, 3)); + face.emplace(facecolors(ic, 0), facecolors(ic, 1), facecolors(ic, 2), facecolors(ic, 3)); } if (Nedgecolors) { From 3ea5b633c38876dbe191b1959d49f71cf03b0442 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 23 May 2025 11:42:06 +0200 Subject: [PATCH 02/57] Fix label_outer in the presence of colorbars. The subgridspec to be considered should be the one containing both the axes and the colorbar, not the sub-subgridspec of just the axes. --- lib/matplotlib/axes/_base.py | 19 +++++++++++++++---- lib/matplotlib/colorbar.py | 4 ++-- lib/matplotlib/tests/test_subplots.py | 10 ++++++++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 1ce205ede613..87d42b4d3014 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -4749,14 +4749,25 @@ def label_outer(self, remove_inner_ticks=False): self._label_outer_yaxis(skip_non_rectangular_axes=False, remove_inner_ticks=remove_inner_ticks) + def _get_subplotspec_with_optional_colorbar(self): + """ + Return the subplotspec for this Axes, except that if this Axes has been + moved to a subgridspec to make room for a colorbar, then return the + subplotspec that encloses both this Axes and the colorbar Axes. + """ + ss = self.get_subplotspec() + if any(cax.get_subplotspec() for cax in self._colorbars): + ss = ss.get_gridspec()._subplot_spec + return ss + def _label_outer_xaxis(self, *, skip_non_rectangular_axes, remove_inner_ticks=False): # see documentation in label_outer. if skip_non_rectangular_axes and not isinstance(self.patch, mpl.patches.Rectangle): return - ss = self.get_subplotspec() - if not ss: + ss = self._get_subplotspec_with_optional_colorbar() + if ss is None: return label_position = self.xaxis.get_label_position() if not ss.is_first_row(): # Remove top label/ticklabels/offsettext. @@ -4782,8 +4793,8 @@ def _label_outer_yaxis(self, *, skip_non_rectangular_axes, if skip_non_rectangular_axes and not isinstance(self.patch, mpl.patches.Rectangle): return - ss = self.get_subplotspec() - if not ss: + ss = self._get_subplotspec_with_optional_colorbar() + if ss is None: return label_position = self.yaxis.get_label_position() if not ss.is_first_col(): # Remove left label/ticklabels/offsettext. diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 19bdbe605d88..4348f02cfc34 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1455,8 +1455,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, cax = fig.add_axes(pbcb, label="") for a in parents: - # tell the parent it has a colorbar - a._colorbars += [cax] + a._colorbars.append(cax) # tell the parent it has a colorbar cax._colorbar_info = dict( parents=parents, location=location, @@ -1549,6 +1548,7 @@ def make_axes_gridspec(parent, *, location=None, orientation=None, fig = parent.get_figure() cax = fig.add_subplot(ss_cb, label="") + parent._colorbars.append(cax) # tell the parent it has a colorbar cax.set_anchor(anchor) cax.set_box_aspect(aspect) cax.set_aspect('auto') diff --git a/lib/matplotlib/tests/test_subplots.py b/lib/matplotlib/tests/test_subplots.py index a899110ac77a..0f00a88aa72d 100644 --- a/lib/matplotlib/tests/test_subplots.py +++ b/lib/matplotlib/tests/test_subplots.py @@ -4,6 +4,7 @@ import numpy as np import pytest +import matplotlib as mpl from matplotlib.axes import Axes, SubplotBase import matplotlib.pyplot as plt from matplotlib.testing.decorators import check_figures_equal, image_comparison @@ -111,10 +112,15 @@ def test_shared(): @pytest.mark.parametrize('remove_ticks', [True, False]) -def test_label_outer(remove_ticks): - f, axs = plt.subplots(2, 2, sharex=True, sharey=True) +@pytest.mark.parametrize('layout_engine', ['none', 'tight', 'constrained']) +@pytest.mark.parametrize('with_colorbar', [True, False]) +def test_label_outer(remove_ticks, layout_engine, with_colorbar): + fig = plt.figure(layout=layout_engine) + axs = fig.subplots(2, 2, sharex=True, sharey=True) for ax in axs.flat: ax.set(xlabel="foo", ylabel="bar") + if with_colorbar: + fig.colorbar(mpl.cm.ScalarMappable(), ax=ax) ax.label_outer(remove_inner_ticks=remove_ticks) check_ticklabel_visible( axs.flat, [False, False, True, True], [True, False, True, False]) From 257430efe5a9d6b97fa00200c73cddf1ea9ec861 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 20 Jun 2025 05:15:23 -0400 Subject: [PATCH 03/57] ci: Enable wheel builds on Python 3.14 This should only end up on the nightly wheel upload for now. Also, re-enable testing in places where Pillow wheels were previously missing, but are now available. --- .github/workflows/cibuildwheel.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 57f1c71c5047..fececb0dfc40 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -140,6 +140,20 @@ jobs: name: cibw-sdist path: dist/ + - name: Build wheels for CPython 3.14 + uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + with: + package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} + env: + CIBW_BUILD: "cp314-* cp314t-*" + CIBW_ENABLE: "cpython-freethreading cpython-prerelease" + CIBW_ARCHS: ${{ matrix.cibw_archs }} + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_BEFORE_TEST: >- + python -m pip install + --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + --upgrade --pre --only-binary=:all: contourpy numpy pillow + - name: Build wheels for CPython 3.13 uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 with: @@ -147,8 +161,6 @@ jobs: env: CIBW_BUILD: "cp313-* cp313t-*" CIBW_ENABLE: cpython-freethreading - # No free-threading wheels available for aarch64 on Pillow. - CIBW_TEST_SKIP: "cp313t-manylinux_aarch64" CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.12 @@ -167,7 +179,6 @@ jobs: CIBW_BUILD: "cp311-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} - - name: Build wheels for PyPy uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 with: @@ -176,8 +187,6 @@ jobs: CIBW_BUILD: "pp311-*" CIBW_ARCHS: ${{ matrix.cibw_archs }} CIBW_ENABLE: pypy - # No wheels available for Pillow with pp311 yet. - CIBW_TEST_SKIP: "pp311*" if: matrix.cibw_archs != 'aarch64' && matrix.os != 'windows-latest' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 From b06d1f4d8d7199746063b1ef0d1a1c37ccf48188 Mon Sep 17 00:00:00 2001 From: chrisjbillington Date: Thu, 26 Jun 2025 12:38:33 +1000 Subject: [PATCH 04/57] Clean up Qt socket notifier to avoid spurious interrupt handler calls Closes #29688 Objects without a parent are not necessarily cleaned up in `PyQt5/6` when their reference count reaches zero, and must be explicitly cleaned up with `deleteLater()` This prevents the notifier firing after the signal handling was supposed to have been reset to its previous state. Rather than have both `bakend_bases._allow_interrupt()` and `backend_qt._allow_interrupt_qt()` hold a reference to the notifier, we pass it to the backend-specific `handle_signint()` function for cleanup. Note the approach to cleaning up the notifier with `.deleteLater()` followed by `sendPostedEvents()` is the documented workaround for when immediate deletion is desired: https://doc.qt.io/qt-6/qobject.html#deleteLater This ensures the object is still deleted up even if the same event loop does not run again. --- lib/matplotlib/backend_bases.py | 10 ++++++---- lib/matplotlib/backends/backend_qt.py | 11 ++++++++--- src/_macosx.m | 4 ++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 527d8c010710..626852f2aa34 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1619,7 +1619,8 @@ def _allow_interrupt(prepare_notifier, handle_sigint): If SIGINT was indeed caught, after exiting the on_signal() function the interpreter reacts to the signal according to the handler function which had been set up by a signal.signal() call; here, we arrange to call the - backend-specific *handle_sigint* function. Finally, we call the old SIGINT + backend-specific *handle_sigint* function, passing the notifier object + as returned by prepare_notifier(). Finally, we call the old SIGINT handler with the same arguments that were given to our custom handler. We do this only if the old handler for SIGINT was not None, which means @@ -1629,7 +1630,7 @@ def _allow_interrupt(prepare_notifier, handle_sigint): Parameters ---------- prepare_notifier : Callable[[socket.socket], object] - handle_sigint : Callable[[], object] + handle_sigint : Callable[[object], object] """ old_sigint_handler = signal.getsignal(signal.SIGINT) @@ -1645,9 +1646,10 @@ def _allow_interrupt(prepare_notifier, handle_sigint): notifier = prepare_notifier(rsock) def save_args_and_handle_sigint(*args): - nonlocal handler_args + nonlocal handler_args, notifier handler_args = args - handle_sigint() + handle_sigint(notifier) + notifier = None signal.signal(signal.SIGINT, save_args_and_handle_sigint) try: diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index 5cde4866cad7..9089e982cea6 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -169,9 +169,14 @@ def _may_clear_sock(): # be forgiving about reading an empty socket. pass - return sn # Actually keep the notifier alive. - - def handle_sigint(): + # We return the QSocketNotifier so that the caller holds a reference, and we + # also explicitly clean it up in handle_sigint(). Without doing both, deletion + # of the socket notifier can happen prematurely or not at all. + return sn + + def handle_sigint(sn): + sn.deleteLater() + QtCore.QCoreApplication.sendPostedEvents(sn, QtCore.QEvent.Type.DeferredDelete) if hasattr(qapp_or_eventloop, 'closeAllWindows'): qapp_or_eventloop.closeAllWindows() qapp_or_eventloop.quit() diff --git a/src/_macosx.m b/src/_macosx.m index aa2a6e68cda5..1372157bc80d 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -258,7 +258,7 @@ static void lazy_init(void) { } static PyObject* -stop(PyObject* self) +stop(PyObject* self, PyObject* _ /* ignored */) { stopWithEvent(); Py_RETURN_NONE; @@ -1863,7 +1863,7 @@ - (void)flagsChanged:(NSEvent *)event "written on the file descriptor given as argument.")}, {"stop", (PyCFunction)stop, - METH_NOARGS, + METH_VARARGS, PyDoc_STR("Stop the NSApp.")}, {"show", (PyCFunction)show, From ac863bc96a2b991fa4452a39b029feed0ccd88c4 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:09:40 -0400 Subject: [PATCH 05/57] BUG: Include python-including headers first --- src/ft2font.cpp | 6 +++--- src/ft2font.h | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index bdfa2873ca80..da1bd19dca57 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -1,5 +1,8 @@ /* -*- mode: c++; c-basic-offset: 4 -*- */ +#include "ft2font.h" +#include "mplutils.h" + #include #include #include @@ -9,9 +12,6 @@ #include #include -#include "ft2font.h" -#include "mplutils.h" - #ifndef M_PI #define M_PI 3.14159265358979323846264338328 #endif diff --git a/src/ft2font.h b/src/ft2font.h index 8db0239ed4fd..6676a7dd4818 100644 --- a/src/ft2font.h +++ b/src/ft2font.h @@ -6,6 +6,9 @@ #ifndef MPL_FT2FONT_H #define MPL_FT2FONT_H +#include +#include + #include #include #include @@ -22,8 +25,6 @@ extern "C" { #include FT_TRUETYPE_TABLES_H } -#include -#include namespace py = pybind11; // By definition, FT_FIXED as 2 16bit values stored in a single long. From 0b2fa3f952b6b97a121a3d7f05a0753b52b802cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Sat, 28 Jun 2025 11:39:49 +0200 Subject: [PATCH 06/57] BUG: fix future incompatibility with Pillow 13 --- lib/matplotlib/backends/_backend_tk.py | 2 +- lib/matplotlib/backends/backend_pdf.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 0bbff1379ffa..eaf868fd8bec 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -775,7 +775,7 @@ def _recolor_icon(image, color): image_data = np.asarray(image).copy() black_mask = (image_data[..., :3] == 0).all(axis=-1) image_data[black_mask, :3] = color - return Image.fromarray(image_data, mode="RGBA") + return Image.fromarray(image_data) # Use the high-resolution (48x48 px) icon if it exists and is needed with Image.open(path_large if (size > 24 and path_large.exists()) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index f20bdffd4a3a..4429dc9ba707 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1784,7 +1784,8 @@ def _writeImg(self, data, id, smask=None): data[:, :, 2]) indices = np.argsort(palette24).astype(np.uint8) rgb8 = indices[np.searchsorted(palette24, rgb24, sorter=indices)] - img = Image.fromarray(rgb8, mode='P') + img = Image.fromarray(rgb8) + img.convert("P") img.putpalette(palette) png_data, bit_depth, palette = self._writePng(img) if bit_depth is None or palette is None: From 7edd74a20b59d8b61a7349b59056b8706058878b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trygve=20Magnus=20R=C3=A6der?= Date: Sat, 28 Jun 2025 16:13:01 +0200 Subject: [PATCH 07/57] Abstract base class for Normalize (#30178) * Abstract base class for Normalize * Include the Norm ABC in the docs * Changed name of temporary class Norm to ScaleNorm in _make_norm_from_scale() * removal of inverse() in Norm ABC --- doc/api/colors_api.rst | 1 + lib/matplotlib/colorizer.py | 2 +- lib/matplotlib/colorizer.pyi | 14 +- lib/matplotlib/colors.py | 143 ++++++++++++------ lib/matplotlib/colors.pyi | 24 ++- .../test_colors/test_norm_abc.png | Bin 0 -> 16129 bytes lib/matplotlib/tests/test_colors.py | 47 ++++++ 7 files changed, 177 insertions(+), 54 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png diff --git a/doc/api/colors_api.rst b/doc/api/colors_api.rst index 6b02f723d74d..49a42c8f9601 100644 --- a/doc/api/colors_api.rst +++ b/doc/api/colors_api.rst @@ -21,6 +21,7 @@ Color norms :toctree: _as_gen/ :template: autosummary.rst + Norm Normalize NoNorm AsinhNorm diff --git a/lib/matplotlib/colorizer.py b/lib/matplotlib/colorizer.py index b4223f389804..92a6e4ea4c4f 100644 --- a/lib/matplotlib/colorizer.py +++ b/lib/matplotlib/colorizer.py @@ -90,7 +90,7 @@ def norm(self): @norm.setter def norm(self, norm): - _api.check_isinstance((colors.Normalize, str, None), norm=norm) + _api.check_isinstance((colors.Norm, str, None), norm=norm) if norm is None: norm = colors.Normalize() elif isinstance(norm, str): diff --git a/lib/matplotlib/colorizer.pyi b/lib/matplotlib/colorizer.pyi index f35ebe5295e4..9a5a73415d83 100644 --- a/lib/matplotlib/colorizer.pyi +++ b/lib/matplotlib/colorizer.pyi @@ -10,12 +10,12 @@ class Colorizer: def __init__( self, cmap: str | colors.Colormap | None = ..., - norm: str | colors.Normalize | None = ..., + norm: str | colors.Norm | None = ..., ) -> None: ... @property - def norm(self) -> colors.Normalize: ... + def norm(self) -> colors.Norm: ... @norm.setter - def norm(self, norm: colors.Normalize | str | None) -> None: ... + def norm(self, norm: colors.Norm | str | None) -> None: ... def to_rgba( self, x: np.ndarray, @@ -63,10 +63,10 @@ class _ColorizerInterface: def get_cmap(self) -> colors.Colormap: ... def set_cmap(self, cmap: str | colors.Colormap) -> None: ... @property - def norm(self) -> colors.Normalize: ... + def norm(self) -> colors.Norm: ... @norm.setter - def norm(self, norm: colors.Normalize | str | None) -> None: ... - def set_norm(self, norm: colors.Normalize | str | None) -> None: ... + def norm(self, norm: colors.Norm | str | None) -> None: ... + def set_norm(self, norm: colors.Norm | str | None) -> None: ... def autoscale(self) -> None: ... def autoscale_None(self) -> None: ... @@ -74,7 +74,7 @@ class _ColorizerInterface: class _ScalarMappable(_ColorizerInterface): def __init__( self, - norm: colors.Normalize | None = ..., + norm: colors.Norm | None = ..., cmap: str | colors.Colormap | None = ..., *, colorizer: Colorizer | None = ..., diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 254e2c1a203b..a09b4f3d4f5c 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -41,6 +41,7 @@ import base64 from collections.abc import Sequence, Mapping +from abc import ABC, abstractmethod import functools import importlib import inspect @@ -2257,7 +2258,87 @@ def _init(self): self._isinit = True -class Normalize: +class Norm(ABC): + """ + Abstract base class for normalizations. + + Subclasses include `Normalize` which maps from a scalar to + a scalar. However, this class makes no such requirement, and subclasses may + support the normalization of multiple variates simultaneously, with + separate normalization for each variate. + """ + + def __init__(self): + self.callbacks = cbook.CallbackRegistry(signals=["changed"]) + + @property + @abstractmethod + def vmin(self): + """Lower limit of the input data interval; maps to 0.""" + pass + + @property + @abstractmethod + def vmax(self): + """Upper limit of the input data interval; maps to 1.""" + pass + + @property + @abstractmethod + def clip(self): + """ + Determines the behavior for mapping values outside the range ``[vmin, vmax]``. + + See the *clip* parameter in `.Normalize`. + """ + pass + + @abstractmethod + def __call__(self, value, clip=None): + """ + Normalize the data and return the normalized data. + + Parameters + ---------- + value + Data to normalize. + clip : bool, optional + See the description of the parameter *clip* in `.Normalize`. + + If ``None``, defaults to ``self.clip`` (which defaults to + ``False``). + + Notes + ----- + If not already initialized, ``self.vmin`` and ``self.vmax`` are + initialized using ``self.autoscale_None(value)``. + """ + pass + + @abstractmethod + def autoscale(self, A): + """Set *vmin*, *vmax* to min, max of *A*.""" + pass + + @abstractmethod + def autoscale_None(self, A): + """If *vmin* or *vmax* are not set, use the min/max of *A* to set them.""" + pass + + @abstractmethod + def scaled(self): + """Return whether *vmin* and *vmax* are both set.""" + pass + + def _changed(self): + """ + Call this whenever the norm is changed to notify all the + callback listeners to the 'changed' signal. + """ + self.callbacks.process('changed') + + +class Normalize(Norm): """ A class which, when called, maps values within the interval ``[vmin, vmax]`` linearly to the interval ``[0.0, 1.0]``. The mapping of @@ -2307,15 +2388,15 @@ def __init__(self, vmin=None, vmax=None, clip=False): ----- If ``vmin == vmax``, input data will be mapped to 0. """ + super().__init__() self._vmin = _sanitize_extrema(vmin) self._vmax = _sanitize_extrema(vmax) self._clip = clip self._scale = None - self.callbacks = cbook.CallbackRegistry(signals=["changed"]) @property def vmin(self): - """Lower limit of the input data interval; maps to 0.""" + # docstring inherited return self._vmin @vmin.setter @@ -2327,7 +2408,7 @@ def vmin(self, value): @property def vmax(self): - """Upper limit of the input data interval; maps to 1.""" + # docstring inherited return self._vmax @vmax.setter @@ -2339,11 +2420,7 @@ def vmax(self, value): @property def clip(self): - """ - Determines the behavior for mapping values outside the range ``[vmin, vmax]``. - - See the *clip* parameter in `.Normalize`. - """ + # docstring inherited return self._clip @clip.setter @@ -2352,13 +2429,6 @@ def clip(self, value): self._clip = value self._changed() - def _changed(self): - """ - Call this whenever the norm is changed to notify all the - callback listeners to the 'changed' signal. - """ - self.callbacks.process('changed') - @staticmethod def process_value(value): """ @@ -2400,24 +2470,7 @@ def process_value(value): return result, is_scalar def __call__(self, value, clip=None): - """ - Normalize the data and return the normalized data. - - Parameters - ---------- - value - Data to normalize. - clip : bool, optional - See the description of the parameter *clip* in `.Normalize`. - - If ``None``, defaults to ``self.clip`` (which defaults to - ``False``). - - Notes - ----- - If not already initialized, ``self.vmin`` and ``self.vmax`` are - initialized using ``self.autoscale_None(value)``. - """ + # docstring inherited if clip is None: clip = self.clip @@ -2468,7 +2521,7 @@ def inverse(self, value): return vmin + value * (vmax - vmin) def autoscale(self, A): - """Set *vmin*, *vmax* to min, max of *A*.""" + # docstring inherited with self.callbacks.blocked(): # Pause callbacks while we are updating so we only get # a single update signal at the end @@ -2477,7 +2530,7 @@ def autoscale(self, A): self._changed() def autoscale_None(self, A): - """If *vmin* or *vmax* are not set, use the min/max of *A* to set them.""" + # docstring inherited A = np.asanyarray(A) if isinstance(A, np.ma.MaskedArray): @@ -2491,7 +2544,7 @@ def autoscale_None(self, A): self.vmax = A.max() def scaled(self): - """Return whether *vmin* and *vmax* are both set.""" + # docstring inherited return self.vmin is not None and self.vmax is not None @@ -2775,7 +2828,7 @@ def _make_norm_from_scale( unlike to arbitrary lambdas. """ - class Norm(base_norm_cls): + class ScaleNorm(base_norm_cls): def __reduce__(self): cls = type(self) # If the class is toplevel-accessible, it is possible to directly @@ -2855,15 +2908,15 @@ def autoscale_None(self, A): return super().autoscale_None(in_trf_domain) if base_norm_cls is Normalize: - Norm.__name__ = f"{scale_cls.__name__}Norm" - Norm.__qualname__ = f"{scale_cls.__qualname__}Norm" + ScaleNorm.__name__ = f"{scale_cls.__name__}Norm" + ScaleNorm.__qualname__ = f"{scale_cls.__qualname__}Norm" else: - Norm.__name__ = base_norm_cls.__name__ - Norm.__qualname__ = base_norm_cls.__qualname__ - Norm.__module__ = base_norm_cls.__module__ - Norm.__doc__ = base_norm_cls.__doc__ + ScaleNorm.__name__ = base_norm_cls.__name__ + ScaleNorm.__qualname__ = base_norm_cls.__qualname__ + ScaleNorm.__module__ = base_norm_cls.__module__ + ScaleNorm.__doc__ = base_norm_cls.__doc__ - return Norm + return ScaleNorm def _create_empty_object_of_class(cls): diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index eadd759bcaa3..cdc6e5e7d89f 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -1,4 +1,5 @@ from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence +from abc import ABC, abstractmethod from matplotlib import cbook, scale import re @@ -249,8 +250,29 @@ class BivarColormapFromImage(BivarColormap): origin: Sequence[float] = ..., name: str = ... ) -> None: ... -class Normalize: +class Norm(ABC): callbacks: cbook.CallbackRegistry + def __init__(self) -> None: ... + @property + @abstractmethod + def vmin(self) -> float | tuple[float] | None: ... + @property + @abstractmethod + def vmax(self) -> float | tuple[float] | None: ... + @property + @abstractmethod + def clip(self) -> bool | tuple[bool]: ... + @abstractmethod + def __call__(self, value: np.ndarray, clip: bool | None = ...) -> ArrayLike: ... + @abstractmethod + def autoscale(self, A: ArrayLike) -> None: ... + @abstractmethod + def autoscale_None(self, A: ArrayLike) -> None: ... + @abstractmethod + def scaled(self) -> bool: ... + + +class Normalize(Norm): def __init__( self, vmin: float | None = ..., vmax: float | None = ..., clip: bool = ... ) -> None: ... diff --git a/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png b/lib/matplotlib/tests/baseline_images/test_colors/test_norm_abc.png new file mode 100644 index 0000000000000000000000000000000000000000..077365674ac27803460ddb533d5efa09ea7ef9c7 GIT binary patch literal 16129 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 m@y{*)?+fJr!spj} Date: Sat, 28 Jun 2025 13:04:07 +0100 Subject: [PATCH 08/57] Fix polar inner patch boundary and spine location for log scale --- lib/matplotlib/projections/polar.py | 8 +++++++- lib/matplotlib/spines.py | 8 +++++++- lib/matplotlib/tests/test_polar.py | 20 ++++++++++++++++++++ lib/matplotlib/tests/test_spines.py | 12 ++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 948b3a6e704f..8fdb31b4256e 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -817,6 +817,10 @@ def _init_axis(self): self.xaxis = ThetaAxis(self, clear=False) self.yaxis = RadialAxis(self, clear=False) self.spines['polar'].register_axis(self.yaxis) + inner_spine = self.spines.get('inner', None) + if inner_spine is not None: + # Subclasses may not have inner spine. + inner_spine.register_axis(self.yaxis) def _set_lim_and_transforms(self): # A view limit where the minimum radius can be locked if the user @@ -961,7 +965,9 @@ def draw(self, renderer): thetamin, thetamax = np.rad2deg(self._realViewLim.intervalx) if thetamin > thetamax: thetamin, thetamax = thetamax, thetamin - rmin, rmax = ((self._realViewLim.intervaly - self.get_rorigin()) * + rscale_tr = self.yaxis.get_transform() + rmin, rmax = ((rscale_tr.transform(self._realViewLim.intervaly) - + rscale_tr.transform(self.get_rorigin())) * self.get_rsign()) if isinstance(self.patch, mpatches.Wedge): # Backwards-compatibility: Any subclassed Axes might override the diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index 7e77a393f2a2..9732a2f3347a 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -265,11 +265,17 @@ def _adjust_location(self): self._path = mpath.Path.arc(np.rad2deg(low), np.rad2deg(high)) if self.spine_type == 'bottom': - rmin, rmax = self.axes.viewLim.intervaly + if self.axis is None: + tr = mtransforms.IdentityTransform() + else: + tr = self.axis.get_transform() + rmin, rmax = tr.transform(self.axes.viewLim.intervaly) try: rorigin = self.axes.get_rorigin() except AttributeError: rorigin = rmin + else: + rorigin = tr.transform(rorigin) scaled_diameter = (rmin - rorigin) / (rmax - rorigin) self._height = scaled_diameter self._width = scaled_diameter diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py index 31e8cdd89a21..c0bf72b89eb0 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -482,6 +482,26 @@ def test_polar_log(): ax.plot(np.linspace(0, 2 * np.pi, n), np.logspace(0, 2, n)) +@check_figures_equal() +def test_polar_log_rorigin(fig_ref, fig_test): + # Test that equivalent linear and log radial settings give the same axes patch + # and spines. + ax_ref = fig_ref.add_subplot(projection='polar', facecolor='red') + ax_ref.set_rlim(0, 2) + ax_ref.set_rorigin(-3) + ax_ref.set_rticks(np.linspace(0, 2, 5)) + + ax_test = fig_test.add_subplot(projection='polar', facecolor='red') + ax_test.set_rscale('log') + ax_test.set_rlim(1, 100) + ax_test.set_rorigin(10**-3) + ax_test.set_rticks(np.logspace(0, 2, 5)) + + for ax in ax_ref, ax_test: + # Radial tick labels should be the only difference, so turn them off. + ax.tick_params(labelleft=False) + + def test_polar_neg_theta_lims(): fig = plt.figure() ax = fig.add_subplot(projection='polar') diff --git a/lib/matplotlib/tests/test_spines.py b/lib/matplotlib/tests/test_spines.py index 353aede00298..d6ddcabb6878 100644 --- a/lib/matplotlib/tests/test_spines.py +++ b/lib/matplotlib/tests/test_spines.py @@ -154,3 +154,15 @@ def test_spines_black_axes(): ax.set_xticks([]) ax.set_yticks([]) ax.set_facecolor((0, 0, 0)) + + +def test_arc_spine_inner_no_axis(): + # Backcompat: smoke test that inner arc spine does not need a registered + # axis in order to be drawn + fig = plt.figure() + ax = fig.add_subplot(projection="polar") + inner_spine = ax.spines["inner"] + inner_spine.register_axis(None) + assert ax.spines["inner"].axis is None + + fig.draw_without_rendering() From 6ede069136c6e9c7f1bceb9b1d4e264ba928aa55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Mon, 30 Jun 2025 13:41:34 +0200 Subject: [PATCH 09/57] fixup Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/backends/backend_pdf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 4429dc9ba707..a75a8a86eb92 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1784,8 +1784,7 @@ def _writeImg(self, data, id, smask=None): data[:, :, 2]) indices = np.argsort(palette24).astype(np.uint8) rgb8 = indices[np.searchsorted(palette24, rgb24, sorter=indices)] - img = Image.fromarray(rgb8) - img.convert("P") + img = Image.fromarray(rgb8).convert("P") img.putpalette(palette) png_data, bit_depth, palette = self._writePng(img) if bit_depth is None or palette is None: From 0e430b9b19f6cdcdbfc583392f6887686a47b74c Mon Sep 17 00:00:00 2001 From: ZPyrolink <73246085+ZPyrolink@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:43:52 +0200 Subject: [PATCH 10/57] Add explicit signatures for pyplot.{polar,savefig,set_loglevel} (#30200) * Create tests (cherry picked from commit 608b51fd6321ac07133b8d66a14e15a906e21169) * Update test_pyplot.py to include type on signature (cherry picked from commit 4ea0ff8e50f3a2460d18694aa1d58e7757c8726a) * Update polar and set_loglevel signature on pyplot.py (cherry picked from commit 41b701b41858cb2868485ca9f5747db4cd1f6d4a) * Update savefig signature on pyplot.py (cherry picked from commit b863ba298abe37c08c92f1ac1afc41f985d0bbff) * Add type hint on polar and set_loglevel on pyplot.py. Correct polar content (cherry picked from commit 92dc04501bab539586cac48a3266891c75a4cb7c) * Format with ruff (cherry picked from commit 64e7921b0b3f56c88c1f449a4f2081e862289279) * Revert polar on pyplot.py and remove corresponding test * Remove extra work when stub file doesn't exists Co-authored-by: Elliott Sales de Andrade * Replace assert_signatures_identical (check return_annotation and full parameters) with assert_same_signature (only check len(parameters), names and kinds) * Remove unused import * Renaming assert_signature arguments * Correct typo and ruff error --------- Co-authored-by: Corenthin ZOZOR Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/pyplot.py | 10 +++++----- lib/matplotlib/tests/test_pyplot.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index d77b06115268..8c9d1e1e5a29 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -50,7 +50,7 @@ import sys import threading import time -from typing import TYPE_CHECKING, cast, overload +from typing import IO, TYPE_CHECKING, cast, overload from cycler import cycler # noqa: F401 import matplotlib @@ -338,8 +338,8 @@ def uninstall_repl_displayhook() -> None: # Ensure this appears in the pyplot docs. @_copy_docstring_and_deprecators(matplotlib.set_loglevel) -def set_loglevel(*args, **kwargs) -> None: - return matplotlib.set_loglevel(*args, **kwargs) +def set_loglevel(level: str) -> None: + return matplotlib.set_loglevel(level) @_copy_docstring_and_deprecators(Artist.findobj) @@ -1259,11 +1259,11 @@ def draw() -> None: @_copy_docstring_and_deprecators(Figure.savefig) -def savefig(*args, **kwargs) -> None: +def savefig(fname: str | os.PathLike | IO, **kwargs) -> None: fig = gcf() # savefig default implementation has no return, so mypy is unhappy # presumably this is here because subclasses can return? - res = fig.savefig(*args, **kwargs) # type: ignore[func-returns-value] + res = fig.savefig(fname, **kwargs) # type: ignore[func-returns-value] fig.canvas.draw_idle() # Need this if 'transparent=True', to reset colors. return res diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index ab713707bace..55f7c33cb52e 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -1,4 +1,5 @@ import difflib +import inspect import numpy as np import sys @@ -484,3 +485,26 @@ def test_matshow(): # Smoke test that matshow does not ask for a new figsize on the existing figure plt.matshow(arr, fignum=fig.number) + + +def assert_same_signature(func1, func2): + """ + Assert that `func1` and `func2` have the same arguments, + i.e. same parameter count, names and kinds. + + :param func1: First function to check + :param func2: Second function to check + """ + params1 = inspect.signature(func1).parameters + params2 = inspect.signature(func2).parameters + + assert len(params1) == len(params2) + assert all([ + params1[p].name == params2[p].name and + params1[p].kind == params2[p].kind + for p in params1 + ]) + + +def test_setloglevel_signature(): + assert_same_signature(plt.set_loglevel, mpl.set_loglevel) From 4c06e718d4a28d14586b38fcad8d3734e901e719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 23:47:56 +0000 Subject: [PATCH 11/57] Bump github/codeql-action from 3.29.0 to 3.29.2 in the actions group Bumps the actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 3.29.0 to 3.29.2 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/ce28f5bb42b7a9f2c824e633a3f6ee835bab6858...181d5eefc20863364f96762470ba6f862bdef56b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.29.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c05454884b55..d6d1eba02560 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,7 +32,7 @@ jobs: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: languages: ${{ matrix.language }} @@ -43,4 +43,4 @@ jobs: pip install --user -v . - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 From b5235812a83c7eee9901757320c70120caea3d1e Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 1 Jul 2025 12:47:28 +0200 Subject: [PATCH 12/57] Copy-edit the docstring of AuxTransformBox. It can like be made even clearer; here I tried to just repeat what was written but with a clearer style. Some minor additional edits are included as well. --- lib/matplotlib/offsetbox.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index 1e07125cdc2a..974cc4f2db05 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -201,7 +201,7 @@ def _get_aligned_offsets(yspans, height, align="baseline"): class OffsetBox(martist.Artist): """ - The OffsetBox is a simple container artist. + A simple container artist. The child artists are meant to be drawn at a relative position to its parent. @@ -826,17 +826,18 @@ def draw(self, renderer): class AuxTransformBox(OffsetBox): """ - Offset Box with the aux_transform. Its children will be - transformed with the aux_transform first then will be - offsetted. The absolute coordinate of the aux_transform is meaning - as it will be automatically adjust so that the left-lower corner - of the bounding box of children will be set to (0, 0) before the - offset transform. - - It is similar to drawing area, except that the extent of the box - is not predetermined but calculated from the window extent of its - children. Furthermore, the extent of the children will be - calculated in the transformed coordinate. + An OffsetBox with an auxiliary transform. + + All child artists are first transformed with *aux_transform*, then + translated with an offset (the same for all children) so the bounding + box of the children matches the drawn box. (In other words, adding an + arbitrary translation to *aux_transform* has no effect as it will be + cancelled out by the later offsetting.) + + `AuxTransformBox` is similar to `.DrawingArea`, except that the extent of + the box is not predetermined but calculated from the window extent of its + children, and the extent of the children will be calculated in the + transformed coordinate. """ def __init__(self, aux_transform): self.aux_transform = aux_transform @@ -853,10 +854,7 @@ def add_artist(self, a): self.stale = True def get_transform(self): - """ - Return the :class:`~matplotlib.transforms.Transform` applied - to the children - """ + """Return the `.Transform` applied to the children.""" return (self.aux_transform + self.ref_offset_transform + self.offset_transform) @@ -908,7 +906,7 @@ def draw(self, renderer): class AnchoredOffsetbox(OffsetBox): """ - An offset box placed according to location *loc*. + An OffsetBox placed according to location *loc*. AnchoredOffsetbox has a single child. When multiple children are needed, use an extra OffsetBox to enclose them. By default, the offset box is From 1295158096805f6675cbb42f982ff6c1402e9ba7 Mon Sep 17 00:00:00 2001 From: Roman A <121314722+GameRoMan@users.noreply.github.com> Date: Tue, 1 Jul 2025 16:13:17 +0100 Subject: [PATCH 13/57] Add explicit `**options: Any` for `add_subplot` method to remove "partially unknown type" warnings from type checkers like Pyright and Pylance --- lib/matplotlib/figure.pyi | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi index e7c5175d8af9..61dc79619a80 100644 --- a/lib/matplotlib/figure.pyi +++ b/lib/matplotlib/figure.pyi @@ -89,19 +89,20 @@ class FigureBase(Artist): # TODO: docstring indicates SubplotSpec a valid arg, but none of the listed signatures appear to be that @overload - def add_subplot(self, *args, projection: Literal["3d"], **kwargs) -> Axes3D: ... + def add_subplot(self, *args: Any, projection: Literal["3d"], **kwargs: Any) -> Axes3D: ... @overload def add_subplot( - self, nrows: int, ncols: int, index: int | tuple[int, int], **kwargs + self, nrows: int, ncols: int, index: int | tuple[int, int], **kwargs: Any ) -> Axes: ... @overload - def add_subplot(self, pos: int, **kwargs) -> Axes: ... + def add_subplot(self, pos: int, **kwargs: Any) -> Axes: ... @overload - def add_subplot(self, ax: Axes, **kwargs) -> Axes: ... + def add_subplot(self, ax: Axes, **kwargs: Any) -> Axes: ... @overload - def add_subplot(self, ax: SubplotSpec, **kwargs) -> Axes: ... + def add_subplot(self, ax: SubplotSpec, **kwargs: Any) -> Axes: ... @overload - def add_subplot(self, **kwargs) -> Axes: ... + def add_subplot(self, **kwargs: Any) -> Axes: ... + @overload def subplots( self, From c8361f0a33c8e88abf8f3862dce8388bd1a00eac Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Wed, 2 Jul 2025 07:36:04 +0200 Subject: [PATCH 14/57] DOC: Recommend to use bare Figure instances for saving to file --- doc/users/faq.rst | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/doc/users/faq.rst b/doc/users/faq.rst index b08bd75cee4e..c6bbc5ca8d87 100644 --- a/doc/users/faq.rst +++ b/doc/users/faq.rst @@ -281,8 +281,23 @@ locators as desired because the two axes are independent. Generate images without having a window appear ---------------------------------------------- -Simply do not call `~matplotlib.pyplot.show`, and directly save the figure to -the desired format:: +The recommended approach since matplotlib 3.1 is to explicitly create a Figure +instance:: + + from matplotlib.figure import Figure + fig = Figure() + ax = fig.subplots() + ax.plot([1, 2, 3]) + fig.savefig('myfig.png') + +This prevents any interaction with GUI frameworks and the window manager. + +It's alternatively still possible to use the pyplot interface. Instead of +calling `matplotlib.pyplot.show`, call `matplotlib.pyplot.savefig`. + +Additionally, you must ensure to close the figure after saving it. Not +closing the figure is a memory leak, because pyplot keeps references +to all not-yet-shown figures:: import matplotlib.pyplot as plt plt.plot([1, 2, 3]) From 919c7b9c440fe97393ee8e15ff35ddc6a338c5c6 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 2 Jul 2025 08:34:51 +0200 Subject: [PATCH 15/57] adjust logic to allow inherritance --- lib/matplotlib/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 8ffd002d43bc..8f672af70ebc 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -799,13 +799,13 @@ def find_all(self, pattern): """ pattern_re = re.compile(pattern) - return RcParams((key, value) - for key, value in self.items() - if pattern_re.search(key)) + return self.__class__( + (key, value) for key, value in self.items() if pattern_re.search(key) + ) def copy(self): """Copy this RcParams instance.""" - rccopy = RcParams() + rccopy = self.__class__() for k in self: # Skip deprecations and revalidation. rccopy._set(k, self._get(k)) return rccopy From 77f868cbcc5e7cd6e8860fd5eb7934eb9397181c Mon Sep 17 00:00:00 2001 From: ianlv Date: Wed, 2 Jul 2025 15:54:25 +0800 Subject: [PATCH 16/57] chore: remove redundant words in comment Signed-off-by: ianlv --- doc/devel/tag_guidelines.rst | 2 +- doc/users/prev_whats_new/whats_new_3.8.0.rst | 4 ++-- galleries/examples/scales/custom_scale.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/devel/tag_guidelines.rst b/doc/devel/tag_guidelines.rst index 2c80065982bc..2ff77d5279d5 100644 --- a/doc/devel/tag_guidelines.rst +++ b/doc/devel/tag_guidelines.rst @@ -61,7 +61,7 @@ Proposing new tags 1. Review existing tag list, looking out for similar entries (i.e. ``axes`` and ``axis``). 2. If a relevant tag or subcategory does not yet exist, propose it. Each tag is two parts: ``subcategory: tag``. Tags should be one or two words. -3. New tags should be be added when they are relevant to existing gallery entries too. +3. New tags should be added when they are relevant to existing gallery entries too. Avoid tags that will link to only a single gallery entry. 4. Tags can recreate other forms of organization. diff --git a/doc/users/prev_whats_new/whats_new_3.8.0.rst b/doc/users/prev_whats_new/whats_new_3.8.0.rst index 88f987172adb..fe1d5f7a7952 100644 --- a/doc/users/prev_whats_new/whats_new_3.8.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.8.0.rst @@ -359,7 +359,7 @@ The following delimiter names have been supported earlier, but can now be sized * ``\leftparen`` and ``\rightparen`` There are really no obvious advantages in using these. -Instead, they are are added for completeness. +Instead, they are added for completeness. ``mathtext`` documentation improvements --------------------------------------- @@ -513,7 +513,7 @@ Plot Directive now can make responsive images with "srcset" The plot sphinx directive (``matplotlib.sphinxext.plot_directive``, invoked in rst as ``.. plot::``) can be configured to automatically make higher res -figures and add these to the the built html docs. In ``conf.py``:: +figures and add these to the built html docs. In ``conf.py``:: extensions = [ ... diff --git a/galleries/examples/scales/custom_scale.py b/galleries/examples/scales/custom_scale.py index 0eedb16ec5cf..1b6bdd6f3e09 100644 --- a/galleries/examples/scales/custom_scale.py +++ b/galleries/examples/scales/custom_scale.py @@ -22,7 +22,7 @@ * You want to override the default locators and formatters for the axis (``set_default_locators_and_formatters`` below). - * You want to limit the range of the the axis (``limit_range_for_scale`` below). + * You want to limit the range of the axis (``limit_range_for_scale`` below). """ From 70d5ad48fc26045739d5c57538686f7619cfa8bb Mon Sep 17 00:00:00 2001 From: N R Navaneet <156576749+nrnavaneet@users.noreply.github.com> Date: Thu, 3 Jul 2025 10:44:51 +0530 Subject: [PATCH 17/57] Fix FancyArrow rendering for zero-length arrows (#30243) * WIP: changed FancyArrow empty logic * Update test_patches.py * Update test_patches.py * Fix crash when drawing FancyArrow with zero length * Update patches.py * Delete .python-version --- lib/matplotlib/patches.py | 2 +- lib/matplotlib/tests/test_patches.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 63453d416b99..477eee9f5a7a 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1538,7 +1538,7 @@ def _make_verts(self): length = distance else: length = distance + head_length - if not length: + if np.size(length) == 0: self.verts = np.empty([0, 2]) # display nothing if empty else: # start by drawing horizontal arrow, point at (0, 0) diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 4ed9222eb95e..d69a9dad4337 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -1093,3 +1093,9 @@ def test_facecolor_none_edgecolor_force_edgecolor(): rcParams['patch.edgecolor'] = 'red' rect = Rectangle((0, 0), 1, 1, facecolor="none") assert mcolors.same_color(rect.get_edgecolor(), rcParams['patch.edgecolor']) + + +def test_empty_fancyarrow(): + fig, ax = plt.subplots() + arrow = ax.arrow([], [], [], []) + assert arrow is not None From a87684faa62f86e65da21bfb0942046e5e59b55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Tue, 1 Jul 2025 19:28:51 +0300 Subject: [PATCH 18/57] Upgrade to Visual Studio 2022 in appveyor.yml --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index a637fe545466..c3fcb0ea9591 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -17,7 +17,7 @@ skip_commits: clone_depth: 50 -image: Visual Studio 2019 +image: Visual Studio 2022 environment: From d231a25efa0764fc9cbe2e2f4e7aa3f56cb75b7c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 1 Jul 2025 05:13:45 -0400 Subject: [PATCH 19/57] Don't expose private styles in style.available They remain in `style.library`, because that's how we look them up, but this prevents them being exposed as something someone might use. Also, fix `reload_library`, which was accidentally modifying the original base library information each time. Fixes itprojects/MasVisGtk#13 --- lib/matplotlib/style/__init__.py | 7 ++++--- lib/matplotlib/tests/test_style.py | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/style/__init__.py b/lib/matplotlib/style/__init__.py index a202cfe08b20..80c6de00a18d 100644 --- a/lib/matplotlib/style/__init__.py +++ b/lib/matplotlib/style/__init__.py @@ -6,7 +6,8 @@ ``context`` Context manager to use a style sheet temporarily. ``available`` - List available style sheets. + List available style sheets. Underscore-prefixed names are considered private and + not listed, though may still be accessed directly from ``library``. ``library`` A dictionary of style names and matplotlib settings. """ @@ -245,8 +246,8 @@ def update_nested_dict(main_dict, new_dict): def reload_library(): """Reload the style library.""" library.clear() - library.update(_update_user_library(_base_library)) - available[:] = sorted(library.keys()) + library.update(_update_user_library(_base_library.copy())) + available[:] = sorted(name for name in library if not name.startswith('_')) reload_library() diff --git a/lib/matplotlib/tests/test_style.py b/lib/matplotlib/tests/test_style.py index 4d76a4ecfa8b..7b54f1141720 100644 --- a/lib/matplotlib/tests/test_style.py +++ b/lib/matplotlib/tests/test_style.py @@ -21,6 +21,7 @@ def temp_style(style_name, settings=None): if not settings: settings = DUMMY_SETTINGS temp_file = f'{style_name}.mplstyle' + orig_library_paths = style.USER_LIBRARY_PATHS try: with TemporaryDirectory() as tmpdir: # Write style settings to file in the tmpdir. @@ -32,6 +33,7 @@ def temp_style(style_name, settings=None): style.reload_library() yield finally: + style.USER_LIBRARY_PATHS = orig_library_paths style.reload_library() @@ -46,8 +48,17 @@ def test_invalid_rc_warning_includes_filename(caplog): def test_available(): - with temp_style('_test_', DUMMY_SETTINGS): - assert '_test_' in style.available + # Private name should not be listed in available but still usable. + assert '_classic_test_patch' not in style.available + assert '_classic_test_patch' in style.library + + with temp_style('_test_', DUMMY_SETTINGS), temp_style('dummy', DUMMY_SETTINGS): + assert 'dummy' in style.available + assert 'dummy' in style.library + assert '_test_' not in style.available + assert '_test_' in style.library + assert 'dummy' not in style.available + assert '_test_' not in style.available def test_use(): From 6a01311f9181f3ba1801034cb81b31a427deae03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 3 Jul 2025 19:37:49 +0300 Subject: [PATCH 20/57] Time out in _get_executable_info Time out after 30 seconds. This is used for version queries which should be very fast, so a 30-second delay would be unusual. GitHub Actions test runs have been hanging trying to get the inkscape version when using Python 3.14: https://github.com/matplotlib/matplotlib/actions/runs/16043158943/job/45268507848#step:13:836 --- lib/matplotlib/__init__.py | 5 ++++- lib/matplotlib/tests/test_matplotlib.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 8f672af70ebc..e98e8ea07502 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -400,12 +400,15 @@ def impl(args, regex, min_ver=None, ignore_exit_code=False): try: output = subprocess.check_output( args, stderr=subprocess.STDOUT, - text=True, errors="replace") + text=True, errors="replace", timeout=30) except subprocess.CalledProcessError as _cpe: if ignore_exit_code: output = _cpe.output else: raise ExecutableNotFoundError(str(_cpe)) from _cpe + except subprocess.TimeoutExpired as _te: + msg = f"Timed out running {cbook._pformat_subprocess(args)}" + raise ExecutableNotFoundError(msg) from _te except OSError as _ose: raise ExecutableNotFoundError(str(_ose)) from _ose match = re.search(regex, output) diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index 37b41fafdb78..d0a3f8c617e1 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -1,6 +1,7 @@ import os import subprocess import sys +from unittest.mock import patch import pytest @@ -80,3 +81,16 @@ def test_importable_with__OO(): [sys.executable, "-OO", "-c", program], env={**os.environ, "MPLBACKEND": ""}, check=True ) + + +@patch('matplotlib.subprocess.check_output') +def test_get_executable_info_timeout(mock_check_output): + """ + Test that _get_executable_info raises ExecutableNotFoundError if the + command times out. + """ + + mock_check_output.side_effect = subprocess.TimeoutExpired(cmd=['mock'], timeout=30) + + with pytest.raises(matplotlib.ExecutableNotFoundError, match='Timed out'): + matplotlib._get_executable_info.__wrapped__('inkscape') From bf96be4bc0a53e947b6993238b61be4081461cae Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 3 Jul 2025 23:52:36 -0400 Subject: [PATCH 21/57] Fix linting in test notebooks --- .../backends/web_backend/nbagg_uat.ipynb | 27 ++++++++++--------- pyproject.toml | 3 +-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb index e9fc62bc2883..0513fee2b54c 100644 --- a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb +++ b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb @@ -309,7 +309,7 @@ "metadata": {}, "outputs": [], "source": [ - "from matplotlib.backends.backend_nbagg import new_figure_manager,show\n", + "from matplotlib.backends.backend_nbagg import new_figure_manager\n", "\n", "manager = new_figure_manager(1000)\n", "fig = manager.canvas.figure\n", @@ -341,15 +341,18 @@ "x = np.arange(0, 2*np.pi, 0.01) # x-array\n", "line, = ax.plot(x, np.sin(x))\n", "\n", + "\n", "def animate(i):\n", " line.set_ydata(np.sin(x+i/10.0)) # update the data\n", " return line,\n", "\n", - "#Init only required for blitting to give a clean slate.\n", + "\n", + "# Init only required for blitting to give a clean slate.\n", "def init():\n", " line.set_ydata(np.ma.array(x, mask=True))\n", " return line,\n", "\n", + "\n", "ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,\n", " interval=100., blit=True)\n", "plt.show()" @@ -405,6 +408,8 @@ "ln, = ax.plot(x,y)\n", "evt = []\n", "colors = iter(itertools.cycle(['r', 'g', 'b', 'k', 'c']))\n", + "\n", + "\n", "def on_event(event):\n", " if event.name.startswith('key'):\n", " fig.suptitle('%s: %s' % (event.name, event.key))\n", @@ -417,6 +422,7 @@ " fig.canvas.draw()\n", " fig.canvas.draw_idle()\n", "\n", + "\n", "fig.canvas.mpl_connect('button_press_event', on_event)\n", "fig.canvas.mpl_connect('button_release_event', on_event)\n", "fig.canvas.mpl_connect('scroll_event', on_event)\n", @@ -448,10 +454,12 @@ "fig, ax = plt.subplots()\n", "text = ax.text(0.5, 0.5, '', ha='center')\n", "\n", + "\n", "def update(text):\n", " text.set(text=time.ctime())\n", " text.axes.figure.canvas.draw()\n", - " \n", + "\n", + "\n", "timer = fig.canvas.new_timer(500, [(update, [text], {})])\n", "timer.start()\n", "plt.show()" @@ -471,7 +479,7 @@ "outputs": [], "source": [ "fig, ax = plt.subplots()\n", - "text = ax.text(0.5, 0.5, '', ha='center') \n", + "text = ax.text(0.5, 0.5, '', ha='center')\n", "timer = fig.canvas.new_timer(500, [(update, [text], {})])\n", "\n", "timer.single_shot = True\n", @@ -578,11 +586,12 @@ "cnt = itertools.count()\n", "bg = None\n", "\n", + "\n", "def onclick_handle(event):\n", " \"\"\"Should draw elevating green line on each mouse click\"\"\"\n", " global bg\n", " if bg is None:\n", - " bg = ax.figure.canvas.copy_from_bbox(ax.bbox) \n", + " bg = ax.figure.canvas.copy_from_bbox(ax.bbox)\n", " ax.figure.canvas.restore_region(bg)\n", "\n", " cur_y = (next(cnt) % 10) * 0.1\n", @@ -590,6 +599,7 @@ " ax.draw_artist(ln)\n", " ax.figure.canvas.blit(ax.bbox)\n", "\n", + "\n", "fig, ax = plt.subplots()\n", "ax.plot([0, 1], [0, 1], 'r')\n", "ln, = ax.plot([0, 1], [0, 0], 'g', animated=True)\n", @@ -598,13 +608,6 @@ "\n", "ax.figure.canvas.mpl_connect('button_press_event', onclick_handle)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/pyproject.toml b/pyproject.toml index b980e512769a..ba2f9d29cf28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,8 +101,6 @@ exclude = [ "tools/gh_api.py", ".tox", ".eggs", - # TODO: fix .ipynb files - "*.ipynb" ] line-length = 88 target-version = "py311" @@ -174,6 +172,7 @@ convention = "numpy" [tool.ruff.lint.per-file-ignores] "*.pyi" = ["E501"] +"*.ipynb" = ["E402"] "doc/conf.py" = ["E402"] "galleries/examples/animation/frame_grabbing_sgskip.py" = ["E402"] "galleries/examples/images_contours_and_fields/tricontour_demo.py" = ["E201"] From ca3d239bc8f2fcb52a459346bd682056dea9fdd1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Jul 2025 00:11:38 -0400 Subject: [PATCH 22/57] Clean up unused ruff config ruff uses `project.requires-python`, so the ruff-only `target-version` is not needed. Other exceptions can be dropped since they are all clear of warnings. --- pyproject.toml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ba2f9d29cf28..8f8c73a3344f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,17 +93,13 @@ sections = "FUTURE,STDLIB,THIRDPARTY,PYDATA,FIRSTPARTY,LOCALFOLDER" force_sort_within_sections = true [tool.ruff] -exclude = [ - ".git", +extend-exclude = [ "build", "doc/gallery", "doc/tutorials", "tools/gh_api.py", - ".tox", - ".eggs", ] line-length = 88 -target-version = "py311" [tool.ruff.lint] ignore = [ @@ -129,9 +125,7 @@ ignore = [ "D404", "D413", "D415", - "D416", "D417", - "E24", "E266", "E305", "E306", @@ -174,14 +168,11 @@ convention = "numpy" "*.pyi" = ["E501"] "*.ipynb" = ["E402"] "doc/conf.py" = ["E402"] -"galleries/examples/animation/frame_grabbing_sgskip.py" = ["E402"] "galleries/examples/images_contours_and_fields/tricontour_demo.py" = ["E201"] "galleries/examples/images_contours_and_fields/tripcolor_demo.py" = ["E201"] "galleries/examples/images_contours_and_fields/triplot_demo.py" = ["E201"] "galleries/examples/lines_bars_and_markers/marker_reference.py" = ["E402"] -"galleries/examples/misc/print_stdout_sgskip.py" = ["E402"] "galleries/examples/misc/table_demo.py" = ["E201"] -"galleries/examples/style_sheets/bmh.py" = ["E501"] "galleries/examples/subplots_axes_and_figures/demo_constrained_layout.py" = ["E402"] "galleries/examples/text_labels_and_annotations/custom_legends.py" = ["E402"] "galleries/examples/ticks/date_concise_formatter.py" = ["E402"] @@ -210,11 +201,9 @@ convention = "numpy" "lib/mpl_toolkits/axisartist/angle_helper.py" = ["E221"] "lib/mpl_toolkits/mplot3d/proj3d.py" = ["E201"] -"galleries/users_explain/artists/paths.py" = ["E402"] "galleries/users_explain/quick_start.py" = ["E402"] "galleries/users_explain/artists/patheffects_guide.py" = ["E402"] -"galleries/users_explain/artists/transforms_tutorial.py" = ["E402", "E501"] -"galleries/users_explain/colors/colormaps.py" = ["E501"] +"galleries/users_explain/artists/transforms_tutorial.py" = ["E402"] "galleries/users_explain/colors/colors.py" = ["E402"] "galleries/tutorials/artists.py" = ["E402"] "galleries/users_explain/axes/constrainedlayout_guide.py" = ["E402"] @@ -224,7 +213,6 @@ convention = "numpy" "galleries/tutorials/images.py" = ["E501"] "galleries/tutorials/pyplot.py" = ["E402", "E501"] "galleries/users_explain/text/annotations.py" = ["E402", "E501"] -"galleries/users_explain/text/mathtext.py" = ["E501"] "galleries/users_explain/text/text_intro.py" = ["E402"] "galleries/users_explain/text/text_props.py" = ["E501"] From 18f93ee05c75985c17a9336c92c77bc03d719cd4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Jul 2025 00:43:21 -0400 Subject: [PATCH 23/57] Clean up mypy settings The removed excludes are covered by the first line. The Unpack feature was enabled by default in 1.9, which is our minimum. --- pyproject.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8f8c73a3344f..6202a386069c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -223,18 +223,12 @@ enable_error_code = [ "redundant-expr", "truthy-bool", ] -enable_incomplete_feature = [ - "Unpack", -] exclude = [ #stubtest ".*/matplotlib/(sphinxext|backends|pylab|testing/jpl_units)", #mypy precommit "galleries/", "doc/", - "lib/matplotlib/backends/", - "lib/matplotlib/sphinxext", - "lib/matplotlib/testing/jpl_units", "lib/mpl_toolkits/", #removing tests causes errors in backends "lib/matplotlib/tests/", From f4398bf068e49bcc39b3f6f70106b5cad0587cee Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Jul 2025 05:00:44 -0400 Subject: [PATCH 24/57] Clean up some simple linting exceptions These examples only needed a single fix to remove the exception. --- galleries/examples/ticks/date_formatters_locators.py | 1 + galleries/tutorials/images.py | 4 ++-- pyproject.toml | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/galleries/examples/ticks/date_formatters_locators.py b/galleries/examples/ticks/date_formatters_locators.py index 39492168242f..8c3b24bb4c26 100644 --- a/galleries/examples/ticks/date_formatters_locators.py +++ b/galleries/examples/ticks/date_formatters_locators.py @@ -12,6 +12,7 @@ import matplotlib.pyplot as plt import numpy as np +# While these appear unused directly, they are used from eval'd strings. from matplotlib.dates import (FR, MO, MONTHLY, SA, SU, TH, TU, WE, AutoDateFormatter, AutoDateLocator, ConciseDateFormatter, DateFormatter, DayLocator, diff --git a/galleries/tutorials/images.py b/galleries/tutorials/images.py index 0867f7b6d672..a7c474dab40b 100644 --- a/galleries/tutorials/images.py +++ b/galleries/tutorials/images.py @@ -33,8 +33,8 @@ In [1]: %matplotlib inline -This turns on inline plotting, where plot graphics will appear in your -notebook. This has important implications for interactivity. For inline plotting, commands in +This turns on inline plotting, where plot graphics will appear in your notebook. This +has important implications for interactivity. For inline plotting, commands in cells below the cell that outputs a plot will not affect the plot. For example, changing the colormap is not possible from cells below the cell that creates a plot. However, for other backends, such as Qt, that open a separate window, diff --git a/pyproject.toml b/pyproject.toml index 6202a386069c..b580feff930e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -210,7 +210,6 @@ convention = "numpy" "galleries/users_explain/axes/legend_guide.py" = ["E402"] "galleries/users_explain/axes/tight_layout_guide.py" = ["E402"] "galleries/users_explain/animations/animations.py" = ["E501"] -"galleries/tutorials/images.py" = ["E501"] "galleries/tutorials/pyplot.py" = ["E402", "E501"] "galleries/users_explain/text/annotations.py" = ["E402", "E501"] "galleries/users_explain/text/text_intro.py" = ["E402"] From 757282354c88eb25610a2ca60a4dda88f2053fda Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 1 Jul 2025 03:47:19 -0400 Subject: [PATCH 25/57] Check that stem input is 1D This also has the side-effect of casting torch Tensors to NumPy arrays, which fixes #30216. Since `stem` is made up of `plot` and `[hv]lines` whic already do this cast, this just moves it up one level which prevents doing it twice. --- lib/matplotlib/axes/_axes.py | 3 +++ lib/matplotlib/tests/test_axes.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b4ed7ae22d35..8ac300296538 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3436,6 +3436,9 @@ def stem(self, *args, linefmt=None, markerfmt=None, basefmt=None, bottom=0, else: # horizontal heads, locs = self._process_unit_info([("x", heads), ("y", locs)]) + heads = cbook._check_1d(heads) + locs = cbook._check_1d(locs) + # resolve line format if linefmt is None: linefmt = args[0] if len(args) > 0 else "C0-" diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index db0629de99b5..bba9f8648799 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4744,6 +4744,11 @@ def _assert_equal(stem_container, expected): _assert_equal(ax.stem(y, linefmt='r--'), expected=([0, 1, 2], y)) _assert_equal(ax.stem(y, 'r--'), expected=([0, 1, 2], y)) + with pytest.raises(ValueError): + ax.stem([[y]]) + with pytest.raises(ValueError): + ax.stem([[x]], y) + def test_stem_markerfmt(): """Test that stem(..., markerfmt=...) produces the intended markers.""" From e78735bb8e0375205c7597a6a151c5972ff31426 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 4 Jul 2025 23:32:49 +0200 Subject: [PATCH 26/57] Tweak docstrings of get_window_extent/get_tightbbox. Make the difference between the two methods clearer (only the latter takes clipping into account). --- lib/matplotlib/artist.py | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index fd35b312835a..50c9842ff010 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -321,13 +321,12 @@ def stale(self, val): def get_window_extent(self, renderer=None): """ - Get the artist's bounding box in display space. + Get the artist's bounding box in display space, ignoring clipping. The bounding box's width and height are non-negative. - Subclasses should override for inclusion in the bounding box - "tight" calculation. Default is to return an empty bounding - box at 0, 0. + Subclasses should override for inclusion in the bounding box "tight" + calculation. Default is to return an empty bounding box at 0, 0. .. warning:: @@ -341,28 +340,40 @@ def get_window_extent(self, renderer=None): screen render incorrectly when saved to file. To get accurate results you may need to manually call - `matplotlib.figure.Figure.savefig` or - `matplotlib.figure.Figure.draw_without_rendering` to have Matplotlib - compute the rendered size. + `~.Figure.savefig` or `~.Figure.draw_without_rendering` to have + Matplotlib compute the rendered size. + Parameters + ---------- + renderer : `~matplotlib.backend_bases.RendererBase`, optional + Renderer used to draw the figure (i.e. ``fig.canvas.get_renderer()``). + + See Also + -------- + `~.Artist.get_tightbbox` : + Get the artist bounding box, taking clipping into account. """ return Bbox([[0, 0], [0, 0]]) def get_tightbbox(self, renderer=None): """ - Like `.Artist.get_window_extent`, but includes any clipping. + Get the artist's bounding box in display space, taking clipping into account. Parameters ---------- - renderer : `~matplotlib.backend_bases.RendererBase` subclass, optional - renderer that will be used to draw the figures (i.e. - ``fig.canvas.get_renderer()``) + renderer : `~matplotlib.backend_bases.RendererBase`, optional + Renderer used to draw the figure (i.e. ``fig.canvas.get_renderer()``). Returns ------- `.Bbox` or None - The enclosing bounding box (in figure pixel coordinates). - Returns None if clipping results in no intersection. + The enclosing bounding box (in figure pixel coordinates), or None + if clipping results in no intersection. + + See Also + -------- + `~.Artist.get_window_extent` : + Get the artist bounding box, ignoring clipping. """ bbox = self.get_window_extent(renderer) if self.get_clip_on(): From 9c3f73bad3952ba3cb0cbde904e989d38e7ccaa2 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sat, 5 Jul 2025 19:33:04 +0100 Subject: [PATCH 27/57] DOC: fix artist see also sections --- lib/matplotlib/artist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 50c9842ff010..eaaae43e283a 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -350,7 +350,7 @@ def get_window_extent(self, renderer=None): See Also -------- - `~.Artist.get_tightbbox` : + .Artist.get_tightbbox : Get the artist bounding box, taking clipping into account. """ return Bbox([[0, 0], [0, 0]]) @@ -372,7 +372,7 @@ def get_tightbbox(self, renderer=None): See Also -------- - `~.Artist.get_window_extent` : + .Artist.get_window_extent : Get the artist bounding box, ignoring clipping. """ bbox = self.get_window_extent(renderer) From db6cf1579e6da5c787d89b47088b9ce853343f04 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 18 Jun 2025 23:06:16 +0100 Subject: [PATCH 28/57] Fix figure legend when drawing stackplots --- lib/matplotlib/legend_handler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index 263945b050d0..65a78891b17f 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -799,7 +799,6 @@ def get_first(prop_array): legend_handle.set_linewidth(get_first(orig_handle.get_linewidths())) legend_handle.set_linestyle(get_first(orig_handle.get_linestyles())) legend_handle.set_transform(get_first(orig_handle.get_transforms())) - legend_handle.set_figure(orig_handle.get_figure()) # Alpha is already taken into account by the color attributes. def create_artists(self, legend, orig_handle, From b370e30c4cc5412ded32796ea56a4a1926e39b02 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sun, 6 Jul 2025 21:51:28 +0100 Subject: [PATCH 29/57] Add smoke test --- lib/matplotlib/tests/test_axes.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index bba9f8648799..c96173e340f7 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3420,6 +3420,20 @@ def test_stackplot_hatching(fig_ref, fig_test): ax_ref.set_ylim(0, 70) +def test_stackplot_subfig_legend(): + # Smoke test for https://github.com/matplotlib/matplotlib/issues/30158 + + fig = plt.figure() + subfigs = fig.subfigures(nrows=1, ncols=2) + + for _fig in subfigs: + ax = _fig.subplots(nrows=1, ncols=1) + ax.stackplot([3, 4], [[1, 2]], labels=['a']) + + fig.legend() + fig.draw_without_rendering() + + def _bxp_test_helper( stats_kwargs={}, transform_stats=lambda s: s, bxp_kwargs={}): np.random.seed(937) From f3e29660f06fc348c1741976d3dbd2e9a2b98290 Mon Sep 17 00:00:00 2001 From: ZPyrolink <38cz74@gmail.com> Date: Mon, 7 Jul 2025 17:08:17 +0200 Subject: [PATCH 30/57] Create events type and update plt.connect and mpl_connect --- lib/matplotlib/backend_bases.pyi | 38 ++++++++++++++++++-------------- lib/matplotlib/pyplot.py | 3 ++- lib/matplotlib/typing.py | 30 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index 24669bfb3aeb..f9065c9f3a17 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -21,7 +21,19 @@ from matplotlib.transforms import Bbox, BboxBase, Transform, TransformedPath from collections.abc import Callable, Iterable, Sequence from typing import Any, IO, Literal, NamedTuple, TypeVar, overload from numpy.typing import ArrayLike -from .typing import ColorType, LineStyleType, CapStyleType, JoinStyleType +from .typing import ( + ColorType, + LineStyleType, + CapStyleType, + JoinStyleType, + MouseEventType, + KeyEventType, + DrawEventType, + PickEventType, + ResizeEventType, + CloseEventType, + EventType +) def register_backend( format: str, backend: str | type[FigureCanvasBase], description: str | None = ... @@ -354,37 +366,31 @@ class FigureCanvasBase: @overload def mpl_connect( self, - s: Literal[ - "button_press_event", - "motion_notify_event", - "scroll_event", - "figure_enter_event", - "figure_leave_event", - "axes_enter_event", - "axes_leave_event", - "button_release_event", - ], + s: MouseEventType, func: Callable[[MouseEvent], Any], ) -> int: ... @overload def mpl_connect( self, - s: Literal["key_press_event", "key_release_event"], + s: KeyEventType, func: Callable[[KeyEvent], Any], ) -> int: ... @overload - def mpl_connect(self, s: Literal["pick_event"], func: Callable[[PickEvent], Any]) -> int: ... + def mpl_connect(self, s: PickEventType, func: Callable[[PickEvent], Any]) -> int: ... + + @overload + def mpl_connect(self, s: ResizeEventType, func: Callable[[ResizeEvent], Any]) -> int: ... @overload - def mpl_connect(self, s: Literal["resize_event"], func: Callable[[ResizeEvent], Any]) -> int: ... + def mpl_connect(self, s: CloseEventType, func: Callable[[CloseEvent], Any]) -> int: ... @overload - def mpl_connect(self, s: Literal["close_event"], func: Callable[[CloseEvent], Any]) -> int: ... + def mpl_connect(self, s: DrawEventType, func: Callable[[DrawEvent], Any]) -> int: ... @overload - def mpl_connect(self, s: str, func: Callable[[Event], Any]) -> int: ... + def mpl_connect(self, s: EventType, func: Callable[[Event], Any]) -> int: ... def mpl_disconnect(self, cid: int) -> None: ... def new_timer( self, diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 8c9d1e1e5a29..20e6bb540d63 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -131,6 +131,7 @@ HashableList, LineStyleType, MarkerType, + EventType ) from matplotlib.widgets import SubplotTool @@ -1176,7 +1177,7 @@ def get_current_fig_manager() -> FigureManagerBase | None: @_copy_docstring_and_deprecators(FigureCanvasBase.mpl_connect) -def connect(s: str, func: Callable[[Event], Any]) -> int: +def connect(s: EventType, func: Callable[[Event], Any]) -> int: return gcf().canvas.mpl_connect(s, func) diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index df192df76b33..e3719235cdb8 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -107,3 +107,33 @@ _HT = TypeVar("_HT", bound=Hashable) HashableList: TypeAlias = list[_HT | "HashableList[_HT]"] """A nested list of Hashable values.""" + +MouseEventType: TypeAlias = Literal[ + "button_press_event", + "button_release_event", + "motion_notify_event", + "scroll_event", + "figure_enter_event", + "figure_leave_event", + "axes_enter_event", + "axes_leave_event", +] + +KeyEventType: TypeAlias = Literal[ + "key_press_event", + "key_release_event" +] + +DrawEventType: TypeAlias = Literal["draw_event"] +PickEventType: TypeAlias = Literal["pick_event"] +ResizeEventType: TypeAlias = Literal["resize_event"] +CloseEventType: TypeAlias = Literal["close_event"] + +EventType: TypeAlias = Literal[ + MouseEventType, + KeyEventType, + DrawEventType, + PickEventType, + ResizeEventType, + CloseEventType, +] From f31ba35f41f2a1074fdf9ecc33e34317fe9e19a0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 22 Aug 2022 18:43:28 -0400 Subject: [PATCH 31/57] Use old stride_windows implementation on 32-bit builds This was originally for i686 on Fedora, but is now applicable to WASM, which is 32-bit. The older implementation doesn't OOM. --- lib/matplotlib/mlab.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/mlab.py b/lib/matplotlib/mlab.py index f538b79e44f0..b4b4c3f96828 100644 --- a/lib/matplotlib/mlab.py +++ b/lib/matplotlib/mlab.py @@ -48,7 +48,8 @@ """ import functools -from numbers import Number +from numbers import Integral, Number +import sys import numpy as np @@ -210,6 +211,23 @@ def detrend_linear(y): return y - (b*x + a) +def _stride_windows(x, n, noverlap=0): + x = np.asarray(x) + + _api.check_isinstance(Integral, n=n, noverlap=noverlap) + if not (1 <= n <= x.size and n < noverlap): + raise ValueError(f'n ({n}) and noverlap ({noverlap}) must be positive integers ' + f'with n < noverlap and n <= x.size ({x.size})') + + if n == 1 and noverlap == 0: + return x[np.newaxis] + + step = n - noverlap + shape = (n, (x.shape[-1]-noverlap)//step) + strides = (x.strides[0], step*x.strides[0]) + return np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides) + + def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, window=None, noverlap=None, pad_to=None, sides=None, scale_by_freq=None, mode=None): @@ -304,8 +322,12 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, raise ValueError( "The window length must match the data's first dimension") - result = np.lib.stride_tricks.sliding_window_view( - x, NFFT, axis=0)[::NFFT - noverlap].T + if sys.maxsize > 2**32: + result = np.lib.stride_tricks.sliding_window_view( + x, NFFT, axis=0)[::NFFT - noverlap].T + else: + # The NumPy version on 32-bit will OOM, so use old implementation. + result = _stride_windows(x, NFFT, noverlap=noverlap) result = detrend(result, detrend_func, axis=0) result = result * window.reshape((-1, 1)) result = np.fft.fft(result, n=pad_to, axis=0)[:numFreqs, :] @@ -313,8 +335,12 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, if not same_data: # if same_data is False, mode must be 'psd' - resultY = np.lib.stride_tricks.sliding_window_view( - y, NFFT, axis=0)[::NFFT - noverlap].T + if sys.maxsize > 2**32: + resultY = np.lib.stride_tricks.sliding_window_view( + y, NFFT, axis=0)[::NFFT - noverlap].T + else: + # The NumPy version on 32-bit will OOM, so use old implementation. + resultY = _stride_windows(y, NFFT, noverlap=noverlap) resultY = detrend(resultY, detrend_func, axis=0) resultY = resultY * window.reshape((-1, 1)) resultY = np.fft.fft(resultY, n=pad_to, axis=0)[:numFreqs, :] From 3208831c5218d4802ff8cf76d1c6eba7dc2cd3df Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 8 Jul 2025 11:59:37 +0100 Subject: [PATCH 32/57] Reduce pause time in interactive tests --- lib/matplotlib/tests/test_backends_interactive.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index a27783fa4be1..9f8522a9df4a 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -649,12 +649,9 @@ def _impl_test_interactive_timers(): # milliseconds, which the mac framework interprets as singleshot. # We only want singleshot if we specify that ourselves, otherwise we want # a repeating timer - import os from unittest.mock import Mock import matplotlib.pyplot as plt - # increase pause duration on CI to let things spin up - # particularly relevant for gtk3cairo - pause_time = 2 if os.getenv("CI") else 0.5 + pause_time = 0.5 fig = plt.figure() plt.pause(pause_time) timer = fig.canvas.new_timer(0.1) From 3d7b6442df9e9b8439b73289061f0bd9e7941777 Mon Sep 17 00:00:00 2001 From: ZPyrolink <38cz74@gmail.com> Date: Tue, 8 Jul 2025 22:26:18 +0200 Subject: [PATCH 33/57] Create connect overloads on pyplot --- lib/matplotlib/pyplot.py | 42 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 20e6bb540d63..1f9c4606af27 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -100,7 +100,14 @@ import matplotlib.backend_bases from matplotlib.axis import Tick from matplotlib.axes._base import _AxesBase - from matplotlib.backend_bases import Event + from matplotlib.backend_bases import ( + CloseEvent, + DrawEvent, + KeyEvent, + MouseEvent, + PickEvent, + ResizeEvent, + ) from matplotlib.cm import ScalarMappable from matplotlib.contour import ContourSet, QuadContourSet from matplotlib.collections import ( @@ -126,12 +133,17 @@ from matplotlib.quiver import Barbs, Quiver, QuiverKey from matplotlib.scale import ScaleBase from matplotlib.typing import ( + CloseEventType, ColorType, CoordsType, + DrawEventType, HashableList, + KeyEventType, LineStyleType, MarkerType, - EventType + MouseEventType, + PickEventType, + ResizeEventType, ) from matplotlib.widgets import SubplotTool @@ -1176,8 +1188,32 @@ def get_current_fig_manager() -> FigureManagerBase | None: return gcf().canvas.manager +@overload +def connect(s: MouseEventType, func: Callable[[MouseEvent], Any]) -> int: ... + + +@overload +def connect(s: KeyEventType, func: Callable[[KeyEvent], Any]) -> int: ... + + +@overload +def connect(s: PickEventType, func: Callable[[PickEvent], Any]) -> int: ... + + +@overload +def connect(s: ResizeEventType, func: Callable[[ResizeEvent], Any]) -> int: ... + + +@overload +def connect(s: CloseEventType, func: Callable[[CloseEvent], Any]) -> int: ... + + +@overload +def connect(s: DrawEventType, func: Callable[[DrawEvent], Any]) -> int: ... + + @_copy_docstring_and_deprecators(FigureCanvasBase.mpl_connect) -def connect(s: EventType, func: Callable[[Event], Any]) -> int: +def connect(s, func) -> int: return gcf().canvas.mpl_connect(s, func) From d2adb8d138d4a73c383988b34204cacbdbecb9d5 Mon Sep 17 00:00:00 2001 From: ZPyrolink <38cz74@gmail.com> Date: Tue, 8 Jul 2025 22:31:58 +0200 Subject: [PATCH 34/57] Remove unnecessary mpl_connect overload and sort imports --- lib/matplotlib/backend_bases.pyi | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/backend_bases.pyi b/lib/matplotlib/backend_bases.pyi index f9065c9f3a17..c65d39415472 100644 --- a/lib/matplotlib/backend_bases.pyi +++ b/lib/matplotlib/backend_bases.pyi @@ -22,17 +22,16 @@ from collections.abc import Callable, Iterable, Sequence from typing import Any, IO, Literal, NamedTuple, TypeVar, overload from numpy.typing import ArrayLike from .typing import ( - ColorType, - LineStyleType, CapStyleType, + CloseEventType, + ColorType, + DrawEventType, JoinStyleType, - MouseEventType, KeyEventType, - DrawEventType, + LineStyleType, + MouseEventType, PickEventType, ResizeEventType, - CloseEventType, - EventType ) def register_backend( @@ -388,9 +387,6 @@ class FigureCanvasBase: @overload def mpl_connect(self, s: DrawEventType, func: Callable[[DrawEvent], Any]) -> int: ... - - @overload - def mpl_connect(self, s: EventType, func: Callable[[Event], Any]) -> int: ... def mpl_disconnect(self, cid: int) -> None: ... def new_timer( self, From 9bbe32b4f870309af1468f0e1a14e5d38bf060f3 Mon Sep 17 00:00:00 2001 From: Sebastien Wieckowski Date: Wed, 9 Jul 2025 08:54:30 +0200 Subject: [PATCH 35/57] fix(config): Correct invalid value for svg.fonttype in matplotlibrc --- lib/matplotlib/mpl-data/matplotlibrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index ec649560ba3b..ccc5de5e372c 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -761,7 +761,7 @@ #svg.fonttype: path # How to handle SVG fonts: # path: Embed characters as paths -- supported # by most SVG renderers - # None: Assume fonts are installed on the + # none: Assume fonts are installed on the # machine where the SVG will be viewed. #svg.hashsalt: None # If not None, use this string as hash salt instead of uuid4 #svg.id: None # If not None, use this string as the value for the `id` From 86983e4f42969962351fe13a44e2957621d8510d Mon Sep 17 00:00:00 2001 From: Colton Lathrop Date: Wed, 6 Nov 2019 12:54:26 -0600 Subject: [PATCH 36/57] Log a warning if selected font weight differs from requested --- .../next_api_changes/behavior/30272-ES.rst | 2 ++ lib/matplotlib/font_manager.py | 14 +++++++-- lib/matplotlib/font_manager.pyi | 10 +++---- lib/matplotlib/tests/test_font_manager.py | 29 ++++++++++++++++++- 4 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/30272-ES.rst diff --git a/doc/api/next_api_changes/behavior/30272-ES.rst b/doc/api/next_api_changes/behavior/30272-ES.rst new file mode 100644 index 000000000000..5a03f9bc7972 --- /dev/null +++ b/doc/api/next_api_changes/behavior/30272-ES.rst @@ -0,0 +1,2 @@ +``font_manager.findfont`` logs if selected font weight does not match requested +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 2db98b75ab2e..ab6b495631de 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -35,7 +35,7 @@ from io import BytesIO import json import logging -from numbers import Number +from numbers import Integral import os from pathlib import Path import plistlib @@ -172,6 +172,10 @@ ] +def _normalize_weight(weight): + return weight if isinstance(weight, Integral) else weight_dict[weight] + + def get_fontext_synonyms(fontext): """ Return a list of file extensions that are synonyms for @@ -1256,8 +1260,8 @@ def score_weight(self, weight1, weight2): # exact match of the weight names, e.g. weight1 == weight2 == "regular" if cbook._str_equal(weight1, weight2): return 0.0 - w1 = weight1 if isinstance(weight1, Number) else weight_dict[weight1] - w2 = weight2 if isinstance(weight2, Number) else weight_dict[weight2] + w1 = _normalize_weight(weight1) + w2 = _normalize_weight(weight2) return 0.95 * (abs(w1 - w2) / 1000) + 0.05 def score_size(self, size1, size2): @@ -1480,6 +1484,10 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default, best_font = font if score == 0: break + if best_font is not None and (_normalize_weight(prop.get_weight()) != + _normalize_weight(best_font.weight)): + _log.warning('findfont: Failed to find font weight %s, now using %s.', + prop.get_weight(), best_font.weight) if best_font is None or best_score >= 10.0: if fallback_to_default: diff --git a/lib/matplotlib/font_manager.pyi b/lib/matplotlib/font_manager.pyi index c64ddea3e073..e865f67384cd 100644 --- a/lib/matplotlib/font_manager.pyi +++ b/lib/matplotlib/font_manager.pyi @@ -1,14 +1,13 @@ +from collections.abc import Iterable from dataclasses import dataclass +from numbers import Integral import os +from pathlib import Path +from typing import Any, Literal from matplotlib._afm import AFM from matplotlib import ft2font -from pathlib import Path - -from collections.abc import Iterable -from typing import Any, Literal - font_scalings: dict[str | None, float] stretch_dict: dict[str, int] weight_dict: dict[str, int] @@ -19,6 +18,7 @@ MSUserFontDirectories: list[str] X11FontDirectories: list[str] OSXFontDirectories: list[str] +def _normalize_weight(weight: str | Integral) -> Integral: ... def get_fontext_synonyms(fontext: str) -> list[str]: ... def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ... def win32FontDirectory() -> str: ... diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 97ee8672b1d4..24421b8e30b3 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -15,7 +15,8 @@ from matplotlib.font_manager import ( findfont, findSystemFonts, FontEntry, FontProperties, fontManager, json_dump, json_load, get_font, is_opentype_cff_font, - MSUserFontDirectories, _get_fontconfig_fonts, ttfFontProperty) + MSUserFontDirectories, ttfFontProperty, + _get_fontconfig_fonts, _normalize_weight) from matplotlib import cbook, ft2font, pyplot as plt, rc_context, figure as mfigure from matplotlib.testing import subprocess_run_helper, subprocess_run_for_testing @@ -407,3 +408,29 @@ def test_fontproperties_init_deprecation(): # Since this case is not covered by docs, I've refrained from jumping # extra hoops to detect this possible API misuse. FontProperties(family="serif-24:style=oblique:weight=bold") + + +def test_normalize_weights(): + assert _normalize_weight(300) == 300 # passthrough + assert _normalize_weight('ultralight') == 100 + assert _normalize_weight('light') == 200 + assert _normalize_weight('normal') == 400 + assert _normalize_weight('regular') == 400 + assert _normalize_weight('book') == 400 + assert _normalize_weight('medium') == 500 + assert _normalize_weight('roman') == 500 + assert _normalize_weight('semibold') == 600 + assert _normalize_weight('demibold') == 600 + assert _normalize_weight('demi') == 600 + assert _normalize_weight('bold') == 700 + assert _normalize_weight('heavy') == 800 + assert _normalize_weight('extra bold') == 800 + assert _normalize_weight('black') == 900 + with pytest.raises(KeyError): + _normalize_weight('invalid') + + +def test_font_match_warning(caplog): + findfont(FontProperties(family=["DejaVu Sans"], weight=750)) + logs = [rec.message for rec in caplog.records] + assert 'findfont: Failed to find font weight 750, now using 700.' in logs From 7432ef44f0023d37a4e3cc10be1e3f9098ce99a2 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 23 Jun 2025 17:53:03 -0400 Subject: [PATCH 37/57] Fix several minor typos --- ci/schemas/conda-environment.json | 2 +- .../prev_api_changes/api_changes_0.90.1.rst | 2 +- .../prev_api_changes/api_changes_1.5.0.rst | 2 +- .../prev_whats_new/github_stats_3.0.0.rst | 8 ++--- .../prev_whats_new/github_stats_3.0.1.rst | 2 +- .../prev_whats_new/github_stats_3.0.2.rst | 4 +-- .../prev_whats_new/github_stats_3.1.0.rst | 2 +- .../prev_whats_new/github_stats_3.1.2.rst | 2 +- .../prev_whats_new/github_stats_3.1.3.rst | 4 +-- .../prev_whats_new/github_stats_3.2.0.rst | 8 ++--- .../prev_whats_new/github_stats_3.3.0.rst | 20 ++++++------- .../prev_whats_new/github_stats_3.3.1.rst | 6 ++-- .../prev_whats_new/github_stats_3.4.0.rst | 30 +++++++++---------- .../prev_whats_new/github_stats_3.4.2.rst | 2 +- .../prev_whats_new/github_stats_3.4.3.rst | 2 +- .../prev_whats_new/github_stats_3.5.0.rst | 6 ++-- .../prev_whats_new/github_stats_3.5.1.rst | 2 +- doc/users/prev_whats_new/whats_new_3.10.0.rst | 4 ++- lib/matplotlib/axes/_base.py | 2 +- lib/matplotlib/tests/test_triangulation.py | 2 +- lib/matplotlib/tri/_triinterpolate.py | 10 +++---- 21 files changed, 62 insertions(+), 60 deletions(-) diff --git a/ci/schemas/conda-environment.json b/ci/schemas/conda-environment.json index 458676942a44..fb1e821778c3 100644 --- a/ci/schemas/conda-environment.json +++ b/ci/schemas/conda-environment.json @@ -1,6 +1,6 @@ { "title": "conda environment file", - "description": "Support for conda's enviroment.yml files (e.g. `conda env export > environment.yml`)", + "description": "Support for conda's environment.yml files (e.g. `conda env export > environment.yml`)", "id": "https://raw.githubusercontent.com/Microsoft/vscode-python/main/schemas/conda-environment.json", "$schema": "http://json-schema.org/draft-04/schema#", "definitions": { diff --git a/doc/api/prev_api_changes/api_changes_0.90.1.rst b/doc/api/prev_api_changes/api_changes_0.90.1.rst index 89311d4ed102..8caef5e35ced 100644 --- a/doc/api/prev_api_changes/api_changes_0.90.1.rst +++ b/doc/api/prev_api_changes/api_changes_0.90.1.rst @@ -32,7 +32,7 @@ Changes for 0.90.1 named units.ConversionInterface.convert. Axes.errorbar uses Axes.vlines and Axes.hlines to draw its error - limits int he vertical and horizontal direction. As you'll see + limits in the vertical and horizontal direction. As you'll see in the changes below, these functions now return a LineCollection rather than a list of lines. The new return signature for errorbar is ylins, caplines, errorcollections where diff --git a/doc/api/prev_api_changes/api_changes_1.5.0.rst b/doc/api/prev_api_changes/api_changes_1.5.0.rst index b482d8bd7acd..513971098b93 100644 --- a/doc/api/prev_api_changes/api_changes_1.5.0.rst +++ b/doc/api/prev_api_changes/api_changes_1.5.0.rst @@ -189,7 +189,7 @@ algorithm that was not necessarily applicable to custom Axes. Three new private methods, ``matplotlib.axes._base._AxesBase._get_view``, ``matplotlib.axes._base._AxesBase._set_view``, and ``matplotlib.axes._base._AxesBase._set_view_from_bbox``, allow for custom -*Axes* classes to override the pan and zoom algorithms. Implementors of +*Axes* classes to override the pan and zoom algorithms. Implementers of custom *Axes* who override these methods may provide suitable behaviour for both pan and zoom as well as the view navigation buttons on the interactive toolbars. diff --git a/doc/users/prev_whats_new/github_stats_3.0.0.rst b/doc/users/prev_whats_new/github_stats_3.0.0.rst index 0e9c4b3b588d..cae3ee9b570d 100644 --- a/doc/users/prev_whats_new/github_stats_3.0.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.0.0.rst @@ -595,7 +595,7 @@ Pull Requests (598): * :ghpull:`11757`: PGF backend output text color even if black * :ghpull:`11751`: Remove the unused 'verbose' option from setupext. * :ghpull:`9084`: Require calling a _BoundMethodProxy to get the underlying callable. -* :ghpull:`11752`: Fix section level of Previous Whats New +* :ghpull:`11752`: Fix section level of Previous What's New * :ghpull:`10513`: Replace most uses of getfilesystemencoding by os.fs{en,de}code. * :ghpull:`11739`: fix tight_layout bug #11737 * :ghpull:`11744`: minor doc update on axes_grid1's inset_axes @@ -899,7 +899,7 @@ Pull Requests (598): * :ghpull:`11075`: Drop alpha channel when saving comparison failure diff image. * :ghpull:`9022`: Help tool * :ghpull:`11045`: Help tool. -* :ghpull:`11076`: Don't create texput.{aux,log} in rootdir everytime tests are run. +* :ghpull:`11076`: Don't create texput.{aux,log} in rootdir every time tests are run * :ghpull:`11073`: py3fication of some tests. * :ghpull:`11074`: bytes % args is back since py3.5 * :ghpull:`11066`: Use chained comparisons where reasonable. @@ -1138,7 +1138,7 @@ Issues (123): * :ghissue:`11373`: Passing an incorrectly sized colour list to scatter should raise a relevant error * :ghissue:`11756`: pgf backend doesn't set color of text when the color is black * :ghissue:`11766`: test_axes.py::test_csd_freqs failing with numpy 1.15.0 on macOS -* :ghissue:`11750`: previous whats new is overindented on "what's new in mpl3.0 page" +* :ghissue:`11750`: previous what's new is overindented on "what's new in mpl3.0 page" * :ghissue:`11728`: Qt5 Segfaults on window resize * :ghissue:`11709`: Repaint region is wrong on Retina display with Qt5 * :ghissue:`11578`: wx segfaulting on OSX travis tests @@ -1149,7 +1149,7 @@ Issues (123): * :ghissue:`11607`: AttributeError: 'QEvent' object has no attribute 'pos' * :ghissue:`11486`: Colorbar does not render with PowerNorm and min extend when using imshow * :ghissue:`11582`: wx segfault -* :ghissue:`11515`: using 'sharex' once in 'subplots' function can affect subsequent calles to 'subplots' +* :ghissue:`11515`: using 'sharex' once in 'subplots' function can affect subsequent calls to 'subplots' * :ghissue:`10269`: input() blocks any rendering and event handling * :ghissue:`10345`: Python 3.4 with Matplotlib 1.5 vs Python 3.6 with Matplotlib 2.1 * :ghissue:`10443`: Drop use of pytz dependency in next major release diff --git a/doc/users/prev_whats_new/github_stats_3.0.1.rst b/doc/users/prev_whats_new/github_stats_3.0.1.rst index 95e899d1a9de..8ebc7f5f11c1 100644 --- a/doc/users/prev_whats_new/github_stats_3.0.1.rst +++ b/doc/users/prev_whats_new/github_stats_3.0.1.rst @@ -150,7 +150,7 @@ Pull Requests (127): * :ghpull:`12230`: Backport PR #12213 on branch v3.0.x (Change win32InstalledFonts return value) * :ghpull:`12213`: Change win32InstalledFonts return value * :ghpull:`12223`: Backport PR #11688 on branch v3.0.x (Don't draw axis (spines, ticks, labels) twice when using parasite axes.) -* :ghpull:`12224`: Backport PR #12207 on branch v3.0.x (FIX: dont' check for interactive framework if none required) +* :ghpull:`12224`: Backport PR #12207 on branch v3.0.x (FIX: don't check for interactive framework if none required) * :ghpull:`12207`: FIX: don't check for interactive framework if none required * :ghpull:`11688`: Don't draw axis (spines, ticks, labels) twice when using parasite axes. * :ghpull:`12205`: Backport PR #12186 on branch v3.0.x (DOC: fix API note about get_tightbbox) diff --git a/doc/users/prev_whats_new/github_stats_3.0.2.rst b/doc/users/prev_whats_new/github_stats_3.0.2.rst index c5caed404b62..6b4ef3071f1c 100644 --- a/doc/users/prev_whats_new/github_stats_3.0.2.rst +++ b/doc/users/prev_whats_new/github_stats_3.0.2.rst @@ -83,7 +83,7 @@ Pull Requests (224): * :ghpull:`12670`: FIX: add setter for hold to un-break basemap * :ghpull:`12693`: Workaround Text3D breaking tight_layout() * :ghpull:`12727`: Reorder API docs: separate file per module -* :ghpull:`12738`: Add unobtrusive depreaction note to the first line of the docstring. +* :ghpull:`12738`: Add unobtrusive deprecation note to the first line of the docstring * :ghpull:`12740`: DOC: constrained layout guide (fix: Spacing with colorbars) * :ghpull:`11663`: Refactor color parsing of Axes.scatter * :ghpull:`12736`: Move deprecation note to end of docstring @@ -263,7 +263,7 @@ Pull Requests (224): * :ghpull:`12227`: Use (float, float) as parameter type for 2D positions * :ghpull:`12199`: Allow disabling specific mouse actions in blocking_input * :ghpull:`12213`: Change win32InstalledFonts return value -* :ghpull:`12207`: FIX: dont' check for interactive framework if none required +* :ghpull:`12207`: FIX: don't check for interactive framework if none required * :ghpull:`11688`: Don't draw axis (spines, ticks, labels) twice when using parasite axes. * :ghpull:`12210`: Axes.tick_params() argument checking * :ghpull:`12211`: Fix typo diff --git a/doc/users/prev_whats_new/github_stats_3.1.0.rst b/doc/users/prev_whats_new/github_stats_3.1.0.rst index 97bee1af56b8..a0fb2692fdbb 100644 --- a/doc/users/prev_whats_new/github_stats_3.1.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.1.0.rst @@ -871,7 +871,7 @@ Pull Requests (918): * :ghpull:`12749`: Move toolmanager warning from logging to warning. * :ghpull:`12598`: Support Cn colors with n>=10. * :ghpull:`12727`: Reorder API docs: separate file per module -* :ghpull:`12738`: Add unobtrusive depreaction note to the first line of the docstring. +* :ghpull:`12738`: Add unobtrusive deprecation note to the first line of the docstring * :ghpull:`11663`: Refactor color parsing of Axes.scatter * :ghpull:`12736`: Move deprecation note to end of docstring * :ghpull:`12704`: Rename tkinter import from Tk to tk. diff --git a/doc/users/prev_whats_new/github_stats_3.1.2.rst b/doc/users/prev_whats_new/github_stats_3.1.2.rst index e1ed84e26372..d8476cb5c3a8 100644 --- a/doc/users/prev_whats_new/github_stats_3.1.2.rst +++ b/doc/users/prev_whats_new/github_stats_3.1.2.rst @@ -172,7 +172,7 @@ Issues (28): * :ghissue:`15162`: axes.bar fails when x is int-indexed pandas.Series * :ghissue:`15103`: Colorbar for imshow messes interactive cursor with masked data * :ghissue:`8744`: ConnectionPatch hidden by plots -* :ghissue:`14950`: plt.ioff() not supressing figure generation +* :ghissue:`14950`: plt.ioff() not suppressing figure generation * :ghissue:`14959`: Typo in Docs * :ghissue:`14902`: from matplotlib import animation UnicodeDecodeError * :ghissue:`14897`: New yticks behavior in 3.1.1 vs 3.1.0 diff --git a/doc/users/prev_whats_new/github_stats_3.1.3.rst b/doc/users/prev_whats_new/github_stats_3.1.3.rst index b4706569df02..f8c1afb0e177 100644 --- a/doc/users/prev_whats_new/github_stats_3.1.3.rst +++ b/doc/users/prev_whats_new/github_stats_3.1.3.rst @@ -71,8 +71,8 @@ Pull Requests (45): * :ghpull:`15757`: Backport PR #15751 on branch v3.1.x (Modernize FAQ entry for plt.show().) * :ghpull:`15735`: Cleanup some mplot3d docstrings. * :ghpull:`15753`: Backport PR #15661 on branch v3.1.x (Document scope of 3D scatter depthshading.) -* :ghpull:`15741`: Backport PR #15729 on branch v3.1.x (Catch correct parse errror type for dateutil >= 2.8.1) -* :ghpull:`15729`: Catch correct parse errror type for dateutil >= 2.8.1 +* :ghpull:`15741`: Backport PR #15729 on branch v3.1.x (Catch correct parse error type for dateutil >= 2.8.1) +* :ghpull:`15729`: Catch correct parse error type for dateutil >= 2.8.1 * :ghpull:`15737`: Fix env override in WebAgg backend test. * :ghpull:`15244`: Change documentation format of rcParams defaults diff --git a/doc/users/prev_whats_new/github_stats_3.2.0.rst b/doc/users/prev_whats_new/github_stats_3.2.0.rst index 3cb3fce5de52..4efdb191494d 100644 --- a/doc/users/prev_whats_new/github_stats_3.2.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.2.0.rst @@ -264,12 +264,12 @@ Pull Requests (839): * :ghpull:`16112`: CI: Fail when failed to install dependencies * :ghpull:`16119`: Backport PR #16065 on branch v3.2.x (Nicer formatting of community aspects on front page) * :ghpull:`16074`: Backport PR #16061 on branch v3.2.x (Fix deprecation message for axes_grid1.colorbar.) -* :ghpull:`16093`: Backport PR #16079 on branch v3.2.x (Fix restuctured text formatting) +* :ghpull:`16093`: Backport PR #16079 on branch v3.2.x (Fix restructured text formatting) * :ghpull:`16094`: Backport PR #16080 on branch v3.2.x (Cleanup docstrings in backend_bases.py) * :ghpull:`16086`: FIX: use supported attribute to check pillow version * :ghpull:`16084`: Backport PR #16077 on branch v3.2.x (Fix some typos) * :ghpull:`16077`: Fix some typos -* :ghpull:`16079`: Fix restuctured text formatting +* :ghpull:`16079`: Fix restructured text formatting * :ghpull:`16080`: Cleanup docstrings in backend_bases.py * :ghpull:`16061`: Fix deprecation message for axes_grid1.colorbar. * :ghpull:`16006`: Ignore pos in StrCategoryFormatter.__call__ to display correct label in the preview window @@ -811,7 +811,7 @@ Pull Requests (839): * :ghpull:`14310`: Update to Bounding Box for Qt5 FigureCanvasATAgg.paintEvent() * :ghpull:`14380`: Inline $MPLLOCALFREETYPE/$PYTEST_ADDOPTS/$NPROC in .travis.yml. * :ghpull:`14413`: MAINT: small improvements to the pdf backend -* :ghpull:`14452`: MAINT: Minor cleanup to make functions more self consisntent +* :ghpull:`14452`: MAINT: Minor cleanup to make functions more self consistent * :ghpull:`14441`: Misc. docstring cleanups. * :ghpull:`14440`: Interpolations example * :ghpull:`14402`: Prefer ``mpl.get_data_path()``, and support Paths in FontProperties. @@ -827,7 +827,7 @@ Pull Requests (839): * :ghpull:`14311`: travis: add c code coverage measurements * :ghpull:`14393`: Remove remaining unicode-strings markers. * :ghpull:`14391`: Remove explicit inheritance from object -* :ghpull:`14343`: acquiring and releaseing keypresslock when textbox is being activated +* :ghpull:`14343`: acquiring and releasing keypresslock when textbox is being activated * :ghpull:`14353`: Register flaky pytest marker. * :ghpull:`14373`: Properly hide __has_include to support C++<17 compilers. * :ghpull:`14378`: Remove setup_method diff --git a/doc/users/prev_whats_new/github_stats_3.3.0.rst b/doc/users/prev_whats_new/github_stats_3.3.0.rst index c2e6cd132c2d..45813659b890 100644 --- a/doc/users/prev_whats_new/github_stats_3.3.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.3.0.rst @@ -254,7 +254,7 @@ Pull Requests (1066): * :ghpull:`17617`: Rewrite pdf test to use check_figures_equal. * :ghpull:`17654`: Small fixes to recent What's New * :ghpull:`17649`: MNT: make _setattr_cm more forgiving -* :ghpull:`17644`: Doc 33 whats new consolidation +* :ghpull:`17644`: Doc 33 what's new consolidation * :ghpull:`17647`: Fix example in docstring of cbook._unfold. * :ghpull:`10187`: DOC: add a blitting tutorial * :ghpull:`17471`: Removed idiomatic constructs from interactive figures docs @@ -306,7 +306,7 @@ Pull Requests (1066): * :ghpull:`17540`: Fix help window on GTK. * :ghpull:`17535`: Update docs on subplot2grid / SubplotBase * :ghpull:`17510`: Fix exception handling in FT2Font init. -* :ghpull:`16953`: Changed 'colors' paramater in PyPlot vlines/hlines and Axes vlines/hlines to default to configured rcParams 'lines.color' option +* :ghpull:`16953`: Changed 'colors' parameter in PyPlot vlines/hlines and Axes vlines/hlines to default to configured rcParams 'lines.color' option * :ghpull:`17459`: Use light icons on dark themes for wx and gtk, too. * :ghpull:`17539`: Use symbolic icons for buttons in GTK toolbar. * :ghpull:`15435`: Reuse png metadata handling of imsave() in FigureCanvasAgg.print_png(). @@ -469,7 +469,7 @@ Pull Requests (1066): * :ghpull:`15008`: ENH: add variable epoch * :ghpull:`17260`: Text Rotation Example: Correct roation_mode typo * :ghpull:`17258`: Improve info logged by tex subsystem. -* :ghpull:`17211`: Deprecate support for running svg converter from path contaning newline. +* :ghpull:`17211`: Deprecate support for running svg converter from path containing newline. * :ghpull:`17078`: Improve nbAgg & WebAgg toolbars * :ghpull:`17191`: Inline unsampled-image path; remove renderer kwarg from _check_unsampled_image. * :ghpull:`17213`: Replace use of Bbox.bounds by appropriate properties. @@ -604,7 +604,7 @@ Pull Requests (1066): * :ghpull:`16823`: Dedupe implementation of axes grid switching in toolmanager. * :ghpull:`16951`: Cleanup dates docstrings. * :ghpull:`16769`: Fix some small style issues -* :ghpull:`16936`: FIX: Plot is now rendered with correct inital value +* :ghpull:`16936`: FIX: Plot is now rendered with correct initial value * :ghpull:`16937`: Making sure to keep over/under/bad in cmap resample/reverse. * :ghpull:`16915`: Tighten/cleanup wx backend. * :ghpull:`16923`: Test the macosx backend on Travis. @@ -860,7 +860,7 @@ Pull Requests (1066): * :ghpull:`16439`: Rework pylab docstring. * :ghpull:`16441`: Rework pylab docstring. * :ghpull:`16442`: Expire deprecation of \stackrel. -* :ghpull:`16365`: TST: test_acorr (replaced image comparison with figure comparion) +* :ghpull:`16365`: TST: test_acorr (replaced image comparison with figure comparison) * :ghpull:`16206`: Expire deprecation of \stackrel * :ghpull:`16437`: Rework pylab docstring. * :ghpull:`8896`: Fix mplot3d projection @@ -898,7 +898,7 @@ Pull Requests (1066): * :ghpull:`16304`: Simplify Legend.get_children. * :ghpull:`16309`: Remove duplicated computations in Axes.get_tightbbox. * :ghpull:`16314`: Avoid repeatedly warning about too many figures open. -* :ghpull:`16319`: Put doc for XAxis befor YAxis and likewise for XTick, YTick. +* :ghpull:`16319`: Put doc for XAxis before YAxis and likewise for XTick, YTick. * :ghpull:`16313`: Cleanup constrainedlayout_guide. * :ghpull:`16312`: Remove unnecessary Legend._approx_text_height. * :ghpull:`16307`: Cleanup axes_demo. @@ -991,7 +991,7 @@ Pull Requests (1066): * :ghpull:`16078`: Refactor a bit animation start/save interaction. * :ghpull:`16081`: Delay resolution of animation extra_args. * :ghpull:`16088`: Use C++ true/false in ttconv. -* :ghpull:`16082`: Defaut to writing animation frames to a temporary directory. +* :ghpull:`16082`: Default to writing animation frames to a temporary directory. * :ghpull:`16070`: Make animation blit cache robust against 3d viewpoint changes. * :ghpull:`5056`: MNT: more control of colorbar with CountourSet * :ghpull:`16051`: Deprecate parameters to colorbar which have no effect. @@ -1133,7 +1133,7 @@ Pull Requests (1066): * :ghpull:`15645`: Bump minimal numpy version to 1.12. * :ghpull:`15646`: Hide sphinx-gallery config comments * :ghpull:`15642`: Remove interpolation="nearest" from most examples. -* :ghpull:`15671`: Don't mention tcl in tkagg commments anymore. +* :ghpull:`15671`: Don't mention tcl in tkagg comments anymore. * :ghpull:`15607`: Simplify tk loader. * :ghpull:`15651`: Simplify axes_pad handling in axes_grid. * :ghpull:`15652`: Remove mention of Enthought Canopy from the docs. @@ -1400,7 +1400,7 @@ Issues (198): * :ghissue:`16299`: The interactive polar plot animation's axis label won't scale. * :ghissue:`15182`: More tests ``ConciseDateFormatter`` needed * :ghissue:`16140`: Unclear Documentation for get_xticklabels -* :ghissue:`16147`: pp.hist parmeter 'density' does not scale data appropriately +* :ghissue:`16147`: pp.hist parameter 'density' does not scale data appropriately * :ghissue:`16069`: matplotlib glitch when rotating interactively a 3d animation * :ghissue:`14603`: Scatterplot: should vmin/vmax be ignored when a norm is specified? * :ghissue:`15730`: Setting lines.marker = s in matplotlibrc also sets markers in boxplots @@ -1423,7 +1423,7 @@ Issues (198): * :ghissue:`15089`: Coerce MxNx1 images into MxN images for imshow * :ghissue:`5253`: abline() - for drawing arbitrary lines on a plot, given specifications. * :ghissue:`15165`: Switch to requiring Pillow rather than having our own png wrapper? -* :ghissue:`15280`: Add pull request checklist to Reviewers Guidlines +* :ghissue:`15280`: Add pull request checklist to Reviewers Guidelines * :ghissue:`15289`: cbook.warn_deprecated() should warn with a MatplotlibDeprecationWarning not a UserWarning * :ghissue:`15285`: DOC: make copy right year auto-update * :ghissue:`15059`: fig.add_axes() with no arguments silently does nothing diff --git a/doc/users/prev_whats_new/github_stats_3.3.1.rst b/doc/users/prev_whats_new/github_stats_3.3.1.rst index 3fa2d39a4d90..dc8e9996313f 100644 --- a/doc/users/prev_whats_new/github_stats_3.3.1.rst +++ b/doc/users/prev_whats_new/github_stats_3.3.1.rst @@ -102,16 +102,16 @@ Pull Requests (73): * :ghpull:`17995`: Avoid using Bbox machinery in Path.get_extents; special case polylines. * :ghpull:`17994`: Special case degree-1 Bezier curves. * :ghpull:`17990`: Manual backport of pr 17983 on v3.3.x -* :ghpull:`17984`: Backport PR #17972 on branch v3.3.x (Fix PyPy compatiblity issue) +* :ghpull:`17984`: Backport PR #17972 on branch v3.3.x (Fix PyPy compatibility issue) * :ghpull:`17985`: Backport PR #17976 on branch v3.3.x (Fixed #17970 - Docstrings should not accessed with -OO) * :ghpull:`17983`: FIX: undeprecate and update num2epoch/epoch2num * :ghpull:`17976`: Fixed #17970 - Docstrings should not accessed with -OO -* :ghpull:`17972`: Fix PyPy compatiblity issue +* :ghpull:`17972`: Fix PyPy compatibility issue Issues (25): * :ghissue:`18234`: _reshape_2D function behavior changed, breaks hist for some cases in 3.3.0 -* :ghissue:`18232`: different behaviour between 3.3.0 and 3.2.2 (and earlier) for ploting in a Tk canvas +* :ghissue:`18232`: different behaviour between 3.3.0 and 3.2.2 (and earlier) for plotting in a Tk canvas * :ghissue:`18212`: Updated WxAgg NavigationToolbar2 breaks custom toolbars * :ghissue:`18129`: Error reading png image from URL with imread in matplotlib 3.3 * :ghissue:`18163`: Figure cannot be closed if it has associated Agg canvas diff --git a/doc/users/prev_whats_new/github_stats_3.4.0.rst b/doc/users/prev_whats_new/github_stats_3.4.0.rst index b2568058b455..fb6f0044d139 100644 --- a/doc/users/prev_whats_new/github_stats_3.4.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.4.0.rst @@ -426,9 +426,9 @@ Pull Requests (772): * :ghpull:`19207`: Fix Grouper example code * :ghpull:`19204`: Clarify Date Format Example * :ghpull:`19200`: Fix incorrect statement regarding test images cache size. -* :ghpull:`19198`: Fix link in contrbuting docs +* :ghpull:`19198`: Fix link in contributing docs * :ghpull:`19196`: Fix PR welcome action -* :ghpull:`19188`: Cleanup comparision between X11/CSS4 and xkcd colors +* :ghpull:`19188`: Cleanup comparison between X11/CSS4 and xkcd colors * :ghpull:`19194`: Fix trivial quiver doc typo. * :ghpull:`19180`: Fix Artist.remove_callback() * :ghpull:`19192`: Fixed part of Issue - #19100, changed documentation for axisartist @@ -472,7 +472,7 @@ Pull Requests (772): * :ghpull:`19127`: Cleanups to webagg & friends. * :ghpull:`19122`: FIX/DOC - make Text doscstring interp more easily searchable * :ghpull:`19106`: Support setting rcParams["image.cmap"] to Colormap instances. -* :ghpull:`19085`: FIX: update a transfrom from transFigure to transSubfigure +* :ghpull:`19085`: FIX: update a transform from transFigure to transSubfigure * :ghpull:`19117`: Rename a confusing variable. * :ghpull:`18647`: Axes.axline: implement support transform argument (for points but not slope) * :ghpull:`16220`: Fix interaction with unpickled 3d plots. @@ -701,7 +701,7 @@ Pull Requests (772): * :ghpull:`18564`: Prepare for merging SubplotBase into AxesBase. * :ghpull:`15127`: ENH/API: improvements to register_cmap * :ghpull:`18576`: DOC: prefer colormap over color map -* :ghpull:`18340`: Colorbar grid postion +* :ghpull:`18340`: Colorbar grid position * :ghpull:`18568`: Added Reporting to code_of_conduct.md * :ghpull:`18555`: Convert _math_style_dict into an Enum. * :ghpull:`18567`: Replace subplot(ijk) calls by subplots(i, j) @@ -759,7 +759,7 @@ Pull Requests (772): * :ghpull:`18449`: Remove the private Axes._set_position. * :ghpull:`18460`: DOC: example gray level in 'Specifying Colors' tutorial * :ghpull:`18426`: plot directive: caption-option -* :ghpull:`18444`: Support doubleclick in webagg/nbagg +* :ghpull:`18444`: Support double-click in webagg/nbagg * :ghpull:`12518`: Example showing scale-invariant angle arc * :ghpull:`18446`: Normalize properties passed to ToolHandles. * :ghpull:`18445`: Warn if an animation is gc'd before doing anything. @@ -808,9 +808,9 @@ Pull Requests (772): * :ghpull:`17901`: DOC: Autoreformating of backend/\*.py * :ghpull:`17291`: Normalize gridspec ratios to lists in the setter. * :ghpull:`18226`: Use CallbackRegistry in Widgets and some related cleanup -* :ghpull:`18203`: Force locator and formatter inheritence +* :ghpull:`18203`: Force locator and formatter inheritance * :ghpull:`18279`: boxplot: Add conf_intervals reference to notch docs. -* :ghpull:`18276`: Fix autoscaling to exclude inifinite data limits when possible. +* :ghpull:`18276`: Fix autoscaling to exclude infinite data limits when possible. * :ghpull:`18261`: Migrate tk backend tests into subprocesses * :ghpull:`17961`: DOCS: Remove How-to: Contributing * :ghpull:`18201`: Remove mpl.colors deprecations for 3.4 @@ -964,7 +964,7 @@ Pull Requests (772): * :ghpull:`17697`: Add description examples/pyplots/pyplot simple.py * :ghpull:`17694`: CI: Only skip devdocs deploy if PR is to this repo. * :ghpull:`17691`: ci: Print out reasons for not deploying docs. -* :ghpull:`17099`: Make Spines accessable by the attributes. +* :ghpull:`17099`: Make Spines accessible by the attributes Issues (204): @@ -1044,7 +1044,7 @@ Issues (204): * :ghissue:`19099`: axisartist axis_direction bug * :ghissue:`19171`: 3D surface example bug for non-square grid * :ghissue:`18112`: set_{x,y,z}bound 3d limits are not persistent upon interactive rotation -* :ghissue:`19078`: _update_patch_limits should not use CLOSEPOLY verticies for updating +* :ghissue:`19078`: _update_patch_limits should not use CLOSEPOLY vertices for updating * :ghissue:`16123`: test_dpi_ratio_change fails on Windows/Qt5Agg * :ghissue:`15796`: [DOC] PDF build of matplotlib own documentation crashes with LaTeX error "too deeply nested" * :ghissue:`19091`: 3D Axes don't work in SubFigures @@ -1091,13 +1091,13 @@ Issues (204): * :ghissue:`18641`: Conversion cache cleaning is broken with xdist * :ghissue:`15614`: named color examples need borders * :ghissue:`5519`: The linestyle 'None', ' ' and '' not supported by PathPatch. -* :ghissue:`17487`: Polygon selector with useblit=True - polygon dissapears +* :ghissue:`17487`: Polygon selector with useblit=True - polygon disappears * :ghissue:`17476`: RectangleSelector fails to clear itself after being toggled inactive and then back to active. * :ghissue:`18600`: plt.errorbar raises error when given marker= * :ghissue:`18355`: Optional components required to build docs aren't documented * :ghissue:`18428`: small bug in the mtplotlib gallery * :ghissue:`4438`: inconsistent behaviour of the errorevery option in pyplot.errorbar() to the markevery keyword -* :ghissue:`5823`: pleas dont include the Google Analytics tracking in the off-line doc +* :ghissue:`5823`: please don't include the Google Analytics tracking in the off-line doc * :ghissue:`13035`: Path3DCollection from 3D scatter cannot set_color * :ghissue:`9725`: scatter - set_facecolors is not working on Axes3D * :ghissue:`3370`: Patch3DCollection doesn't update color after calling set_color @@ -1123,12 +1123,12 @@ Issues (204): * :ghissue:`17712`: constrained_layout fails on suptitle+colorbars+some figure sizes * :ghissue:`14638`: colorbar.make_axes doesn't anchor in constrained_layout * :ghissue:`18299`: New configure_subplots behaves badly on TkAgg backend -* :ghissue:`18300`: Remove the examples category "Our Favorite Recipies" +* :ghissue:`18300`: Remove the examples category "Our Favorite Recipes" * :ghissue:`18077`: Imshow breaks if given a unyt_array input * :ghissue:`7074`: Using a linestyle cycler with plt.errorbar results in strange plots * :ghissue:`18236`: FuncAnimation fails to display with interval 0 on Tkagg backend * :ghissue:`8107`: invalid command name "..._on_timer" in FuncAnimation for (too) small interval -* :ghissue:`18272`: Add CI Intervall to boxplot notch documentation +* :ghissue:`18272`: Add CI Interval to boxplot notch documentation * :ghissue:`18137`: axhspan() in empty plots changes the xlimits of plots sharing the X axis * :ghissue:`18246`: test_never_update is flaky * :ghissue:`5856`: Horizontal stem plot @@ -1146,7 +1146,7 @@ Issues (204): * :ghissue:`12198`: axvline incorrectly tries to handle unitized ymin, ymax * :ghissue:`9139`: Python3 matplotlib 2.0.2 with Times New Roman misses unicode minus sign in pdf * :ghissue:`5970`: pyplot.scatter raises obscure error when mistakenly passed a third string param -* :ghissue:`17936`: documenattion and behavior do not match for suppressing (PDF) metadata +* :ghissue:`17936`: documentation and behavior do not match for suppressing (PDF) metadata * :ghissue:`17932`: latex textrm does not work in Cairo backend * :ghissue:`17714`: Universal fullscreen command * :ghissue:`4584`: ColorbarBase draws edges in slightly wrong positions. @@ -1161,7 +1161,7 @@ Issues (204): * :ghissue:`15821`: Should constrained_layout work as plt.figure() argument? * :ghissue:`15616`: Colormaps should have a ``_repr_html_`` that is an image of the colormap * :ghissue:`17579`: ``BoundaryNorm`` yield a ``ZeroDivisionError: division by zero`` -* :ghissue:`17652`: NEP 29 : Stop support fro Python 3.6 soon ? +* :ghissue:`17652`: NEP 29 : Stop support for Python 3.6 soon ? * :ghissue:`11095`: Repeated plot calls with xunits=None throws exception * :ghissue:`17733`: Rename "array" (and perhaps "fields") section of Axes API * :ghissue:`15610`: Link to most recent DevDocs when installing from Master Source diff --git a/doc/users/prev_whats_new/github_stats_3.4.2.rst b/doc/users/prev_whats_new/github_stats_3.4.2.rst index 22b4797c2fc2..d16a69b43151 100644 --- a/doc/users/prev_whats_new/github_stats_3.4.2.rst +++ b/doc/users/prev_whats_new/github_stats_3.4.2.rst @@ -146,7 +146,7 @@ Issues (21): * :ghissue:`19960`: Failed to init RangeSlider with valinit attribute * :ghissue:`19736`: subplot_mosaic axes are not added in consistent order * :ghissue:`19979`: Blank EPS figures if plot contains 'd' -* :ghissue:`19938`: unuseful deprecation warning figbox +* :ghissue:`19938`: useless deprecation warning figbox * :ghissue:`19958`: subfigures missing bbox_inches attribute in inline backend * :ghissue:`19936`: Errorbars elinewidth raise error when numpy array * :ghissue:`19879`: Using "drawstyle" raises AttributeError in errorbar, when yerr is specified. diff --git a/doc/users/prev_whats_new/github_stats_3.4.3.rst b/doc/users/prev_whats_new/github_stats_3.4.3.rst index b248bf69b6ef..ff98041e2d72 100644 --- a/doc/users/prev_whats_new/github_stats_3.4.3.rst +++ b/doc/users/prev_whats_new/github_stats_3.4.3.rst @@ -119,7 +119,7 @@ Issues (22): * :ghissue:`20628`: Out-of-bounds read leads to crash or broken TrueType fonts * :ghissue:`20612`: Broken EPS for Type 42 STIX * :ghissue:`19982`: regression for 3.4.x - ax.figbox replacement incompatible to all version including 3.3.4 -* :ghissue:`19938`: unuseful deprecation warning figbox +* :ghissue:`19938`: useless deprecation warning figbox * :ghissue:`16400`: Inconsistent behavior between Normalizers when input is Dataframe * :ghissue:`20583`: Lost class descriptions since 3.4 docs * :ghissue:`20551`: set_segments(get_segments()) makes lines coarse diff --git a/doc/users/prev_whats_new/github_stats_3.5.0.rst b/doc/users/prev_whats_new/github_stats_3.5.0.rst index bde4d917b38b..c39b614e7bad 100644 --- a/doc/users/prev_whats_new/github_stats_3.5.0.rst +++ b/doc/users/prev_whats_new/github_stats_3.5.0.rst @@ -1121,7 +1121,7 @@ Issues (187): * :ghissue:`20847`: [Bug]: Contourf not filling contours. * :ghissue:`21300`: [Bug]: zooming in on contour plot gives false extra contour lines * :ghissue:`21466`: [Bug]: EPS export shows hidden tick labels when using tex for text rendering -* :ghissue:`21463`: [Bug]: Plotting lables with Greek latters in math mode produces Parsing error when plt.show() runs +* :ghissue:`21463`: [Bug]: Plotting labels with Greek latters in math mode produces Parsing error when plt.show() runs * :ghissue:`20534`: Document formatting for sections * :ghissue:`21246`: [Doc]: Install info takes up too much room on new front page * :ghissue:`21432`: [Doc]: Double clicking parameter name also highlights next item of text @@ -1157,7 +1157,7 @@ Issues (187): * :ghissue:`16251`: API changes are too hard to find in the rendered docs * :ghissue:`20770`: [Doc]: How to replicate behaviour of ``plt.gca(projection=...)``? * :ghissue:`17052`: Colorbar update error with clim change in multi_image.py example -* :ghissue:`4387`: make ``Normalize`` objects notifiy scalar-mappables on changes +* :ghissue:`4387`: make ``Normalize`` objects notify scalar-mappables on changes * :ghissue:`20001`: rename fig.draw_no_output * :ghissue:`20936`: [Bug]: edgecolor 'auto' doesn't work properly * :ghissue:`20909`: [Bug]: Animation error message @@ -1241,7 +1241,7 @@ Issues (187): * :ghissue:`17508`: Quadmesh.set_array should validate dimensions * :ghissue:`20372`: Incorrect axes positioning in axes_grid.Grid with direction='column' * :ghissue:`19419`: Dev version hard to check -* :ghissue:`17310`: Matplotlib git master version fails to pass serveral pytest's tests. +* :ghissue:`17310`: Matplotlib git master version fails to pass several pytest's tests * :ghissue:`7742`: plot_date() after axhline() doesn't rescale axes * :ghissue:`20322`: QuadMesh default for shading inadvertently changed. * :ghissue:`9653`: SVG savefig + LaTeX extremely slow on macOS diff --git a/doc/users/prev_whats_new/github_stats_3.5.1.rst b/doc/users/prev_whats_new/github_stats_3.5.1.rst index 7eb37b769d6c..626cf319c23c 100644 --- a/doc/users/prev_whats_new/github_stats_3.5.1.rst +++ b/doc/users/prev_whats_new/github_stats_3.5.1.rst @@ -131,7 +131,7 @@ Issues (29): * :ghissue:`21803`: [Bug]: using ``set_offsets`` on scatter object raises TypeError * :ghissue:`21839`: [Bug]: Top of plot clipped when using Subfigures without suptitle * :ghissue:`21841`: [Bug]: Wrong tick labels and colorbar of discrete normalizer -* :ghissue:`21783`: [MNT]: wheel of 3.5.0 apears to depend on setuptools-scm which apears to be unintentional +* :ghissue:`21783`: [MNT]: wheel of 3.5.0 appears to depend on setuptools-scm which appears to be unintentional * :ghissue:`21733`: [Bug]: Possible bug on arrows in annotation * :ghissue:`21749`: [Bug]: Regression on ``tight_layout`` when manually adding axes for colorbars * :ghissue:`19197`: Unexpected error after using Figure.canvas.draw on macosx backend diff --git a/doc/users/prev_whats_new/whats_new_3.10.0.rst b/doc/users/prev_whats_new/whats_new_3.10.0.rst index 06282cedad9a..f1231be53cc4 100644 --- a/doc/users/prev_whats_new/whats_new_3.10.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.10.0.rst @@ -329,7 +329,9 @@ In the following example the norm and cmap are changed on multiple plots simulta colorizer.vmax = 2 colorizer.cmap = 'RdBu' -All plotting methods that use a data-to-color pipeline now create a colorizer object if one is not provided. This can be re-used by subsequent artists such that they will share a single data-to-color pipeline: +All plotting methods that use a data-to-color pipeline now create a colorizer object if +one is not provided. This can be reused by subsequent artists such that they will share +a single data-to-color pipeline: .. plot:: :include-source: true diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 87d42b4d3014..e5175ea8761c 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -897,7 +897,7 @@ def _request_autoscale_view(self, axis="all", tight=None): Mark a single axis, or all of them, as stale wrt. autoscaling. No computation is performed until the next autoscaling; thus, separate - calls to control individual axises incur negligible performance cost. + calls to control individual `Axis`s incur negligible performance cost. Parameters ---------- diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index 337443eb1e27..ae065a231fd9 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -612,7 +612,7 @@ def test_triinterpcubic_cg_solver(): # 1) A commonly used test involves a 2d Poisson matrix. def poisson_sparse_matrix(n, m): """ - Return the sparse, (n*m, n*m) matrix in coo format resulting from the + Return the sparse, (n*m, n*m) matrix in COO format resulting from the discretisation of the 2-dimensional Poisson equation according to a finite difference numerical scheme on a uniform (n, m) grid. """ diff --git a/lib/matplotlib/tri/_triinterpolate.py b/lib/matplotlib/tri/_triinterpolate.py index 90ad6cf3a76c..2dc62770c7ed 100644 --- a/lib/matplotlib/tri/_triinterpolate.py +++ b/lib/matplotlib/tri/_triinterpolate.py @@ -928,7 +928,7 @@ def get_Kff_and_Ff(self, J, ecc, triangles, Uc): Returns ------- - (Kff_rows, Kff_cols, Kff_vals) Kff matrix in coo format - Duplicate + (Kff_rows, Kff_cols, Kff_vals) Kff matrix in COO format - Duplicate (row, col) entries must be summed. Ff: force vector - dim npts * 3 """ @@ -961,12 +961,12 @@ def get_Kff_and_Ff(self, J, ecc, triangles, Uc): # [ Kcf Kff ] # * As F = K x U one gets straightforwardly: Ff = - Kfc x Uc - # Computing Kff stiffness matrix in sparse coo format + # Computing Kff stiffness matrix in sparse COO format Kff_vals = np.ravel(K_elem[np.ix_(vec_range, f_dof, f_dof)]) Kff_rows = np.ravel(f_row_indices[np.ix_(vec_range, f_dof, f_dof)]) Kff_cols = np.ravel(f_col_indices[np.ix_(vec_range, f_dof, f_dof)]) - # Computing Ff force vector in sparse coo format + # Computing Ff force vector in sparse COO format Kfc_elem = K_elem[np.ix_(vec_range, f_dof, c_dof)] Uc_elem = np.expand_dims(Uc, axis=2) Ff_elem = -(Kfc_elem @ Uc_elem)[:, :, 0] @@ -1178,7 +1178,7 @@ def compute_dz(self): triangles = self._triangles Uc = self.z[self._triangles] - # Building stiffness matrix and force vector in coo format + # Building stiffness matrix and force vector in COO format Kff_rows, Kff_cols, Kff_vals, Ff = reference_element.get_Kff_and_Ff( J, eccs, triangles, Uc) @@ -1215,7 +1215,7 @@ def compute_dz(self): class _Sparse_Matrix_coo: def __init__(self, vals, rows, cols, shape): """ - Create a sparse matrix in coo format. + Create a sparse matrix in COO format. *vals*: arrays of values of non-null entries of the matrix *rows*: int arrays of rows of non-null entries of the matrix *cols*: int arrays of cols of non-null entries of the matrix From 1ca2136ba736744c5bb056063e5567291a350f74 Mon Sep 17 00:00:00 2001 From: IdiotCoffee Date: Thu, 10 Jul 2025 19:00:57 +0530 Subject: [PATCH 38/57] changed the FAQ link to point to the correct path --- galleries/examples/README.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/examples/README.txt b/galleries/examples/README.txt index 31d4beae578d..363494ac7e6b 100644 --- a/galleries/examples/README.txt +++ b/galleries/examples/README.txt @@ -13,7 +13,7 @@ and source code. For longer tutorials, see our :ref:`tutorials page `. You can also find :ref:`external resources ` and -a :ref:`FAQ ` in our :ref:`user guide `. +a :ref:`FAQ ` in our :ref:`user guide `. .. admonition:: Tagging! From d7c5c6770be57a6b8bd787fbb43ccf5c954c8cda Mon Sep 17 00:00:00 2001 From: Brian Christian Date: Thu, 10 Jul 2025 14:42:56 -0700 Subject: [PATCH 39/57] Fix whitespace in _axes.py error message This fixes issue #30285. --- lib/matplotlib/axes/_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 8ac300296538..3e39bbd4acdc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2296,8 +2296,8 @@ def _parse_bar_color_args(self, kwargs): facecolor = mcolors.to_rgba_array(facecolor) except ValueError as err: raise ValueError( - "'facecolor' or 'color' argument must be a valid color or" - "sequence of colors." + "'facecolor' or 'color' argument must be a valid color or " + "sequence of colors." ) from err return facecolor, edgecolor From c6798ce88bd20fd624090d867b9925a03ed2db79 Mon Sep 17 00:00:00 2001 From: IdiotCoffee Date: Fri, 11 Jul 2025 07:22:16 +0530 Subject: [PATCH 40/57] changed second instance of a wrong link --- galleries/tutorials/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galleries/tutorials/index.rst b/galleries/tutorials/index.rst index ace37dcb6f57..48187a862a2e 100644 --- a/galleries/tutorials/index.rst +++ b/galleries/tutorials/index.rst @@ -7,7 +7,7 @@ This page contains a few tutorials for using Matplotlib. For the old tutorials, For shorter examples, see our :ref:`examples page `. You can also find :ref:`external resources ` and -a :ref:`FAQ ` in our :ref:`user guide `. +a :ref:`FAQ ` in our :ref:`user guide `. .. raw:: html From d2d969ef9d01297728c15c0fdfa957852201834b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 11 Jul 2025 01:30:39 -0400 Subject: [PATCH 41/57] DOC: Fix build with pybind11 3 As of https://github.com/pybind/pybind11/pull/5212, pybind11 now uses `numpy.typing.NDArray` instead of `numpy.ndarray`, and as of https://github.com/pybind/pybind11/pull/5580, it changed the name of the internal wrapper that Sphinx sees. Since we already ignore `numpy.float64` missing references for the same method, add the new name and new class to ignores as well. --- doc/missing-references.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/missing-references.json b/doc/missing-references.json index efe676afbb85..1a3693c990e5 100644 --- a/doc/missing-references.json +++ b/doc/missing-references.json @@ -122,8 +122,12 @@ "doc/api/_as_gen/mpl_toolkits.axisartist.floating_axes.rst:32::1" ], "numpy.float64": [ + "doc/docstring of matplotlib.ft2font.pybind11_detail_function_record_v1_system_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_1.set_text:1", "doc/docstring of matplotlib.ft2font.PyCapsule.set_text:1" ], + "numpy.typing.NDArray": [ + "doc/docstring of matplotlib.ft2font.pybind11_detail_function_record_v1_system_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_1.set_text:1" + ], "numpy.uint8": [ ":1" ] From b02ed41eb521146db3921c624bf10d04f8c45189 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 14 Jun 2025 00:13:13 +0200 Subject: [PATCH 42/57] FIX: Ensure Locators on RadialAxis are always correctly wrapped All the wrapping logic is now contained in RadialAxis Closes #30164 and rearchitects #29798. --- lib/matplotlib/projections/polar.py | 23 +++------- .../test_polar/polar_alignment.png | Bin 55353 -> 55019 bytes lib/matplotlib/tests/test_polar.py | 42 ++++++++++++++++++ 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 948b3a6e704f..f20498b27115 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -679,20 +679,15 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sticky_edges.y.append(0) - def _wrap_locator_formatter(self): - self.set_major_locator(RadialLocator(self.get_major_locator(), - self.axes)) - self.isDefault_majloc = True + def set_major_locator(self, locator): + if not isinstance(locator, RadialLocator): + locator = RadialLocator(locator, self.axes) + super().set_major_locator(locator) def clear(self): # docstring inherited super().clear() self.set_ticks_position('none') - self._wrap_locator_formatter() - - def _set_scale(self, value, **kwargs): - super()._set_scale(value, **kwargs) - self._wrap_locator_formatter() def _is_full_circle_deg(thetamin, thetamax): @@ -1242,19 +1237,11 @@ def set_rlabel_position(self, value): """ self._r_label_position.clear().translate(np.deg2rad(value), 0.0) - def set_yscale(self, *args, **kwargs): - super().set_yscale(*args, **kwargs) - self.yaxis.set_major_locator( - self.RadialLocator(self.yaxis.get_major_locator(), self)) - def set_rscale(self, *args, **kwargs): return Axes.set_yscale(self, *args, **kwargs) def set_rticks(self, *args, **kwargs): - result = Axes.set_yticks(self, *args, **kwargs) - self.yaxis.set_major_locator( - self.RadialLocator(self.yaxis.get_major_locator(), self)) - return result + return Axes.set_yticks(self, *args, **kwargs) def set_thetagrids(self, angles, labels=None, fmt=None, **kwargs): """ diff --git a/lib/matplotlib/tests/baseline_images/test_polar/polar_alignment.png b/lib/matplotlib/tests/baseline_images/test_polar/polar_alignment.png index e979e7ebb6b8ecdc694b88018e8c449fa758b726..7a12c5d5c783c5270faa6ef339189cce45c31637 100644 GIT binary patch literal 55019 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 N6aA$5e&o!Ie*>|F)oTC% literal 55353 zcmd?Rc|6r^8$G&}GG!`LO6G`&B1PtHRv{!wrZQCs8H;3|RVax}Wk^yq5*edGJfu{{ zj45M=C}-WhzjMz2=ik%kectD3v-kdf@B6y1Yh7!t>lMr z0u;(B3;MNqCGA{@JN`xMsi9{|k3WI*4w3jfgS+k#PYQ+Ami$ALuX@KBFB*DjS$G|C zJL%DL1cE&UOOlPMr00c0036T2@*{+TG35OIA){ucV`_to=#*y|OZP z+XbAwyxbLc?D&7*Bkgw9afk22ZWjtgfTBlLH}y>)|LSLP_19mO$s086&T6z&>K9E8 zBac2W!?ThKLak?4D{S8-#cZWyDeau*>T-L_f5)SphAc(wP7Z~qg?tNP5-JKFe=T#D zrDbNRwj_OPHSKjjo1qKGxPun1J3fsl3ingZe5=Nx#ZLYA$5P;mB88p&8D-@X`1ew- z{C2fS@<-RzBAAW*DCxs}il7krQ*2A4@xS-gt_uD4OJ(6}=*jP}J2Nw}{rjCZ-v1we zP`;_eo9nyi?%lgL(w4_``@qfUnwlEMMow;S`?2-{@|yqLc+~zIyUqNS!!$0k3v1sx z`uNPkSW$@_UuIsHnJTrAc0-+x)A61FZbjGCI7uLjTJ!jt(oaC1kT z`_c8XwUvGT=hxMg%_@OB-QC^d5)vUvN$ej=jwfivaG5FQ#^5s(lG)~?yu1BY1Qmy?NJUl!GPuE7U>ePlaw>~&w z;Pw3jbJgG9k`1vNbCTx~$KL!AHVFK^nwqux0b)CM?V_P{-aA&CEP0qh`SJO2iFSG*Y*va(Vjo6Q7AKipLU>-P@&_; z)f&{?;Z>_w3tnlG@%d?0=GL?2z<~oJ|J`Bb-F=F_w`p_IT4r8~?*pre9)=L33!B;5 zTUE(%%pE2nwMpSj_1m}1M~)oP-M_#7Rg$o*Z4E6&ErNy5#?6hDqHADKX9QGyULtbJ32a2wY3ZRI&Z13mUC&RXAING z&c&~0y0hS{o4Nm8_M>WPWgUBdZm^R7CsaW?s2!+?9-Tn@I7Xs8IIrIFh5(^Gy1_JoVJo*VlM_=Ii@BgH(xx zx9{FHG+hz1J$Z61D{BJdCZ6@|?8;34Pa;!!^e?0Re@=U`xh!f_YFCa)0UH9ntV}L9 zFHdmSt_`0)eR}oooi@%M4tbW5_$^ZjoDE|Oi^#S%lgMm(bvA)$yG2$C4-e1Pl~)cs z@o#J!4}vH0^&tKyqo5EG7Pe;l_U$&#&I|?y1{Oz;<{Wu=(&@w9jfj3GVd1}fg@oF; zm+QoY+Gy(Q>my@h>uxIfuyJyREiCwoh=~bHNipx;yZ2Q~i;jnfEUo%Nqw7X|$@}+g z+2S?$e|*(oeV70}If(@Y+oGeR1yWxNMhh`AF=d$)*x)!kc(4;!nymlJYJTav@+LVa z3KJ94o;`cWq5RQRe(BPs)#>T!z5V^GHf*@U{l^Str0&TEMRsFl+_3xH_z|lYfg2Uh zd{HwrTxW9VP*P@QLZM3YenUf>GiO+EOCLXetZr^*+)=f>Gi~>2M!X_-x{CvuGYrWj zP2P3wp+o--zuR^8n=VNjva+)7o}QXGFpI7iFJ9zPy%4xqg$qhbNeORnH+67u5D*q# zP1gO*8>+4@y^yf5t&t^TZhrLWkyFnLMGlYP3avOi9B#q z@#V{xG+1=)#&|y4Q>X4GzhPituy}e_lpNQ~m)E~Z-}_L5#w5Aua7lmiVe6L>{1+mn zjy|GLF8p0|^!4+zZMrP#yY%C+$M+9XR2>~*X=#?JsVQAuT{ROElQ4~X>{8q&h49Ks zWks)%;GbWg55y^St0inv7P#WkEX;0v>?KOHu26`8!1nFyRR7FwKt^Im0K9o)Sl%5Q z&b+Cnt!+KNFf=De`0uZhAacX*XU`U1IbIlqJ6yMU&*W34L>xj^EGY7a=9R7cn?D~9 z_#rPMA|fCwTiWcoM!gz0NH#MxB0?BngFD3O7vHwcSm^^c@1~-6$n)n);jH{D3JQ-E z7ng*E+UU|rqFWOp&^tZ&nx2udrap$7T-Nybsq&^TA3l6=_wk9qn~>~64P z!obWNR#a4EIYmP$srsv^va;-1*VveYU`Wo8ywXI1jGH@^u_QSqrJoeL!PMhyn>WYi z=89N7^J2)#%CabT;S3hjio&h>&;MlJv17;VSVxQ&1A7t|fi`WFWc{$r3%?nV;~NKG zrzWMPSz4dM!Na*4DDSksgzM3c<++GO?tH$ZN>EU6a&po)%eAhd!LBZnowF;Gi?6h_ z)Qn4@#qQLp*b9Gtlf@id6CpqkkW`(Z`ZqE$k?l)W5P#6}6rJ?3XQAb;ovSFPPM@Aa z<*)htc@xUi@R${q%3FQVTqxUevNL1=H-c0y6;$jo(1113L1uj&!+dP==ktle%9b$+ zY3XqNybvqtlF2ln0*ZjDYE?t$iV`wvhH^l*V@rlbshyVJ(tp|CW=H>{XJu_{&C&6g z9Wf@!Katjm<2t3s_ANpWJYy$^^a9Ss?ydKk!59N(_2_}?b_w_^UKB; zFJ4Uk`W1${Tjwl4(8wPYm^G7m;d;rbj-orKyB2DCDg%7HCc61vr|zJmI5;}mw`3?U zjNZv~|MT0sJS5j|W@vCtRB9?W(|Vz7-d-^=_Eyj4H*e|?f$ra4u_LroCp>UKVpY!n zwpp0$ySB$8`yJQjpiUeNZpEHgujq${hLA4rO~4&e49^(!z?MvTVUk@^_TbG*YRS=Wt<$>3i7U zrpasmcYA;7*+Kof`uZ%Lge?}2&+t8X@W9B#B;=uEOHEJDakuD%uKw}y*w2sM4m(wy zU)t@kbED$9adCAC@0663Ysvw;-`!M-`0~Zd;lv4Be4tb3Jw7`-yUBX)<5ws0c6#(v zt*R~@8+(QH!Hc?*UByOA6T!;gu*t20=Fp)-1e4%uabjM6FR6+@eqnJ9pxhJxUlK!a zuGfa-@SZ(;Hu37!+(&QjM?}!|_xA&k8+V&qT1MZxB{2K*>n1KPE)(kctw@h=ZYZ#_ zZrN8o(3rr!etn2SPvC~IurT^{>!>Jq$lg=<&fdws+Lp>q``D9S^r{F@bLVN*t9^RT zN4ULMT$_}XG&sYGn3N4%aDS7byorT{W&NGI0pTos(Hq1LP$&RuVaQuwp8K%g*nPU@ zTho=PlAu3K6qysv96TF0+Ac3leSGZ3k1K9wiotQ-qo`QZ(6CnN+<0(uGAB8-%Rr&z zaLPEn7ij7}z8`yU|L6CwJr|buT)upnvhVtGH*h8r`R7QFFky?D=;un#=ap||FC2B%IN3EJFpXU6ktO&YisMg#=+rXP3hFb zmcbM?U*A3WT{b>G=H4cezuwaX*Nce=U0#b_Ym>y{SUhlGAeRvmvWgO1UM@e>lF6#7s(Q0$q2%6rErMS)QA!aT zk=k**dVB@u$VPSuuaBknI(}IvTwJ0BHN$&nT2xECc4-L-9jixr_OfNV*m$LmxNN;cN^nPx%8*u=ry*UXg z{ekR4ZF=bTbnhVkZQrHurDtJjK>V}<_|5+K%pJoerGb_rH~!9{?CflmHF58CH$4FV zNO+)j2O|NMmdfr^RxUmBRk7^!XY(;%Dz%)ngrlLs!IZ$MdT!szUK+|Ry(?R-VBkcf{k9I*u}i zt(qBb(Xp`+0Q|58_Mln4)&>{@?Nm(rlvnqQ^Gh;kzU(QmA1S+`;MV_VzDEZ;j?gyP zQ5njxVXMuru@02-RK+dq#&O8^+5Tet_H9N2dEzy?o@yKlc)q;6eE-y^o%igt6bs7A zIFQO3fjgVIO^|sTk*VH4bUYaKAHXjw!l6(>LqkKxbI1_+e--7K*Jy}Yk-6>2`>ge1 z!fhGI^oBQf7YDi}r5?Yq5ho=iA|j%$DKawS-+O1~#y?$J!^9;ZD!PUugKZk`uH1C) z+&R4W@&OZ*^(wzdglXyMG}&n7+`2axnHSg8MQzB7*QU-ITJ*uIdLNF z*Y{3*c7gDEcC%=*y4$b=OkCSZXNld3sj1OeSX$C7FttNKr~4j_uzG%O{}?@O^_MT3 z(fo;{>Qq&!0uPcN<=TxK_I)*BDETHjB0?s$f)-R$v3*i<s!ylY4G%Zjaw| zZhLE_ZUTWKaZIeNI(H7-?CtBjh=yTdVL=UhQ6J5@d6h{5dUHL#i!N$guh`-hBXJJj ziOTuOkyhb#8+X#8D+ekKZJ&zNWhIT8v!T8|W4GTh-||!=KWU_nx>bhZP+C}6kgNWC z5bXz$S-j`)I~t1rudy&-5YG}mp*9{Bf01lUG5S?1e;4dOe*QcF{GvAe?j}1wKM5%= zeyVpYx{r4m$}yp#w4?~7?l`XfIz_5)c)0QHx+q;?(#w{Q4QB>5kl4I&BWCFBje+-B znj>GIKW;CL9NA0S!5@!%w6(R@@+zE(RSuZ5`FP*fq)|wSuVxDl80WTa+kodU0W~l( zGTskdJe!-Fdw=1*Eh05hag}p002v5{f9uG>QT_Tg)0s18boKO-6nWW4>F0vkTAA2s zX>h_nJ~+WdtF8t_;_-Xd8S!es7bkZY$5q$R@T%fkXCuOY#PNppTv%dWyLRn0><=RH zRlRWJNEpc!yiAOYAqdlmqwJ$kVz_to7eDUSIBgX7>=ZJDj+)GH(O`P@dzAC(&4_mL}$Pk6De}Gzm5UDLEqFstVSLwY$ zABu)X6Fn4YiAYw}Wj1C1S+$cVZzF{BSq9j)3l+8_b@mPqM>Hnz*Z%k41^_bKK6R>@ zgp^eMecM|1>2G>~3w^*l4M3)O@mgSwaDq%`ynM~5R9esoEL-+PW@$#~liP~w?X`*J zQ{q~)X3a=z4&A>ywzT~BBQ|o!XX#kCP*JD=jA(p(d;r5r7X-3hre?_4pg z{$~I+kShGa7p?M{=&m3C^f2k_)ll$F!U6(nV-{2qK_DW3T{hZv{3@G)&1Ga|Uq1C5 zCfN$#&v(m694D^m1J{-<@hCI5%F0ZO&tu0vK6Fg5JZfhr^vJEpx{sIoy|bk8`Yy-! zDD?}!f7_Apy{wgc=O1O!#mUDPhpQGw&y3xz{`AmMF!eQdiq~tZUkl(P|Mcc3$jjqj zo^FVWigIauG2JZ5qY}6W^v>()M->(O_W94|*QC~iB)FQKOhY;I<;lg>bS%C2%qIc! z($;>1oKK!TOW|JvCPl-GE_eD75cU8{H;N_Q^vsO0sVS-;UF^nPYj^M7T|UorOECvM zmF~fV5k*CkfcrL%juA}U((Z>0gp_OdVnrOje<&vBmug&hS=4}_qyk}MDMj~Q3OPVX zZbju-Pd@$&3xc-|ubXt=1{-N-w@NsNJBXz`VE&BZ^_^>wjh#L}k|G@%VpvE{jsv}e zSq3*UA3%nXtStN2XWo~rqzS$NziEA8@dPNPJIfZ}a*_XSfB^_+(Dz2961zPRv*SfD z;tSdDsUv;TmOdN~ts9XkByLkUB9xWxLzlyL&cT0mF(PY+JG`~aT!oq4MuxtX7x zJGuqbNgnNi;q4g7q~R{LldXy zU8ZWnt5>=pTIHPHtNr+xp1%21cg05T9oD9xuk6x(R^^z51)m9y(r zmM1k?nuOYfK@a;M0Hs3M4)DLf=O+>ne?^sB5Ek#>8-e^2R8ZgsXz~5?tF!gFk6ZZw zR^!}6_pwoBYY+rL{)OdODW-tAado6N+4H}>ERGy8lYFd^MHq_7-j}P9Gy9eoXAr#w zALMlvf&6L+fCa2n+q%@VD+lpeTVJ1N?l0rw^6ga;Is2rh;^iFI&U=F;`!n`!>UbfV;rVOioSp zP1g9|dh&z^JZAl$rTM)aEMot_K;PhC7>J-w{{upm0_$6MDf>>UoodUCqQA8)1~Rh71LueB#Lnv< zv$dF7KYcoK_4S|-rS``Z{!*PzrqH(J( zjqhsx@WFif@6v>;vRIOe4UNVuD;B1{;Lx3Dl-1%VAOIwo5`o}jz)laYNP{6;sXd1n zn4Y_{=G9n7Q9TMp(I5JdTj$<7|93kI;Csi)W84Z48{4GS)77h2Yh5?ox{9sp&)ny+ z7pL3M;mMTd=j#^#!1-cZS)MroYT_D)+t+dbKYv&Icq9s{}3>o%-)~ z+{n+L(tnp{TKXrdR-_MPsvMKsy_*XZrK_@Q)rCKGKHYwIK9m@g4?mxHu>qUqG`LCu~&e*7qP>rttzt0TyYN5+P7Zv5j#dDjo7im8KIH}`tpuA%*dzur1@ z=KyC4&sJwcoTtP0tkf^9WtO?H?7OhISUD*V21wTR!}hz!p3hy65nBE;^AX4Y$1kMy z^10zZ`?h9BqXBg`va;Gtxdk4d(;7iti?CqdwCU(zox$DyS zIt0PS4Y&Y-`6<2_Z!i{Eq8X4N?O zv}J%06WY6XBMQmF@9!mjKYy|$>zfUH$CABlYGMG_aAC+}baeEI=P(NgqgGzY^CwSk zfllX#uG*bl-FMK=GGMi3eq6bvZf-GF~G_Ru#VNAb+XQ_R32( zfGuQH-$oKQ;ONIP=M5?gy_Z}Z zg@oMKD;j&JD|#0;?-jsdfUw|lg+~buqV3KTwzgR$mw8}=`++y=s;kvn-@e^XD3d3cS0aP@>&=i@Wpo{!Jc(&B>1@aZTA*@D5&U-Oxa%NXSO)M@XH@|e3;Qw|*^uy1 zKxO6S&6aVX>jCPb;&|oPmsnT+e)FBA00_R>V1TcsrXDUfR9&9DBz~od3ivby_NPNJ z)#WGZmz9|w2Prf$lsaIc{POv+4ui6X4<(OV2r4NZ4P2Pa>e67E8R3Zkzr6r&Zzyo{ z@$q$-M{UqDPREg@SF7-wxjodQCcli5)}JDEG&epg6<~;pB-vVBeXAzO)$F>gefUUk zuK*V)@yBN7!KNnxjD#p2AWY=n8B)#cX*5ikS4@MKkDWXB$jQ!b+DdW)!ml>1fb|pi zzNa($&NrU|D}|(_GVeEQhqQUH6cu;*W=!_N!jye$QV|vZn7Sw0rNjO&BKafPQFWTN-i%G zLGhB4m*=F3MBxt{!V;Ed8*bg;h~oAa^?Ohi8F#~5u*G1`jn}cVmP`lcnQTKj^FU`^ z{%3cjt^;mIszXYRYRvJu+*NFDq;-HlVF|Q4mC~YI*E^b8f&MP4si~>uW<;Twq@)2Z z_)OTH*;!S5SAP|hkLj!k=1sdfNF^(`mylG0;#&`0I~qG|vgsKC4>2GI+vh&#CxKL$ zf=-jD7~T!!&m|=#wc_z)G&AV$k--ZF2ZM0(4i7G(?aPMdu*aoce18SAP-Uau2$h#( z(WuI>_Y9Pc!+r{ZYmqwH-Zpd~Jm@A4A!r=YaKvyK93eb{f zmQgR{U^F0|V*v%*Hf-1cOs!pf8cH|mAn}W$`gzaC$Hx;B6NAl*Eo~7e07nJ0PjED` zV>vY~fCn0(7ixLsQLY>LFY^7*Q-vfRyLL)ny?T`(d`d8I8J~XM z`hUzY(MZ5$<0D_b-chv<@&tDJA0sCtV|4WO1rQmpUcc6ay3u;~$S%~h0n!+P>jlB~ z#_^t&DE%seKLH^WU5~U5m9LR!~sHZH~$Ael-nVFd-zs}@f?ZA_W z2T4I{+ja6yHO_7;U<5oJ=_==WaR$QT;+W@+gxWX?p4&u@T_W54DK0ZJbI0x6+(^Jk zPUdy%+O0i3JlM(UD0e;m<%ux>pGzY;>nJoU9Q>*mtWJc38f$HBHKr9{*|;-QGeph? z-CuS>LPEQ&E**j=3L2Mx0Oftfda|3!_|gTgBu0F@6#E(BfRcFQt)@IqGEVPxL1`#3Or_U_Hf&!-1bF>vpB zxo)0`$KN+hpdUkWa&p>b$BKa+eEt3V5i{CAa=Gz`CQhTxLAH>!23|&UMptZEepwN0 z=fySj#(Xg1(81oo!O3Zhg2bb6CcQmrthnUGZ^o+SX(mE0AW}F$!=nc4^JxwPl5%54hYd>YY8qlVp%=y3Ecy&{Iv}-Q)I-ov@zRa+pLseKOz794Ch+V7j^>d+Y|N9tW zs7$@zzJ>Pp+rieu1UX<>WR$me!LLmom@N#H98{2|z04iAObYJ%OjuVSHV6j+vR|99;5t2et7!m`9xV8>0LJmiyKvB|i@VuRWhsNZFwuF>gqy5ERa_i= zng-Gp^mPnEJ{x@*yde0PL~u6Px}T3gGFt_c za6n+WlYCJy-O`*StYb$O9!3T%JYrKsSDPACU)4oj4|EiUi-f4a&ckCd z8two#UB}FPBzRd19AHI7g@lv_&h90+ zSy&L!?j9cMXU^QCWr!Nt_KrsIvaYl9Hqr-x4m#EIf)CeJJWU#LFtF8tK`{*9Qy@_j zIa{qgQ*a`GBCH#ZzPXMtm1 zaoGgza!Q)(@`Yu*MCUk0Kg$j8guhuJe&r=9Au3CUiUO$k%7FO>P)~gW134GHsI%LV z6Uv;RQZ4jF@Dr;S7|IImPGY)17>A?Gcbt9pOcUfdnhXm-kapvdjSzM~eYW6hk?9D0 zEy!`A=4i*ckAE!XonM;?PaCis(Y?Xa9DY3-IdTM!C0zsb;gM(oUIKSRsroW}DN;@v zq7l3_P=rzy^>5~y6iiOP-XV-*4go8;s;a6zDROodh)k(H;G7!b`IsQe!YZLAo!Z$| z9upIjl$;#KC1u&(C0{NDdVvsdqyOE>i`SrfTbw<+^X}cdHg z#%R>qkZSszTvIJL9ai*x|0fBRTLw}N4uu-tR7A2aG%qRIk8MT2k=!mD>D7(aWNPS! z+lSXJOz<|K7ysb&uJIx0AdojN!Qt9B#@C}^aQ*e0YRZmyk`o6n0(?PK7qE3SNRn}1 zbdYX!xt+hFrV}x$0^a&|iBE``FlNl7ohAH)><6P)%*Bt|hOF?u>rbHpkPY-~r?1b-vD0fx7v3{N zKNm&L$%{8GK<$7R!S%<5jEoE--NIu;ddA|fsk%L2_S+}`{GMaBSXuA794#Mz1L`))|f6;@sj5KNS7#(Z76cKHa5YMhwrst?-vGKZ`&&f zl!^44^FL95_dcjkuknxUAp%Z+x?wPijEZXgeO}TYfDg$J4pKsMLuxY75o+6r6X)Z?#P!2nq*7_ zS?XCKmKgYr`iJ)!OQG(zGDXJ6YeQ)x>rl48L|lr@h!LPC8VWAr z$oCDU0|Nt~=8bpk76m@ket7aNTzlwhmETtQY;>wf*%Ov zH5wDYFnb^+V@u1zPq##(x_(brLQM4U*z|p9C>*&}SXr5GqGBcfm0|<;$z` zOc*Gz8^TLb6^bThyVVo6|Lz=Y1ZslCk(g2;FBjbm0xBT%4JzH>X(Uxx?t?Qk`A@#N zb`j{87jalVm7=#A8a|qaSil&4KCSi0m>?>ZC;vOBDoC`DJ=9<8hlAP74nfuEDtD#B zF@xTXd~3ir8kWA#Z+(1xyc5{JcH{gMfOujK0@yIww0oD|v;#8p)p;Ar!54oqZtk(3BF@NRK&NBn-M=*ZZzC*SZuz!F5408#8EunWWy zB)$lwoLGLa{3|W}aloK(m6n8JLI)Iprr3D}?PtALZ)B@9fFuN|N0XYZ@ zvvcRp_AU^!d5{YZHq1YN_Uyxx1=8O@U6aMP?em?a;2^#PD82}P2;jnW+vSeT%yDnHtFxFqXf^om6QZN#@8{w_t6!C@g%3}K|*=>XbE9dKOv z7pYyMsjt~LZ(fHI1M)t3Mm@67KqPy3=*JJKRcttKQr;YTu)Q7ka(8E&nV!y#&rXG& zTlejoL-`{_UkrGVtKv!G_pF|pfPR2cs`zVX=V3z5ax!Wq55vlSh*n^W(z&~q^W-CN z7e{g`ayR~y$P(+Y2wIeXjgxWY7$-7`{{eKSbwsX1|ACm*BW!ThZ}Z-q?SB!eI79Q?x!vd% z>A+f9J#kkb+rwKPv~mFJE-L>l5_(4ME%i&l^6u~k{__Ut_>oY2_Oe8Y!lqT_cP*;*JJbK6rRc)uyZKv)tjElo{iK?&tdAR*>KkUh4=Z&7A+ zbDMA(E`9j$(9%4i9^slHyiiwX_E=JrL-OBw|DiV}tx-?n+`>fgeUcUEgQ%_wW7|)s;FpW1Bx) z9Xr-HHYQ64W$HC9Bv4wwkfyyL1i;m3>k{yMVBS8?vkRTiJ8oD3Tfm5)* zJvqO0E^H38fGrfLYl_}3aV7g(&LMTh^2!%3Kcseoq|J8gd9lam=Vr*N@vJHfYACGFks}x=Xj%``CswM+69PhVshE?Gty#5bi(h3=H|k-Ee`+zr#p-6 zFx`PvwvI8;Nz6P}HE9OMbz%-7c!(lv+b%JRc5SDfn5n>UXw8a+VwBqAzW zz{v?z2PfrlOXgvv`O(d}_l`YxS>RiS&m%wvyj$_tgNMI9@yMxoa;0f@LNXkhGMoUg z2wa3ps?QgZ|E_;$1vMF~hR@|AMuxk0w^xq-fxO&^q^w*8OQB-WvJ-C<&2YC&JnEF6v z$pV-C^9D|Ra?DKT<~M`D`w~Y;7I1(1&Jgq`;x;dZRPZ)Dhy~cM0Vo#y@&=5wu>K#~ za7>)n5l&qq<--V5gT#R~74#O#R*X;?jg~LaH)f-0&L9i_Tw)$gTc@DnxH#> z_2x}x;C^D@f>?f4F+LYONaB?%tLf+>T->(u0LXx21YbcUe_s8CewgUP5Xj#a%NhmgIIRq9wQ*sM5px$5RDzg)sasR#lYJVIO{k$#oW5Wz{scpZ$Y>30CVLw z6-qA%tnDfpSM>90NVouP7UZ-upzfRKjFR>2qq|^;>T6nXfH(&~n*j3Czf4}J01bIfP*70WnXi^(;?&DHPzXaZJd_uo z?Ewr%&;`VqNsUfpaSs2Py*F;$V8VyM*9R)`hUR=^HQId&dS6x0$fdB;-CW55(2_hA zJ}*Sd!njYCB--kTgQ|Z_$9PN|n;af6*6Wy>f&xkuPa49-RQL3(gq7rc&wLmJrRbul zL5cvpfdJQ+NjAgBw*{c91$iA|CEXb#NP~3?$BBj9C+K}uaRNxN9>NINX|xH2jt~{1 z(CYM}-vCiIgBArK%=?97HLOmcD+wnGq8qC%vW-h1cRu65K2~OCE%Zov;0>ex^=Sq6 ziCm;b20uH-R!%AND)&l{s&K;m>B-{P$fKuBy32%J(G(aOUssvD!Mt5J5W zRHoAF8)G(Tv4h+C$6e`{OQBmv@l93Ku55ekcDz8i-8pE7O8n-%CKKT&L`6iZVFFAG zG++Gc(_@RG#{2xjF$k3LhpqkAfV;-oO;Kb?%7tIZ-ym~qhM0)RtrM?PEd6+IV7ScQ z-X11VtAY8#{b*z~NVA8Y$NQ>3$v-WXLF6M`Atb|)Sf*^0=e09t`ztwwl9QGXOnHll<9xQml02}*gIli0BrNDU~)YnlL zIVOp9Or1U5|MaT;ukZbP7V`7OD}H@AN<>W`v8_!2l>j;IPxnjZEeAb9D*XBLC;3@y zT%2grE#`w_xng2swS)xPL$y5o_ys4Ma}&Z(Fgm+?ynp+NsVRsr1E4Z4f2oon9a4YH z4vWCVT%fNAm@d$~92*lKB*z(ZbVnaNCBH)~DeQcFQcZu#2SKY1W+qU7M=A^C`e2mn zeU1(*@hhTRMK$sxBX7v9@Wz9LDSv~Fc%bqw|FxV)Hy?6@Q2Z>eK zJ60dIa+g0DxKkLaUwoJ-`VR0S>P>s|Axx#~w(PSgn!nn=(lqQn*{cpF*nm${m<&e( z;gcH>JD35SkYw&b&o!D1GO8EGR?Jp}Bc-%^%pSlw>h}LG1!Jpwzo;?igNF}oot+Dk z1M+U)CT?Rw+22f`Rfe4hIGQBakrV|e}60WN;Z{nMX!-^_4y!09IgeA~7$z%-PEYy5!0 z%GnSB`umR_X~R$pVgBYH`z4_>Jul)=6Cehejfr1=hUSXE9bn^!sbOQ4=yBLlr5i9H zmK(3ZYl5tc)c+FZWr)%yje>2AbHRz$#3PK&m2rHx`Q~<&U@-%x#`=0azML*_%C5is zk3I8Jhj$ix(>q}?Lkw4l3ynOJjxKrErIo$@vVc2e29zW{mktS>4Cght_hG0kNR2cU zv^Zn!ms86N5b)S1lg9iMP)3C31#z;O>oDti?D;=}QZ}Cqcuc-inh+2d&tLW|eF2*u zE0RM9jJ8PiikNAA;$aURNIlD_8h$RIOWwR2J#~?P?O?Ko8@mA}^MZ%1*}1?AuNM`0z*Ux0c?TDN8^F%psi*LlVP4j$QWCfQy5ro)d-X9MUW}aH#}Ewo z*N#t|>!MsX;BM}K_RU70aKEgKYH69)?xDMy6jFHUZXL{2G0_ohdji;9=F-7PdM-$W zNUP;vreuX_UK?zYRi#0k*D!JhwN z@1fgvT5X0|@?<~ul*rB2k>JhwgD7<`Gnh{m=c{3#B8UUg9_+Y6!ZFezVHUNm!-js0@5(eKb;YFb+I;z~0D@^}iLj}5R){I0cWzkbytMrEMkVI#GW zPXN^~0lndn6=LWX#5kI5Q=Vvj)1J}p0oyiLv`D_QBkXXwDX)#qgp^Iso%@QK z1hE7Tn`ok#qW{aE_U7X6$}^QH#b$_X-C9^af$m?@{H~EJZU|>#;J`1-xw|W3D0NT= zyw7${HV2|hAde#eNniA0~xwRYroL9ZxnzN!B;In1Rf1m!+EZ=C-4T($?5PTpI68TYsz0TV2bgAhfi82@0 ztB;(d%G=FPw_5g>S3I(mC_gM!l9$5fa@dvr3Y&&d)T^Bh-}Y6hE}yjiPejD?dzXtjzFXW9_2$d)z824_2!!&cQvrwsGP{EFa_L8`EIbWkD=TQpugTc1YDnNu zz<84&tR*YuL|p?N?e^AlW@!Ii%W_6kj@?6N^$9};GDT=22(6{unJ*t>qjCo@f)!F8 zx4p!&TmspQZNr93F#dOY(91GwM{*ML+3M=KpI=`qG0N89xWxK@g&_7Zl3my^z2Wyw z<$x5kLep(MRjIhBIn2cg@!!!;mWY*YfMu-~fpr~;f{B5_7P{}7mX-vVT4fq|0;c-c zK#JPsdm8L|k=4_kOW-DcEwvXNPN1xj^U8^^_`Eq{cJ z_TEV@|H#9wU60)oz!|H9$Ha9Ljfglr5T2Z)7KAGXa+=kw1o+sWkxBu2y>#c!9Wx*H z$L5?8roq}dSwWlXB;G?A3P3CqW(`zZlTx(apV|KoirSfNf^>!RcPW}R3G^{U++c+7 z##22HPQ)CE2A%5dT@N|;mU)pZ%0B=qMGbNw8{G03pyC8f3Ht7F?|4-_(R5e-&Q}49 zXSkI=2IoPh7tz$qzV^N<0vxqn_O;lGXJpnFWHBR>MBTrS|s?hNpp&@z{k7=i1kyXby4dCobE^!7Pux zP-rU6y$5HZxqPM*iYAKjB3FRF`vTxr5|wpqfHxHz zjAx)MzPSDQ_6=^6L=-IY1O~!e8V}D;PQL2ozXQRapgiM7NoNpAckYORX?dx{xGfuq z^_ELV;m68=#~p7y1Vx!R1*nfwq>nMiX&fhd#m5xb9%0Qvq&LR-9}TkFo+z8jE;VgwkVA#z)?c0Ukzr$w^6W8 ze3mx!RT~-8#bCu|rE_l_44$l?X+`2Q&RKh=yW)tQdG9s`r1cnLual8s!`2Z^9HtF2 zia~}J@X>Aa!yEUw3lI~+?Ch+9N>sjC5f|ueGQ?(ZZ0%wY+SVH2nd#!)+PTB%Du~2^ zMkfrgaynj9x3sm`UdRB3`AwVo_=;~Y5KADj{ot54fC~XV_$4L*k5AA$`KktkExn_o z+rAI^6JQ0-1UX|h+C>sRBwG`9a@(QXy3h8GC{0vc0&6=HPt)6xPToHie-Ta=1?dRG zTwz!mkW2{6Ny6pdI=t&BI?T?-M#d`iDx7Z+W^3!gj98R~nRvSnfOrT5(a_0f;zOV} zs^_#?IP84!6xEe?mXGj#Fe_nQL{&={5uG68cO!8(C^3GxcPs`|whyJs4gSK>8M73} zc8GzAsTM2h0p-HqMazL4ZKd<1|ecWbfBqJjO16DP7jtS-O_ z?586bMpz{@59Bc&-NwN1v`3`EjZDF#JWdna4*?+GBrjSk<)K`~BQhm18g=Xbpv8bf$m4CrAf5>Fot+yL1rjb|74|y&ZN)qm&}~Ui-7p z4*ElLq5t+#APF8Mr(wQYhnm`2$XRjlPEE&a=$4ZDD2dQS%g&Rbay}Z4o9TFAOHf3m zm>w?LcxAf+PCr`Z+cnj?WC8|Z;ML5E~F59A&(|QZe3uxHYIDg0jxw<0^^htOi)g->9 zwzd{ngkff8rWV!e1bkR(n9ur&0fq5SJ`5xS;Ox>A5)#EKz>!>k;VZTfth#MDH6;lZ z4rixZ)=+EVa>#;PXiwmROonQZ^b%(dpf(C~7|t~)onUqO+ebEKw+HRj6(^mOBwa59l!! zufZq_dJ1jUWW4QJTrezp@h=}iFlsM3&QA*hvw8D2P#D}Am5;5V!Tr2)R7KbzU ziV1#uiyU|vX<#=|)G^&rk#miDedh^Z9hvJu1IgEJ9|n|_hj!3AIJgGmyxkrmwP}nl zvR}S_#iZRDIyyS?#1v?S-}suwVd;{!tE0#9xDAkC-r9K^GQ5#Q^2cZ1l4RG|-@=5M z*Wp6E8KA@tr8*1gg$Sr+-CEis%6@+0B3>JS+Ba)zWCuEgi~y5m_y54wB=MP^Yz2b;tKZ8f7je-2K?Muauez}E z$A=ss4rN?K2bh2IkP&2kEkH=WMp=?5Q7*}ohWaP2HP<6iBtXry2g&R9Wdh`OzD21} zbBdH}mIIX$tq^&T6=aPnk1C+>0aUuO&^@L3*frBaQwGRR^=?&v8jzLOWFOR|K0vQ+ z{36ir2shx5(jXYxM````P`b^7hUW=($0%ISv*UOyhJn`tR}DXBHWW6SaWQj5HugS{rhmr z;mRYQX<3|tux51lnkf`{iWquhW#LZ2A-5iYH5nl`9WSVdjES_a3Gd{DIM*&vJNOPV z=YI6xp$#V?Z6lGEWbg}EEhV6g~8=$Ay3z>R=l!C@cbt38F5$|gl&4CKKT{zLkA z{peHnzVN{vOSJW`4OMluQ2dIga3BNb`Uy}k|NF=rE3HL`cOXeDVW8tQXWT6ktwlWC zr}fA)0N`o__lkmj12&53cpI*b8#m&`)TS6VB_*XI>uOtQARu#hA&ntBv3^pm{O~r5 ze0sDK3w_e&v=zbq?Tb$r@F7OH84zoe@fy(=Wy4WoS|>8?6w%S)>_s3+PsIy$!8>e( z!VkrNGTu%ujZPys7U*p+q#{t(WQc!4ye^s)0?2Rmq`>M3ioSwRzgIf__R2#j`;v+zE{%W*ZP( zTB*{vzPX542ns%4+`geNK+QsIC-&AvPDXf3@2@3_UZin5lu;?L>0M$Ii zG)&oD_?qGMACISEZ`83ObJME_W>nrRn!q85WXKJ~-yZ7?dJD_&@>Bul3 z6%wZner<@aUV@Bp<2egBL1pkULXw`07mWT{=>>3+eud305(~NwcNq!-0x%Y^m7)Um z7%a~XpLJIh@c_-oo<|Si^b_4?f_`i5Oo09A*YdE<*vyW!0+|hsjxvyREK6;zOwHKm zciD5j#=2G!FQIJ!gQ!29Zf-q}Fcf-iSoPUadB7|Yl#H2tuuv8Lzngh-=FNn* z0Y~VDgVi@MxY7}EEd*0%OmK@=*e9+E1Rrtv`oa==VDel9wB{8#-#h_BY1ghDa&4G{ zo2?fyxAt?pi#)9K?r^BsFXEb#&%eilAgOYJ=9F3d zwR{NA`6G`@0q9BlW2IFJXe@W?VyI)fgM#xL}NgEX_LN2ladrM@UUJ$yEM#iYO z%^#mRMjTrI(7M)D*iC@+r{irqfVJ?*h}ZJuv7_#2@78VFZCE9R2~5(IpoT}c>w2OP z;hC)Bu(04c6qk@)h_@f0m8h4qO9VucXDC5{UI*@ra2>c5ZGsRg{k_4;3IUA)d0A9l zs62ARSHEu}gU#TYob!74T4F#%F`*f%7+_l!3=F~0y8rTbJaTkcq#rhKwYmBT?VO}P zCa=y4gt1m*tWy2#*#|FoqdSE4bF_Gk8rF`;uP7ARsODsVmdp#1aVfxNn9p{8>_P5< z@#*)#+BIwXKwDt%YRO82sQSN%I`6m~`}hA}RwAR4WJINstOhqlMN^q&Wv7z8b4#SC zG=<8{ED=SrB@&VlviFV%6(Lgno`?JU{r>*>JnqkZb6w|kp6B}*ujBQ49bPaXB#ews zpET#ON_E4|f1Zs$L-9ji;YCa6HA}NpIkDI7#r4j^DR;uO4zIm~lRR|6?4OJep7s_K7%3NcT)2QPt8)seQao z*Y5C)JBA2;5;;Yo?K^hqqJkdq5*?Gwz54~$wB!1V@q=~V6f_k3MPM0dWiyX>ZM_IL z@}SS+44P3P;}D;U2sJdRp48+i<4<-kHFD&I5@SiOI;Mlo4KlPsQp7q?kPn@9w>XC? zA#HCZFjXb!Wr5|kJPne4IeOuoZ9hYNFSZ*}MAj!`4hU`BvI+1dNxWXqHU;CWn)Cj6 z!hQLjUzzN(VU_LfpzYhY=Pxw7T*hC9p7F#xuV=&K(Qy_v4?l}F@;px(`|om(J&n7~aZ zx1Jtz-|WTtHghA<+q@#%s@BgjJ`{GPL$!vwbDzMPenDfF3@J(j!OZHouI|9ImA6cb z8oos^vz)Q1Vc$tpXhSBwZ0Pgsa4Hp3t6%P7+6#eREFT#o=~-Gn_O?i#w7gij5nqRu zg#UrDiRYEnXCw#3@H)2ouXz&{Z=XFJtXlt358WmT(L(+0fQ`x_od%OFk|qZ-hVMH5 z#PyJf#(0IOOmJ~Oc`&8JUZ_x_vKpB>x~cF5iFoDDmzB2OJ_vtVYAeU=9qS%6TBTFc z=sSpGowDqo<81wsXjWEzIW(ArVbRHShwbjek)s?5qn<5?E$rE*DSdBl-_?b{_n{ED z&t0i{|MsocpX|7}xcwcAMD0&}MD1Brwe?UQb~D$;uljX}L6T|7lPYzM)hrQLgDli6wQ5;7z4kj&}l@%d6O!l3$!HO^;rc zp&!741%dP;w*s0DBS(;%JjzYQ^wyK~)FjauZ}9t5X-YldqP$9Rd|bDLUmZMU;sqe5 zx%6ZCFtK+A2+6?HFIabk=-cUe+V}HQv~AmV&%S*fi3s-&jJk6(52BwF!4+%85UP*` zofG$b1U2>h@o7uin8|v#U&G_4YX$@4Xh^3SjOi?;m$#MAGSGc3GjHWmtxGQ0C@4|O z#hyt`zc&*N8NQi4CCJ165f1)}KjltviG)0=2m%5TmnXjXfz6azn9L9kVt}`I)4C|- z9Rwsgw{n~->?r^c_a8(0H!|JC(S5~$L_k>D84#TdlYM*)Iim2RsLt@EXoJ4tLf_R! zw5qT{tV^}_s4^7Fp9JAJpjDJ<|GAt7*2K%+2o@cN*uvY?&8>C`Ay_83O3`MTui<%oUQp=n-XpAh14s z(P$E7n@?w^TvRf-+~i#=h$gE{N59<37%02^Dg1rm&VEmw%}pP)a9G%2;I-7v(dX#% zPX3b|pGngqLh39ptsxJlMP=@8(Ro+y~H_TyFTdAZ5YvM}Hmsuh%mU@?@(r>95 z75icAO`f}6cQj5t7*J8@1n_geHjwY==D))9TE*2|hHOYGf(Strs8>->t=qAqdx`Es z3Sn@N!z-M}pPs=Z3>xzy3hZIQP&*gPt@UhAsWfdWI2vwzPTJ!lrgI04M(k|@ZY!fP znzY^g;RgTeo{YzXd+wlrQb!uYNvy}&DO$K<&{B*Nhi>b$QUD)U``yi+{%N#IL5mQA zSK3;e>av-k|H}oad8r*1KfmHh(0nl_;-D8Mv@mT%Qcmq+&BzNb)U3k?`KpKK0{ZT= zPcQq_>L^6!;FgK_#Y7UBudX=#AnFCv@`2(Lx&ng2?NU+r4dd>t8*c|;abt^U66OuHn zT?o4J*}yM5mo!NN8XBAavVlS@^yp3e@>B~Re_2r?7Lg;DYvf>al2W;@&*|E>sQu%= zCn|uB_l-Inu1~Z+<(7I+wf@SIKL2I_Pg5-v@XGVw^T5>k90L;e@tOBtYX?ieqNl5y zllcDF+drav1JN1#X}Q}S#%Yw3)Wlaw|7E;V-(qxKNEOnd8$*1ZcyidYp2tD*Hd5(yM2+m+wH=3su%8o zii5|jr1bDOA5@2eOs_ROTYK`p7!^Ku_1;gW6@NC^2N!rwu^Aj1DSQ7qs$wMhe8Ip* zjsg9Z$DlJ)QV54@sAra(kho#ig}v=NUed5!{L#B*YKL;$MLx>;!+O6k>)t))@Ang0 z?$I^ZQ7ry=_tmybejL9|i_6uRNE7mDUTawYP#26AjWv*`=FS$LF4T9_gRiuMRZ|n& zu1a23Y7o-Om@jvBcBUUlw4Pr6uOQ-T_P-b*uiL!&3eSvAI1gGaICDI&=PSldK2~8C zfj`nGoL&WsBH?r8!iut2c8^|k#S-?^r*yt`)sp&2AD+*Oi;V?!GtlPQq;iWdK7YPr zNfTG;*qv0TlCd{q#7mBoX*5)G8Wuz4x)>DBiuGE$Y#BvCdqa!SQ}CLZb;9dsabZ2m z!o;;5mFx0Y4tm;V5+E6h@_-z657)7@+{#Cb)~;mK9_HxMd_OlG*vKjFR--XQdynYC z9(J}0G4aEJ;7H}n(U%`XHSemqRtw&(`1{>W8yiQfer(*qw6NvsHDtHC_=9l~;1@0? za+9WBjbDkO^`|g}ny2VX6$%vCaUGT+CI+ZmH~XeGFE5C+FhV(6p7*4`AkphE?O3AW zwyKZ^wgHUfyXsy5X)&jf5Xk6Z$@k;`0I~RVwhkKe=6;8MejXdW5-#vpy{%5{P(7W( z#2?n$(mb8NuX)=wj`c2YUNN@61=pxvw_ZJ9zQ<@F3z5X};zBx%Ow02cA9?cR$vp|@ zLEi@b_z->fN`UpC1qriV*61i7`QlQFwNh1So%J^SuHKmjw!HW^EF#MBOkE8o0v3Br z+E+YCc_`|TJA~q3YQOaB+la2|Q*h16>ZoxIo+kDrz4-U}#1F(!oa$uqvha^7R1nQ| zv0U;6fVt4O^fcJRn{6TS-sGUUzAbJR^m-eTwSCj3O`p!ZI^>>F_isaN7mwGj2iByT zR_`H)qi(U+euLYXWA^A|w<#1Ji8m%spv6bD5bkhu#!L$QBlCt^08@>%`85~PtM#w9 zEeH&Is{($#&2MqTd7!f+kH0r)k~mce>u))>njF##1wgumxSs$#yMNloseu1R>3=Bq z#;;77D}V#_{1mCGGM>4le5$AznRRBpS*IRJKQq_2OAQcS#3J=K2PbFmSW@4!ft=F9 z*Y*5*bqX0nOU`SM{5n{*89yELi79z3@W_QBB!9)yRyuI&UuqAChk)`SyZIjPqn9qu z_kTH5IWTj|&5AQS%j_E{zjFL}?Pfv28opsWjr)@Nt|#U`YwYCY#P1Vg$6u^TcUiP4 zJydb4)g~fi;~xhmD*vKk@N-fv0uB`23J;|S$1V9fqJH$>gSlwGLbo4Nh*LZ`McIKa zsT1W znYIvV#HR1+dyx5};;zS;#)Ke>i~h@mStTz(=1&h>zu{k-^>^w1tVg!l)T{G{+P*cG zV_V;qilXb5=j}M(xhsY&7@ZM&JMIR6h|z|x0adde9@eGs$RiNJZO`f$zVQ7G?dNLI zdzXD2!flEeoul`Zr`4dgx-`(hR6khN6O%)k^#p*Ix*5`$JG;+IzW>FCW}h0_~9?Tt84{5o(r5M~sIOY_uZT!Mqf zOk9BQXwKHC44s3cyOjLu@_%{N1Zep$OqO@pg>Muuq+Z#3QGJRE8Zg>^RlLj8^^MmR z7f(Mn`;qvl`+-=mrxBgEQ>Yqn7Htt5H!aa~$Qu~37!w*FFRzP=;@W5}Q@s}}XzFj5 zQXgD$+0?y2VL}qbXlUBu(hH#~trhYojYp>Jsx76cY5nOWb+}RxLQw>P1*C51f@US( z)$M58ritGMXZws_xnAmsea(2#C|x)62+!60p2bsnS5%bSca;%W;|Y=VEh&@c(g$rK zk?tU|bT%1Og5Lg-W|Jqs=>6pzS>0T5{r>%p7Jd8j4>xYaJj+@-BKszl#bzcavrwAj z%rb>m;S)*iUCgjF#j~x(IcPoctqqfT78P1IUz>^PQK*hu0JyPEq8W=Kc0&~+y;iRP z8_J7z?koP-h@%)G%8tBk0*ZWeLZ{B|)csA*1ulF; z1x`LLkD1bw#+<=zlsE?NsK&)IAG?zF06?K%_Ki*z;Q?4i^$8fB^%VFJtat*(xE^z9nj_|z_0Fd@ zKMfM=h;vj<8T5laO;XETy`A= zlTq`By)kc4l%c8qaB>Z9vNMmjf%+j6TWXT;*=27j8wdOtH9-9!yc>6=)`|w#% zeZ0@Tyfl+C_W4Y*pzM9&b22<~>IxBZ`qNXU)EF5|tgBaK_SNGwDy2VoW{|n;=b;r( zm>9@3K#~4CNFX)Z%6I29;)Ik;r$!trq=)QY;xq2j5k|0}h~tYN%$YOC;9g0vs#_nf ztTziOc39o9dE_^-e}9GIC7D<;sc8=M7InxMGanpUL)6f$w6v{pKDT{yc<$hx*4h~A z<$?f+-$UKX7seLp&7VuLkbhEmZqphNey=@bR$Lqp=6B`+BE|vp{58wk^zGW)K%CJjS<4# zAJ)Fd?9)S@*`Ex2ZZ$PJKeIVf+U7urBF-S(f2FhptdLxkVBU+r_uMtI8_=j=-Wd1> zfsbD~qWp-aiR9&7RcG_pZ^v;{=DJ)?G`PZx^|k526%(fn8|eG3@AHvas%{!R>qlj| zBBB&y4s=wXSZsvH0Rg?fjL12aukhiyQ(977tbE!1+=gDh-}H%&>q;x38Qwfu_W>=l zh{)&$gFo}iI8a_RJwc-_Zq~m>ziHji?{JN4c?l0!+}4BVpqdNWw{O}x*Nb*+C>t9W%4r z9<_`@H2c_h!EA-@SC(Oj^*<1F6QNxob)-z6dUzh!C6HI}90&@0-L>A^4wHO-X+FSL zK9oNNMGWVILpv}2!$o1oBw7(Ii-r39`*pa#3>$WH`Jy|Mvav=Gk}KbtNIIH+H${zp zclV1W1UR2FF<_||Z{8f1{h78v7CT+VV&rD}w-Y6|L_yCG0pA!)t=YZv7K^tw)De}v(gmHDh1 z{;8zuC5C;gA3c6-2FFcPUHt+t>}Ko}201$~ePb$~?*Y%b9&;!YNgK?P$0I?4T^%ITTiT+>5L>KIg zjlBRvSxa%B#^651?r115jNGon)b_c5o&Nti5Rd8QhvbHefHS^<-0MqUi)|ZqG|#(pm5P$NLAb(JSW?9A|O6N2K2KIjf{Mk8=L9`>O_5WR9&kj8A>=XJ~ug z1e(FShZmo$=IJz1fctFEAq<{t(*IoZj(}5B`EJ8=>et2sX^uy6UaA!`e=!lFy5UCL z_b+RC$rlIeERO>&Fju6pndkQ$(0@;bI|zNj*72Pf7+Un{bC`q~P$&!l_9omUW>;K( z0KT=A(kr~EsL1rP*0q9ysfg*OC?4{97HnOHZ#Fi+&WT^M=hCC4F_B1v1ftok|E0GN zS`Lvb<9Xei7g+Ph;tUpGV;7Z#=iA_`suU!qaM|6gt#^z{8NRaD^jqiCPH*3~4cg*8 zEw5-KL$-W8sNlWnq+mNl_ z+k|Ji)^WSg6h}E8_)*#~GX;vaP4a(0lr|kOp)S@t54Ezf)!r}fE&($%J<->~!W3xh zPH12->{b)SCzc&4J)yz9&YSFxtw2W1CXhy65$3!<2e&rg2?fi1gUi&|KcF^V@^V6Y zOama*?A#xo@+EfD7#P=bKDi&zRTBEn-F@jT;?AkmTLmY@CU5Zmkz4zfluVc~VRKlR z)49)lnYZF$V9mD<^2qrnnKxBYkdGw_wpRSW&$#4gZE@0C_x*0kxC?B}SpZrvme;w! zzp4H66h-G(lLFuVzG;)$yhUL3_g1OA)92Dp&VJ+;m^T9Bqzsa6+fQSFYU|nfH=p zu@7MrDZ*FP@!;TgsW=7dZ(LT-o#@x8s%$XE<7S?JO^^Z-?di9k!n!3>EKijA8< zDr_pmQLa#U@1-S~bJBnIFpi`RACP)4k-44Wz+eA*e(QmXoi{xPWc~WOL-T1F%oTz- zIKI!O86;YkeSa|d4fXeG$E1}2tnASdja@)`=S9*l+@cqD4Xh>gUE~zs0-c6!Tt@(J zj`MsX7&yVR!TFP?M7PXbm^_p)MkWoFo)NqO7H0lQom?(aO;dAY>GAZ*fdc|SlcG7~lTLX5x}?L*t2NTZTAn1vS-cgIg((4`>}v$8a>_F7-Z^({v(CtJ6vQ1J`c zx$1X=tnPTbDTdR`E}wko;O3vq(iiN0;c+*`Rkl#d)XgL8=fa|^b^MOIO?IVVY0Blx zCJ0`VX-CrL=@|y%#3Kw}#zkb=+}W*P*blBD4S+F-Zz2|^J&W0_q#pG#?#dYsAeA)_c+h;WGpKE&84y&>^fH%D|`~O9dW@B!(}NBS?JMw{1@JsL_9?j zzt+;S3UOOJuw@P}r8oT+h75fI?o3{^l)2B<_|6?aYGuwc&&}@O_EoU(!tYJjrvj@a z346{nO2MP#rqIq7Lgy6~l`sj-FWOtABQ5?TJ%L zVb7;M%`H4U#ZG}m8J!P*u;6AjjUf`;CUv6`~+rg7+n;y<#mjGt4E z5w$0blHWFB!iH4_@oE9%RzBUmxJWoRb6c_gB-=^pEAc#5; zG9AGo76pi7A?L>lmJWR_Ej24&6~XOL z)chQ9d)xobD?ipX$Ak(^Ab1+aVIAokas1e=SG|491-_|{)=nF2vCgNi-T_ee+lE#q zJSxf#OhgPVvr*J(6A*3(}kHy!U47I_`BU-67Q)63m3*NuEV0a32!Y--4I5GF(#9F z1;NSt>^fQSaf`nBxoP8(cXjPEGd|)^Yw~afVlpF*#xN8+DCVQ=*=Duu{>!R+SxXl| zp$H!O8MNutifT)sor>+R3p!(P0cB1UX%A(S1KIN9&UnV6!b(-jK8i%7aQ}!-Xf5jV z9@r9oukZM|GrHgf%q1L1-&Wy(7K|;4=(~X(O~!u_!vG32p8J`_B?wpN!?y{3&>o*H z#r4dAxilh>033>d_sx)0?I>Nc`Ahzg0V+K=H}+}*4wLIYWWWP~wP2PKIaPkok<0t{ zvn|jBel)X>T)%X6{@7(vs+S)!u5Yc_xP5y8gO3Ru@FRa|(OO>V8*g==zU5hO0L5mM zXx-ehgBUrRD+VXKeK`5-^rA4brwNVx4&aBQ%REAT;C36 zxc&9%Wcr-BG4T0RLJMQNGhMg7YUy}zbjg^JdY4hE}8Iys91oVg94YL6hZC1F@yv;cI~v{L8!85mD}M6u z;g!rDEi(F{&pcJw%Q$XP9nTw!-*wwfS^R6X$ND&=rza1&={MxD-8>l?bDB1Tw)JA- z`{mCVnKB+ZwSDpp;^T|Q#iOorID&CeDT{IS{P28Hw~PrrbwlAm%ugEs>dl<9O5wkV z-UHN^>{v7EsLv3vmqx5J5N=zas=VdV*(-+3g^MR0~cEr8y4Lv{2 zJ!2K^gVYYhMg$tuGk;E)WOq-w&U9tXR|Cfc6}NBL!n4VuB~4C*KqJ|FcF_-;YS_w& zYeh%I{);Cm51*np;u>CmYhAav3T)*r^+n1vPBcSnI%WQPu>AkeEEhaB7ZvgIk=j{8hJIw7| z{#kmiL@gC63Rx`}q3v_m=zloHASBTY30bL;)3%a8jcYGNJ;T1EQ7acT>b&aI zsXlX4L$*Y2O9&emOc6w?xV6Uo0$PHQRBjMFGem|--wECXA1I&ksLaY@bRx$cyiB;` zw?uFUq}=nd_`z*_`RdgahIwogR4)3{?ti%e4JoTY0^bf{dlA*)TWvdy&b93})@P_F z+yfXPAs}b-M;)4AF?N{kJm5zEisBqbAPt+fW0Io*e*cHp85-*)CTC@gdA1fkqyq|O z<~KHGPGe^JoBx@!b!<#Eb?MdLj$y~wR}EPUK5`9{P%EovpUyfeFKs*1e&DGC@ZHqa z)$wz>NRWOCd0~xX$Aoi+48blTRrAa9OM>+%{vg)YlTIv}6Ml@^^EJ_$rNghabB$ys z%9noHw7%0<$LuwY#=vCB@T+A}mdozpo6|%RB7#Zhm{D4b4)s3)FCA-{aO)WCE$?Y` zfP-sG@mZ>6u)`%eb`EnhI@;f(j2gXisL5U*kw&;^8f^*$CQtA$9bGwJ5EYTzmRJam zal)&il9Fhz(9fpr#tmVu^>5|BqlctRao=vneK~uA7Ek7HL(kPlhK~|KpW-Dz?6x?q zQt_Olpy8oym8s}~v_%Bw)NvE1(NQNh{)Tf0_8(hJT+xHA@acO3_7wP3!N1Ebfc32a z$ZzmO7VfOoU6MIvOyk<5qDTMKCJ!j@`rEa}x@&c8DaZQs|+WxN^x#1lC^jB3CPAwajG?`2L8cH}CTd|D} z>-fXd5OmlCm#_C3bJ@eFRUg@O)44r@YlGIdl5@dOanGuauMD0tJ`%+%uuE2w){xYD zZezxJ{zGNM>mB{pdN{_HEx~f$XkYv&QH5;%SA^xHYc_`R42H$C1MjY>Esec@rgi7_ z?XMy7=hPmL>J$#JjOn*@qR7Sl!{^IPZ|S zH=N#1G0>Kqe6Kd!cK5E`2I>r=!sukmA=6-kUxUf6G+xitv);`e?lRC-+Lc+T`xPbH z*+atjgLNr@ao%g!qpY5AQ-G|oHWu9?=_0S`;&`D^~;&Tn3$GaDE`BC z^cz5Nz^UF3>7=C7*kvr`>CIUe_-*@>%rqd>yj2DiiAW+M)6NX(&V>_Fzhw zC!k3Z!2jJ_u>-O&uvV! zK2{7G?0}~7LP#S%WT({xnLT_Q_{;rky%lVD;#N%hORrihBR|vizYs69CB{Ske}FV7 z-~a20FK0*noHR&Tpk!z1>l9b~*DO|yF8t6NTmvo@Lu7B<%;-r#*(8{^w0!?&4(&SR zl4yX!EqylZ7O#q;Q{eqFf&h$~USTSAA=Aeedyj0x#-Ta+j1SUhqq5$$z`*;W8lk`Z z-|v;bMj_6e<6_@PBXGL7YHtTv`G4iHQ_QGsHx;Pg+`BdQJ^Vu69mEPx_4AJQgl#G$ zdg6v*U&A3wj`c%4DjC=R!?;=SuK|?@GZAPBTaqgkldK+ne35N~f70N8ucJRJ%oDqN z9EoP#pL%M}?AazbX!e}DFSlTe!VH`L1%7x7PVGG>IbadByr0jhY1M1pXl3JPz} zSug`wMKQ+=?mU%)=6lHX_s@KTyMQsr)!ys-_UWTVuh9GBX0+!$?$Q`yeZad!#O1_oL};zEYHO;9ku0Toqg{vj8Vh}UJ=A1w1&iw3_v5H9x>d{? zckrJVEn>NzL3^j{)2R7U+Zk^k3Ukq#!FC>Ey(ygHP2qQ3r=hMJv3z|r*;BEu+P;D( z{EDQUQ;4>xfaV^l%BuLj(gPZ;KThY*7V$Y3%ogO9rSiVC#&8#r8*%O8Da4Y?OB6pgOcDjCj;& z)%mH*GofBqS`^F>SyIQ%P0~vXKuE;$wflR+d652u4A-0!Lf$+d5sS2Ize68kEU3+6XnDrXy;aLDnRVd2RtT$TSkl7-9I2BlHU22qw z&E9TazrSonBSQ#?n10MG>|2?6yt2sJ@cB3^uovii!;Om1ZPVgS3x&!9rxBHyLKcb8 zWB^tc-u!312AS*a$b;ira*E;Hio%WYwR>(@0TH3!@=8@^H80U-r$TCF?}WjxbNZKM zIqLJk+5lc1l2F-!Ix_$>X5{wPvl!W|Mg9yDHOQo4RozQ%BAdS)Rj;?Pb#xp%2r~0J z+C-Zkt*Q9{mmkURj1zCZA#CoMY9#C2yHrgLxpVt=*{4V5zpIDnM~HF{t&t)K!2fGg zTbB?3bFsZMw3!PPK$as@b#M9e@KP3EwweKWNBH7h^LBD=toNM0(W}KBl7#TAxVgon zN8dTSPhHP|LHocM6tmzd;)e(CX2+6tuGoQ)vhPKoy*KkX(g4ohT`_G%o=fW<*P$<; ze4r|YE`z+y3r{G(JmV+FK7G54NyZuyMG*(W3ZjLqAp=Q#>?5@YtX!~{QT2yADR zkfpv(O+R;mf2Phl4MNkgd-oe%^G^I|ewZTjrFPEjwCZKkW=g{-Rdk+C`spR5?GY;P zuhfZ!cej2IC5&xS;guEbuk?PgydG=x_)JeYATs$b)$&1>JT3MO)SOKzhFdZArsULP zz=D*%>qjloLQr!Ji67`sQ{`>roxc?Yh-`48WPOVOzAgo_e*U<3D-rwcg1N-64>@}D z=%`*hxTe zl+$+={rJ$WwFCAlQQyV;az42?|=hCVN>w=`i)3wu9t>3PYITCU*I@4!g3O&osq z{#@%D`L+MKQ)74+-cj3w{OO}AHdIB(N>y}kWEI1V>zdi-u|^2W!H z$9YA3)=Nn~s5z(p@l>6PnVl2I4NCoY@qQiq;k_f3T4h8v9%CLcJA7c=`b*vm-~Dt7 zT$tSG$o1>y*WOqAFFotG@ZGjy`itHzzxl1-`vH(BijC}(&2Ds2Hfv1hBlIuiWe!gJ zk4dJ9)h{&d2~DUDHFOJYyIH7ihrfIMhF91RGV8_`9~g9wj)>8@1OfpVdv?>@j?U_I zJjqG+uPZ*@Q$g&M=ygk64aFE)x$&kEv4)*w8Qy zm60Nd2&XdCw!*XBDhGe0Nn#bxkOg^FR&gKiRpM68Lb9x~q1mR*9g;RJyLa>X!}|)e z1|&SmAvv|z5#i7OaND3?!0-5PHUsCi{(&@a>*>=L)VK4|0~ESmv2WVGpBDZ#J+ki1 zDQ;yvazXm82^j01Sl8x3{%ZOHh6?B#g&Mp(vCtnDIq{Gi27!k~~$tf?JZ zoQy@|qy+XgC|>TY{j=|-{)SG@v9A3l*DR$H&0)`hL*A`Ml%Z5Eqx`xldsVP)Iy`1q zE6%!@XO#pCtysXvw$JJ*vy%fSa3f2{WUH#QCe4~@ZVM?clCQuCWfi+HsZVq) zU6S)zjoxSe`IPP+>~eOGqUg{Q1yP3tf#rXV+GzyLKBX8Vm|(KHYq0z>n5p z9#&zw4{Jy6cWVI5n&?%0iE#>B7>q1eUuFhSsMhl}jTG(T08kAFCtZJozZS<$$B)bK z41KBZJrfH)@gJ&~$KX9}{K=sndbc|foVzV*l%?xDnIux5=f8>=5@xQ2rz#t&!JXT}9PQ89=}RU8$?@hC#938c|(p zwqwvq)U(gKmBMpJ7@k#DBK%hB!NDh#Wm$5u@&#F11(2KEY=1>rnMr0{94+b)8#T1; z-dryzD9>rwAw0%)hD@s1QpZ%m-bvTEg{F?TDu#W{U#2_XTs@RkQD~INpQ_ppNff|g z*v4|dN63zFIB-51vl@=9ALhR~hF7!v{lUC*RbTolYOB)pZ!ek9XnT`Y`pzkM_wkoj zLrjT?88HJ`fUW zzAjo>obY%=jW`lVdfQ()Ty`?P$tpZY+s?~c37DAV7u_wYdG#?kY54V}Uq~Ngq`@S( z&_l1<#~9;DDF?=%n7#kjtY=T38vhx0n=QBBZ@=2GnuQ^HEGywDPb(jw6r8=^+XTN0 z9p~7>Zb4IL$Garw)ID&l{9cia@oYh-(&=0<#>+Wr$@4PBBv;lu5`kmS)#uS&w&pT^CEOU+x;cb15 z>CZ}-`o(?hTdJ$6)nSG~EfNuBA&HV{J<$h#l)m_8>zNMH!Y+D zd#6HWm^U2mB=?R{gH>$Q@?TQ+*86W$aZIsMMdMg~QG(M2CnhO>$5*;@XXoMS#s%<3 zE$$ZA7QK7-8jneJk`<-xUl(@YCTk!?=3z))NI6{S8h&+61LfA+D4XgkFrnztC*mCg z!L~&X`v)7^!5L+2*WI#lQ1-j!Y>yv1UCBAN3`=e=L_oHSZq*9*QmnU+qj^8ULRYr2 z9~hI~-E#GPSSod?vv@|AXczzo&DUR9$A+xdR6Cu(&D!Q})lrDM)E`vO;sso6!Cwh$(zon*gR}6yMOidj|!&OpnxDT5=8IO8Ymc8PZ)yX(~IEd*h ziB*nx@9I8P>KpE$>Xr>3)X)(ziE2BSe{Fk zLtWdcqrS5=mg32Rhnc%?J9WmOn$TJ#~l?bfWz_x`&U2RdY->%iu$vC-&aO)!EIAQrPL_t!Y> ze3wdB0Gl|jmACg@+SJupLy<$)W|w&1s(dm?=`+Bfk^&0!M9zIV$vmR@MtS@ILv5bz zo6tQEB%@d6umC;QU|0WM%t=ggKa*n7EOXnwvMl(6^n3f<$FmaJ&^*0zDrp*nd%$imG@V6&( zJ#jR={A1I$ZDI!j&NE~7&fi9K4#X8=ffwZuaH`;n>hBeLb{8#lyvt6^ezeNe>#_(^ zdLL+>z?k+WgbyZ{!No@2L1NPf?4{GE&qI$6>Bi3LWbiNm4K#3reB0^m7I$H8(-hz? zQpPdj`-Tg3L!xaN&(%@TPpfUSUxXS@5U$tU5;?S)XcyqBp>%MaJ@5s&S7ZTamo0x! zTWG?{c`-DRI_Hh*2W>(uWlwI3_4_Nn7}vKBg;t}BoB>*jrnZBZn0J5=MNufB^mFw0 z_dh+Ub$q*ZK&I{5DF=(&trGGP{dw-=9Yw4g zmS^~*jGz|uRm zt6&YqdEP}tfKT1+{_}(5d*e;A-#vlX7u?3JX5N+h1t_FI4~qN?U_>?ywcVX;|63#- zxa2sF2@x?OC1@pmo8lrwg!*_H?nm<_%l@Xo}pz-rt$ZRfTrb1 zHzP4i;ejw-8dGXK1LN*zL-d^u%YCgFFK|!We5$)A*%?D?29>w?SXO1Z@Z8sU)UkE! z2=K+PFMg1|v+!Vhw#zk^UBc{j+)!+EVgJ3EIO0dd4A|4Va)-%W;nk7Ng=hP>ZnoQ_ zb~&(b$l%rK={C#%J50_xkcXb3lE`qOs zPGY4mcl96#VUpGd_>~Q|4E9m~M%=L_WU94oJ8Ps$1^-$oLXRG`hDO96pAlcabIX;t zvID0%XbH*@k*5 zj){>=3quc>p4-RA0bGOd8e2wQK8SxT4}^2vl@rQd$mosSMo>@K#Pe%w0ty!#lf@FK zDjdJ*en$p^%$3dEqiODF zVF$ga6@-<{Ba0A-xgSh)N5#_^^78E-ZsfP3W75=iW4hTVWAHB0bei{ik{W&69o&rV zfEX@;Anax8hKtnuK~XdzDlfQYtdJexl%LYGqhqwHc8+%dY88^Ce^EggSn(9_`;@Uq z_r}D!PW^-r8u}+$aK{!DSFh&Et&h(&nFmW!N8xNgU?e0Y!V!yI9vijVk8kCv#U+|B zW^^qZtF6D^x~g9*w#G4pqW+>c8^^OgX!?AKsalQY77~;i_g;40JLDDik+!afHCoCh zL!LG?J)Rd6`=RiXS1x=@S(~uV@h7P@EG(?BS=D&pTOO#4G!IwmB;`@W_*1;0>ft8z zxU;UoZMg02nBRJUh|n6P-#y1Oi=;A=Dc<4;vm213Xu1){bIc z8QQ6x-3MP*&2?bTCg)HdwR3EJ)MXl)nz;KUTui@GT<#Jby#rW6%0s?N_Aqe6e?i}t zWpst6SCcs^d|rw#$B3HE{HjWefD{ z;InLL1UGs+3rDxq-!gbLNd0S0#(!WyVI&vb*1lXeR&8)e{{H4V=ojGWIGdQxvLKs( zl-l_I+9bFA6*cdtU$=UoFhQ{^OPmmu`fBKHpHj!x(2G(;T+QJi%sw1v(sVm=c8U{9 z!rkA!*0i%w#|yjg_k!@AL(o3s>C&e4)=FcvTGHLo{M}zWN{zDHZt&pfVy$r~T0@x< z-90$I72USFcG$pa>>eI`6`wch7^LYYXD7ayeh&}^l3rI!$3_gcjl#~WwKhK2DOb!6 zpf)Ucdqs`QvXkE~*SHUXGK~jYoSYc5^NvLlo>*zo27e(|O0?8=>M<}@tXRtJc2v0G z=tmD}pQoek;^M-a)^Ji_^N%^^Kno{*w}nDt`@+-hNEqtcxzk}xjny&utu7C==3vY&Do{>Y|y>)0}U%zF`qSz#~2#C(GGgL0(llVMi z4co&q#x+jg*-y7Z503c&sDh_IntT|EXFot$#g72p-}t$(LG{z4J1OszB^c9`K>5K3 zU5!WfzT(>_r#(|i`@n`5cuA%FFum1S1#kWU6u@r2P0Vz89R!F# zkELsQRV8A@%^xKXZl*-7r7_e4hWjtFdJ|a(-#?n$2$D11R)1!!)JQ9@b}71i1baa0K74LbWCr zUc4Wn5vAPvL~agW7hJ#W0kR1k^RPe1PE^D;@8z6hW$O z+eR@?`3u9o(du`6TvN;|3r&sFcu|X)Z_0SrGV-}7hI4*2reAhqXGpX5{fsPKLT6N2 zX)~Jxn!@a7^sKyF4QZNW;4F}7&7Y-(3&KM@dFe|Zg;enVh?0zuM4GW>j%_$}n-L$( z?)joi6=s|XCrW&P%2|JG5~uAL(r?(%q5q^zGSQpkQQKilKOmCjzd!5~vNfJPoEuBX ztjk$()ntkMx-=^JXA2m`a6@M3tgpFt_3DL0hbVE-A>Kvuink($ z6xTY~+wTgjt&vUlu04hiwAUZUe6b9Zq8ygK`f42f#*!9B3B;F`B;W}7dxYMKTErBL z2cpC{h`5&cruaJfQ^q8gs|Sc-1QBJyk56L@T|+)%{suG{g5<5@=j5^EPVk=n}+wUuE5tXxi*Ni1hFw_)ir8toS=s)j^GRP2OiBhq6? zNqn|hQN(X5aOvKPwJubXDnXq<^bDw~Vdn}zOT2+-e*8n_D*&=kXGoVR8^L_rZ5P?c zd>R0=dD-wo;YW$ZUO-LADn9Z^=ET^BS&$8AsA8|S*R=*L@K<;DYIiBiE$|fgs zWvrQe(&Z$(U2#p0YNq~SEl8^^b|vhqhDt4cQp+24bd4QkSW|ShCG5xzsY`+ekHL)x z*W)1MAi6p_{e9cr-o=_N>B0y5TI(skp$&f{Vmt8zQ8+1^?*a9o(stY(?QeGDB0;W1MV<00lUvD2lI*x(Tx(5JT)w-vS-^pUA#$J3qfu&Q_+w_K&-aa~Gt&?M`*|F@an`=RO>e~e$H|Hf za+ViqqY{F-18dJ_?Oo)fb6^*aspkO(P8-K7)6(RM?h{3_wc{yvI z$`pzQBV3$$)7|?AY(suQ#_Km{Zr-kn~7N~eUB?%N z^i1;AKPW8+>s}_$zR|E$BZ|FzYq(!;*fcWzdDiLTK1x%dnu;%laDCjCyl{xqZv<8D zJbg!Gb#^C1!`Do4plE7r9(qPrLC}0j=|?C+DV5k!?Ss{h?Gabcb2Fv4=Qm5!O9(SI zF*$rGWRYeEPmziPXZK`Z<8jZ=z~-0vRrQm9?*yPzv8o}ExTF=7qtOv#`mWZ#vjMSA zA%u&lB~2|!!d8A4kQrfBv?`Zg2d6~)tu{1H;=t>en5g!i`}DkQOSFtJp{_vNuL;HI z)R{9E+2`y2gx6sK)urbgQLRy44ISh8;NGn^&atqAAoRGVRWr8ggelj!G&&fc<(>%& zp>0IGa2_E~16X+q5a$Jzg?e_N>-I%7PrSi6bwtam8W`?s+WT)a)*(h+n=mqT^=e7g zOd{?rG&5d{6xS2CJp!L+#+h5Kx=#pVvqesYGG!KoMohV~Q$7Qu3pNfsk>{Y@^eGfe zQ-n;+Tf$@9X25vML%tpb^_b=<-{ZNL{qLX#%B~g$1_t5}$Fvg#^Xi=R&C7XDWv7u* zcjc5m&ap2sL6O54D7|%Sv2EZQCI75;~?`v z5yoPb`|#wv4umOHb@f$1f`#itT3avgBfQM-U7CcW(56CgImQyt`02ROOxfRj5{Js* zoj0I!60aGn4!hrP-=f8ck1Mh9>MnR0=)-c%pf<+hW&N)QclmcNXv z8F$pD>ED7z>BD&t_jO=3bkS?RjOo7V4Kq)r1@$`7zHwFsJ;M`W;ztulJ>#~7yA8*D z3fjn$Gh*09R4)#BYJb&-?B3Sz(@1TG3wm|1FVEXc{#5^0twBuofEy@~OV2x2ajG+} zKo}LE10b}_u2J9+y}5;np+g@e>F9l)c|UE7bE^*2C{EZjP(S$X=yY3g$0|W}#x42- z0bFGxgor?j`{c6xZTkuj79hXoBp3!{*m@TWYTV9xx%>M(ZJQNYH#ARmO(kx4QfY{g zjgKIxnl+|y-=j;Ck?u>T{^hS7{?_#M6{n!57j_&L(X+FWkup(7lL-Ktq}>oTgm*-D zDB_9{NaO8Y9OvDfH2P9ox}tX4+73?Y-NAAp z`CcZ3$(B9KW;1XcY()v)(NbysXm1}MQJ)EXLpFpXrc$^*JX$Glpz4g_Ai#&oB#?-t zJCSkcp3+&o?1?HDDL&eX6JHoJJ`e*1pbRZ&Fwyl~j z{p#KQh|8V^a{mk%k)G!mQUo*+Dx+mzAqr7vRQTU;ViyIKyfQF0qpHF9rUrqHO*-Ti zkN*w_wAi;#35}WPagrF(h;-KX-wApn0G|gAqCDU>3;ZL-BS4R8+RpRfMoxj%1qU{S zF3;Y9R#j}5m%JDW&mH?RvVV}r7bN0^6jaYCn7~vtdq_&`?53d*r zBZxDC-q!fP;3{v6d=fjt*L+S0cLB)J(-zNZ*?LD?z@33SFi38l;q?|Cq4ls z3E0wl*N8Q193b&BuHP(Qc}%1lXpcJs4B>=z`E~F>CwDIO3JNMyh-2Q~-WrR)j~O{> z(hjA#4-EmEu6Wn>HGt=Tc3D@W^}A04TY;PFGDjTwZh{eSVL*Vaz7}^H8qqd`9qoHi zmJSS)ITMO+c|IJ6L$|DDl8gUlo2hDFvp6(jtqSF45MV3Ns(&bNZkx|7D!Hs%JabUo z!?x)^fBwv)P8IW3y7HNQwAIcwd5;hFt>i(iw)I=8E(16!@2lU(G5SP0aQDEdL4!V2 z$i4uq1R!*@qUSDr((>34SGsdC2&dAff^0^cB8m`Kc-o$4_f3_p8?+^&+O6buJsuwu zJ}HEq>2+ltJUU8CVdOA4*jRGhY|C|hN?e1;wIu2R&M7^+T2~C>!)53~fQpJqG)J~> zyrkR|s;zvcU@Oo=-OTj-Tk}pdN*rknbhMH-kGj4Y7)ZIw$pCzF>GGy9M86pFdmnUp z7Q0>|9XdGa>RBcIWeH;N7uIU-QQ+NZ_V)1Ra>j#bd78CQFEsyDM_2t4^Z?lE2&J_R z2X78SL$WDzA}lG$Wg)OgwL9Ilk<*?+&>>mU5?=t8PS;X&CW^>RF1u9beucK+HeR0?9r>E3p*8nQs)u<|x zD0C=@3v1t;ch$2JnjV^=^Nd`<)N-?HzV|3x-z0`1q7XpI5Roib)#aYk%`VMgA#Bk% zcHX6?R8snyMK#y6y2$yr^2Oa5W3G0LUqyc(%4;1N<6TDJ!vEaZQmL?uY#gMLdzZDh z@nb4|UjJqc>li@>lP=nWfs9m=MRtM`RlX6&@}WLjG3NfF3UhS&c@Tx8+V8)ysHbb|l!)SF8ObW`0@ohhn|-vO-@e_Ww2P;9 z6a$djk*W7)E_QK=1b83UwEyRi9}mpEy(a=LjqMz^XV-vVMSkwlsgqOFU3YD4{*>8e z%t>MbcZMpr-FKHm-0d-hp`|kyE&7(Y=m7o&`>VR#wD^tOWhN;3_xR2u?$R;7wAh*w z-KSwlaZC7ZvQSEPY)R_^tYsQlvs~gFD^--r9yHF9*M+`ieQfM1XdQwR$WRb+4qgR; z@efPKOb!SI`}7QiK^N@#1SXt9)Ph6=q1i*Wqr&;{w(1|po9UaRFqI8uxJK1&(fR}x zQe=!r88=oC)u@;$x6lBD5R_&83*31oE*E-a|5=653V5pJocCB`(H2R*7IE67(=U!} zOM1ZHCc;iZ+bj0QLTi^tM6CqsqN3^X^88AN)OT0C%~)R`ZnpC7DR%LQWwsHu_~)6^ zrv(EiLnTj20c&L9)2hnL$7l5H1fCbsT?H(czqb+~fZ6b_+|v8LROfGSjtdPAW*V*( zHLP`)oumve3i#Z|#MB1KrtO8YBeD6$dO+M_+8XJ>rS6;av(Tx7F!k}8GrDu(&{QiY zo9x8QlpYI8YNz)7 z`~*%MU8z-M<%Ht-B?9|xAwx%MCU)Ab+IlMjTY*6`$@w#}=ezKk8?sMnSIv8y+OcU? zy?c+bVpY_4p3yz5Hd6gD&6!Eb-AnPEUvn*9cHVJxi;Gik-JT(n zI3616P#AZ7(p0RaIPnb|HYB{bY{_nDoJ}u%U|PXeNOjLYfbIfzw$#u_p(jguestQ} zVk1Lc0|N*79ZI|Ci~TdwkrdchGpYE_W`RS^8Z~lF*s?{`Y?D8(m^3;ley`s9>3aUA zjJA!F81bHGTL<+*Ll_^wFElz;%K}_eYC`ZdFVdQUVYiKY!8x^PYGIM^HjLie518yC z0Y_|-(YWe%?|x{)i{=|ZbpyiKR;V#cOwoB34GeBK|4Y~y(1$q~T(RvfzAn~*mI!(% z&=Dv!Vu*2{O5g}lU*^HaiDCL6%i_g02Ge%qfKQQfGjru;4qzP%qN6Czm>yy$=t|ue zp8>aR${bvvcoyAGJd~TeV-6wiau0Jl4t!*VgDu55RsXd=;|{dLIMj34L)|w^VBThd zze445bWLE84R^2*7mbzq(J#QsWZ@EoW-nkI8~|1n%@SE2p^nC9jd#iwi>=hlb&0Or z5}92hArpckFn7TIC%ov*C{86y@vuE+%ozL53kb`T&tfs8%jq88fV`^&&tcq6?99cW z{OWiA16qNBwd!%v7cX28!Uf;1;^OewoBiBe&ua?ER@%9HLPA1xXb;5kAI2EZX`R{G zFYp^o%*})GxJou3`L5j%dxLV*EcKI_kWKZx zQETfPgvV^mRGdc>u+j!IRDOjc*q&qsS{J4NSKYb))tu*X{M*T0*^X{<38z!6VI#R6 zO67Da(MU9Iqm*HcL2?_3Qs|s1nUi&!u|;9rT7?FSaw%$t$YqhiBvylVa-FtG_W91^ zvH!z*>@Pc=F5mC>^L@Wx@7MeFe!XR;4CxBh=udyk=jD)E5O5<4Ov(M0u%q!~4yCW8 zaly3T(u(RWS!G!L{P@OML_nlBT5k3rc@bAa@uXIwflBT7Wk4lj1ep*4bBh?}I6g{D zbp?OQwF^l@SW4{vsmbkRM zLjQV*gos~Hij}+9I3u*1FSDofjP)_WT(hc;l{Y8yN5@ z3c`H{=|nv}c0Vqd@K9B?a}`|I?zaJ(a1nPYV3T$O#u})HtFbler%N(0th1-s_5dYD zi`_j78V>!w{^ZG%q;3iVcdsF+DvsP(#I#-5O-`c!U4UrqT3O#3;5hI5%ZohK?@Gqx zh96iGT4Pi39%_IAcN)mzj4Vlbe|YZx{C5O{>hBg5t|o(!0ltol2uw(G_UeqQ$eG(e z=LLM}9GZ!8@KSOK(tlM*x=HgKb0BAvfO+`?mB3Rk6o-S$V=27%R%>Wl8HcJbbY>uAI_|?ExDk z6#gIsNC?-MuvKrp^i?-R@Ia6=Bz6^p#1#wo+Q~RFc(%1dOQQ>0pn48ftLIpP*j;$& zgN`nE;EZJKEax~MM!dpY<0|qq`s|3Aa1!0O*9$2JxxsPkr8-&RL>1v5-LyKxtNX49 z*~QM6=v;Tkp46(>3R|ZEkWB_*OqhNU-zz!TjbH&+x1f!y=s2y-%8JNWdGG-O&*Qio zM+U^Ph(R1iAbjmu2ifO~4eQsh<$cQcOMe|kX)Sh5gq?FNfd<($vo)#4qsC)N#E|9rpFMIs;2B%`|>#HvKb^b%4=ravwkVzHAzNz2u9GFm6c133 z8lA($bHow$8IO+&tPo@Xtc#k5jKYU;=3u*zHhns7tZn;KCB?;&ptq7r$*puMqp5OA zDleuvB~!ZD%{_AX@Hs&E@`^7KMP0$uVP(4r#)RceDOGQ4Q+VAprj#Te!nU*_#G8ciQ(=>;U99cBy!C)sF z41wuKAl&W*TP!yz%f~P?YRrnd{^qv4kbXotDO8Do=h;LA$vlctFMWVZKg6D5t7>}t z#PQCGSvWQ@ezwTuycX5$=Fc~l%@kTT^ft|Q+s&IdmsE=0pRLRtFGhd7eaSx91lIFLR8{oacFQU%f^CVR-~@pR1vT2enU1MnyM&hZC5&8Fz0US^Zb z4q;U`AilmXdtpODR!#7P&VRq|b0`+2fM{39wGa|WFqi_+Q^(dE%5Rjm32A&Bszod} zU;q1F7a+)D&uh`!i+tUMM_}75U*MEA!vAs@T0dD~qgH?m*+pe!ZI07ISWlELkz}-H zsdK8)N>-{#S`$D4_7l`8%;iOm#_QZvwSGccPL2!1ZTV2Zj2GFF9}T#V-R|P2ci&C( zkIEYkHLdCm$X)IqQ69K&1Ntdc1s>w(xy%JvO=4-sEHCf&J6@WgzYz_T3So- zJ{IcJHY>QmwW!sEl$HJ;h+hgwlJcD{eo7*$j0$yNf6@ib#U1!v!cfl%J%s4{1Bnv4 zjXS;rd5BcRj8@AY%(v^d4uweLa0D#2>Zs+H5*|dK#)SAn@`8i2eu5i3Nwx=eUb0|J z6z-(%^MI<~PQB`Yc#7lZJ1Kta9@vt_2*Bmak@d-32}P<|uGNV^*k_Bx9ww`JnMxps z+ChMFt|u+-tGTwCzJin~G>G}CKRi@30q>x8>LgF4-EgL{xtr_x4e~Lrz#lg+J@?)^ zl~G{)DIiuPno#BC;9!;=tv0WQf>BQdX1x=NOM)sGUQiUH4?oVBtT#No6LWwO3Hr@; zr*F|j5B-dSD|<9eL7M-vZe1+YF_b3;xOjR_-~b&)lare6Gv#$-%tzpCbpO!^Os8Gm zZtSyuwpw$o4ee90Pp8?Rm!E%@bOlp3mJn}DH%##Ws`GT(li-8`x|B7tJ309be7Cqi zBX{{a-JmxlvA;90S{rnZGFR?2J_nLK=vh4Kwn*c(ncYsXgR&B2)~s1#-9&%uPPMPT zGhoel)Qu)0QLdN)14uEMM2)5F@x!=$t@rlVRavJ%8z4; z%(?Tw=t3r-JGg5Rvp;9K6(UPQJ7-Bne}Sy7znC2xyKtdzT--luI!)+66bitx5%#5u zGk}Bz1Eb#GT5G!hur1x5JKKWCg#hWuF47ddZUKON_Y z_vV^ALLVBjawxgNP_~Mubs5ru3MCmDv*s&(Trdd?!&MpNrf1R`LFAas5l-O1H8d9Z zfZm@lY@Ut)gV8nF^^BE>35rbv&KBIl^Wlyw3r@Xg)jS%ruI#7s@(Un18oj>Ydg<#+ zUk$4J&Wb>NoN2b3=2|e&9+X@H)Xrm6o>)AC89gD$ZeGXRZnrGX3NPFWdY{J^5}u`qv*jXJ3tMS z@}pY9zqkCJ@9z$jnwf{%Rs5@^oxnY#h-oK=)1paYeob!jCV_F7S6fqMifW3PpmeJ8 zD%91FruEi%OzPTWw7%6(sElZCN#87Q`Kwu;n8Qd)=2^+XWSRh*5mWk{qj+rf)Fp+E z-#0;AX`ne$NvqOE5z27zg8Npe_(&C~Pc_LG$P*rYkQ8EB)fustF)2wUQ*n@GejIw} zCao?rl=t?-+hC}sgDC5`#hZwaBIn4)z79@w7${BNtZSX*lF=@oBkAX%Z3mGn3M8V; zIJ1}w3>X~acV5zt?W~Otj#leKU zr&6T-1U`bRaVAf;?4=FM;MnnF;3irFbXqGAp0;GT65?bPqRXK@)#jxVLpFAFbWEXq zk-Wzka$Dt>f?LyN8KOK^Z~xSze$4(M5fZ{k{CQLg{B{Lyv+|r}ka_zz#=vEA=F4cb zbt$!2#C}p6#1#_XME#Fg;=(YO-2kkC%@cU*rkfZ?BQUG&?bp%^QxXXxGVjRm zq^$Y&m5v`L7aEtY4HDQ1m`$O;cCfyFnEnNYkcj}k=<%EPx40CG-#P3G*|iSD9NY1v z35-*`VPumL{8>%^rFZY%ib9ghfhNOWs2xm>E4#$aO;FwX2?nyJgdQ9qlc|#&t#9(K z$cdJEfX+Z(j{zRkRdk(g;R0lc8Y#A=!XSy58Z=gSe}9I6-P{5Hv)Vj^&VYV`1rZIa z>SA$9MF7X}p!JoI??rpTTRMQq?4WJcn;KEZW0|6axJaQbp{hUd3r%T%q08gzW5i7t znkngeN41kPCtBYb%Rd`rcGUF__S!2HDm&>PNV7pmccrJ!aoT9JwyiiT z(T$at3fcpymy%#FngUo3FO{VwS9m5V*%)ZaQBu!2`>JIbk42W7za9kJ!^`uKb2*A8 zp*ZEz7Vb1H6l9+SX@^343_VsTdXYQh+Jbo^C?qF)=VC>aqz+y6`Y9K9UV*K74XK30 zEKIgxFj~#zZtsSFY}@7p&XZdx>0jhiGDR3SqxgTtZy!R5s0S%XhTL(*# z3Y$Q!Se1M1z8^Gm#h$TUl0Q9OG`^Yl%nvwozz_Xm0_3PW0k!z+hw`4nS z7ZQ==B$p zTDDP}otF>pRrTO~(-8ax)^K86=-}caWgz$D&&$otoiS%lTPAO%2g6n~;Q(Ns(ovW_ zUGiJdWTgCPqHTr9htXkxTei~B^`VPYq&JHWi{&Xoeaklj6=a1&&bCFP^fS@i*{Ge2 z^mUo!X*auk`&nKW+2x5228_4kXxIsEDpF%XU;L0WWZTTlO!+z7Y3_iLgMXGqHyi*F z1`<;WGCtioPG-gsnFOFYlvXT6J|LQjLN$M*580iZYz7^0Kpmd7jWaOX3+*K56wy@$ zB%3@xVs#Ri9)8$XZJs4>zK04ZB4~${W~#6MN=Omzlhl9cJLKol<{#gFQxT03%eq@7 ztKg&~A6}MG|ca{)V1G9l16rjGg26VeJ?S&9kT4Yxo|j3e-q~ zjfofHfF&sgnZX+Fcs5N3L>T*Tyth@WO@bgx*L+}TDBxQYK72zz){`{B_+r$}t>qoz ze>jK|f>9BGPWRq?nq6d>VBY~e03`KKfGTT*W+|2>bNRAA=>C0Y1T+3#?&1=# zbm=-cIYfkb~^KaKc;;;M*oc+H~*PTew>)mEi zo=L+5RTV9Z7Z4x-^`kY0}0fx{Jigt6U5(JK$5o>eyWY-3O%>6|reIr@G z3$=fq+f()U@$M4~(u_tcRrmA3{B<(F1@)sOLQFw~qWO^F8@vLKpE!Z`Sj_=;|2c(; zwnwqcD~jMX`vXFly<+XGGiQDs2adG_8&Iy;&pSr3Wz|h*$54h)!w6rfYw0=e&t0&^ zXnbvx5~)^i`-Tc58Z0N42h?EvPH8s)qHTfoE$Tj5&EBF<9$umCt0(}Oew7jIq~%Gr z3Jwm2dg)2f1d*@d_tLhU{`@u|uvqkN;AV08fZU&y7rDB;a2faaPbgKmM&$9K&mRy; z#Y0UL9wLY?m^yj#rmt#|J7K}mpT2SS5AKEaDgb9aKEA*F>tC%Dl&J=(l<9X$&c~_I zLJ7=h(Y}3Z)WvZ25;VRw8cUTtpq-JSuW|VLJFZ$urHT{7+6@~%&eyQhxU*76C&~V< zcmMu2N*(P7$AYmySHQRdI`#$D7V`F$eC?#scvw?}YCIN_d6FT8C~G2)Uxv!HXr4z; zEnqjcN+BkPA3RN(RuTcWIeTl-*2zP_RoEI>+(_Sq?giMsIH-p6^BjC5)H(7xDfcj{ zj{1&wOZx8Ab;*7^d%qpT5E;1MH)B^+t@NomDDMlFrSvr~vFMuee*TTAX|}~ZF7;>= z<#ga?q~ihK79)+mdF`UqRt~BDb?rM@g|+avn)27Sn!Q$?FIcA@e)V+z@_VlepN;r< z#FN-zLz<5NzWwg!p0ROp4FOpeiAAYHdMGQO(ucl0Yv8^4L;m>VkM0)&URKuz9J_m& zfN=5z(?3lazyIey!-7`*G1*ahSLSS!=zU}Uv4NNYg?zseohv)SnqF2CY_w(kppz%| z?}sLO>Cp8MVJ$-o=jIph_61n{;_m9BGz3nZxI8|- zF=l>bs;H| zDBdM6UMxgie(lS_1HHYQBHUXizW=j@N>S{hZFPz=E={pel&{r26vbzNss(2KhD1eC uO88D?N2lh`xv+LBF{(wI|BnxE{cZWlCuP?^*=x;ZC_(-q`oH@{CjA%l`75FT diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py index a0969df5de90..a36100e1031f 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -3,8 +3,10 @@ import pytest import matplotlib as mpl +from matplotlib.projections.polar import RadialLocator from matplotlib import pyplot as plt from matplotlib.testing.decorators import image_comparison, check_figures_equal +import matplotlib.ticker as mticker @image_comparison(['polar_axes.png'], style='default', tol=0.012) @@ -526,3 +528,43 @@ def test_radial_limits_behavior(): # negative data also autoscales to negative limits ax.plot([1, 2], [-1, -2]) assert ax.get_ylim() == (-2, 2) + + +def test_radial_locator_wrapping(): + # Check that the locator is always wrapped inside a RadialLocator + # and that RaidialAxis.isDefault_majloc is set correctly. + fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) + assert ax.yaxis.isDefault_majloc + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + + # set an explicit locator + locator = mticker.MaxNLocator(3) + ax.yaxis.set_major_locator(locator) + assert not ax.yaxis.isDefault_majloc + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + assert ax.yaxis.get_major_locator().base is locator + + ax.clear() # reset to the default locator + assert ax.yaxis.isDefault_majloc + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + + ax.set_rticks([0, 1, 2, 3]) # implicitly sets a FixedLocator + assert not ax.yaxis.isDefault_majloc # because of the fixed ticks + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + assert isinstance(ax.yaxis.get_major_locator().base, mticker.FixedLocator) + + ax.clear() + + ax.set_rgrids([0, 1, 2, 3]) # implicitly sets a FixedLocator + assert not ax.yaxis.isDefault_majloc # because of the fixed ticks + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + assert isinstance(ax.yaxis.get_major_locator().base, mticker.FixedLocator) + + ax.clear() + + ax.set_yscale("log") # implicitly sets a LogLocator + # Note that the LogLocator is still considered the default locator + # for the log scale + assert ax.yaxis.isDefault_majloc + assert isinstance(ax.yaxis.get_major_locator(), RadialLocator) + assert isinstance(ax.yaxis.get_major_locator().base, mticker.LogLocator) From 347dceb1dd2edfa395c553c866ff62e842dbb56a Mon Sep 17 00:00:00 2001 From: David Stansby Date: Mon, 14 Jul 2025 09:44:32 +0100 Subject: [PATCH 43/57] Only error if redirect is different --- doc/sphinxext/redirect_from.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/sphinxext/redirect_from.py b/doc/sphinxext/redirect_from.py index 37b56373a3bf..5402c9a83f55 100644 --- a/doc/sphinxext/redirect_from.py +++ b/doc/sphinxext/redirect_from.py @@ -94,7 +94,8 @@ def run(self): domain = self.env.get_domain('redirect_from') current_doc = self.env.path2doc(self.state.document.current_source) redirected_reldoc, _ = self.env.relfn2path(redirected_doc, current_doc) - if redirected_reldoc in domain.redirects: + if (redirected_reldoc in domain.redirects + and domain.redirects[redirected_reldoc] != current_doc): raise ValueError( f"{redirected_reldoc} is already noted as redirecting to " f"{domain.redirects[redirected_reldoc]}") From ef45c7bf2b9f0a08b2d185b428ab5226918f0a10 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Mon, 14 Jul 2025 12:55:11 +0100 Subject: [PATCH 44/57] Note where redirect is trying to go --- doc/sphinxext/redirect_from.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/sphinxext/redirect_from.py b/doc/sphinxext/redirect_from.py index 5402c9a83f55..329352b3a3c8 100644 --- a/doc/sphinxext/redirect_from.py +++ b/doc/sphinxext/redirect_from.py @@ -94,11 +94,15 @@ def run(self): domain = self.env.get_domain('redirect_from') current_doc = self.env.path2doc(self.state.document.current_source) redirected_reldoc, _ = self.env.relfn2path(redirected_doc, current_doc) - if (redirected_reldoc in domain.redirects - and domain.redirects[redirected_reldoc] != current_doc): + if ( + redirected_reldoc in domain.redirects + and domain.redirects[redirected_reldoc] != current_doc + ): raise ValueError( f"{redirected_reldoc} is already noted as redirecting to " - f"{domain.redirects[redirected_reldoc]}") + f"{domain.redirects[redirected_reldoc]}\n" + f"Cannot also redirect it to {current_doc}" + ) domain.redirects[redirected_reldoc] = current_doc return [] From 7d113022f862a441938a78b10cb60697bdceecf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 00:07:22 +0000 Subject: [PATCH 45/57] Bump the actions group with 2 updates Bumps the actions group with 2 updates: [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) and [cygwin/cygwin-install-action](https://github.com/cygwin/cygwin-install-action). Updates `pypa/cibuildwheel` from 3.0.0 to 3.0.1 - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/5f22145df44122af0f5a201f93cf0207171beca7...95d2f3a92fbf80abe066b09418bbf128a8923df2) Updates `cygwin/cygwin-install-action` from 5 to 6 - [Release notes](https://github.com/cygwin/cygwin-install-action/releases) - [Commits](https://github.com/cygwin/cygwin-install-action/compare/f61179d72284ceddc397ed07ddb444d82bf9e559...f2009323764960f80959895c7bc3bb30210afe4d) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-version: 3.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: cygwin/cygwin-install-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/cibuildwheel.yml | 10 +++++----- .github/workflows/cygwin.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index fececb0dfc40..15ec0e405400 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -141,7 +141,7 @@ jobs: path: dist/ - name: Build wheels for CPython 3.14 - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -155,7 +155,7 @@ jobs: --upgrade --pre --only-binary=:all: contourpy numpy pillow - name: Build wheels for CPython 3.13 - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -164,7 +164,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.12 - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -172,7 +172,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for CPython 3.11 - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: @@ -180,7 +180,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_archs }} - name: Build wheels for PyPy - uses: pypa/cibuildwheel@5f22145df44122af0f5a201f93cf0207171beca7 # v3.0.0 + uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1 with: package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }} env: diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index 4a5b79c0538e..a52343c5d22c 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -84,7 +84,7 @@ jobs: fetch-depth: 0 persist-credentials: false - - uses: cygwin/cygwin-install-action@f61179d72284ceddc397ed07ddb444d82bf9e559 # v5 + - uses: cygwin/cygwin-install-action@f2009323764960f80959895c7bc3bb30210afe4d # v6 with: packages: >- ccache gcc-g++ gdb git graphviz libcairo-devel libffi-devel From 6019c466a29475bde678d971f665c4abd588b006 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 15 Jul 2025 09:41:00 +0100 Subject: [PATCH 46/57] Replace deprecated imports --- lib/matplotlib/_api/__init__.pyi | 2 +- lib/matplotlib/axis.pyi | 2 +- lib/matplotlib/cbook.pyi | 2 +- lib/matplotlib/dviread.pyi | 2 +- lib/matplotlib/sankey.pyi | 2 +- lib/matplotlib/tests/test_api.py | 2 +- lib/matplotlib/typing.py | 3 ++- pyproject.toml | 1 + 8 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/_api/__init__.pyi b/lib/matplotlib/_api/__init__.pyi index 9bf67110bb54..c8ea814fc13d 100644 --- a/lib/matplotlib/_api/__init__.pyi +++ b/lib/matplotlib/_api/__init__.pyi @@ -1,6 +1,6 @@ from collections.abc import Callable, Generator, Iterable, Mapping, Sequence from typing import Any, TypeVar, overload -from typing_extensions import Self # < Py 3.11 +from typing import Self from numpy.typing import NDArray diff --git a/lib/matplotlib/axis.pyi b/lib/matplotlib/axis.pyi index 6119b946fd7b..4bcfb1e1cfb7 100644 --- a/lib/matplotlib/axis.pyi +++ b/lib/matplotlib/axis.pyi @@ -1,7 +1,7 @@ from collections.abc import Callable, Iterable, Sequence import datetime from typing import Any, Literal, overload -from typing_extensions import Self # < Py 3.11 +from typing import Self import numpy as np from numpy.typing import ArrayLike diff --git a/lib/matplotlib/cbook.pyi b/lib/matplotlib/cbook.pyi index 6c2d9c303eb2..ad14841463e8 100644 --- a/lib/matplotlib/cbook.pyi +++ b/lib/matplotlib/cbook.pyi @@ -14,10 +14,10 @@ from typing import ( Generic, IO, Literal, - Sequence, TypeVar, overload, ) +from collections.abc import Sequence _T = TypeVar("_T") diff --git a/lib/matplotlib/dviread.pyi b/lib/matplotlib/dviread.pyi index 12a9215b5308..82c0238d39d1 100644 --- a/lib/matplotlib/dviread.pyi +++ b/lib/matplotlib/dviread.pyi @@ -6,7 +6,7 @@ from enum import Enum from collections.abc import Generator from typing import NamedTuple -from typing_extensions import Self # < Py 3.11 +from typing import Self class _dvistate(Enum): pre = ... diff --git a/lib/matplotlib/sankey.pyi b/lib/matplotlib/sankey.pyi index 33565b998a9c..083d590559ca 100644 --- a/lib/matplotlib/sankey.pyi +++ b/lib/matplotlib/sankey.pyi @@ -2,7 +2,7 @@ from matplotlib.axes import Axes from collections.abc import Callable, Iterable from typing import Any -from typing_extensions import Self # < Py 3.11 +from typing import Self import numpy as np diff --git a/lib/matplotlib/tests/test_api.py b/lib/matplotlib/tests/test_api.py index f04604c14cce..58e7986bfce6 100644 --- a/lib/matplotlib/tests/test_api.py +++ b/lib/matplotlib/tests/test_api.py @@ -13,7 +13,7 @@ if typing.TYPE_CHECKING: - from typing_extensions import Self + from typing import Self T = TypeVar('T') diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index e3719235cdb8..c6e62cd472b5 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -12,7 +12,8 @@ """ from collections.abc import Hashable, Sequence import pathlib -from typing import Any, Callable, Literal, TypeAlias, TypeVar, Union +from typing import Any, Literal, TypeAlias, TypeVar, Union +from collections.abc import Callable from . import path from ._enums import JoinStyle, CapStyle diff --git a/pyproject.toml b/pyproject.toml index b580feff930e..b06a5bcc5740 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,6 +140,7 @@ select = [ "E", "F", "W", + "UP035", # The following error codes require the preview mode to be enabled. "E201", "E202", From eef03343123c92c79ab3fdf3c1ed2e5e2f4878cc Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 15 Jul 2025 14:01:55 +0100 Subject: [PATCH 47/57] Fix link to pango --- doc/devel/MEP/MEP14.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devel/MEP/MEP14.rst b/doc/devel/MEP/MEP14.rst index 2c696adf8a58..d79d3c2d3115 100644 --- a/doc/devel/MEP/MEP14.rst +++ b/doc/devel/MEP/MEP14.rst @@ -78,7 +78,7 @@ number of other projects: - `Microsoft DirectWrite`_ - `Apple Core Text`_ -.. _pango: https://pango.gnome.org +.. _pango: https://github.com/GNOME/pango .. _harfbuzz: https://github.com/harfbuzz/harfbuzz .. _QtTextLayout: https://doc.qt.io/archives/qt-4.8/qtextlayout.html .. _Microsoft DirectWrite: https://docs.microsoft.com/en-ca/windows/win32/directwrite/introducing-directwrite From b77ba11f535543e0ab7f859ea125f4e292c2fc8d Mon Sep 17 00:00:00 2001 From: jocelynvj Date: Tue, 15 Jul 2025 14:36:02 +0100 Subject: [PATCH 48/57] fix broken configobj link --- doc/users/prev_whats_new/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/prev_whats_new/changelog.rst b/doc/users/prev_whats_new/changelog.rst index 8f505e4fdd37..93fd4df2200a 100644 --- a/doc/users/prev_whats_new/changelog.rst +++ b/doc/users/prev_whats_new/changelog.rst @@ -1689,7 +1689,7 @@ recent changes, please refer to the :doc:`/users/release_notes`. required by the experimental traited config and are somewhat out of date. If needed, install them independently, see http://code.enthought.com/pages/traits.html and - http://www.voidspace.org.uk/python/configobj.html + https://configobj.readthedocs.io/en/latest/ 2008-12-12 Added support to assign labels to histograms of multiple data. - MM From 2e6533287b7a8ce374c618d96f0a4508e571702a Mon Sep 17 00:00:00 2001 From: ZPyrolink <73246085+ZPyrolink@users.noreply.github.com> Date: Tue, 15 Jul 2025 22:22:54 +0200 Subject: [PATCH 49/57] [TYP] Add more literals to MarkerType (#30261) * Update mpl.typing.MarkerType * Revert superfluous changes * Moved import * Remove unnecessary import Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> * Correct integers values on MarkerType --------- Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/typing.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/typing.py b/lib/matplotlib/typing.py index c6e62cd472b5..270da6bcd5e0 100644 --- a/lib/matplotlib/typing.py +++ b/lib/matplotlib/typing.py @@ -70,7 +70,16 @@ ) """See :doc:`/gallery/lines_bars_and_markers/markevery_demo`.""" -MarkerType: TypeAlias = str | path.Path | MarkerStyle +MarkerType: TypeAlias = ( + path.Path | MarkerStyle | str | # str required for "$...$" marker + Literal[ + ".", ",", "o", "v", "^", "<", ">", + "1", "2", "3", "4", "8", "s", "p", + "P", "*", "h", "H", "+", "x", "X", + "D", "d", "|", "_", "none", " ", + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + ] | list[tuple[int, int]] | tuple[int, Literal[0, 1, 2], int] +) """ Marker specification. See :doc:`/gallery/lines_bars_and_markers/marker_reference`. """ From d0eed0e9effc24275b6298d6502b3a24f37ed201 Mon Sep 17 00:00:00 2001 From: N R Navaneet Date: Thu, 17 Jul 2025 02:42:28 +0530 Subject: [PATCH 50/57] Added None to get_legend() type annotation --- lib/matplotlib/axes/_base.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index 4933d0d1e236..cb538a49172a 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -225,7 +225,7 @@ class _AxesBase(martist.Artist): ymin: float | None = ..., ymax: float | None = ... ) -> tuple[float, float, float, float]: ... - def get_legend(self) -> Legend: ... + def get_legend(self) -> Legend | None: ... def get_images(self) -> list[AxesImage]: ... def get_lines(self) -> list[Line2D]: ... def get_xaxis(self) -> XAxis: ... From b520f86775a08dee5d046db20fc3af619ad02a6d Mon Sep 17 00:00:00 2001 From: N R Navaneet Date: Thu, 17 Jul 2025 03:01:02 +0530 Subject: [PATCH 51/57] Added an edge case test --- lib/matplotlib/tests/test_axes.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index c96173e340f7..70c9129a2ac3 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -9900,3 +9900,14 @@ def test_pie_all_zeros(): fig, ax = plt.subplots() with pytest.raises(ValueError, match="All wedge sizes are zero"): ax.pie([0, 0], labels=["A", "B"]) + + +def test_set_axes_with_none_limits(): + fig, ax = plt.subplots() + xlim_before = ax.get_xlim() + ylim_before = ax.get_ylim() + + ax.set(xlim=None, ylim=None) + + assert ax.get_xlim() == xlim_before + assert ax.get_ylim() == ylim_before From 24e9946d3c9cc26acc25a2e5bd8eb25afeaff96b Mon Sep 17 00:00:00 2001 From: N R Navaneet Date: Thu, 17 Jul 2025 03:54:26 +0530 Subject: [PATCH 52/57] Test changes --- lib/matplotlib/tests/test_axes.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 70c9129a2ac3..4aa6978bc1c2 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -9902,12 +9902,14 @@ def test_pie_all_zeros(): ax.pie([0, 0], labels=["A", "B"]) -def test_set_axes_with_none_limits(): +def test_get_legend_return_type(): fig, ax = plt.subplots() - xlim_before = ax.get_xlim() - ylim_before = ax.get_ylim() - ax.set(xlim=None, ylim=None) + assert ax.get_legend() is None - assert ax.get_xlim() == xlim_before - assert ax.get_ylim() == ylim_before + ax.plot([1, 2], label="Line") + ax.legend() + + legend = ax.get_legend() + assert legend is not None + assert isinstance(legend, matplotlib.legend.Legend) From 3070ac86705f42e8be7b777f18d2eb10ec777800 Mon Sep 17 00:00:00 2001 From: N R Navaneet Date: Thu, 17 Jul 2025 21:59:32 +0530 Subject: [PATCH 53/57] Removed the test --- lib/matplotlib/tests/test_axes.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 4aa6978bc1c2..c96173e340f7 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -9900,16 +9900,3 @@ def test_pie_all_zeros(): fig, ax = plt.subplots() with pytest.raises(ValueError, match="All wedge sizes are zero"): ax.pie([0, 0], labels=["A", "B"]) - - -def test_get_legend_return_type(): - fig, ax = plt.subplots() - - assert ax.get_legend() is None - - ax.plot([1, 2], label="Line") - ax.legend() - - legend = ax.get_legend() - assert legend is not None - assert isinstance(legend, matplotlib.legend.Legend) From 5a9f7cbbf819c5a8ec2a1c39b19460a8e01fbeec Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 29 Mar 2023 16:18:54 -0400 Subject: [PATCH 54/57] TST: Add tests for saving a figure after removing a widget axes --- lib/matplotlib/tests/test_widgets.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 808863fd6a94..3da678076489 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -3,7 +3,7 @@ import operator from unittest import mock -from matplotlib.backend_bases import MouseEvent +from matplotlib.backend_bases import MouseEvent, DrawEvent import matplotlib.colors as mcolors import matplotlib.widgets as widgets import matplotlib.pyplot as plt @@ -1757,3 +1757,26 @@ def test_MultiCursor(horizOn, vertOn): assert l.get_xdata() == (.5, .5) for l in multi.hlines: assert l.get_ydata() == (.25, .25) + + +def test_parent_axes_removal(): + + fig, (ax_radio, ax_checks) = plt.subplots(1, 2) + + radio = widgets.RadioButtons(ax_radio, ['1', '2'], 0) + checks = widgets.CheckButtons(ax_checks, ['1', '2'], [True, False]) + + ax_checks.remove() + ax_radio.remove() + with io.BytesIO() as out: + # verify that saving does not raise + fig.savefig(out, format='raw') + + # verify that this method which is triggered by a draw_event callback when + # blitting is enabled does not raise. Calling private methods is simpler + # than trying to force blitting to be enabled with Agg or use a GUI + # framework. + renderer = fig._get_renderer() + evt = DrawEvent('draw_event', fig.canvas, renderer) + radio._clear(evt) + checks._clear(evt) From 5c83d7b104636f84b11e91a0451a6ada1b1c022d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 17 Jul 2025 20:13:37 -0400 Subject: [PATCH 55/57] FIX: callbacks do not raise after removal of widget axes closes #25572 --- lib/matplotlib/widgets.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 9ded7c61ce2d..41e05e6d9a05 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -117,7 +117,9 @@ def __init__(self, ax): self.ax = ax self._cids = [] - canvas = property(lambda self: self.ax.get_figure(root=True).canvas) + canvas = property( + lambda self: getattr(self.ax.get_figure(root=True), 'canvas', None) + ) def connect_event(self, event, callback): """ @@ -144,6 +146,10 @@ def _get_data_coords(self, event): return ((event.xdata, event.ydata) if event.inaxes is self.ax else self.ax.transData.inverted().transform((event.x, event.y))) + def ignore(self, event): + # docstring inherited + return super().ignore(event) or self.canvas is None + class Button(AxesWidget): """ @@ -2181,7 +2187,9 @@ def connect_default_events(self): def ignore(self, event): # docstring inherited - if not self.active or not self.ax.get_visible(): + if super().ignore(event): + return True + if not self.ax.get_visible(): return True # If canvas was locked if not self.canvas.widgetlock.available(self): From ee1c70bd99a9c8c0f0a28f9566db23047b0f1828 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 18 Jul 2025 19:52:27 +0100 Subject: [PATCH 56/57] FIX: cast Patch linewidth to float for dash scaling --- lib/matplotlib/patches.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 477eee9f5a7a..d750e86e401f 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -459,7 +459,8 @@ def set_linewidth(self, w): w : float or None """ w = mpl._val_or_rc(w, 'patch.linewidth') - self._linewidth = float(w) + w = float(w) + self._linewidth = w self._dash_pattern = mlines._scale_dashes(*self._unscaled_dash_pattern, w) self.stale = True From 192b7c24d153ab36b779bcd51f4050134d1ab254 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sat, 19 Jul 2025 08:00:31 +0100 Subject: [PATCH 57/57] Include close matches in error message when key not found (#30001) * Include close matches when key not found * Improve return type of check_getitem * Automatically determine whether to suggest options * Remove deprecated import * Style fixes Co-authored-by: Elliott Sales de Andrade --------- Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/__init__.py | 9 ++++----- lib/matplotlib/_api/__init__.py | 19 +++++++++++++++---- lib/matplotlib/_api/__init__.pyi | 4 +++- lib/matplotlib/cm.py | 6 ++---- lib/matplotlib/tests/test_colors.py | 10 ++++++++++ lib/matplotlib/tests/test_style.py | 4 +++- 6 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index e98e8ea07502..03d288efe342 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -743,12 +743,11 @@ def __setitem__(self, key, val): and val is rcsetup._auto_backend_sentinel and "backend" in self): return + valid_key = _api.check_getitem( + self.validate, rcParam=key, _error_cls=KeyError + ) try: - cval = self.validate[key](val) - except KeyError as err: - raise KeyError( - f"{key} is not a valid rc parameter (see rcParams.keys() for " - f"a list of valid parameters)") from err + cval = valid_key(val) except ValueError as ve: raise ValueError(f"Key {key}: {ve}") from None self._set(key, cval) diff --git a/lib/matplotlib/_api/__init__.py b/lib/matplotlib/_api/__init__.py index 47c32f701729..39496cfb0e82 100644 --- a/lib/matplotlib/_api/__init__.py +++ b/lib/matplotlib/_api/__init__.py @@ -10,6 +10,7 @@ """ +import difflib import functools import itertools import pathlib @@ -174,12 +175,17 @@ def check_shape(shape, /, **kwargs): ) -def check_getitem(mapping, /, **kwargs): +def check_getitem(mapping, /, _error_cls=ValueError, **kwargs): """ *kwargs* must consist of a single *key, value* pair. If *key* is in *mapping*, return ``mapping[value]``; else, raise an appropriate ValueError. + Parameters + ---------- + _error_cls : + Class of error to raise. + Examples -------- >>> _api.check_getitem({"foo": "bar"}, arg=arg) @@ -190,9 +196,14 @@ def check_getitem(mapping, /, **kwargs): try: return mapping[v] except KeyError: - raise ValueError( - f"{v!r} is not a valid value for {k}; supported values are " - f"{', '.join(map(repr, mapping))}") from None + if len(mapping) > 5: + if len(best := difflib.get_close_matches(v, mapping.keys(), cutoff=0.5)): + suggestion = f"Did you mean one of {best}?" + else: + suggestion = "" + else: + suggestion = f"Supported values are {', '.join(map(repr, mapping))}" + raise _error_cls(f"{v!r} is not a valid value for {k}. {suggestion}") from None def caching_module_getattr(cls): diff --git a/lib/matplotlib/_api/__init__.pyi b/lib/matplotlib/_api/__init__.pyi index c8ea814fc13d..5db251c551e5 100644 --- a/lib/matplotlib/_api/__init__.pyi +++ b/lib/matplotlib/_api/__init__.pyi @@ -42,7 +42,9 @@ def check_in_list( values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any ) -> None: ... def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ... -def check_getitem(mapping: Mapping[Any, Any], /, **kwargs: Any) -> Any: ... +def check_getitem( + mapping: Mapping[Any, _T], /, _error_cls: type[Exception], **kwargs: Any +) -> _T: ... def caching_module_getattr(cls: type) -> Callable[[str], Any]: ... @overload def define_aliases( diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index ef5bf0719d3b..299059177a20 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -92,10 +92,8 @@ def __init__(self, cmaps): self._builtin_cmaps = tuple(cmaps) def __getitem__(self, item): - try: - return self._cmaps[item].copy() - except KeyError: - raise KeyError(f"{item!r} is not a known colormap name") from None + cmap = _api.check_getitem(self._cmaps, colormap=item, _error_cls=KeyError) + return cmap.copy() def __iter__(self): return iter(self._cmaps) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 73de50408401..f54ac46afea5 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -1876,3 +1876,13 @@ def scaled(self): axes[0,1].pcolor(r, colorizer=colorizer) axes[1,0].contour(r, colorizer=colorizer) axes[1,1].contourf(r, colorizer=colorizer) + + +def test_close_error_name(): + with pytest.raises( + KeyError, + match=( + "'grays' is not a valid value for colormap. " + "Did you mean one of ['gray', 'Grays', 'gray_r']?" + )): + matplotlib.colormaps["grays"] diff --git a/lib/matplotlib/tests/test_style.py b/lib/matplotlib/tests/test_style.py index 7b54f1141720..14110209fa15 100644 --- a/lib/matplotlib/tests/test_style.py +++ b/lib/matplotlib/tests/test_style.py @@ -150,7 +150,9 @@ def test_context_with_badparam(): with style.context({PARAM: other_value}): assert mpl.rcParams[PARAM] == other_value x = style.context({PARAM: original_value, 'badparam': None}) - with pytest.raises(KeyError): + with pytest.raises( + KeyError, match="\'badparam\' is not a valid value for rcParam. " + ): with x: pass assert mpl.rcParams[PARAM] == other_value