Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9d8561c

Browse files
committed
Merge branch 'slice-and-fancy-indexing-for-markevery'
Conflicts: CHANGELOG doc/api/api_changes.rst doc/users/whats_new.rst
2 parents 5432720 + eaec4bd commit 9d8561c

18 files changed

+21712
-28
lines changed

CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
2014-02-27 Implemented separate horizontal/vertical axes padding to the
4949
ImageGrid in the AxesGrid toolkit
5050

51+
2014-02-27 Allowed markevery property of matplotlib.lines.Line2D to be, an int
52+
numpy fancy index, slice object, or float. The float behaviour
53+
turns on markers at approximately equal display-coordinate-distances
54+
along the line.
55+
5156
2014-02-25 In backend_qt4agg changed from using update -> repaint under
5257
windows. See comment in source near `self._priv_update` for
5358
longer explaination.

doc/api/api_changes.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,21 @@ original location:
160160

161161
* Added clockwise parameter to control sectors direction in `axes.pie`
162162

163+
* In `matplotlib.lines.Line2D` the `markevery` functionality has been extended.
164+
Previously an integer start-index and stride-length could be specified using
165+
either a two-element-list or a two-element-tuple. Now this can only be done
166+
using a two-element-tuple. If a two-element-list is used then it will be
167+
treated as numpy fancy indexing and only the two markers corresponding to the
168+
given indexes will be shown.
169+
170+
163171
Code removal
164172
------------
165173

166174
* Removed ``mlab.levypdf``. The code raised a numpy error (and has for
167175
a long time) and was not the standard form of the Levy distribution.
168176
``scipy.stats.levy`` should be used instead
169177

170-
171-
172-
173178
.. _changes_in_1_3:
174179

175180

doc/users/whats_new.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ New plotting features
4646
Power-law normalization
4747
```````````````````````
4848
Ben Gamari added a power-law normalization method,
49-
:class:`~matplotlib.colors.PowerNorm`. This class maps a range of
49+
:class:`~matplotlib.colors.PowerNorm`. This class maps a range of
5050
values to the interval [0,1] with power-law scaling with the exponent
5151
provided by the constructor's `gamma` argument. Power law normalization
5252
can be useful for, e.g., emphasizing small populations in a histogram.
@@ -173,6 +173,15 @@ handling on a par with artists, collections, containers, lines, patches,
173173
and tables.
174174

175175

176+
More `markevery` options to show only a subset of markers
177+
`````````````````````````````````````````````````````````
178+
Rohan Walker extended the `markevery` property in
179+
:class:`~matplotlib.lines.Line2D`. You can now specify a subset of markers to
180+
show with an int, slice object, numpy fancy indexing, or float. Using a float
181+
shows markers at approximately equal display-coordinate-distances along the
182+
line.
183+
184+
176185
Date handling
177186
-------------
178187

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""
2+
This example demonstrates the various options for showing a marker at a
3+
subset of data points using the `markevery` property of a Line2D object.
4+
5+
Integer arguments are fairly intuitive. e.g. `markevery`=5 will plot every
6+
5th marker starting from the first data point.
7+
8+
Float arguments allow markers to be spaced at approximately equal distances
9+
along the line. The theoretical distance along the line between markers is
10+
determined by multiplying the display-coordinate distance of the axes
11+
bounding-box diagonal by the value of `markevery`. The data points closest
12+
to the theoretical distances will be shown.
13+
14+
A slice or list/array can also be used with `markevery` to specify the markers
15+
to show.
16+
17+
"""
18+
19+
from __future__ import division
20+
import numpy as np
21+
import matplotlib.pyplot as plt
22+
import matplotlib.gridspec as gridspec
23+
24+
#define a list of markevery cases to plot
25+
cases = [None,
26+
8,
27+
(30, 8),
28+
[16, 24, 30], [0,-1],
29+
slice(100,200,3),
30+
0.1, 0.3, 1.5,
31+
(0.0, 0.1), (0.45, 0.1)]
32+
33+
#define the figure size and grid layout properties
34+
figsize = (10, 8)
35+
cols = 3
36+
gs = gridspec.GridSpec(len(cases) // cols + 1, cols)
37+
38+
#define the data for cartesian plots
39+
delta = 0.11
40+
x = np.linspace(0, 10 - 2 * delta, 200) + delta
41+
y = np.sin(x) + 1.0 + delta
42+
43+
#plot each markevery case for linear x and y scales
44+
fig1 = plt.figure(num=1, figsize=figsize)
45+
ax = []
46+
for i, case in enumerate(cases):
47+
row = (i // cols)
48+
col = i % cols
49+
ax.append(fig1.add_subplot(gs[row, col]))
50+
ax[-1].set_title('markevery=%s' % str(case))
51+
ax[-1].plot(x, y, 'o', ls='-', ms=4, markevery=case)
52+
#fig1.tight_layout()
53+
54+
#plot each markevery case for log x and y scales
55+
fig2 = plt.figure(num=2, figsize=figsize)
56+
axlog = []
57+
for i, case in enumerate(cases):
58+
row = (i // cols)
59+
col = i % cols
60+
axlog.append(fig2.add_subplot(gs[row, col]))
61+
axlog[-1].set_title('markevery=%s' % str(case))
62+
axlog[-1].set_xscale('log')
63+
axlog[-1].set_yscale('log')
64+
axlog[-1].plot(x, y, 'o', ls='-', ms=4, markevery=case)
65+
fig2.tight_layout()
66+
67+
#plot each markevery case for linear x and y scales but zoomed in
68+
#note the behaviour when zoomed in. When a start marker offset is specified
69+
#it is always interpreted with respect to the first data point which might be
70+
#different to the first visible data point.
71+
fig3 = plt.figure(num=3, figsize=figsize)
72+
axzoom = []
73+
for i, case in enumerate(cases):
74+
row = (i // cols)
75+
col = i % cols
76+
axzoom.append(fig3.add_subplot(gs[row, col]))
77+
axzoom[-1].set_title('markevery=%s' % str(case))
78+
axzoom[-1].plot(x, y, 'o', ls='-', ms=4, markevery=case)
79+
axzoom[-1].set_xlim((6, 6.7))
80+
axzoom[-1].set_ylim((1.1, 1.7))
81+
fig3.tight_layout()
82+
83+
#define data for polar plots
84+
r = np.linspace(0, 3.0, 200)
85+
theta = 2 * np.pi * r
86+
87+
#plot each markevery case for polar plots
88+
fig4 = plt.figure(num=4, figsize=figsize)
89+
axpolar = []
90+
for i, case in enumerate(cases):
91+
row = (i // cols)
92+
col = i % cols
93+
axpolar.append(fig4.add_subplot(gs[row, col], polar = True))
94+
axpolar[-1].set_title('markevery=%s' % str(case))
95+
axpolar[-1].plot(theta, r, 'o', ls='-', ms=4, markevery=case)
96+
fig4.tight_layout()
97+
98+
plt.show()

lib/matplotlib/lines.py

Lines changed: 158 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
76188
class 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

Binary file not shown.

0 commit comments

Comments
 (0)