@@ -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