|
2 | 2 | Classes to support contour plotting and labelling for the Axes class. |
3 | 3 | """ |
4 | 4 |
|
| 5 | +from contextlib import ExitStack |
5 | 6 | import functools |
6 | 7 | import math |
7 | 8 | from numbers import Integral |
@@ -1409,64 +1410,39 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True): |
1409 | 1410 |
|
1410 | 1411 | Returns |
1411 | 1412 | ------- |
1412 | | - contour : `.Collection` |
1413 | | - The contour that is closest to ``(x, y)``. |
1414 | | - segment : int |
1415 | | - The index of the `.Path` in *contour* that is closest to |
1416 | | - ``(x, y)``. |
| 1413 | + path : int |
| 1414 | + The index of the path that is closest to ``(x, y)``. Each path corresponds |
| 1415 | + to one contour level. |
| 1416 | + subpath : int |
| 1417 | + The index within that closest path of the subpath that is closest to |
| 1418 | + ``(x, y)``. Each subpath corresponds to one unbroken contour line. |
1417 | 1419 | index : int |
1418 | | - The index of the path segment in *segment* that is closest to |
| 1420 | + The index of the vertices within that subpath that are closest to |
1419 | 1421 | ``(x, y)``. |
1420 | 1422 | xmin, ymin : float |
1421 | 1423 | The point in the contour plot that is closest to ``(x, y)``. |
1422 | 1424 | d2 : float |
1423 | 1425 | The squared distance from ``(xmin, ymin)`` to ``(x, y)``. |
1424 | 1426 | """ |
| 1427 | + segment = index = d2 = None |
1425 | 1428 |
|
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. |
| 1429 | + with ExitStack() as stack: |
| 1430 | + if not pixel: |
| 1431 | + # _find_nearest_contour works in pixel space. We want axes space, so |
| 1432 | + # effectively disable the transformation here by setting to identity. |
| 1433 | + stack.enter_context(self._cm_set( |
| 1434 | + transform=mtransforms.IdentityTransform())) |
1433 | 1435 |
|
1434 | | - if self.filled: |
1435 | | - raise ValueError("Method does not support filled contours.") |
| 1436 | + i_level, i_vtx, (xmin, ymin) = self._find_nearest_contour((x, y), indices) |
1436 | 1437 |
|
1437 | | - if indices is None: |
1438 | | - indices = range(len(self.collections)) |
| 1438 | + if i_level is not None: |
| 1439 | + cc_cumlens = np.cumsum( |
| 1440 | + [*map(len, self._paths[i_level]._iter_connected_components())]) |
| 1441 | + segment = cc_cumlens.searchsorted(i_vtx, "right") |
| 1442 | + index = i_vtx if segment == 0 else i_vtx - cc_cumlens[segment - 1] |
| 1443 | + d2 = (xmin-x)**2 + (ymin-y)**2 |
1439 | 1444 |
|
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) |
| 1445 | + return (i_level, segment, index, xmin, ymin, d2) |
1470 | 1446 |
|
1471 | 1447 | def draw(self, renderer): |
1472 | 1448 | paths = self._paths |
|
0 commit comments