@@ -73,6 +73,118 @@ def segment_hits(cx, cy, x, y, radius):
73
73
return np .concatenate ((points , lines ))
74
74
75
75
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
+
76
188
class Line2D (Artist ):
77
189
"""
78
190
A line - the line can have both a solid linestyle connecting all
@@ -341,24 +453,54 @@ def set_fillstyle(self, fs):
341
453
self ._marker .set_fillstyle (fs )
342
454
343
455
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.
351
457
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.
354
459
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.
360
501
361
502
"""
503
+
362
504
self ._markevery = every
363
505
364
506
def get_markevery (self ):
@@ -582,16 +724,8 @@ def draw(self, renderer):
582
724
# subsample the markers if markevery is not None
583
725
markevery = self .get_markevery ()
584
726
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 )
595
729
else :
596
730
subsampled = tpath
597
731
0 commit comments