@@ -73,6 +73,118 @@ def segment_hits(cx, cy, x, y, radius):
7373    return  np .concatenate ((points , lines ))
7474
7575
76+ def  _mark_every_path (markevery , tpath , affine , ax_transform ):
77+     """ 
78+     Helper function that sorts out how to deal the input 
79+     `markevery` and returns the points where markers should be drawn. 
80+ 
81+     Takes in the `markevery` value and the line path and returns the 
82+     sub-sampled path. 
83+     """ 
84+     # pull out the two bits of data we want from the path 
85+     codes , verts  =  tpath .codes , tpath .vertices 
86+ 
87+     def  _slice_or_none (in_v , slc ):
88+         ''' 
89+         Helper function to cope with `codes` being an 
90+         ndarray or `None` 
91+         ''' 
92+         if  in_v  is  None :
93+             return  None 
94+         return  in_v [slc ]
95+ 
96+     # if just a float, assume starting at 0.0 and make a tuple 
97+     if  isinstance (markevery , float ):
98+         markevery  =  (0.0 , markevery )
99+     # if just an int, assume starting at 0 and make a tuple 
100+     elif  isinstance (markevery , int ):
101+         markevery  =  (0 , markevery )
102+ 
103+     if  isinstance (markevery , tuple ):
104+         if  len (markevery ) !=  2 :
105+             raise  ValueError ('`markevery` is a tuple but its ' 
106+                 'len is not 2; ' 
107+                 'markevery=%s'  %  (markevery ,))
108+         start , step  =  markevery 
109+         # if step is an int, old behavior 
110+         if  isinstance (step , int ):
111+             #tuple of 2 int is for backwards compatibility, 
112+             if  not (isinstance (start , int )):
113+                 raise  ValueError ('`markevery` is a tuple with ' 
114+                     'len 2 and second element is an int, but ' 
115+                     'the first element is not an int; ' 
116+                     'markevery=%s'  %  (markevery ,))
117+             # just return, we are done here 
118+ 
119+             return  Path (verts [slice (start , None , step )],
120+                         _slice_or_none (codes , slice (start , None , step )))
121+ 
122+         elif  isinstance (step , float ):
123+             if  not  (isinstance (start , int ) or 
124+                     isinstance (start , float )):
125+                 raise  ValueError ('`markevery` is a tuple with ' 
126+                     'len 2 and second element is a float, but ' 
127+                     'the first element is not a float or an ' 
128+                     'int; ' 
129+                     'markevery=%s'  %  (markevery ,))
130+             #calc cumulative distance along path (in display 
131+             # coords): 
132+             disp_coords  =  affine .transform (tpath .vertices )
133+             delta  =  np .empty ((len (disp_coords ), 2 ),
134+                              dtype = float )
135+             delta [0 , :] =  0.0 
136+             delta [1 :, :] =  (disp_coords [1 :, :] - 
137+                                 disp_coords [:- 1 , :])
138+             delta  =  np .sum (delta ** 2 , axis = 1 )
139+             delta  =  np .sqrt (delta )
140+             delta  =  np .cumsum (delta )
141+             #calc distance between markers along path based on 
142+             # the axes bounding box diagonal being a distance 
143+             # of unity: 
144+             scale  =  ax_transform .transform (
145+                 np .array ([[0 , 0 ], [1 , 1 ]]))
146+             scale  =  np .diff (scale , axis = 0 )
147+             scale  =  np .sum (scale ** 2 )
148+             scale  =  np .sqrt (scale )
149+             marker_delta  =  np .arange (start  *  scale ,
150+                                      delta [- 1 ],
151+                                      step  *  scale )
152+             #find closest actual data point that is closest to 
153+             # the theoretical distance along the path: 
154+             inds  =  np .abs (delta [np .newaxis , :] - 
155+                             marker_delta [:, np .newaxis ])
156+             inds  =  inds .argmin (axis = 1 )
157+             inds  =  np .unique (inds )
158+             # return, we are done here 
159+             return  Path (verts [inds ],
160+                         _slice_or_none (codes , inds ))
161+         else :
162+             raise  ValueError ('`markevery` is a tuple with ' 
163+                 'len 2, but its second element is not an int ' 
164+                 'or a float; ' 
165+                 'markevery=%s'  %  (markevery ,))
166+ 
167+     elif  isinstance (markevery , slice ):
168+         # mazol tov, it's already a slice, just return 
169+         return  Path (verts [markevery ],
170+                     _slice_or_none (codes , markevery ))
171+ 
172+     elif  iterable (markevery ):
173+         #fancy indexing 
174+         try :
175+             return  Path (verts [markevery ],
176+                     _slice_or_none (codes , markevery ))
177+ 
178+         except  (ValueError , IndexError ):
179+             raise  ValueError ('`markevery` is iterable but ' 
180+                 'not a valid form of numpy fancy indexing; ' 
181+                 'markevery=%s'  %  (markevery ,))
182+     else :
183+         raise  ValueError ('Value of `markevery` is not ' 
184+             'recognized; ' 
185+             'markevery=%s'  %  (markevery ,))
186+ 
187+ 
76188class  Line2D (Artist ):
77189    """ 
78190    A line - the line can have both a solid linestyle connecting all 
@@ -341,24 +453,54 @@ def set_fillstyle(self, fs):
341453        self ._marker .set_fillstyle (fs )
342454
343455    def  set_markevery (self , every ):
344-         """ 
345-         Set the markevery property to subsample the plot when using 
346-         markers.  e.g., if ``markevery=5``, every 5-th marker will be 
347-         plotted.  *every* can be 
348- 
349-         None 
350-             Every point will be plotted 
456+         """Set the markevery property to subsample the plot when using markers. 
351457
352-         an integer N 
353-             Every N-th marker will be plotted starting with marker 0 
458+         e.g., if `every=5`, every 5-th marker will be plotted. 
354459
355-         A length-2 tuple of integers 
356-             every=(start, N) will start at point start and plot every N-th 
357-             marker 
358- 
359-         ACCEPTS: None | integer | (startind, stride) 
460+         Parameters 
461+         ---------- 
462+         every: None | int | length-2 tuple of int | slice | list/array of int | 
463+         float | length-2 tuple of float 
464+             Which markers to plot. 
465+ 
466+             - every=None, every point will be plotted. 
467+             - every=N, every N-th marker will be plotted starting with 
468+               marker 0. 
469+             - every=(start, N), every N-th marker, starting at point 
470+               start, will be plotted. 
471+             - every=slice(start, end, N), every N-th marker, starting at 
472+               point start, upto but not including point end, will be plotted. 
473+             - every=[i, j, m, n], only markers at points i, j, m, and n 
474+               will be plotted. 
475+             - every=0.1, (i.e. a float) then markers will be spaced at 
476+               approximately equal distances along the line; the distance 
477+               along the line between markers is determined by multiplying the 
478+               display-coordinate distance of the axes bounding-box diagonal 
479+               by the value of every. 
480+             - every=(0.5, 0.1) (i.e. a length-2 tuple of float), the 
481+               same functionality as every=0.1 is exhibited but the first 
482+               marker will be 0.5 multiplied by the 
483+               display-cordinate-diagonal-distance along the line. 
484+ 
485+         Notes 
486+         ----- 
487+         Setting the markevery property will only show markers at actual data 
488+         points.  When using float arguments to set the markevery property 
489+         on irregularly spaced data, the markers will likely not appear evenly 
490+         spaced because the actual data points do not coincide with the 
491+         theoretical spacing between markers. 
492+ 
493+         When using a start offset to specify the first marker, the offset will 
494+         be from the first data point which may be different from the first 
495+         the visible data point if the plot is zoomed in. 
496+ 
497+         If zooming in on a plot when using float arguments then the actual 
498+         data points that have markers will change because the distance between 
499+         markers is always determined from the display-coordinates 
500+         axes-bounding-box-diagonal regardless of the actual axes data limits. 
360501
361502        """ 
503+ 
362504        self ._markevery  =  every 
363505
364506    def  get_markevery (self ):
@@ -582,16 +724,8 @@ def draw(self, renderer):
582724                # subsample the markers if markevery is not None 
583725                markevery  =  self .get_markevery ()
584726                if  markevery  is  not   None :
585-                     if  iterable (markevery ):
586-                         startind , stride  =  markevery 
587-                     else :
588-                         startind , stride  =  0 , markevery 
589-                     if  tpath .codes  is  not   None :
590-                         codes  =  tpath .codes [startind ::stride ]
591-                     else :
592-                         codes  =  None 
593-                     vertices  =  tpath .vertices [startind ::stride ]
594-                     subsampled  =  Path (vertices , codes )
727+                     subsampled  =  _mark_every_path (markevery , tpath ,
728+                                                   affine , self .axes .transAxes )
595729                else :
596730                    subsampled  =  tpath 
597731
0 commit comments