@@ -49,7 +49,7 @@ def get_rotation(self):
4949
5050
5151class  ContourLabeler :
52-     ''' Mixin to provide labelling capability to ContourSet''' 
52+     """ Mixin to provide labelling capability to ContourSet""" 
5353
5454    def  clabel (self , * args , ** kwargs ):
5555        """ 
@@ -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
@@ -608,7 +620,7 @@ def add_label_near(self, x, y, inline=True, inline_spacing=5,
608620                    paths .append (mpath .Path (n ))
609621
610622    def  pop_label (self , index = - 1 ):
611-         ''' Defaults to removing last label, but any index can be supplied''' 
623+         """ Defaults to removing last label, but any index can be supplied""" 
612624        self .labelCValues .pop (index )
613625        t  =  self .labelTexts .pop (index )
614626        t .remove ()
@@ -621,8 +633,8 @@ def labels(self, inline, inline_spacing):
621633            add_label  =  self .add_label 
622634
623635        for  icon , lev , fsize , cvalue  in  zip (
624-             self .labelIndiceList , self .labelLevelList ,  self . labelFontSizeList ,
625-             self .labelCValueList ):
636+                  self .labelIndiceList , self .labelLevelList ,
637+                  self . labelFontSizeList ,  self .labelCValueList ):
626638
627639            con  =  self .collections [icon ]
628640            trans  =  con .get_transform ()
@@ -674,6 +686,64 @@ def labels(self, inline, inline_spacing):
674686                paths .extend (additions )
675687
676688
689+ def  _find_closest_point_on_leg (p1 , p2 , p0 ):
690+     """find closest point to p0 on line segment connecting p1 and p2""" 
691+ 
692+     # handle degenerate case 
693+     if  np .all (p2  ==  p1 ):
694+         d  =  np .sum ((p0  -  p1 )** 2 )
695+         return  d , p1 
696+ 
697+     d21  =  p2  -  p1 
698+     d01  =  p0  -  p1 
699+ 
700+     # project on to line segment to find closest point 
701+     proj  =  np .dot (d01 , d21 ) /  np .dot (d21 , d21 )
702+     if  proj  <  0 :
703+         proj  =  0 
704+     if  proj  >  1 :
705+         proj  =  1 
706+     pc  =  p1  +  proj  *  d21 
707+ 
708+     # find squared distance 
709+     d  =  np .sum ((pc - p0 )** 2 )
710+ 
711+     return  d , pc 
712+ 
713+ 
714+ def  _find_closest_point_on_path (lc , point ):
715+     """ 
716+     lc: coordinates of vertices 
717+     point: coordinates of test point 
718+     """ 
719+ 
720+     # find index of closest vertex for this segment 
721+     ds  =  np .sum ((lc  -  point [None , :])** 2 , 1 )
722+     imin  =  np .argmin (ds )
723+ 
724+     dmin  =  np .inf 
725+     xcmin  =  None 
726+     legmin  =  (None , None )
727+ 
728+     closed  =  mlab .is_closed_polygon (lc )
729+ 
730+     # build list of legs before and after this vertex 
731+     legs  =  []
732+     if  imin  >  0  or  closed :
733+         legs .append (((imin - 1 ) %  len (lc ), imin ))
734+     if  imin  <  len (lc ) -  1  or  closed :
735+         legs .append ((imin , (imin + 1 ) %  len (lc )))
736+ 
737+     for  leg  in  legs :
738+         d , xc  =  _find_closest_point_on_leg (lc [leg [0 ]], lc [leg [1 ]], point )
739+         if  d  <  dmin :
740+             dmin  =  d 
741+             xcmin  =  xc 
742+             legmin  =  leg 
743+ 
744+     return  (dmin , xcmin , legmin )
745+ 
746+ 
677747class  ContourSet (cm .ScalarMappable , ContourLabeler ):
678748    """ 
679749    Store a set of contour lines or filled regions. 
@@ -832,12 +902,13 @@ def __init__(self, ax, *args, **kwargs):
832902                paths  =  self ._make_paths (segs , kinds )
833903                # Default zorder taken from Collection 
834904                zorder  =  kwargs .get ('zorder' , 1 )
835-                 col  =  mcoll .PathCollection (paths ,
836-                                      antialiaseds = (self .antialiased ,),
837-                                      edgecolors = 'none' ,
838-                                      alpha = self .alpha ,
839-                                      transform = self .get_transform (),
840-                                      zorder = zorder )
905+                 col  =  mcoll .PathCollection (
906+                     paths ,
907+                     antialiaseds = (self .antialiased ,),
908+                     edgecolors = 'none' ,
909+                     alpha = self .alpha ,
910+                     transform = self .get_transform (),
911+                     zorder = zorder )
841912                self .ax .add_collection (col )
842913                self .collections .append (col )
843914        else :
@@ -851,13 +922,14 @@ def __init__(self, ax, *args, **kwargs):
851922                    zip (self .levels , tlinewidths , tlinestyles , self .allsegs ):
852923                # Default zorder taken from LineCollection 
853924                zorder  =  kwargs .get ('zorder' , 2 )
854-                 col  =  mcoll .LineCollection (segs ,
855-                                      antialiaseds = aa ,
856-                                      linewidths = width ,
857-                                      linestyle = [lstyle ],
858-                                      alpha = self .alpha ,
859-                                      transform = self .get_transform (),
860-                                      zorder = zorder )
925+                 col  =  mcoll .LineCollection (
926+                     segs ,
927+                     antialiaseds = aa ,
928+                     linewidths = width ,
929+                     linestyle = [lstyle ],
930+                     alpha = self .alpha ,
931+                     transform = self .get_transform (),
932+                     zorder = zorder )
861933                col .set_label ('_nolegend_' )
862934                self .ax .add_collection (col , False )
863935                self .collections .append (col )
@@ -902,29 +974,27 @@ def legend_elements(self, variable_name='x', str_format=str):
902974            n_levels  =  len (self .collections )
903975
904976            for  i , (collection , lower , upper ) in  enumerate (
905-                                                     zip (self .collections ,
906-                                                         lowers , uppers )):
907-                     patch  =  mpatches .Rectangle (
908-                                     (0 , 0 ), 1 , 1 ,
909-                                     facecolor = collection .get_facecolor ()[0 ],
910-                                     hatch = collection .get_hatch (),
911-                                     alpha = collection .get_alpha (),
912-                                     )
913-                     artists .append (patch )
914- 
915-                     lower  =  str_format (lower )
916-                     upper  =  str_format (upper )
917- 
918-                     if  i  ==  0  and  self .extend  in  ('min' , 'both' ):
919-                         labels .append (r'$%s \leq %s$'  %  (variable_name ,
920-                                                          lower ))
921-                     elif  i  ==  n_levels  -  1  and  self .extend  in  ('max' , 'both' ):
922-                         labels .append (r'$%s > %s$'  %  (variable_name ,
923-                                                       upper ))
924-                     else :
925-                         labels .append (r'$%s < %s \leq %s$'  %  (lower ,
926-                                                               variable_name ,
927-                                                               upper ))
977+                     zip (self .collections , lowers , uppers )):
978+                 patch  =  mpatches .Rectangle (
979+                     (0 , 0 ), 1 , 1 ,
980+                     facecolor = collection .get_facecolor ()[0 ],
981+                     hatch = collection .get_hatch (),
982+                     alpha = collection .get_alpha ())
983+                 artists .append (patch )
984+ 
985+                 lower  =  str_format (lower )
986+                 upper  =  str_format (upper )
987+ 
988+                 if  i  ==  0  and  self .extend  in  ('min' , 'both' ):
989+                     labels .append (r'$%s \leq %s$'  %  (variable_name ,
990+                                                      lower ))
991+                 elif  i  ==  n_levels  -  1  and  self .extend  in  ('max' , 'both' ):
992+                     labels .append (r'$%s > %s$'  %  (variable_name ,
993+                                                   upper ))
994+                 else :
995+                     labels .append (r'$%s < %s \leq %s$'  %  (lower ,
996+                                                           variable_name ,
997+                                                           upper ))
928998        else :
929999            for  collection , level  in  zip (self .collections , self .levels ):
9301000
@@ -963,7 +1033,7 @@ def _process_args(self, *args, **kwargs):
9631033
9641034        # Check length of allkinds. 
9651035        if  (self .allkinds  is  not   None  and 
966-             len (self .allkinds ) !=  len (self .allsegs )):
1036+                  len (self .allkinds ) !=  len (self .allsegs )):
9671037            raise  ValueError ('allkinds has different length to allsegs' )
9681038
9691039        # Determine x,y bounds and update axes data limits. 
@@ -1032,7 +1102,7 @@ def changed(self):
10321102        cm .ScalarMappable .changed (self )
10331103
10341104    def  _autolev (self , z , N ):
1035-         ''' 
1105+         """ 
10361106        Select contour levels to span the data. 
10371107
10381108        We need two more levels for filled contours than for 
@@ -1041,7 +1111,7 @@ def _autolev(self, z, N):
10411111        a single contour boundary, say at z = 0, requires only 
10421112        one contour line, but two filled regions, and therefore 
10431113        three levels to provide boundaries for both regions. 
1044-         '''  
1114+         """  
10451115        if  self .locator  is  None :
10461116            if  self .logscale :
10471117                self .locator  =  ticker .LogLocator ()
@@ -1210,11 +1280,11 @@ def _process_linestyles(self):
12101280        return  tlinestyles 
12111281
12121282    def  get_alpha (self ):
1213-         ''' returns alpha to be applied to all ContourSet artists''' 
1283+         """ returns alpha to be applied to all ContourSet artists""" 
12141284        return  self .alpha 
12151285
12161286    def  set_alpha (self , alpha ):
1217-         ''' sets alpha for all ContourSet artists''' 
1287+         """ sets alpha for all ContourSet artists""" 
12181288        self .alpha  =  alpha 
12191289        self .changed ()
12201290
@@ -1256,32 +1326,33 @@ def find_nearest_contour(self, x, y, indices=None, pixel=True):
12561326        if  indices  is  None :
12571327            indices  =  range (len (self .levels ))
12581328
1259-         dmin  =  1e10 
1329+         dmin  =  np . inf 
12601330        conmin  =  None 
12611331        segmin  =  None 
12621332        xmin  =  None 
12631333        ymin  =  None 
12641334
1335+         point  =  np .array ([x , y ])
1336+ 
12651337        for  icon  in  indices :
12661338            con  =  self .collections [icon ]
12671339            trans  =  con .get_transform ()
12681340            paths  =  con .get_paths ()
1341+ 
12691342            for  segNum , linepath  in  enumerate (paths ):
12701343                lc  =  linepath .vertices 
1271- 
12721344                # transfer all data points to screen coordinates if desired 
12731345                if  pixel :
12741346                    lc  =  trans .transform (lc )
12751347
1276-                 ds  =  (lc [:, 0 ] -  x ) **  2  +  (lc [:, 1 ] -  y ) **  2 
1277-                 d  =  min (ds )
1348+                 d , xc , leg  =  _find_closest_point_on_path (lc , point )
12781349                if  d  <  dmin :
12791350                    dmin  =  d 
12801351                    conmin  =  icon 
12811352                    segmin  =  segNum 
1282-                     imin  =  mpl . mlab . find ( ds   ==   d )[ 0 ]
1283-                     xmin  =  lc [ imin ,  0 ]
1284-                     ymin  =  lc [ imin ,  1 ]
1353+                     imin  =  leg [ 1 ]
1354+                     xmin  =  xc [ 0 ]
1355+                     ymin  =  xc [ 1 ]
12851356
12861357        return  (conmin , segmin , imin , xmin , ymin , dmin )
12871358
@@ -1340,7 +1411,7 @@ def _process_args(self, *args, **kwargs):
13401411            # if the transform is not trans data, and some part of it 
13411412            # contains transData, transform the xs and ys to data coordinates 
13421413            if  (t  !=  self .ax .transData  and 
1343-                          any (t .contains_branch_seperately (self .ax .transData ))):
1414+                     any (t .contains_branch_seperately (self .ax .transData ))):
13441415                trans_to_data  =  t  -  self .ax .transData 
13451416                pts  =  (np .vstack ([x .flat , y .flat ]).T )
13461417                transformed_pts  =  trans_to_data .transform (pts )
@@ -1408,14 +1479,14 @@ def _contour_args(self, args, kwargs):
14081479        return  (x , y , z )
14091480
14101481    def  _check_xyz (self , args , kwargs ):
1411-         ''' 
1482+         """ 
14121483        For functions like contour, check that the dimensions 
14131484        of the input arrays match; if x and y are 1D, convert 
14141485        them to 2D using meshgrid. 
14151486
14161487        Possible change: I think we should make and use an ArgumentError 
14171488        Exception class (here and elsewhere). 
1418-         '''  
1489+         """  
14191490        x , y  =  args [:2 ]
14201491        self .ax ._process_unit_info (xdata = x , ydata = y , kwargs = kwargs )
14211492        x  =  self .ax .convert_xunits (x )
@@ -1450,11 +1521,11 @@ def _check_xyz(self, args, kwargs):
14501521
14511522            if  x .shape  !=  z .shape :
14521523                raise  TypeError ("Shape of x does not match that of z: found " 
1453-                             "{0} instead of {1}." .format (x .shape , z .shape ))
1524+                                  "{0} instead of {1}." .format (x .shape , z .shape ))
14541525
14551526            if  y .shape  !=  z .shape :
14561527                raise  TypeError ("Shape of y does not match that of z: found " 
1457-                             "{0} instead of {1}." .format (y .shape , z .shape ))
1528+                                  "{0} instead of {1}." .format (y .shape , z .shape ))
14581529
14591530        else :
14601531
@@ -1463,7 +1534,7 @@ def _check_xyz(self, args, kwargs):
14631534        return  x , y , z 
14641535
14651536    def  _initialize_x_y (self , z ):
1466-         ''' 
1537+         """ 
14671538        Return X, Y arrays such that contour(Z) will match imshow(Z) 
14681539        if origin is not None. 
14691540        The center of pixel Z[i,j] depends on origin: 
@@ -1474,7 +1545,7 @@ def _initialize_x_y(self, z):
14741545        as in imshow. 
14751546        If origin is None and extent is not None, then extent 
14761547        will give the minimum and maximum values of x and y. 
1477-         '''  
1548+         """  
14781549        if  z .ndim  !=  2 :
14791550            raise  TypeError ("Input must be a 2D array." )
14801551        else :
0 commit comments