Skip to content

Commit

Permalink
Backport PR matplotlib#27088: Update find_nearest_contour and rever…
Browse files Browse the repository at this point in the history
…t contour deprecations
  • Loading branch information
ksunden authored and meeseeksmachine committed Oct 26, 2023
1 parent 2521750 commit ad92457
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 73 deletions.
5 changes: 5 additions & 0 deletions doc/api/next_api_changes/deprecations/27088-JK.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Deprecations removed in ``contour``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``contour.allsegs``, ``contour.allkinds``, and ``contour.find_nearest_contour`` are no
longer marked for deprecation.
79 changes: 27 additions & 52 deletions lib/matplotlib/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -944,12 +945,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: [
Expand Down Expand Up @@ -1403,7 +1404,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)``.
Expand All @@ -1424,64 +1424,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
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/contour.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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): ...
32 changes: 12 additions & 20 deletions lib/matplotlib/tests/test_contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,23 +569,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)

Expand All @@ -595,16 +591,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)


Expand Down Expand Up @@ -864,12 +857,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():
Expand Down

0 comments on commit ad92457

Please sign in to comment.