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

Skip to content

Commit 1790bd1

Browse files
committed
Add where="between"/"edges" to step and step="between" to fill_between
1 parent 0b797a1 commit 1790bd1

File tree

9 files changed

+285
-21
lines changed

9 files changed

+285
-21
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
step() and fill_between() take a new option where/step="between"
2+
-------------------------------------------------------------------
3+
4+
Previously one would need to trick step() and fill_between() to plot
5+
data where x has one point than y, typically when plotting pre-binned
6+
histograms.
7+
8+
step() now takes where="between" for x, y satisfying either
9+
len(x) + 1 = len(y) or len(x) = len(y) + 1. Plotting a step line "between"
10+
specified edges in either direction. Convenience option where="edges" is
11+
added to close the shape. New kwarg bottom is added to add an underlying
12+
offset.
13+
14+
fill_between() now takes step="between" for x, y satisfying
15+
len(x) + 1 = len(y). Plotting fill "between" specified edges.
16+
17+
fill_betweenx() now takes step="between" for x, y satisfying
18+
len(x) = len(y) + 1. Plotting fill "between" specified edges.

examples/lines_bars_and_markers/filled_step.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""
22
=========================
3-
Hatch-filled histograms
3+
Filled histograms
44
=========================
55
6-
Hatching capabilities for plotting histograms.
6+
Filled histograms and hatching capabilities for plotting histograms.
77
"""
88

99
import itertools
@@ -15,6 +15,28 @@
1515
import matplotlib.ticker as mticker
1616
from cycler import cycler
1717

18+
###############################################################################
19+
# Plain filled steps
20+
21+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True)
22+
ax1.fill_between([0, 1, 2, 3], [1, 2, 3], step='between')
23+
ax2.fill_betweenx([0, 1, 2, 3], [0, 1, 2], step='between')
24+
ax1.set_ylabel('counts')
25+
ax1.set_xlabel('edges')
26+
ax2.set_xlabel('counts')
27+
ax2.set_ylabel('edges')
28+
29+
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True)
30+
ax1.fill_between([0, 1, 2, 3], [1, 2, 3], [0, 1, 0], step='between')
31+
ax2.fill_betweenx([0, 1, 2, 3], [1, 2, 3], [0, 1, 0], step='between')
32+
ax1.set_ylabel('counts')
33+
ax1.set_xlabel('edges')
34+
ax2.set_xlabel('counts')
35+
ax2.set_ylabel('edges')
36+
37+
38+
###############################################################################
39+
# Hatches
1840

1941
def filled_hist(ax, edges, values, bottoms=None, orientation='v',
2042
**kwargs):

examples/lines_bars_and_markers/step_demo.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@
2929
plt.legend(title='Parameter where:')
3030
plt.show()
3131

32+
# Plotting with where='between'/'edges'
33+
values = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist
34+
edges = np.array([1., 2., 3., 4., 5., 6., 7., 8., 9.]) # bins
35+
bottom = -10
36+
fig, axes = plt.subplots(4, 2)
37+
axes = axes.flatten()
38+
axes[0].step(edges, values, where='between')
39+
axes[1].step(values, edges, where='between')
40+
axes[2].step(edges, values, where='edges')
41+
axes[3].step(values, edges, where='edges')
42+
axes[4].step(edges, values, where='edges', bottom=bottom)
43+
axes[5].step(values, edges, where='edges', bottom=bottom)
44+
axes[6].step(edges, values, where='edges', bottom=bottom)
45+
axes[6].semilogy()
46+
axes[7].step(edges, values, where='edges', bottom=bottom)
47+
axes[7].semilogy()
48+
49+
fig.show()
50+
3251
#############################################################################
3352
#
3453
# ------------

lib/matplotlib/axes/_axes.py

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2043,7 +2043,8 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none,
20432043
#### Specialized plotting
20442044

20452045
# @_preprocess_data() # let 'plot' do the unpacking..
2046-
def step(self, x, y, *args, where='pre', data=None, **kwargs):
2046+
def step(self, x, y, *args, where='pre', data=None,
2047+
bottom=0, **kwargs):
20472048
"""
20482049
Make a step plot.
20492050
@@ -2079,7 +2080,8 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
20792080
An object with labelled data. If given, provide the label names to
20802081
plot in *x* and *y*.
20812082
2082-
where : {'pre', 'post', 'mid'}, optional, default 'pre'
2083+
where : {'pre', 'post', 'mid', 'between', 'edges'}, optional
2084+
Default 'pre'
20832085
Define where the steps should be placed:
20842086
20852087
- 'pre': The y value is continued constantly to the left from
@@ -2089,6 +2091,14 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
20892091
every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the
20902092
value ``y[i]``.
20912093
- 'mid': Steps occur half-way between the *x* positions.
2094+
- 'between': Expects len(x) = len(y) + 1, steps have y[i] value on
2095+
the interval ``[x[i], x[i+1])``
2096+
- 'edges': Expects len(x) = len(y) + 1, steps have y[i] value on
2097+
the interval ``[x[i], x[i+1]), shape is closed at x[0], x[-1]``
2098+
2099+
bottom : scalar, optional, default : 0
2100+
If plotting with 'edges', sets low bound.
2101+
20922102
20932103
Returns
20942104
-------
@@ -2104,7 +2114,31 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
21042114
-----
21052115
.. [notes section required to get data note injection right]
21062116
"""
2107-
cbook._check_in_list(('pre', 'post', 'mid'), where=where)
2117+
cbook._check_in_list(('pre', 'post', 'mid', 'between', 'edges'),
2118+
where=where)
2119+
2120+
if where in ['between', 'edges']:
2121+
if len(x) == len(y) or abs(len(x)-len(y)) > 1:
2122+
raise ValueError(f"When plotting with 'between' or 'edges'"
2123+
f"input sizes have to be have to satisfy "
2124+
f"len(x) + 1 == len(y) or "
2125+
f"len(x) == len(y) + 1 but x "
2126+
f"and y have size {len(x)} and {len(y)}")
2127+
2128+
if where == 'edges':
2129+
if len(x) + 1 == len(y):
2130+
self.add_line(
2131+
mlines.Line2D([bottom, x[0]], [y[0], y[0]]))
2132+
self.add_line(
2133+
mlines.Line2D([bottom, x[-1]], [y[-1], y[-1]]))
2134+
elif len(x) == len(y) + 1:
2135+
self.add_line(
2136+
mlines.Line2D([x[0], x[0]], [bottom, y[0]]))
2137+
self.add_line(
2138+
mlines.Line2D([x[-1], x[-1]], [bottom, y[-1]]))
2139+
2140+
where = 'between'
2141+
21082142
kwargs['drawstyle'] = 'steps-' + where
21092143
return self.plot(x, y, *args, data=data, **kwargs)
21102144

@@ -5130,11 +5164,23 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
51305164
kwargs['facecolor'] = \
51315165
self._get_patches_for_fill.get_next_color()
51325166

5167+
if step in ['between']:
5168+
if not len(x) == len(y1) + 1:
5169+
raise ValueError(f"When plotting with 'between' "
5170+
f"input sizes have to be have to satisfy "
5171+
f"len(x) == len(y1) + 1, but x "
5172+
f"and y1 have size {len(x)} and {len(y1)}")
5173+
else:
5174+
histlike = True
5175+
else:
5176+
histlike = False
5177+
51335178
# Handle united data, such as dates
51345179
self._process_unit_info(xdata=x, ydata=y1, kwargs=kwargs)
51355180
self._process_unit_info(ydata=y2)
51365181

51375182
# Convert the arrays so we can work with them
5183+
51385184
x = ma.masked_invalid(self.convert_xunits(x))
51395185
y1 = ma.masked_invalid(self.convert_yunits(y1))
51405186
y2 = ma.masked_invalid(self.convert_yunits(y2))
@@ -5154,10 +5200,16 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
51545200
message="The parameter where must have the same size as x "
51555201
"in fill_between(). This will become an error in "
51565202
"future versions of Matplotlib.")
5157-
where = where & ~functools.reduce(np.logical_or,
5158-
map(np.ma.getmask, [x, y1, y2]))
51595203

5160-
x, y1, y2 = np.broadcast_arrays(np.atleast_1d(x), y1, y2)
5204+
if histlike:
5205+
y_arrays = [np.r_[y1, np.nan],
5206+
np.r_[y2, np.nan] if not y2.shape == () else y2]
5207+
else:
5208+
y_arrays = [y1, y2]
5209+
5210+
where = where & ~functools.reduce(np.logical_or, map(np.ma.getmask,
5211+
[x, *y_arrays]))
5212+
x, y1, y2 = np.broadcast_arrays(np.atleast_1d(x), *y_arrays)
51615213

51625214
polys = []
51635215
for ind0, ind1 in cbook.contiguous_regions(where):
@@ -5319,6 +5371,17 @@ def fill_betweenx(self, y, x1, x2=0, where=None,
53195371
kwargs['facecolor'] = \
53205372
self._get_patches_for_fill.get_next_color()
53215373

5374+
if step in ['between']:
5375+
if not len(y) == len(x1) + 1:
5376+
raise ValueError(f"When plotting with 'between' "
5377+
f"input sizes have to be have to satisfy "
5378+
f"len(y) == len(x1) + 1, but y "
5379+
f"and x1 have size {len(y)} and {len(x1)}")
5380+
else:
5381+
histlike = True
5382+
else:
5383+
histlike = False
5384+
53225385
# Handle united data, such as dates
53235386
self._process_unit_info(ydata=y, xdata=x1, kwargs=kwargs)
53245387
self._process_unit_info(xdata=x2)
@@ -5343,10 +5406,16 @@ def fill_betweenx(self, y, x1, x2=0, where=None,
53435406
message="The parameter where must have the same size as y "
53445407
"in fill_between(). This will become an error in "
53455408
"future versions of Matplotlib.")
5346-
where = where & ~functools.reduce(np.logical_or,
5347-
map(np.ma.getmask, [y, x1, x2]))
53485409

5349-
y, x1, x2 = np.broadcast_arrays(np.atleast_1d(y), x1, x2)
5410+
if histlike:
5411+
x_arrays = [np.r_[x1, np.nan],
5412+
np.r_[x2, np.nan] if not x2.shape == () else x2]
5413+
else:
5414+
x_arrays = [x1, x2]
5415+
5416+
where = where & ~functools.reduce(np.logical_or, map(np.ma.getmask,
5417+
[y, *x_arrays]))
5418+
y, x1, x2 = np.broadcast_arrays(np.atleast_1d(y), *x_arrays)
53505419

53515420
polys = []
53525421
for ind0, ind1 in cbook.contiguous_regions(where):

lib/matplotlib/axes/_base.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,10 @@ def _plot_args(self, tup, kwargs):
338338
if self.axes.yaxis is not None:
339339
self.axes.yaxis.update_units(y)
340340

341-
if x.shape[0] != y.shape[0]:
342-
raise ValueError(f"x and y must have same first dimension, but "
343-
f"have shapes {x.shape} and {y.shape}")
341+
if not kwargs.get('drawstyle') in ['steps-between']:
342+
if x.shape[0] != y.shape[0]:
343+
raise ValueError(f"x and y must have same first dimension, but"
344+
f" have shapes {x.shape} and {y.shape}")
344345
if x.ndim > 2 or y.ndim > 2:
345346
raise ValueError(f"x and y can be no greater than 2-D, but have "
346347
f"shapes {x.shape} and {y.shape}")

lib/matplotlib/cbook/__init__.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,56 @@ def pts_to_prestep(x, *args):
14981498
return steps
14991499

15001500

1501+
def pts_to_betweenstep(x, *args):
1502+
"""
1503+
Convert continuous line to between-steps.
1504+
1505+
Given a set of ``N`` points convert to ``2N + 1`` points, which when
1506+
connected linearly give a step function which changes values at the end of
1507+
the intervals, keeping a values between steps.
1508+
1509+
Parameters
1510+
----------
1511+
x : array
1512+
The x location of the step edges. May be empty.
1513+
1514+
y1, ..., yp : array
1515+
y arrays to be turned into steps; must have length as ``x`` +/- 1.
1516+
Returns
1517+
-------
1518+
out : array
1519+
The x and y values converted to steps in the same order as the input;
1520+
can be unpacked as ``x_out, y1_out, ..., yp_out``. If the input is
1521+
length ``N``, each of these arrays will be length ``2N + 1``. For
1522+
``N=0``, the length will be 0.
1523+
1524+
Examples
1525+
--------
1526+
>>> x_s, y1_s, y2_s = pts_to_poststep(x, y1, y2)
1527+
"""
1528+
1529+
# Replace padded Nans
1530+
def nan_replace(k):
1531+
k.setflags(write=1)
1532+
if np.isnan(k[0]):
1533+
k[0] = k[1]
1534+
if np.isnan(k[-1]):
1535+
k[-1] = k[-2]
1536+
return k
1537+
1538+
steps = np.zeros((1 + len(args), max(2 * len(x) - 1, 0)))
1539+
1540+
for arg in args:
1541+
arg = nan_replace(arg)
1542+
x = nan_replace(x)
1543+
1544+
steps[0, 0::2] = x
1545+
steps[0, 1::2] = steps[0, 2::2]
1546+
steps[1:, 0::2] = args
1547+
steps[1:, 1::2] = steps[1:, 0:-2:2]
1548+
return steps
1549+
1550+
15011551
def pts_to_poststep(x, *args):
15021552
"""
15031553
Convert continuous line to post-steps.
@@ -1513,7 +1563,6 @@ def pts_to_poststep(x, *args):
15131563
15141564
y1, ..., yp : array
15151565
y arrays to be turned into steps; all must be the same length as ``x``.
1516-
15171566
Returns
15181567
-------
15191568
out : array
@@ -1576,7 +1625,8 @@ def pts_to_midstep(x, *args):
15761625
'steps': pts_to_prestep,
15771626
'steps-pre': pts_to_prestep,
15781627
'steps-post': pts_to_poststep,
1579-
'steps-mid': pts_to_midstep}
1628+
'steps-mid': pts_to_midstep,
1629+
'steps-between': pts_to_betweenstep}
15801630

