Skip to content

Commit 7eea44c

Browse files
committed
Simplify projection-of-point-on-polyline in contour.py.
1 parent 98961f6 commit 7eea44c

File tree

1 file changed

+33
-61
lines changed

1 file changed

+33
-61
lines changed

lib/matplotlib/contour.py

Lines changed: 33 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -599,31 +599,6 @@ def labels(self, inline, inline_spacing):
599599
paths[:] = additions
600600

601601

602-
def _find_closest_point_on_leg(p1, p2, p0):
603-
"""Find the closest point to p0 on line segment connecting p1 and p2."""
604-
605-
# handle degenerate case
606-
if np.all(p2 == p1):
607-
d = np.sum((p0 - p1)**2)
608-
return d, p1
609-
610-
d21 = p2 - p1
611-
d01 = p0 - p1
612-
613-
# project on to line segment to find closest point
614-
proj = np.dot(d01, d21) / np.dot(d21, d21)
615-
if proj < 0:
616-
proj = 0
617-
if proj > 1:
618-
proj = 1
619-
pc = p1 + proj * d21
620-
621-
# find squared distance
622-
d = np.sum((pc-p0)**2)
623-
624-
return d, pc
625-
626-
627602
def _is_closed_polygon(X):
628603
"""
629604
Return whether first and last object in a sequence are the same. These are
@@ -633,39 +608,36 @@ def _is_closed_polygon(X):
633608
return np.allclose(X[0], X[-1], rtol=1e-10, atol=1e-13)
634609

635610

636-
def _find_closest_point_on_path(lc, point):
611+
def _find_closest_point_on_path(xys, p):
637612
"""
638613
Parameters
639614
----------
640-
lc : coordinates of vertices
641-
point : coordinates of test point
615+
xys : (N, 2) array-like
616+
Coordinates of vertices.
617+
p : (float, float)
618+
Coordinates of point.
619+
620+
Returns
621+
-------
622+
d2min : float
623+
Minimum square distance of *p* to *xys*.
624+
proj : (float, float)
625+
Projection of *p* onto *xys*.
626+
imin : (int, int)
627+
Consecutive indices of vertices of segment in *xys* where *proj* is.
642628
"""
643-
644-
# find index of closest vertex for this segment
645-
ds = np.sum((lc - point[None, :])**2, 1)
646-
imin = np.argmin(ds)
647-
648-
dmin = np.inf
649-
xcmin = None
650-
legmin = (None, None)
651-
652-
closed = _is_closed_polygon(lc)
653-
654-
# build list of legs before and after this vertex
655-
legs = []
656-
if imin > 0 or closed:
657-
legs.append(((imin-1) % len(lc), imin))
658-
if imin < len(lc) - 1 or closed:
659-
legs.append((imin, (imin+1) % len(lc)))
660-
661-
for leg in legs:
662-
d, xc = _find_closest_point_on_leg(lc[leg[0]], lc[leg[1]], point)
663-
if d < dmin:
664-
dmin = d
665-
xcmin = xc
666-
legmin = leg
667-
668-
return (dmin, xcmin, legmin)
629+
if len(xys) == 1:
630+
return (((p - xys[0]) ** 2).sum(), xys[0], (0, 0))
631+
dxys = xys[1:] - xys[:-1] # Individual segment vectors.
632+
norms = (dxys ** 2).sum(axis=1)
633+
norms[norms == 0] = 1 # For zero-length segment, replace 0/0 by 0/1.
634+
rel_projs = np.clip( # Project onto each segment in relative 0-1 coords.
635+
((p - xys[:-1]) * dxys).sum(axis=1) / norms,
636+
0, 1)[:, None]
637+
projs = xys[:-1] + rel_projs * dxys # Projs. onto each segment, in (x, y).
638+
d2s = ((projs - p) ** 2).sum(axis=1) # Distances.
639+
imin = np.argmin(d2s)
640+
return (d2s[imin], projs[imin], (imin, imin+1))
669641

670642

671643
docstring.interpd.update(contour_set_attributes=r"""
@@ -1341,8 +1313,8 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
13411313
``(x, y)``.
13421314
xmin, ymin : float
13431315
The point in the contour plot that is closest to ``(x, y)``.
1344-
d : float
1345-
The distance from ``(xmin, ymin)`` to ``(x, y)``.
1316+
d2 : float
1317+
The squared distance from ``(xmin, ymin)`` to ``(x, y)``.
13461318
"""
13471319

13481320
# This function uses a method that is probably quite
@@ -1356,7 +1328,7 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
13561328
if indices is None:
13571329
indices = list(range(len(self.levels)))
13581330

1359-
dmin = np.inf
1331+
d2min = np.inf
13601332
conmin = None
13611333
segmin = None
13621334
xmin = None
@@ -1375,16 +1347,16 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
13751347
if pixel:
13761348
lc = trans.transform(lc)
13771349

1378-
d, xc, leg = _find_closest_point_on_path(lc, point)
1379-
if d < dmin:
1380-
dmin = d
1350+
d2, xc, leg = _find_closest_point_on_path(lc, point)
1351+
if d2 < d2min:
1352+
d2min = d2
13811353
conmin = icon
13821354
segmin = segNum
13831355
imin = leg[1]
13841356
xmin = xc[0]
13851357
ymin = xc[1]
13861358

1387-
return (conmin, segmin, imin, xmin, ymin, dmin)
1359+
return (conmin, segmin, imin, xmin, ymin, d2min)
13881360

13891361

13901362
@docstring.dedent_interpd

0 commit comments

Comments
 (0)