|
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