Skip to content

Commit 691549e

Browse files
rcomeranntzer
andcommitted
Update find_nearest_contour to not use collections attribute
Co-authored-by: Antony Lee <anntzer.lee@gmail.com>
1 parent e394940 commit 691549e

File tree

2 files changed

+23
-48
lines changed

2 files changed

+23
-48
lines changed

lib/matplotlib/contour.py

Lines changed: 20 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Classes to support contour plotting and labelling for the Axes class.
33
"""
44

5+
from contextlib import ExitStack
56
import functools
67
import math
78
from numbers import Integral
@@ -1409,64 +1410,38 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
14091410
14101411
Returns
14111412
-------
1412-
contour : `.Collection`
1413-
The contour that is closest to ``(x, y)``.
1413+
path : int
1414+
The index of the path that is closest to ``(x, y)``.
14141415
segment : int
1415-
The index of the `.Path` in *contour* that is closest to
1416+
The index within that closest path of the segment that is closest to
14161417
``(x, y)``.
14171418
index : int
1418-
The index of the path segment in *segment* that is closest to
1419+
The index of the vertices within that segment that are closest to
14191420
``(x, y)``.
14201421
xmin, ymin : float
14211422
The point in the contour plot that is closest to ``(x, y)``.
14221423
d2 : float
14231424
The squared distance from ``(xmin, ymin)`` to ``(x, y)``.
14241425
"""
1426+
segment = index = d2 = None
14251427

1426-
# This function uses a method that is probably quite
1427-
# inefficient based on converting each contour segment to
1428-
# pixel coordinates and then comparing the given point to
1429-
# those coordinates for each contour. This will probably be
1430-
# quite slow for complex contours, but for normal use it works
1431-
# sufficiently well that the time is not noticeable.
1432-
# Nonetheless, improvements could probably be made.
1428+
with ExitStack() as stack:
1429+
if not pixel:
1430+
# _find_nearest_contour works in pixel space. We want axes space, so
1431+
# effectively disable the transformation here by setting to identity.
1432+
stack.enter_context(self._cm_set(
1433+
transform=mtransforms.IdentityTransform()))
14331434

1434-
if self.filled:
1435-
raise ValueError("Method does not support filled contours.")
1435+
i_level, i_vtx, (xmin, ymin) = self._find_nearest_contour((x, y), indices)
14361436

1437-
if indices is None:
1438-
indices = range(len(self.collections))
1437+
if i_level is not None:
1438+
cc_cumlens = np.cumsum(
1439+
[*map(len, self._paths[i_level]._iter_connected_components())])
1440+
segment = cc_cumlens.searchsorted(i_vtx, "right")
1441+
index = i_vtx if segment == 0 else i_vtx - cc_cumlens[segment - 1]
1442+
d2 = (xmin-x)**2 + (ymin-y)**2
14391443

1440-
d2min = np.inf
1441-
conmin = None
1442-
segmin = None
1443-
imin = None
1444-
xmin = None
1445-
ymin = None
1446-
1447-
point = np.array([x, y])
1448-
1449-
for icon in indices:
1450-
con = self.collections[icon]
1451-
trans = con.get_transform()
1452-
paths = con.get_paths()
1453-
1454-
for segNum, linepath in enumerate(paths):
1455-
lc = linepath.vertices
1456-
# transfer all data points to screen coordinates if desired
1457-
if pixel:
1458-
lc = trans.transform(lc)
1459-
1460-
d2, xc, leg = _find_closest_point_on_path(lc, point)
1461-
if d2 < d2min:
1462-
d2min = d2
1463-
conmin = icon
1464-
segmin = segNum
1465-
imin = leg[1]
1466-
xmin = xc[0]
1467-
ymin = xc[1]
1468-
1469-
return (conmin, segmin, imin, xmin, ymin, d2min)
1444+
return (i_level, segment, index, xmin, ymin, d2)
14701445

14711446
def draw(self, renderer):
14721447
paths = self._paths

lib/matplotlib/tests/test_contour.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -557,15 +557,15 @@ def test_find_nearest_contour_no_filled():
557557
cs = plt.contourf(img, 10)
558558

559559
with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \
560-
pytest.raises(ValueError, match="Method does not support filled contours."):
560+
pytest.raises(ValueError, match="Method does not support filled contours"):
561561
cs.find_nearest_contour(1, 1, pixel=False)
562562

563563
with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \
564-
pytest.raises(ValueError, match="Method does not support filled contours."):
564+
pytest.raises(ValueError, match="Method does not support filled contours"):
565565
cs.find_nearest_contour(1, 10, indices=(5, 7), pixel=False)
566566

567567
with pytest.warns(mpl._api.MatplotlibDeprecationWarning), \
568-
pytest.raises(ValueError, match="Method does not support filled contours."):
568+
pytest.raises(ValueError, match="Method does not support filled contours"):
569569
cs.find_nearest_contour(2, 5, indices=(2, 7), pixel=True)
570570

571571

0 commit comments

Comments
 (0)