15811631

15821632
def index_of(y):

lib/matplotlib/lines.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ class Line2D(Artist):
232232
'steps-mid': '_draw_steps_mid',
233233
'steps-pre': '_draw_steps_pre',
234234
'steps-post': '_draw_steps_post',
235+
'steps-between': '_draw_steps_between',
235236
}
236237

237238
_drawStyles_s = {
@@ -673,7 +674,13 @@ def recache(self, always=False):
673674
else:
674675
y = self._y
675676

676-
self._xy = np.column_stack(np.broadcast_arrays(x, y)).astype(float)
677+
if len(x) == len(y) + 1 and self._drawstyle == "steps-between":
678+
xy_boardcast = np.broadcast_arrays(x, np.r_[y, np.nan])
679+
elif 1 + len(x) == len(y) and self._drawstyle == "steps-between":
680+
xy_boardcast = np.broadcast_arrays(np.r_[np.nan, x], y)
681+
else:
682+
xy_boardcast = np.broadcast_arrays(x, y)
683+
self._xy = np.column_stack(xy_boardcast).astype(float)
677684
self._x, self._y = self._xy.T # views
678685

679686
self._subslice = False
@@ -696,7 +703,10 @@ def recache(self, always=False):
696703
interpolation_steps = self._path._interpolation_steps
697704
else:
698705
interpolation_steps = 1
699-
xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T)
706+
if self._drawstyle in ['steps-between']:
707+
xy = STEP_LOOKUP_MAP[self._drawstyle](self._x, self._y)
708+
else:
709+
xy = STEP_LOOKUP_MAP[self._drawstyle](*self._xy.T)
700710
self._path = Path(np.asarray(xy).T,
701711
_interpolation_steps=interpolation_steps)
702712
self._transformed_path = None
@@ -832,7 +842,7 @@ def draw(self, renderer):
832842
self.recache()
833843
self._transform_path(subslice)
834844
tpath, affine = (self._get_transformed_path()
835-
.get_transformed_points_and_affine())
845+
.get_transformed_points_and_affine())
836846
else:
837847
tpath, affine = (self._get_transformed_path()
838848
.get_transformed_points_and_affine())

lib/matplotlib/pyplot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2909,10 +2909,10 @@ def stem(
29092909

29102910
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
29112911
@docstring.copy(Axes.step)
2912-
def step(x, y, *args, where='pre', data=None, **kwargs):
2912+
def step(x, y, *args, where='pre', data=None, bottom=0, **kwargs):
29132913
return gca().step(
29142914
x, y, *args, where=where, **({"data": data} if data is not
2915-
None else {}), **kwargs)
2915+
None else {}), bottom=bottom, **kwargs)
29162916

29172917

29182918
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.

0 commit comments

Comments
 (0)