@@ -572,6 +572,18 @@ def add_label_near(self, x, y, inline=True, inline_spacing=5,
572572 conmin , segmin , imin , xmin , ymin = self .find_nearest_contour (
573573 x , y , self .labelIndiceList )[:5 ]
574574
575+ # The calc_label_rot_and_inline routine requires that (xmin,ymin)
576+ # be a vertex in the path. So, if it isn't, add a vertex here
577+ paths = self .collections [conmin ].get_paths ()
578+ lc = paths [segmin ].vertices
579+ if transform :
580+ xcmin = transform .inverted ().transform ([xmin ,ymin ])
581+ else :
582+ xcmin = np .array ([xmin ,ymin ])
583+ if not np .allclose (xcmin , lc [imin ]):
584+ lc = np .r_ [lc [:imin ], np .array (xcmin )[None ,:], lc [imin :]]
585+ paths [segmin ] = mpath .Path (lc )
586+
575587 # Get index of nearest level in subset of levels used for labeling
576588 lmin = self .labelIndiceList .index (conmin )
577589
@@ -673,6 +685,59 @@ def labels(self, inline, inline_spacing):
673685 del paths [:]
674686 paths .extend (additions )
675687
688+ def _find_closest_point_on_leg (p1 , p2 , p0 ):
689+ '''find closest point to p0 on line segment connecting p1 and p2'''
690+
691+ # handle degenerate case
692+ if np .all (p2 == p1 ):
693+ d = np .sum ((p0 - p1 )** 2 )
694+ return d , p1
695+
696+ d21 = p2 - p1
697+ d01 = p0 - p1
698+
699+ # project on to line segment to find closest point
700+ proj = np .dot (d01 , d21 ) / np .dot (d21 , d21 )
701+ if proj < 0 : proj = 0
702+ if proj > 1 : proj = 1
703+ pc = p1 + proj * d21
704+
705+ # find squared distance
706+ d = np .sum ((pc - p0 )** 2 )
707+
708+ return d , pc
709+
710+ def _find_closest_point_on_path (lc , point ):
711+ '''
712+ lc: coordinates of vertices
713+ point: coordinates of test point
714+ '''
715+
716+ # find index of closest vertex for this segment
717+ ds = np .sum ((lc - point [None ,:])** 2 , 1 )
718+ imin = np .argmin (ds )
719+
720+ dmin = 1e10
721+ xcmin = None
722+ legmin = (None ,None )
723+
724+ closed = mlab .is_closed_polygon (lc )
725+
726+ # build list of legs before and after this vertex
727+ legs = []
728+ if imin > 0 or closed :
729+ legs .append (((imin - 1 )% len (lc ),imin ))
730+ if imin < len (lc ) - 1 or closed :
731+ legs .append ((imin ,(imin + 1 )% len (lc )))
732+
733+ for leg in legs :
734+ d , xc = _find_closest_point_on_leg (lc [leg [0 ]], lc [leg [1 ]], point )
735+ if d < dmin :
736+ dmin = d
737+ xcmin = xc
738+ legmin = leg
739+
740+ return (dmin , xcmin , legmin )
676741
677742class ContourSet (cm .ScalarMappable , ContourLabeler ):
678743 """
@@ -1262,26 +1327,27 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
12621327 xmin = None
12631328 ymin = None
12641329
1330+ point = np .array ([x ,y ])
1331+
12651332 for icon in indices :
12661333 con = self .collections [icon ]
12671334 trans = con .get_transform ()
12681335 paths = con .get_paths ()
1336+
12691337 for segNum , linepath in enumerate (paths ):
12701338 lc = linepath .vertices
1271-
12721339 # transfer all data points to screen coordinates if desired
12731340 if pixel :
12741341 lc = trans .transform (lc )
12751342
1276- ds = (lc [:, 0 ] - x ) ** 2 + (lc [:, 1 ] - y ) ** 2
1277- d = min (ds )
1343+ d , xc , leg = _find_closest_point_on_path (lc , point )
12781344 if d < dmin :
12791345 dmin = d
12801346 conmin = icon
12811347 segmin = segNum
1282- imin = mpl . mlab . find ( ds == d )[ 0 ]
1283- xmin = lc [ imin , 0 ]
1284- ymin = lc [ imin , 1 ]
1348+ imin = leg [ 1 ]
1349+ xmin = xc [ 0 ]
1350+ ymin = xc [ 1 ]
12851351
12861352 return (conmin , segmin , imin , xmin , ymin , dmin )
12871353
0 commit comments