Skip to content

Commit a892633

Browse files
committed
Merge pull request #4433 from tacaswell/enh_stepfill_between
ENH : stepfill between
2 parents 69a1908 + 42eb539 commit a892633

File tree

7 files changed

+504
-47
lines changed

7 files changed

+504
-47
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ install:
6767
pip install $PRE python-dateutil $NUMPY pyparsing pillow sphinx!=1.3.0;
6868
fi
6969
# Always install from pypi
70-
- pip install $PRE nose pep8
70+
- pip install $PRE nose pep8 cycler
7171

7272
# Install mock on python 2. Python 2.6 requires mock 1.0.1
7373
# Since later versions have dropped support
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Add step kwargs to fill_between
2+
-------------------------------
3+
4+
Added ``step`` kwarg to `Axes.fill_between` to allow to fill between
5+
lines drawn using the 'step' draw style. The values of ``step`` match
6+
those of the ``where`` kwarg of `Axes.step`. The asymmetry of of the
7+
kwargs names is not ideal, but `Axes.fill_between` already has a
8+
``where`` kwarg.
9+
10+
This is particularly useful for plotting pre-binned histograms.
11+
12+
.. plot:: mpl_examples/api/filled_step.py

examples/api/filled_step.py

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import itertools
2+
from functools import partial
3+
4+
import numpy as np
5+
import matplotlib.pyplot as plt
6+
from cycler import cycler
7+
from six.moves import zip
8+
9+
10+
def filled_hist(ax, edges, values, bottoms=None, orientation='v',
11+
**kwargs):
12+
"""
13+
Draw a histogram as a stepped patch.
14+
15+
Extra kwargs are passed through to `fill_between`
16+
17+
Parameters
18+
----------
19+
ax : Axes
20+
The axes to plot to
21+
22+
edges : array
23+
A length n+1 array giving the left edges of each bin and the
24+
right edge of the last bin.
25+
26+
values : array
27+
A length n array of bin counts or values
28+
29+
bottoms : scalar or array, optional
30+
A length n array of the bottom of the bars. If None, zero is used.
31+
32+
orientation : {'v', 'h'}
33+
Orientation of the histogram. 'v' (default) has
34+
the bars increasing in the positive y-direction.
35+
36+
Returns
37+
-------
38+
ret : PolyCollection
39+
Artist added to the Axes
40+
"""
41+
print(orientation)
42+
if orientation not in set('hv'):
43+
raise ValueError("orientation must be in {'h', 'v'} "
44+
"not {o}".format(o=orientation))
45+
46+
kwargs.setdefault('step', 'post')
47+
edges = np.asarray(edges)
48+
values = np.asarray(values)
49+
if len(edges) - 1 != len(values):
50+
raise ValueError('Must provide one more bin edge than value not: '
51+
'len(edges): {lb} len(values): {lv}'.format(
52+
lb=len(edges), lv=len(values)))
53+
54+
if bottoms is None:
55+
bottoms = np.zeros_like(values)
56+
if np.isscalar(bottoms):
57+
bottoms = np.ones_like(values) * bottoms
58+
59+
values = np.r_[values, values[-1]]
60+
bottoms = np.r_[bottoms, bottoms[-1]]
61+
if orientation == 'h':
62+
return ax.fill_betweenx(edges, values, bottoms, **kwargs)
63+
elif orientation == 'v':
64+
return ax.fill_between(edges, values, bottoms, **kwargs)
65+
else:
66+
raise AssertionError("you should never be here")
67+
68+
69+
def stack_hist(ax, stacked_data, sty_cycle, bottoms=None,
70+
hist_func=None, labels=None,
71+
plot_func=None, plot_kwargs=None):
72+
"""
73+
ax : axes.Axes
74+
The axes to add artists too
75+
76+
stacked_data : array or Mapping
77+
A (N, M) shaped array. The first dimension will be iterated over to
78+
compute histograms row-wise
79+
80+
sty_cycle : Cycler or operable of dict
81+
Style to apply to each set
82+
83+
bottoms : array, optional
84+
The initial positions of the bottoms, defaults to 0
85+
86+
hist_func : callable, optional
87+
Must have signature `bin_vals, bin_edges = f(data)`.
88+
`bin_edges` expected to be one longer than `bin_vals`
89+
90+
labels : list of str, optional
91+
The label for each set.
92+
93+
If not given and stacked data is an array defaults to 'default set {n}'
94+
95+
If stacked_data is a mapping, and labels is None, default to the keys
96+
(which may come out in a random order).
97+
98+
If stacked_data is a mapping and labels is given then only
99+
the columns listed by be plotted.
100+
101+
plot_func : callable, optional
102+
Function to call to draw the histogram must have signature:
103+
104+
ret = plot_func(ax, edges, top, bottoms=bottoms,
105+
label=label, **kwargs)
106+
107+
plot_kwargs : dict, optional
108+
Any extra kwargs to pass through to the plotting function. This
109+
will be the same for all calls to the plotting function and will
110+
over-ride the values in cycle.
111+
112+
Returns
113+
-------
114+
arts : dict
115+
Dictionary of artists keyed on their labels
116+
"""
117+
# deal with default binning function
118+
if hist_func is None:
119+
hist_func = np.histogram
120+
121+
# deal with default plotting function
122+
if plot_func is None:
123+
plot_func = filled_hist
124+
125+
# deal with default
126+
if plot_kwargs is None:
127+
plot_kwargs = {}
128+
print(plot_kwargs)
129+
try:
130+
l_keys = stacked_data.keys()
131+
label_data = True
132+
if labels is None:
133+
labels = l_keys
134+
135+
except AttributeError:
136+
label_data = False
137+
if labels is None:
138+
labels = itertools.repeat(None)
139+
140+
if label_data:
141+
loop_iter = enumerate((stacked_data[lab], lab, s) for lab, s in
142+
zip(labels, sty_cycle))
143+
else:
144+
loop_iter = enumerate(zip(stacked_data, labels, sty_cycle))
145+
146+
arts = {}
147+
for j, (data, label, sty) in loop_iter:
148+
if label is None:
149+
label = 'default set {n}'.format(n=j)
150+
label = sty.pop('label', label)
151+
vals, edges = hist_func(data)
152+
if bottoms is None:
153+
bottoms = np.zeros_like(vals)
154+
top = bottoms + vals
155+
print(sty)
156+
sty.update(plot_kwargs)
157+
print(sty)
158+
ret = plot_func(ax, edges, top, bottoms=bottoms,
159+
label=label, **sty)
160+
bottoms = top
161+
arts[label] = ret
162+
ax.legend()
163+
return arts
164+
165+
166+
# set up histogram function to fixed bins
167+
edges = np.linspace(-3, 3, 20, endpoint=True)
168+
hist_func = partial(np.histogram, bins=edges)
169+
170+
# set up style cycles
171+
color_cycle = cycler('facecolor', 'rgbm')
172+
label_cycle = cycler('label', ['set {n}'.format(n=n) for n in range(4)])
173+
hatch_cycle = cycler('hatch', ['/', '*', '+', '|'])
174+
175+
# make some synthetic data
176+
stack_data = np.random.randn(4, 12250)
177+
dict_data = {lab: d for lab, d in zip(list(c['label'] for c in label_cycle),
178+
stack_data)}
179+
180+
# work with plain arrays
181+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6), tight_layout=True)
182+
arts = stack_hist(ax1, stack_data, color_cycle + label_cycle + hatch_cycle,
183+
hist_func=hist_func)
184+
185+
arts = stack_hist(ax2, stack_data, color_cycle,
186+
hist_func=hist_func,
187+
plot_kwargs=dict(edgecolor='w', orientation='h'))
188+
ax1.set_ylabel('counts')
189+
ax1.set_xlabel('x')
190+
ax2.set_xlabel('counts')
191+
ax2.set_ylabel('x')
192+
193+
# work with labeled data
194+
195+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6),
196+
tight_layout=True, sharey=True)
197+
198+
arts = stack_hist(ax1, dict_data, color_cycle + hatch_cycle,
199+
hist_func=hist_func)
200+
201+
arts = stack_hist(ax2, dict_data, color_cycle + hatch_cycle,
202+
hist_func=hist_func, labels=['set 0', 'set 3'])
203+
204+
ax1.set_ylabel('counts')
205+
ax1.set_xlabel('x')
206+
ax2.set_xlabel('x')

