From 791aaf9d2a4a34c774746e90095675e418428073 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sat, 28 Mar 2026 07:49:03 +0100 Subject: [PATCH 1/9] MNT: Privatize Formatter attributes Co-authored-by: buddy0452004 --- .../deprecations/31416-TH.rst | 8 ++ lib/matplotlib/dates.py | 16 ++-- .../testing/jpl_units/UnitDblFormatter.py | 2 +- lib/matplotlib/tests/test_ticker.py | 2 +- lib/matplotlib/ticker.py | 95 ++++++++++--------- lib/matplotlib/ticker.pyi | 3 + 6 files changed, 73 insertions(+), 53 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/31416-TH.rst diff --git a/doc/api/next_api_changes/deprecations/31416-TH.rst b/doc/api/next_api_changes/deprecations/31416-TH.rst new file mode 100644 index 000000000000..6d6d4b7b7809 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/31416-TH.rst @@ -0,0 +1,8 @@ +Formatter attributes +~~~~~~~~~~~~~~~~~~~~ + +These following attributes are considered internal and users should not have a need to access them: + +- `.ScalarFormatter`: ``orderOfMagnitude`` and ``format`` +- `.ConciseDateFormatter`: ``offset_format`` +- `.Formatter`: ``locs`` diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 369e93f1ac6f..cdd7d3b280a4 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -665,6 +665,10 @@ class ConciseDateFormatter(ticker.Formatter): """ + offset_string = _api.deprecate_privatize_attribute( + "3.11", alternative="get_offset()" + ) + def __init__(self, locator, tz=None, formats=None, offset_formats=None, zero_formats=None, show_offset=True, *, usetex=None): """ @@ -719,7 +723,7 @@ def __init__(self, locator, tz=None, formats=None, offset_formats=None, '%Y-%b-%d', '%Y-%b-%d', '%Y-%b-%d %H:%M'] - self.offset_string = '' + self._offset_string = '' self.show_offset = show_offset self._usetex = mpl._val_or_rc(usetex, 'text.usetex') @@ -799,13 +803,13 @@ def format_ticks(self, values): if (self._locator.axis and self._locator.axis.__name__ in ('xaxis', 'yaxis') and self._locator.axis.get_inverted()): - self.offset_string = tickdatetime[0].strftime(offsetfmts[level]) + self._offset_string = tickdatetime[0].strftime(offsetfmts[level]) else: - self.offset_string = tickdatetime[-1].strftime(offsetfmts[level]) + self._offset_string = tickdatetime[-1].strftime(offsetfmts[level]) if self._usetex: - self.offset_string = _wrap_in_tex(self.offset_string) + self._offset_string = _wrap_in_tex(self._offset_string) else: - self.offset_string = '' + self._offset_string = '' if self._usetex: return [_wrap_in_tex(l) for l in labels] @@ -813,7 +817,7 @@ def format_ticks(self, values): return labels def get_offset(self): - return self.offset_string + return self._offset_string def format_data_short(self, value): return num2date(value, tz=self._tz).strftime('%Y-%m-%d %H:%M:%S') diff --git a/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py b/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py index 30a9914015bc..d5b815d1fc67 100644 --- a/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py +++ b/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py @@ -14,7 +14,7 @@ class UnitDblFormatter(ticker.ScalarFormatter): def __call__(self, x, pos=None): # docstring inherited - if len(self.locs) == 0: + if len(self._locs) == 0: return '' else: return f'{x:.12}' diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 478a54b8a317..0d01677acc8c 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -920,7 +920,7 @@ def test_scilimits(self, sci_type, scilimits, lim, orderOfMag, fewticks): ax.yaxis.set_major_locator(mticker.MaxNLocator(4)) tmp_form.set_locs(ax.yaxis.get_majorticklocs()) - assert orderOfMag == tmp_form.orderOfMagnitude + assert orderOfMag == tmp_form._orderOfMagnitude @pytest.mark.parametrize('value, expected', format_data) def test_format_data(self, value, expected): diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 82b502719267..9fe29e22515c 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -202,7 +202,9 @@ class Formatter(TickHelper): """ # some classes want to see all the locs to help format # individual ones - locs = [] + _locs = [] + + locs = _api.deprecate_privatize_attribute("3.11") def __call__(self, x, pos=None): """ @@ -250,7 +252,7 @@ def set_locs(self, locs): This method is called before computing the tick labels because some formatters need to know all tick locations to do so. """ - self.locs = locs + self._locs = locs @staticmethod def fix_minus(s): @@ -458,6 +460,9 @@ class ScalarFormatter(Formatter): """ + orderOfMagnitude = _api.deprecate_privatize_attribute("3.11") + format = _api.deprecate_privatize_attribute("3.11") + def __init__(self, useOffset=None, useMathText=None, useLocale=None, *, usetex=None): useOffset = mpl._val_or_rc(useOffset, 'axes.formatter.useoffset') @@ -465,8 +470,8 @@ def __init__(self, useOffset=None, useMathText=None, useLocale=None, *, self.set_useOffset(useOffset) self.set_usetex(usetex) self.set_useMathText(useMathText) - self.orderOfMagnitude = 0 - self.format = '' + self._orderOfMagnitude = 0 + self._format = '' self._scientific = True self._powerlimits = mpl.rcParams['axes.formatter.limits'] self.set_useLocale(useLocale) @@ -615,13 +620,13 @@ def __call__(self, x, pos=None): """ Return the format for tick value *x* at position *pos*. """ - if len(self.locs) == 0: + if len(self._locs) == 0: return '' else: - xp = (x - self.offset) / (10. ** self.orderOfMagnitude) + xp = (x - self.offset) / (10. ** self._orderOfMagnitude) if abs(xp) < 1e-8: xp = 0 - return self._format_maybe_minus_and_locale(self.format, xp) + return self._format_maybe_minus_and_locale(self._format, xp) def set_scientific(self, b): """ @@ -715,20 +720,20 @@ def get_offset(self): """ Return scientific notation, plus offset. """ - if len(self.locs) == 0: + if len(self._locs) == 0: return '' - if self.orderOfMagnitude or self.offset: + if self._orderOfMagnitude or self.offset: offsetStr = '' sciNotStr = '' if self.offset: offsetStr = self.format_data(self.offset) if self.offset > 0: offsetStr = '+' + offsetStr - if self.orderOfMagnitude: + if self._orderOfMagnitude: if self._usetex or self._useMathText: - sciNotStr = self.format_data(10 ** self.orderOfMagnitude) + sciNotStr = self.format_data(10 ** self._orderOfMagnitude) else: - sciNotStr = '1e%d' % self.orderOfMagnitude + sciNotStr = '1e%d' % self._orderOfMagnitude if self._useMathText or self._usetex: if sciNotStr != '': sciNotStr = r'\times\mathdefault{%s}' % sciNotStr @@ -740,15 +745,15 @@ def get_offset(self): def set_locs(self, locs): # docstring inherited - self.locs = locs - if len(self.locs) > 0: + self._locs = locs + if len(self._locs) > 0: if self._useOffset: self._compute_offset() self._set_order_of_magnitude() self._set_format() def _compute_offset(self): - locs = self.locs + locs = self._locs # Restrict to visible ticks. vmin, vmax = sorted(self.axis.get_view_interval()) locs = np.asarray(locs) @@ -791,19 +796,19 @@ def _set_order_of_magnitude(self): # if using a numerical offset, find the exponent after applying the # offset. When lower power limit = upper <> 0, use provided exponent. if not self._scientific: - self.orderOfMagnitude = 0 + self._orderOfMagnitude = 0 return if self._powerlimits[0] == self._powerlimits[1] != 0: # fixed scaling when lower power limit = upper <> 0. - self.orderOfMagnitude = self._powerlimits[0] + self._orderOfMagnitude = self._powerlimits[0] return # restrict to visible ticks vmin, vmax = sorted(self.axis.get_view_interval()) - locs = np.asarray(self.locs) + locs = np.asarray(self._locs) locs = locs[(vmin <= locs) & (locs <= vmax)] locs = np.abs(locs) if not len(locs): - self.orderOfMagnitude = 0 + self._orderOfMagnitude = 0 return if self.offset: oom = math.floor(math.log10(vmax - vmin)) @@ -814,20 +819,20 @@ def _set_order_of_magnitude(self): else: oom = math.floor(math.log10(val)) if oom <= self._powerlimits[0]: - self.orderOfMagnitude = oom + self._orderOfMagnitude = oom elif oom >= self._powerlimits[1]: - self.orderOfMagnitude = oom + self._orderOfMagnitude = oom else: - self.orderOfMagnitude = 0 + self._orderOfMagnitude = 0 def _set_format(self): # set the format string to format all the ticklabels - if len(self.locs) < 2: + if len(self._locs) < 2: # Temporarily augment the locations with the axis end points. - _locs = [*self.locs, *self.axis.get_view_interval()] + _locs = [*self._locs, *self.axis.get_view_interval()] else: - _locs = self.locs - locs = (np.asarray(_locs) - self.offset) / 10. ** self.orderOfMagnitude + _locs = self._locs + locs = (np.asarray(_locs) - self.offset) / 10. ** self._orderOfMagnitude loc_range = np.ptp(locs) # Curvilinear coordinates can yield two identical points. if loc_range == 0: @@ -835,7 +840,7 @@ def _set_format(self): # Both points might be zero. if loc_range == 0: loc_range = 1 - if len(self.locs) < 2: + if len(self._locs) < 2: # We needed the end points only for the loc_range calculation. locs = locs[:-2] loc_range_oom = int(math.floor(math.log10(loc_range))) @@ -849,9 +854,9 @@ def _set_format(self): else: break sigfigs += 1 - self.format = f'%1.{sigfigs}f' + self._format = f'%1.{sigfigs}f' if self._usetex or self._useMathText: - self.format = r'$\mathdefault{%s}$' % self.format + self._format = r'$\mathdefault{%s}$' % self._format class LogFormatter(Formatter): @@ -1243,7 +1248,7 @@ def set_minor_number(self, minor_number): self._minor_number = minor_number def set_locs(self, locs): - self.locs = np.array(locs) + self._locs = np.array(locs) self._labelled.clear() if not self._minor: @@ -1269,7 +1274,7 @@ def set_locs(self, locs): # the previous, and between the ticks and the next one. Ticks # with smallest minimum are chosen. As tiebreak, the ticks # with smallest sum is chosen. - diff = np.diff(-np.log(1 / self.locs - 1)) + diff = np.diff(-np.log(1 / self._locs - 1)) space_pessimistic = np.minimum( np.concatenate(((np.inf,), diff)), np.concatenate((diff, (np.inf,))), @@ -1279,7 +1284,7 @@ def set_locs(self, locs): + np.concatenate((diff, (0,))) ) good_minor = sorted( - range(len(self.locs)), + range(len(self._locs)), key=lambda i: (space_pessimistic[i], space_sum[i]), )[-self._minor_number:] self._labelled.update(locs[i] for i in good_minor) @@ -1330,11 +1335,11 @@ def __call__(self, x, pos=None): exponent = round(math.log10(1 - x)) s = self._one_minus("10^{%d}" % exponent) elif x < 0.1: - s = self._format_value(x, self.locs) + s = self._format_value(x, self._locs) elif x > 0.9: - s = self._one_minus(self._format_value(1-x, 1-self.locs)) + s = self._one_minus(self._format_value(1-x, 1-self._locs)) else: - s = self._format_value(x, self.locs, sci_notation=False) + s = self._format_value(x, self._locs, sci_notation=False) return r"$\mathdefault{%s}$" % s def format_data_short(self, value): @@ -1440,18 +1445,18 @@ def __call__(self, x, pos=None): If there is no currently offset in the data, it returns the best engineering formatting that fits the given argument, independently. """ - if len(self.locs) == 0 or self.offset == 0: + if len(self._locs) == 0 or self.offset == 0: return self.fix_minus(self.format_data(x)) else: - xp = (x - self.offset) / (10. ** self.orderOfMagnitude) + xp = (x - self.offset) / (10. ** self._orderOfMagnitude) if abs(xp) < 1e-8: xp = 0 - return self._format_maybe_minus_and_locale(self.format, xp) + return self._format_maybe_minus_and_locale(self._format, xp) def set_locs(self, locs): # docstring inherited - self.locs = locs - if len(self.locs) > 0: + self._locs = locs + if len(self._locs) > 0: vmin, vmax = sorted(self.axis.get_view_interval()) if self._useOffset: self._compute_offset() @@ -1465,17 +1470,17 @@ def set_locs(self, locs): # value: self.offset = round((vmin + vmax)/2, 3) # Use log1000 to use engineers' oom standards - self.orderOfMagnitude = math.floor(math.log(vmax - vmin, 1000))*3 + self._orderOfMagnitude = math.floor(math.log(vmax - vmin, 1000))*3 self._set_format() # Simplify a bit ScalarFormatter.get_offset: We always want to use # self.format_data. Also we want to return a non-empty string only if there - # is an offset, no matter what is self.orderOfMagnitude. If there _is_ an - # offset, self.orderOfMagnitude is consulted. This behavior is verified + # is an offset, no matter what is self._orderOfMagnitude. If there _is_ an + # offset, self._orderOfMagnitude is consulted. This behavior is verified # in `test_ticker.py`. def get_offset(self): # docstring inherited - if len(self.locs) == 0: + if len(self._locs) == 0: return '' if self.offset: offsetStr = '' @@ -1483,7 +1488,7 @@ def get_offset(self): offsetStr = self.format_data(self.offset) if self.offset > 0: offsetStr = '+' + offsetStr - sciNotStr = self.format_data(10 ** self.orderOfMagnitude) + sciNotStr = self.format_data(10 ** self._orderOfMagnitude) if self._useMathText or self._usetex: if sciNotStr != '': sciNotStr = r'\times%s' % sciNotStr diff --git a/lib/matplotlib/ticker.pyi b/lib/matplotlib/ticker.pyi index bed288658909..6590faee7b10 100644 --- a/lib/matplotlib/ticker.pyi +++ b/lib/matplotlib/ticker.pyi @@ -24,6 +24,7 @@ class TickHelper: class Formatter(TickHelper): locs: list[float] + _locs: list[float] def __call__(self, x: float, pos: int | None = ...) -> str: ... def format_ticks(self, values: list[float]) -> list[str]: ... def format_data(self, value: float) -> str: ... @@ -58,7 +59,9 @@ class StrMethodFormatter(Formatter): class ScalarFormatter(Formatter): orderOfMagnitude: int + _orderOfMagnitude: int format: str + _format: str def __init__( self, useOffset: bool | float | None = ..., From ca5240682f27e5345f5dd91678747de8d060289e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 17 Apr 2026 23:43:03 -0400 Subject: [PATCH 2/9] MNT: Update all pre-commit hooks When we moved off the app to running in Actions, we lost the auto update and things fell behind. Now set up Dependabot to automate these like everything else. Also bump the minimum mypy, as such old versions no longer work. --- .github/dependabot.yml | 4 ++++ .github/workflows/linting.yml | 2 +- .pre-commit-config.yaml | 19 +++++++++---------- lib/matplotlib/_docstring.pyi | 1 - lib/matplotlib/colors.pyi | 2 +- lib/matplotlib/pyplot.py | 7 +++---- lib/matplotlib/tests/test__style_helpers.py | 2 +- lib/matplotlib/tests/test_axes.py | 2 +- .../tests/test_backends_interactive.py | 2 +- lib/matplotlib/tests/test_basic.py | 2 +- lib/matplotlib/tests/test_collections.py | 2 +- lib/matplotlib/tests/test_ft2font.py | 4 ++-- lib/matplotlib/tests/test_legend.py | 2 +- lib/matplotlib/tests/test_pickle.py | 4 ++-- lib/matplotlib/tests/test_units.py | 2 +- lib/matplotlib/textpath.pyi | 4 ++-- lib/matplotlib/widgets.pyi | 2 +- pyproject.toml | 2 +- 18 files changed, 33 insertions(+), 32 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ba29818c46de..d391ab4a18eb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,3 +15,7 @@ updates: interval: "weekly" exclude-paths: - "ci/minver-requirements.txt" + - package-ecosystem: "pre-commit" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 32a3f0439b7f..f6af70b8b233 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -20,7 +20,7 @@ jobs: python-version: "3.x" - uses: j178/prek-action@cbc2f23eb5539cf20d82d1aabd0d0ecbcc56f4e3 # v2.0.2 with: - extra_args: --hook-stage manual --all-files + extra-args: --hook-stage manual --all-files ruff: name: ruff diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 947024cde375..bce348eaed56 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ exclude: | ) repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 hooks: - id: check-added-large-files - id: check-docstring-first @@ -29,7 +29,7 @@ repos: - id: trailing-whitespace exclude_types: [diff, svg] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 + rev: 0f369d245750787ce34997d464ed9605391a5283 # frozen: v1.20.1 hooks: - id: mypy additional_dependencies: @@ -42,16 +42,15 @@ repos: args: ["--config-file=pyproject.toml", "lib/matplotlib"] files: lib/matplotlib # Only run when files in lib/matplotlib are changed. pass_filenames: false - - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.15.11 + rev: d1b833175a5d08a925900115526febd8fe71c98e # frozen: v0.15.11 hooks: # Run the linter. - - id: ruff + - id: ruff-check args: [--fix, --show-fixes] - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: 2ccb47ff45ad361a21071a7eedda4c37e6ae8c5a # frozen: v2.4.2 hooks: - id: codespell files: ^.*\.(py|c|cpp|h|m|md|rst|yml)$ @@ -61,25 +60,25 @@ repos: - "--skip" - "doc/project/credits.rst" - repo: https://github.com/pycqa/isort - rev: 6.0.1 + rev: a333737ed43df02b18e6c95477ea1b285b3de15a # frozen: 8.0.1 hooks: - id: isort name: isort (python) files: ^galleries/tutorials/|^galleries/examples/|^galleries/plot_types/ - repo: https://github.com/rstcheck/rstcheck - rev: v6.2.4 + rev: 77490ffa33bfc0928975ae3cf904219903db755d # frozen: v6.2.5 hooks: - id: rstcheck additional_dependencies: - sphinx>=1.8.1 - tomli - repo: https://github.com/adrienverge/yamllint - rev: v1.37.0 + rev: cba56bcde1fdd01c1deb3f945e69764c291a6530 # frozen: v1.38.0 hooks: - id: yamllint args: ["--strict", "--config-file=.yamllint.yml"] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.0 + rev: 13614ab716a3113145f1294ed259d9fbe5678ff3 # frozen: 0.37.1 hooks: # TODO: Re-enable this when https://github.com/microsoft/azure-pipelines-vscode/issues/567 is fixed. # - id: check-azure-pipelines diff --git a/lib/matplotlib/_docstring.pyi b/lib/matplotlib/_docstring.pyi index fb52d0846123..7bb256a3032b 100644 --- a/lib/matplotlib/_docstring.pyi +++ b/lib/matplotlib/_docstring.pyi @@ -14,7 +14,6 @@ class Substitution: @overload def __init__(self, **kwargs: str): ... def __call__(self, func: _T) -> _T: ... - def update(self, *args, **kwargs): ... # type: ignore[no-untyped-def] class _ArtistKwdocLoader(dict[str, str]): diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 07bf01b8f995..d7fbbf181272 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -445,7 +445,7 @@ class MultiNorm(Norm): def __call__(self, values: tuple[float, ...], clip: ArrayLike | bool | None = ...) -> tuple[float, ...]: ... @overload def __call__(self, values: ArrayLike, clip: ArrayLike | bool | None = ...) -> tuple: ... - def inverse(self, values: ArrayLike) -> tuple: ... # type: ignore[override] + def inverse(self, values: ArrayLike) -> tuple: ... def autoscale(self, A: ArrayLike) -> None: ... def autoscale_None(self, A: ArrayLike) -> None: ... def scaled(self) -> bool: ... diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 09f37018cc5f..dd80da45e332 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -870,7 +870,7 @@ def xkcd( "xkcd mode is not compatible with text.usetex = True") stack = ExitStack() - stack.callback(rcParams._update_raw, rcParams.copy()) # type: ignore[arg-type] + stack.callback(rcParams._update_raw, rcParams.copy()) from matplotlib import patheffects rcParams.update({ @@ -1077,8 +1077,7 @@ def figure( else: num = int(num) # crude validation of num argument - # Type of "num" has narrowed to int, but mypy can't quite see it - manager = _pylab_helpers.Gcf.get_fig_manager(num) # type: ignore[arg-type] + manager = _pylab_helpers.Gcf.get_fig_manager(num) if manager is None: max_open_warning = rcParams['figure.max_open_warning'] if len(allnums) == max_open_warning >= 1: @@ -1467,7 +1466,7 @@ def sca(ax: Axes) -> None: # but if you are calling this, it won't be None # Additionally the slight difference between `Figure` and `FigureBase` mypy catches fig = ax.get_figure(root=False) - figure(fig) # type: ignore[arg-type] + figure(fig) fig.sca(ax) # type: ignore[union-attr] diff --git a/lib/matplotlib/tests/test__style_helpers.py b/lib/matplotlib/tests/test__style_helpers.py index ecaccec224ae..23fca281b643 100644 --- a/lib/matplotlib/tests/test__style_helpers.py +++ b/lib/matplotlib/tests/test__style_helpers.py @@ -1,7 +1,7 @@ import pytest import matplotlib.colors as mcolors -from matplotlib.lines import _get_dash_pattern +from matplotlib.lines import _get_dash_pattern # type: ignore[attr-defined] from matplotlib._style_helpers import style_generator diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 7476e7b15e4f..be4e297f074c 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -40,7 +40,7 @@ import matplotlib.text as mtext import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms -import mpl_toolkits.axisartist as AA # type: ignore[import] +import mpl_toolkits.axisartist as AA from numpy.testing import ( assert_allclose, assert_array_equal, assert_array_almost_equal) from matplotlib.testing.decorators import ( diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 306e6d794ccf..5d76300054d7 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -89,7 +89,7 @@ def _get_available_interactive_backends(): reason = "macosx backend fails on Azure" elif env["MPLBACKEND"].startswith('gtk'): try: - import gi # type: ignore[import] + import gi except ImportError: # Though we check that `gi` exists above, it is possible that its # C-level dependencies are not available, and then it still raises an diff --git a/lib/matplotlib/tests/test_basic.py b/lib/matplotlib/tests/test_basic.py index f6aa1e458555..f51f0ccf03ef 100644 --- a/lib/matplotlib/tests/test_basic.py +++ b/lib/matplotlib/tests/test_basic.py @@ -11,7 +11,7 @@ def test_simple(): def test_override_builtins(): - import pylab # type: ignore[import] + import pylab ok_to_override = { '__name__', '__doc__', diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 294c62785159..9c9b4e643014 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -457,7 +457,7 @@ def test_EllipseCollection_setter_getter(): @image_comparison(['polycollection_close.png'], remove_text=True, style='mpl20') def test_polycollection_close(): - from mpl_toolkits.mplot3d import Axes3D # type: ignore[import] + from mpl_toolkits.mplot3d import Axes3D plt.rcParams['axes3d.automargin'] = True vertsQuad = [ diff --git a/lib/matplotlib/tests/test_ft2font.py b/lib/matplotlib/tests/test_ft2font.py index b96505197890..7fd7cf356118 100644 --- a/lib/matplotlib/tests/test_ft2font.py +++ b/lib/matplotlib/tests/test_ft2font.py @@ -188,9 +188,9 @@ def test_ft2font_invalid_args(tmp_path): with pytest.raises(TypeError, match='incompatible constructor arguments'): # failing to be a list will fail before the 0 - ft2font.FT2Font(file, _fallback_list=(0,)) # type: ignore[arg-type] + ft2font.FT2Font(file, _fallback_list=(0,)) with pytest.raises(TypeError, match='incompatible constructor arguments'): - ft2font.FT2Font(file, _fallback_list=[0]) # type: ignore[list-item] + ft2font.FT2Font(file, _fallback_list=[0]) # kerning_factor argument. with pytest.raises(TypeError, match='incompatible constructor arguments'): diff --git a/lib/matplotlib/tests/test_legend.py b/lib/matplotlib/tests/test_legend.py index fe9405bcbdae..67b433fe7447 100644 --- a/lib/matplotlib/tests/test_legend.py +++ b/lib/matplotlib/tests/test_legend.py @@ -413,7 +413,7 @@ def test_error_mixed_args_and_kwargs(self): ax.legend((lnc, lns), labels=('a', 'b')) def test_parasite(self): - from mpl_toolkits.axes_grid1 import host_subplot # type: ignore[import] + from mpl_toolkits.axes_grid1 import host_subplot host = host_subplot(111) par = host.twinx() diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py index 1590990cdeb0..3494dceffe5d 100644 --- a/lib/matplotlib/tests/test_pickle.py +++ b/lib/matplotlib/tests/test_pickle.py @@ -12,12 +12,12 @@ from matplotlib import cm from matplotlib.testing import subprocess_run_helper, is_ci_environment from matplotlib.testing.decorators import check_figures_equal -from matplotlib.dates import rrulewrapper +from matplotlib.dates import rrulewrapper # type: ignore[attr-defined] from matplotlib.lines import VertexSelector import matplotlib.pyplot as plt import matplotlib.transforms as mtransforms import matplotlib.figure as mfigure -from mpl_toolkits.axes_grid1 import axes_divider, parasite_axes # type: ignore[import] +from mpl_toolkits.axes_grid1 import axes_divider, parasite_axes def test_simple(): diff --git a/lib/matplotlib/tests/test_units.py b/lib/matplotlib/tests/test_units.py index d2350667e94f..18106cd1713c 100644 --- a/lib/matplotlib/tests/test_units.py +++ b/lib/matplotlib/tests/test_units.py @@ -7,7 +7,7 @@ import matplotlib.patches as mpatches import matplotlib.units as munits from matplotlib.category import StrCategoryConverter, UnitData -from matplotlib.dates import DateConverter +from matplotlib.dates import DateConverter # type: ignore[attr-defined] import numpy as np import pytest diff --git a/lib/matplotlib/textpath.pyi b/lib/matplotlib/textpath.pyi index 07f81598aa75..49f2a4e1a21e 100644 --- a/lib/matplotlib/textpath.pyi +++ b/lib/matplotlib/textpath.pyi @@ -78,6 +78,6 @@ class TextPath(Path): # These are read only... there actually are protections in the base class, so probably can be deleted... @property # type: ignore[misc] - def vertices(self) -> np.ndarray: ... # type: ignore[override] + def vertices(self) -> np.ndarray: ... @property # type: ignore[misc] - def codes(self) -> np.ndarray: ... # type: ignore[override] + def codes(self) -> np.ndarray: ... diff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi index a80ed8bf8274..45422b6de719 100644 --- a/lib/matplotlib/widgets.pyi +++ b/lib/matplotlib/widgets.pyi @@ -164,7 +164,7 @@ class CheckButtons(AxesWidget): def set_label_props(self, props: dict[str, Sequence[Any]]) -> None: ... def set_frame_props(self, props: dict[str, Any]) -> None: ... def set_check_props(self, props: dict[str, Any]) -> None: ... - def set_active(self, index: int, state: bool | None = ...) -> None: ... # type: ignore[override] + def set_active(self, index: int, state: bool | None = ...) -> None: ... def clear(self) -> None: ... def get_status(self) -> list[bool]: ... def get_checked_labels(self) -> list[str]: ... diff --git a/pyproject.toml b/pyproject.toml index 357ad5786b93..f3c38512a2c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -151,7 +151,7 @@ test-extra = [ # Extra pip requirements for the GitHub Actions mypy build typing = [ - "mypy>=1.9", + "mypy>=1.14", "typing-extensions>=4.6", # Extra stubs distributed separately from the main pypi package "pandas-stubs", From 4a1af9c1b70be1755c6cfdb77b1550059a020cc3 Mon Sep 17 00:00:00 2001 From: Vikash Kumar <163628932+Vikash-Kumar-23@users.noreply.github.com> Date: Thu, 23 Apr 2026 07:12:10 +0530 Subject: [PATCH 3/9] Fix errorbar autoscaling inconsistency on log axes (#31478) * Fix errorbar autoscaling inconsistency on log axes (fixes #31462) * Move log scale fix from _axes.py to collections.py root cause * Use single figure for log autoscale test * Fix formatting issues --- lib/matplotlib/collections.py | 5 ++++- lib/matplotlib/tests/test_axes.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index b2f8255367e8..c9e04a70b356 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -291,8 +291,11 @@ def get_datalim(self, transData): if isinstance(offsets, np.ma.MaskedArray): offsets = offsets.filled(np.nan) # get_path_collection_extents handles nan but not masked arrays + data_trf = transform.get_affine() - transData + if not data_trf.is_affine: + paths = [data_trf.transform_path_non_affine(p) for p in paths] return mpath.get_path_collection_extents( - transform.get_affine() - transData, paths, + data_trf.get_affine(), paths, self.get_transforms(), offset_trf.transform_non_affine(offsets), offset_trf.get_affine().frozen()) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index be4e297f074c..6751666360b1 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4690,6 +4690,25 @@ def test_errorbar_limits(): ax.set_title('Errorbar upper and lower limits') +def test_errorbar_log_autoscale_order_independent(): + x = 10 ** np.array([18, 18.1, 18.2, 18.3]) + y = np.array([100, 80, 60, 30]) + yerr = np.ones_like(y) * 10 + + fig, (ax1, ax2) = plt.subplots(2) + + ax1.set_xscale("log") + ax1.set_yscale("log") + ax1.errorbar(x, y, yerr=yerr) + + ax2.errorbar(x, y, yerr=yerr) + ax2.set_xscale("log") + ax2.set_yscale("log") + + assert_allclose(ax1.get_xlim(), ax2.get_xlim()) + assert_allclose(ax1.get_ylim(), ax2.get_ylim()) + + def test_errorbar_nonefmt(): # Check that passing 'none' as a format still plots errorbars x = np.arange(5) From b84a0c6049ef63c046c9940736630724313c79f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 06:14:27 +0200 Subject: [PATCH 4/9] Bump scientific-python/upload-nightly-action in the actions group (#31552) Bumps the actions group with 1 update: [scientific-python/upload-nightly-action](https://github.com/scientific-python/upload-nightly-action). Updates `scientific-python/upload-nightly-action` from 0.6.3 to 0.6.4 - [Release notes](https://github.com/scientific-python/upload-nightly-action/releases) - [Commits](https://github.com/scientific-python/upload-nightly-action/compare/5748273c71e2d8d3a61f3a11a16421c8954f9ecf...e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99) --- updated-dependencies: - dependency-name: scientific-python/upload-nightly-action dependency-version: 0.6.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/nightlies.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightlies.yml b/.github/workflows/nightlies.yml index 661234c3e16f..47d1de50781c 100644 --- a/.github/workflows/nightlies.yml +++ b/.github/workflows/nightlies.yml @@ -60,7 +60,7 @@ jobs: ls -l dist/ - name: Upload wheels to Anaconda Cloud as nightlies - uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3 + uses: scientific-python/upload-nightly-action@e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99 # 0.6.4 with: artifacts_path: dist anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} From 07b40e644e38f63e233428a74a67e90b9aaf7687 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 06:15:28 +0200 Subject: [PATCH 5/9] Bump https://github.com/pre-commit/mirrors-mypy from v1.20.1 to 1.20.2 (#31551) Bumps [https://github.com/pre-commit/mirrors-mypy](https://github.com/pre-commit/mirrors-mypy) from v1.20.1 to 1.20.2. This release includes the previously tagged commit. - [Commits](https://github.com/pre-commit/mirrors-mypy/compare/0f369d245750787ce34997d464ed9605391a5283...fc0f09a29bb495f4a91f00266155d6282d52485d) --- updated-dependencies: - dependency-name: https://github.com/pre-commit/mirrors-mypy dependency-version: 1.20.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bce348eaed56..bfce3c54a030 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: trailing-whitespace exclude_types: [diff, svg] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 0f369d245750787ce34997d464ed9605391a5283 # frozen: v1.20.1 + rev: fc0f09a29bb495f4a91f00266155d6282d52485d # frozen: v1.20.2 hooks: - id: mypy additional_dependencies: From 60383ac0eb76074065844860c3fc005dfba4c398 Mon Sep 17 00:00:00 2001 From: Pedro Marques Date: Thu, 23 Apr 2026 05:59:18 +0100 Subject: [PATCH 6/9] DOC: fix broken link to wxPython Widget Inspection Tool (#31535) * DOC: fix broken link to wxPython Widget Inspection Tool The wiki.wxpython.org URL returns 404. Point readers to the current official documentation at docs.wxpython.org instead. Refs #30306. * DOC: restructure wx5 example to use wx.App with InspectableApp tip Per review feedback, the regular example now uses wx.App() directly so readers see the standard wxPython entry point without any debug-only machinery. A .. tip:: directive in the module docstring describes wx.lib.mixins.inspection.InspectableApp as an optional debugging aid and links to the wx.lib.inspection documentation (which replaces the now-dead wiki URL). The wx.lib.mixins.inspection import is removed since it is no longer referenced from the example code. * DOC: apply review suggestion on wx5 example Rewrite the .. tip:: in the wx5 example with the wording proposed in review, and add a wx intersphinx mapping entry so the :class:`wx.App` and :class:`wx.lib.mixins.inspection.InspectableApp` cross-references resolve under nitpicky docs builds. --- doc/conf.py | 1 + .../user_interfaces/embedding_in_wx5_sgskip.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index ec522851c673..4be3fcf3afee 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -275,6 +275,7 @@ def autodoc_process_bases(app, name, obj, options, bases): 'python': ('https://docs.python.org/3/', None), 'scipy': ('https://docs.scipy.org/doc/scipy/', None), 'tornado': ('https://www.tornadoweb.org/en/stable/', None), + 'wx': ('https://docs.wxpython.org/', None), 'xarray': ('https://docs.xarray.dev/en/stable/', None), 'meson-python': ('https://mesonbuild.com/meson-python/', None), 'pip': ('https://pip.pypa.io/en/stable/', None), diff --git a/galleries/examples/user_interfaces/embedding_in_wx5_sgskip.py b/galleries/examples/user_interfaces/embedding_in_wx5_sgskip.py index f150e2106ead..f5442ec1f700 100644 --- a/galleries/examples/user_interfaces/embedding_in_wx5_sgskip.py +++ b/galleries/examples/user_interfaces/embedding_in_wx5_sgskip.py @@ -3,11 +3,17 @@ Embed in wx #5 ============== +.. tip:: + + As a development and debugging aid, you can replace :class:`wx.App` + by :class:`wx.lib.mixins.inspection.InspectableApp`. + This adds the capability to start the `Widget Inspection Tool + `_ + via :kbd:`Ctrl-Alt-I`. """ import wx import wx.lib.agw.aui as aui -import wx.lib.mixins.inspection as wit from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas from matplotlib.backends.backend_wxagg import \ @@ -44,11 +50,7 @@ def add(self, name="plot"): def demo(): - # Alternatively you could use: - # app = wx.App() - # InspectableApp is a great debug tool, see: - # http://wiki.wxpython.org/Widget%20Inspection%20Tool - app = wit.InspectableApp() + app = wx.App() frame = wx.Frame(None, -1, 'Plotter') plotter = PlotNotebook(frame) axes1 = plotter.add('figure 1').add_subplot() From 6fd7445cb2916cf4e161dcd97da24adbb256b228 Mon Sep 17 00:00:00 2001 From: beelauuu Date: Thu, 23 Apr 2026 14:11:42 -0400 Subject: [PATCH 7/9] Fixed pybind11 conditional bug --- src/_enums.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_enums.h b/src/_enums.h index 18f3d9aac9fa..e607b93f50f2 100644 --- a/src/_enums.h +++ b/src/_enums.h @@ -80,7 +80,7 @@ namespace p11x { auto ival = PyLong_AsLong(tmp); \ value = decltype(value)(ival); \ Py_DECREF(tmp); \ - return !(ival == -1 && !PyErr_Occurred()); \ + return !(ival == -1 && PyErr_Occurred()); \ } else { \ return false; \ } \ From 44a6337669c29361cbb7dd6b743bea1fc3cb2539 Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Sat, 11 Apr 2026 23:49:37 +0530 Subject: [PATCH 8/9] Implement underline logic as in TeX --- .../next_whats_new/underline-23616.rst | 12 ++++++++++++ lib/matplotlib/_mathtext.py | 18 ++++++++++++++++++ .../test_mathtext/mathtext1_dejavusans_09.png | Bin 0 -> 2548 bytes lib/matplotlib/tests/test_mathtext.py | 1 + 4 files changed, 31 insertions(+) create mode 100644 doc/release/next_whats_new/underline-23616.rst create mode 100644 lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_09.png diff --git a/doc/release/next_whats_new/underline-23616.rst b/doc/release/next_whats_new/underline-23616.rst new file mode 100644 index 000000000000..184c588b842c --- /dev/null +++ b/doc/release/next_whats_new/underline-23616.rst @@ -0,0 +1,12 @@ +Underlining text while using Mathtext +------------------------------------- + +Mathtext now supports the ``\underline`` command. + +.. code-block:: python + + import matplotlib.pyplot as plt + + plt.text(0.4, 0.7, r'This is $\underline{underlined}$ text.') + plt.text(0.4, 0.3, r'So is $\underline{\mathrm{this}}$.') + plt.show() diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 21ec24d73286..b04386e7666a 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -2207,6 +2207,8 @@ def csnames(group: str, names: Iterable[str]) -> Regex: p.overline = cmd(r"\overline", p.required_group("body")) + p.underline = cmd(r"\underline", p.required_group("body")) + p.overset = cmd( r"\overset", p.optional_group("annotation") + p.optional_group("body")) @@ -2259,6 +2261,7 @@ def csnames(group: str, names: Iterable[str]) -> Regex: | p.underset | p.sqrt | p.overline + | p.underline | p.text | p.boldsymbol | p.substack @@ -2945,6 +2948,21 @@ def overline(self, toks: ParseResults) -> T.Any: hlist = Hlist([rightside]) return [hlist] + def underline(self, toks: ParseResults) -> T.Any: + body = toks["body"] + state = self.get_state() + thickness = state.get_current_underline_thickness() + # Place the underline below `body` (node735). + vlist = Vlist([ + Hlist([body]), + Kern(3 * thickness), + Hrule(state, thickness), + ]) + delta = vlist.height + vlist.depth + thickness + vlist.height = body.height + vlist.depth = delta - vlist.height + return [Hlist([vlist])] + def _auto_sized_delimiter(self, front: str, middle: list[Box | Char | str], back: str) -> T.Any: diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_09.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext1_dejavusans_09.png new file mode 100644 index 0000000000000000000000000000000000000000..9d96e8eb87b9c411c40faad7d8cc67ea20306a10 GIT binary patch literal 2548 zcmVV>bE;TMNE@WkPF*P|iF)L?dHfCjIH#atBE@Uz= zGBz+YF)}P5XmoUNb2=|CZDDk9Y;SaIX<{yKa%V5m9hA2K000SaNLh0L01sgR01sgS zs6VG^000R?NklDQmZtjF5fU+D0g>b4s5K+K{D~q@Y!7|8(MhAbiREz?o8G2t6AFQ&4M@DSaD;j2G+kD)Ws=-y~?z;pqTLz_axpy#pE!zNFz>~0Ax*z3yyP*DGQgrKZ zpkQz@x@x~vSRD$7R1G(ctAh^#a8JN_AUXg(kQ^XeK7^@WX~07sCxC~$0mMx}chYNN zO+wovs)n0M%rpS18%F_*7zj@W0Bn^M=s6sq_x%8OjRIiwS~Oe7Pw>8D52}Wn$m!SS}ZURZ;-FFS*zyd20tvQm|+#4DDoaan58SlzWb6ir4EH>}KF6XvB z@4Oo{RI*`(SLDX$I&~e5CPnTUuws4BfD16q>%ORu7J5E~CRP%GvW}yPkpdvE>1=dc z4vAY*Ib3)usGJof+(c0-dROMtoFr`RWEU|h?K=w6j0G{9+@gE6>m4dol> zO*RdYZ^62A;S|$2c^}PuNdV|D9$SmvHsCWkh~`z#Qeet0=+=5>0yKF5dX3T=h(j~m zBhQ}C5h}eW0*nYj*Zv{+*86F=;`u!6l$D^HDupyaxfta&(p_taZ#`&9ta+5Lh0Nft zi`JWG%P};gx&aV4jnlFkV7nxv85a!DYb?6^LjWp0Uf>&v1$bM&Lo;{)!0Vo+B*|ug zz$kR4HrNKWyae1ih_X)IEuae(3k}htFUH`eH550W3=n)b4D3|~O!ull6F31NO*XJi z5&)w8ucG-~1W@j^7+t_rfMnUsW=R5=))I}i=`4VUyTdxk0=}2`(5;Z{_hc2oY~QN6 z5|6_GZ*)>G=xzovtX0gXE0L6Te&i_X9=3UatK%>`RK;^7r=UtW>#{& zBW)CiiXIMIBn9|EmZ8sQF7U2ISKZV{_UPsLc%Q;4C=i$F6&Zt@*1#MYd~fgqNrX+l zJ%)FB+wn>9is!1QM|twgp{QP(XO!vAu@^%e;Dxxowv0 zKr_$J0>n!KHH}^X7}*zxFbPM~NuCC$p8os{%jFubuZwy%+@SQ`$E|0~CF;mTrj zKAPm+6n%cE0;n-(*phdp<8XWzKw<(86<@~ZmH90O$csN*x!kh|%~yK?a?Isam~#O3 z9>rmZcRF^K&Mhy2Q-~iwW2F>veQmp93~pP)FXnGv*=Q_%9;6-;1hhdRdbXV}trCzfJY@}FHNZPe|@xPMh(mm7Dun%Z{AX0G+$ zP#?eWeex*~8{+HJ_W-c4@fX9Jd4y!+v{%+*3~pb8J1LNt!0Djxq7utrnoBOsYPPAe zG|KN3jPeUjwVe!@h|@|*!5CBxuITc6oUQ~;1#InG!Y0`PR5gx))hz};aG)T6YdQb=39whTkRbcez2s8`?2|({tZr0- zL8uyB@_MT(&<*Qsh3XK=AWc@G8_~}>N3MHd3Pz!7aLMK7J3#lL%sqQzZ0n|02n&e) z{>*_HJE2t+!1%5>WO=4y6siW-#0&$S?VaB3!#@4PNT6)Vz*~AwN`SNe8v*)E!{M1R z7>26BHRYWXamhLVbPPk);G*3FthnMy_uZ{Q)o|nHtiT0F2UTGlss>kC!cG7nbpYV< zkbDe8)!?$?2 Date: Thu, 23 Apr 2026 22:49:46 +0200 Subject: [PATCH 9/9] BUG: avoid a deprecation warning from numpy 2.5 (calling `datetime64('NaT')` without a unit is deprecated) (#31554) * BUG: avoid a deprecation warning from numpy 2.5 (calling `datetime64('NaT')` without a unit is deprecated) Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/dates.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index f5f0918dcc13..77c803cde6e5 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -325,9 +325,7 @@ def _dt64_to_ordinalf(d): dt += extra.astype(np.float64) / 1.0e9 dt = dt / SEC_PER_DAY - NaT_int = np.datetime64('NaT').astype(np.int64) - d_int = d.astype(np.int64) - dt[d_int == NaT_int] = np.nan + dt[np.isnat(d)] = np.nan return dt