From 25ec2c0e43066c1bad3b0ca6111644cb8383181d Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sat, 14 Oct 2023 11:57:55 +0100 Subject: [PATCH 1/2] Update find_nearest_contour to not use collections attribute Co-authored-by: Antony Lee --- lib/matplotlib/contour.py | 70 +++++++++------------------- lib/matplotlib/contour.pyi | 2 +- lib/matplotlib/tests/test_contour.py | 6 +-- 3 files changed, 27 insertions(+), 51 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index dc5ed5d626bc..f65538f865a3 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -2,6 +2,7 @@ Classes to support contour plotting and labelling for the Axes class. """ +from contextlib import ExitStack import functools import math from numbers import Integral @@ -1409,64 +1410,39 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True): Returns ------- - contour : `.Collection` - The contour that is closest to ``(x, y)``. - segment : int - The index of the `.Path` in *contour* that is closest to - ``(x, y)``. + path : int + The index of the path that is closest to ``(x, y)``. Each path corresponds + to one contour level. + subpath : int + The index within that closest path of the subpath that is closest to + ``(x, y)``. Each subpath corresponds to one unbroken contour line. index : int - The index of the path segment in *segment* that is closest to + The index of the vertices within that subpath that are closest to ``(x, y)``. xmin, ymin : float The point in the contour plot that is closest to ``(x, y)``. d2 : float The squared distance from ``(xmin, ymin)`` to ``(x, y)``. """ + segment = index = d2 = None - # This function uses a method that is probably quite - # inefficient based on converting each contour segment to - # pixel coordinates and then comparing the given point to - # those coordinates for each contour. This will probably be - # quite slow for complex contours, but for normal use it works - # sufficiently well that the time is not noticeable. - # Nonetheless, improvements could probably be made. + with ExitStack() as stack: + if not pixel: + # _find_nearest_contour works in pixel space. We want axes space, so + # effectively disable the transformation here by setting to identity. + stack.enter_context(self._cm_set( + transform=mtransforms.IdentityTransform())) - if self.filled: - raise ValueError("Method does not support filled contours.") + i_level, i_vtx, (xmin, ymin) = self._find_nearest_contour((x, y), indices) - if indices is None: - indices = range(len(self.collections)) + if i_level is not None: + cc_cumlens = np.cumsum( + [*map(len, self._paths[i_level]._iter_connected_components())]) + segment = cc_cumlens.searchsorted(i_vtx, "right") + index = i_vtx if segment == 0 else i_vtx - cc_cumlens[segment - 1] + d2 = (xmin-x)**2 + (ymin-y)**2 - d2min = np.inf - conmin = None - segmin = None - imin = None - xmin = None - ymin = None - - point = np.array([x, y]) - - for icon in indices: - con = self.collections[icon] - trans = con.get_transform() - paths = con.get_paths() - - for segNum, linepath in enumerate(paths): - lc = linepath.vertices - # transfer all data points to screen coordinates if desired - if pixel: - lc = trans.transform(lc) - - d2, xc, leg = _find_closest_point_on_path(lc, point) - if d2 < d2min: - d2min = d2 - conmin = icon - segmin = segNum - imin = leg[1] - xmin = xc[0] - ymin = xc[1] - - return (conmin, segmin, imin, xmin, ymin, d2min) + return (i_level, segment, index, xmin, ymin, d2) def draw(self, renderer): paths = self._paths diff --git a/lib/matplotlib/contour.pyi b/lib/matplotlib/contour.pyi index 4a1e1e1d9d2e..8a987b762445 100644 --- a/lib/matplotlib/contour.pyi +++ b/lib/matplotlib/contour.pyi @@ -159,6 +159,6 @@ class ContourSet(ContourLabeler, Collection): ) -> tuple[list[Artist], list[str]]: ... def find_nearest_contour( self, x: float, y: float, indices: Iterable[int] | None = ..., pixel: bool = ... - ) -> tuple[Collection, int, int, float, float, float]: ... + ) -> tuple[int, int, int, float, float, float]: ... class QuadContourSet(ContourSet): ... diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index c911d499ea96..81328b0b3218 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -557,15 +557,15 @@ def test_find_nearest_contour_no_filled(): cs = plt.contourf(img, 10) with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours."): + pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(1, 1, pixel=False) with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours."): + pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(1, 10, indices=(5, 7), pixel=False) with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours."): + pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(2, 5, indices=(2, 7), pixel=True) From e9a102f090d91dae6745b7f6313ecc6040def959 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 12 Oct 2023 15:05:15 -0700 Subject: [PATCH 2/2] MNT: revert contour deprecations Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- .../deprecations/27088-JK.rst | 5 +++ lib/matplotlib/contour.py | 9 +++--- lib/matplotlib/tests/test_contour.py | 32 +++++++------------ 3 files changed, 21 insertions(+), 25 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/27088-JK.rst diff --git a/doc/api/next_api_changes/deprecations/27088-JK.rst b/doc/api/next_api_changes/deprecations/27088-JK.rst new file mode 100644 index 000000000000..ea7fef5abf64 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/27088-JK.rst @@ -0,0 +1,5 @@ +Deprecations removed in ``contour`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``contour.allsegs``, ``contour.allkinds``, and ``contour.find_nearest_contour`` are no +longer marked for deprecation. diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index f65538f865a3..58a6d7937731 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -930,12 +930,12 @@ def __init__(self, ax, *args, ", ".join(map(repr, kwargs)) ) - allsegs = _api.deprecated("3.8", pending=True)(property(lambda self: [ + allsegs = property(lambda self: [ [subp.vertices for subp in p._iter_connected_components()] - for p in self.get_paths()])) - allkinds = _api.deprecated("3.8", pending=True)(property(lambda self: [ + for p in self.get_paths()]) + allkinds = property(lambda self: [ [subp.codes for subp in p._iter_connected_components()] - for p in self.get_paths()])) + for p in self.get_paths()]) tcolors = _api.deprecated("3.8")(property(lambda self: [ (tuple(rgba),) for rgba in self.to_rgba(self.cvalues, self.alpha)])) tlinewidths = _api.deprecated("3.8")(property(lambda self: [ @@ -1389,7 +1389,6 @@ def _find_nearest_contour(self, xy, indices=None): return idx_level_min, idx_vtx_min, proj_min - @_api.deprecated("3.8") def find_nearest_contour(self, x, y, indices=None, pixel=True): """ Find the point in the contour plot that is closest to ``(x, y)``. diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 81328b0b3218..b655649898bc 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -530,23 +530,19 @@ def test_find_nearest_contour(): img = np.exp(-np.pi * (np.sum((xy - 5)**2, 0)/5.**2)) cs = plt.contour(img, 10) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning): - nearest_contour = cs.find_nearest_contour(1, 1, pixel=False) + nearest_contour = cs.find_nearest_contour(1, 1, pixel=False) expected_nearest = (1, 0, 33, 1.965966, 1.965966, 1.866183) assert_array_almost_equal(nearest_contour, expected_nearest) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning): - nearest_contour = cs.find_nearest_contour(8, 1, pixel=False) + nearest_contour = cs.find_nearest_contour(8, 1, pixel=False) expected_nearest = (1, 0, 5, 7.550173, 1.587542, 0.547550) assert_array_almost_equal(nearest_contour, expected_nearest) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning): - nearest_contour = cs.find_nearest_contour(2, 5, pixel=False) + nearest_contour = cs.find_nearest_contour(2, 5, pixel=False) expected_nearest = (3, 0, 21, 1.884384, 5.023335, 0.013911) assert_array_almost_equal(nearest_contour, expected_nearest) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning): - nearest_contour = cs.find_nearest_contour(2, 5, indices=(5, 7), pixel=False) + nearest_contour = cs.find_nearest_contour(2, 5, indices=(5, 7), pixel=False) expected_nearest = (5, 0, 16, 2.628202, 5.0, 0.394638) assert_array_almost_equal(nearest_contour, expected_nearest) @@ -556,16 +552,13 @@ def test_find_nearest_contour_no_filled(): img = np.exp(-np.pi * (np.sum((xy - 5)**2, 0)/5.**2)) cs = plt.contourf(img, 10) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours"): + with pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(1, 1, pixel=False) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours"): + with pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(1, 10, indices=(5, 7), pixel=False) - with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \ - pytest.raises(ValueError, match="Method does not support filled contours"): + with pytest.raises(ValueError, match="Method does not support filled contours"): cs.find_nearest_contour(2, 5, indices=(2, 7), pixel=True) @@ -825,12 +818,11 @@ def test_allsegs_allkinds(): cs = plt.contour(x, y, z, levels=[0, 0.5]) - # Expect two levels, first with 5 segments and the second with 4. - with pytest.warns(PendingDeprecationWarning, match="all"): - for result in [cs.allsegs, cs.allkinds]: - assert len(result) == 2 - assert len(result[0]) == 5 - assert len(result[1]) == 4 + # Expect two levels, the first with 5 segments and the second with 4. + for result in [cs.allsegs, cs.allkinds]: + assert len(result) == 2 + assert len(result[0]) == 5 + assert len(result[1]) == 4 def test_deprecated_apis():