lib/matplotlib/axes/_axes.py

+54-26
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import matplotlib
1414

1515
import matplotlib.cbook as cbook
16-
from matplotlib.cbook import _string_to_bool, mplDeprecation
16+
from matplotlib.cbook import mplDeprecation, STEP_LOOKUP_MAP
1717
import matplotlib.collections as mcoll
1818
import matplotlib.colors as mcolors
1919
import matplotlib.contour as mcontour
@@ -4429,49 +4429,59 @@ def fill(self, *args, **kwargs):
44294429

44304430
@docstring.dedent_interpd
44314431
def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
4432+
step=None,
44324433
**kwargs):
44334434
"""
44344435
Make filled polygons between two curves.
44354436
4436-
Call signature::
4437-
4438-
fill_between(x, y1, y2=0, where=None, **kwargs)
44394437
44404438
Create a :class:`~matplotlib.collections.PolyCollection`
44414439
filling the regions between *y1* and *y2* where
44424440
``where==True``
44434441
4444-
*x* :
4442+
Parameters
4443+
----------
4444+
x : array
44454445
An N-length array of the x data
44464446
4447-
*y1* :
4447+
y1 : array
44484448
An N-length array (or scalar) of the y data
44494449
4450-
*y2* :
4450+
y2 : array
44514451
An N-length array (or scalar) of the y data
44524452
4453-
*where* :
4454-
If *None*, default to fill between everywhere. If not *None*,
4453+
where : array, optional
4454+
If `None`, default to fill between everywhere. If not `None`,
44554455
it is an N-length numpy boolean array and the fill will
44564456
only happen over the regions where ``where==True``.
44574457
4458-
*interpolate* :
4459-
If *True*, interpolate between the two lines to find the
4458+
interpolate : bool, optional
4459+
If `True`, interpolate between the two lines to find the
44604460
precise point of intersection. Otherwise, the start and
44614461
end points of the filled region will only occur on explicit
44624462
values in the *x* array.
44634463
4464-
*kwargs* :
4465-
Keyword args passed on to the
4466-
:class:`~matplotlib.collections.PolyCollection`.
4464+
step : {'pre', 'post', 'mid'}, optional
4465+
If not None, fill with step logic.
4466+
4467+
4468+
Notes
4469+
-----
4470+
4471+
Additional Keyword args passed on to the
4472+
:class:`~matplotlib.collections.PolyCollection`.
44674473
44684474
kwargs control the :class:`~matplotlib.patches.Polygon` properties:
44694475
44704476
%(PolyCollection)s
44714477
4478+
Examples
4479+
--------
4480+
44724481
.. plot:: mpl_examples/pylab_examples/fill_between_demo.py
44734482
4474-
.. seealso::
4483+
See Also
4484+
--------
44754485
44764486
:meth:`fill_betweenx`
44774487
for filling between two sets of x-values
@@ -4508,6 +4518,9 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
45084518
xslice = x[ind0:ind1]
45094519
y1slice = y1[ind0:ind1]
45104520
y2slice = y2[ind0:ind1]
4521+
if step is not None:
4522+
step_func = STEP_LOOKUP_MAP[step]
4523+
xslice, y1slice, y2slice = step_func(xslice, y1slice, y2slice)
45114524

45124525
if not len(xslice):
45134526
continue
@@ -4568,7 +4581,8 @@ def get_interp_point(ind):
45684581
return collection
45694582

45704583
@docstring.dedent_interpd
4571-
def fill_betweenx(self, y, x1, x2=0, where=None, **kwargs):
4584+
def fill_betweenx(self, y, x1, x2=0, where=None,
4585+
step=None, **kwargs):
45724586
"""
45734587
Make filled polygons between two horizontal curves.
45744588
@@ -4580,31 +4594,42 @@ def fill_betweenx(self, y, x1, x2=0, where=None, **kwargs):
45804594
filling the regions between *x1* and *x2* where
45814595
``where==True``
45824596
4583-
*y* :
4597+
Parameters
4598+
----------
4599+
y : array
45844600
An N-length array of the y data
45854601
4586-
*x1* :
4602+
x1 : array
45874603
An N-length array (or scalar) of the x data
45884604
4589-
*x2* :
4605+
x2 : array, optional
45904606
An N-length array (or scalar) of the x data
45914607
4592-
*where* :
4593-
If *None*, default to fill between everywhere. If not *None*,
4594-
it is a N length numpy boolean array and the fill will
4595-
only happen over the regions where ``where==True``
4608+
where : array, optional
4609+
If *None*, default to fill between everywhere. If not *None*,
4610+
it is a N length numpy boolean array and the fill will
4611+
only happen over the regions where ``where==True``
45964612
4597-
*kwargs* :
4598-
keyword args passed on to the
4613+
step : {'pre', 'post', 'mid'}, optional
4614+
If not None, fill with step logic.
4615+
4616+
Notes
4617+
-----
4618+
4619+
keyword args passed on to the
45994620
:class:`~matplotlib.collections.PolyCollection`
46004621
46014622
kwargs control the :class:`~matplotlib.patches.Polygon` properties:
46024623
46034624
%(PolyCollection)s
46044625
4626+
Examples
4627+
--------
4628+
46054629
.. plot:: mpl_examples/pylab_examples/fill_betweenx_demo.py
46064630
4607-
.. seealso::
4631+
See Also
4632+
--------
46084633
46094634
:meth:`fill_between`
46104635
for filling between two sets of y-values
@@ -4641,6 +4666,9 @@ def fill_betweenx(self, y, x1, x2=0, where=None, **kwargs):
46414666
yslice = y[ind0:ind1]
46424667
x1slice = x1[ind0:ind1]
46434668
x2slice = x2[ind0:ind1]
4669+
if step is not None:
4670+
step_func = STEP_LOOKUP_MAP[step]
4671+
yslice, x1slice, x2slice = step_func(yslice, x1slice, x2slice)
46444672

46454673
if not len(yslice):
46464674
continue

0 commit comments

Comments
 (0)