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

Skip to content
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c670099
Add where="between"/"edges" to step and step="between" to fill_between
andrzejnovak Aug 28, 2019
f9d9d43
Protect for scalars in fill_between
andrzejnovak Aug 28, 2019
723dcdd
Fix fill_between input handling
andrzejnovak Aug 28, 2019
4e464eb
clean up
andrzejnovak Aug 28, 2019
ce9fbe4
Add svg
andrzejnovak Aug 29, 2019
138d14c
Add fill between step test
andrzejnovak Aug 29, 2019
028d618
Fallback to post-step, when x,y equal len
andrzejnovak Aug 29, 2019
aab6c1b
Protect "edges" from Nan values
andrzejnovak Sep 16, 2019
babca7e
Fix subslicing, add test at edges
andrzejnovak Sep 19, 2019
5f70d07
Switch broadcasting back to np
andrzejnovak Sep 19, 2019
46c2263
Improve example
andrzejnovak Sep 19, 2019
5264dea
Update docs
andrzejnovak Sep 20, 2019
7ed54a6
Clean for speedup
andrzejnovak Sep 28, 2019
b0a6244
Pad where True
andrzejnovak Sep 28, 2019
bf04d4c
Merge branch 'master' into ModLine2D
andrzejnovak Dec 4, 2019
f9634b2
Merge branch 'master' into ModLine2D
andrzejnovak Dec 5, 2019
65f0282
Undo redundant padding
andrzejnovak Jan 2, 2020
9cc16c1
paddding not needed
andrzejnovak Jan 2, 2020
814f180
flake
andrzejnovak Jan 3, 2020
ce92ba5
Merge branch 'master' into ModLine2D
andrzejnovak Jan 3, 2020
8b08a8e
flake
andrzejnovak Jan 3, 2020
3b73e4f
Simplify between-step
andrzejnovak Jan 6, 2020
0d9dec7
flake
andrzejnovak Jan 6, 2020
ef10ca4
Merge branch 'master' into ModLine2D
andrzejnovak Jan 6, 2020
72598d6
Try to fix docs
andrzejnovak Jan 6, 2020
bc3b19b
flake
andrzejnovak Jan 7, 2020
dbc3e92
Merge branch 'master' into ModLine2D
andrzejnovak Jan 7, 2020
80c348b
no np.r_
andrzejnovak Jan 7, 2020
023ebe3
whitespace
andrzejnovak Jan 7, 2020
290be4a
Fix typos
andrzejnovak Jan 13, 2020
46791b7
Externalize polycollection prep into cbook
andrzejnovak Mar 6, 2020
1989735
Fix name
andrzejnovak Mar 6, 2020
94799fd
Merge branch 'master' into ModLine2D
andrzejnovak Mar 6, 2020
c26aa11
flake
andrzejnovak Mar 6, 2020
b4cae0d
private
andrzejnovak Mar 7, 2020
3fd3b43
private
andrzejnovak Mar 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions doc/users/next_whats_new/option_between_for_step_fill_between.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
step() and fill_between() take a new option where/step="between"
------------------------------------------------------------------------

Previously one would need to trick step() and fill_between() to plot
data where x has one more point than y, typically when plotting pre-binned
histograms.

step() now takes where="between" for x, y satisfying either
len(x) + 1 = len(y) or len(x) = len(y) + 1. Plotting a step line "between"
specified edges in either direction. Convenience option where="edges" is
added to close the shape.

fill_between() now takes step="between" for x, y satisfying
len(x) + 1 = len(y). Plotting fill "between" specified edges.

fill_betweenx() now takes step="between" for x, y satisfying
len(x) = len(y) + 1. Plotting fill "between" specified edges.
26 changes: 24 additions & 2 deletions examples/lines_bars_and_markers/filled_step.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""
=========================
Hatch-filled histograms
Filled histograms
=========================
Hatching capabilities for plotting histograms.
Filled histograms and hatching capabilities for plotting histograms.
"""

import itertools
Expand All @@ -15,6 +15,28 @@
import matplotlib.ticker as mticker
from cycler import cycler

###############################################################################
# Plain filled steps

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True)
ax1.fill_between([0, 1, 2, 3], [1, 2, 3], step='between')
ax2.fill_betweenx([0, 1, 2, 3], [0, 1, 2], step='between')
ax1.set_ylabel('counts')
ax1.set_xlabel('edges')
ax2.set_xlabel('counts')
ax2.set_ylabel('edges')

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True)
ax1.fill_between([0, 1, 2, 3], [1, 2, 3], [0, 1, 0], step='between')
ax2.fill_betweenx([0, 1, 2, 3], [1, 2, 3], [0, 1, 0], step='between')
ax1.set_ylabel('counts')
ax1.set_xlabel('edges')
ax2.set_xlabel('counts')
ax2.set_ylabel('edges')


###############################################################################
# Hatches

def filled_hist(ax, edges, values, bottoms=None, orientation='v',
**kwargs):
Expand Down
14 changes: 14 additions & 0 deletions examples/lines_bars_and_markers/step_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@
plt.legend(title='Parameter where:')
plt.show()

# Plotting with where='between'/'edges'
x = np.arange(0, 7, 1)
y = np.array([2, 3, 4, 5, 4, 3])

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
ax1.step(x, y + 2, where='between', label='between')
ax1.step(x, y, where='edges', label='edges')
ax1.legend(title='Parameter where:')

ax2.step(y + 2, x, where='between', label='between')
ax2.step(y, x, where='edges', label='edges')
ax2.legend(title='Parameter where:')

plt.show()
#############################################################################
#
# ------------
Expand Down
103 changes: 86 additions & 17 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2079,7 +2079,8 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
An object with labelled data. If given, provide the label names to
plot in *x* and *y*.

where : {'pre', 'post', 'mid'}, optional, default 'pre'
where : {'pre', 'post', 'mid', 'between', 'edges'}, optional
Default 'pre'
Define where the steps should be placed:

- 'pre': The y value is continued constantly to the left from
Expand All @@ -2089,6 +2090,10 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the
value ``y[i]``.
- 'mid': Steps occur half-way between the *x* positions.
- 'between': Expects abs(len(x)-len(y)) == 1, steps have value y[i]
on the interval ``[x[i], x[i+1])``
- 'edges': Expects abs(len(x)-len(y)) == 1, steps have value y[i]
on interval ``[x[i], x[i+1]), shape is closed at x[0], x[-1]``

Returns
-------
Expand All @@ -2104,7 +2109,17 @@ def step(self, x, y, *args, where='pre', data=None, **kwargs):
-----
.. [notes section required to get data note injection right]
"""
cbook._check_in_list(('pre', 'post', 'mid'), where=where)
cbook._check_in_list(('pre', 'post', 'mid', 'between', 'edges'),
where=where)

if where in ['between', 'edges']:
if len(x) == len(y) or abs(len(x)-len(y)) > 1:
raise ValueError(f"When plotting with 'between' or 'edges'"
f"input sizes have to be have to satisfy "
f"len(x) + 1 == len(y) or "
f"len(x) == len(y) + 1 but x "
f"and y have size {len(x)} and {len(y)}")

kwargs['drawstyle'] = 'steps-' + where
return self.plot(x, y, *args, data=data, **kwargs)

Expand Down Expand Up @@ -5089,7 +5104,7 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
Setting *interpolate* to *True* will calculate the actual
intersection point and extend the filled region up to this point.

step : {'pre', 'post', 'mid'}, optional
step : {'pre', 'post', 'mid', 'between'}, optional
Define *step* if the filling should be a step function,
i.e. constant in between *x*. The value determines where the
step will occur:
Expand All @@ -5101,6 +5116,8 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the
value ``y[i]``.
- 'mid': Steps occur half-way between the *x* positions.
- 'between': Expects abs(len(x)-len(y)) == 1, steps have value y[i]
on the interval ``[x[i], x[i+1])``

Other Parameters
----------------
Expand All @@ -5124,6 +5141,10 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
.. [notes section required to get data note injection right]

"""

cbook._check_in_list((None, 'pre', 'post', 'mid', 'between'),
step=step)

if not rcParams['_internal.classic_mode']:
kwargs = cbook.normalize_kwargs(kwargs, mcoll.Collection)
if not any(c in kwargs for c in ('color', 'facecolor')):
Expand All @@ -5135,6 +5156,7 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
self._process_unit_info(ydata=y2)

# Convert the arrays so we can work with them

x = ma.masked_invalid(self.convert_xunits(x))
y1 = ma.masked_invalid(self.convert_yunits(y1))
y2 = ma.masked_invalid(self.convert_yunits(y2))
Expand All @@ -5148,20 +5170,34 @@ def fill_between(self, x, y1, y2=0, where=None, interpolate=False,
where = True
else:
where = np.asarray(where, dtype=bool)
if where.size != x.size:
if where.size != x.size and step != 'between':
cbook.warn_deprecated(
"3.2",
message="The parameter where must have the same size as x "
"in fill_between(). This will become an error in "
"future versions of Matplotlib.")
where = where & ~functools.reduce(np.logical_or,
map(np.ma.getmask, [x, y1, y2]))

x, y1, y2 = np.broadcast_arrays(np.atleast_1d(x), y1, y2)
if step == 'between':
pad_size = x.size - 1
else:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing to me. If we are using between we don't need to do any broadcasting, right? Or if this is supposed to support x that is not a vector, then x.size-1 must not be correct? Maybe I'd need to play with this, but I'm not following what you are doing here.

Copy link
Contributor Author

@andrzejnovak andrzejnovak Jan 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used when setting y2 as scalar. x is not allowed to be scalar, but y1 could be.

pad_size = x.size
y1 = np.broadcast_to(y1, pad_size, subok=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've abstracted a lot of the logic, but still have logic outside your helper that is repeated here and in fill_betweenx. Is there a good reason for that?

y2 = np.broadcast_to(y2, pad_size, subok=True)
where = np.broadcast_to(where, pad_size, subok=True)

get_masks = cbook.pad_arrays(list(map(np.atleast_1d,
map(np.ma.getmask,
[y1, y2]))), False)

where = where & ~functools.reduce(np.logical_or, get_masks)

polys = []
for ind0, ind1 in cbook.contiguous_regions(where):
xslice = x[ind0:ind1]
pad_where = cbook.pad_arrays([where, x], False)[0].astype(bool)
for ind0, ind1 in cbook.contiguous_regions(pad_where):
if step == 'between':
xslice = x[ind0:ind1+1]
else:
xslice = x[ind0:ind1]
y1slice = y1[ind0:ind1]
y2slice = y2[ind0:ind1]
if step is not None:
Expand Down Expand Up @@ -5217,6 +5253,12 @@ def get_interp_point(ind):
collection = mcoll.PolyCollection(polys, **kwargs)

# now update the datalim and autoscale
# For between pad with mean value
if step == 'between':
y1, y2 = cbook.pad_arrays([y1, y2, x],
np.mean([np.mean(y1.flatten()),
np.mean(y2.flatten())]))[:-1]
where = cbook.pad_arrays([where, x], 1)[0].astype(bool)
XY1 = np.array([x[where], y1[where]]).T
XY2 = np.array([x[where], y2[where]]).T
self.dataLim.update_from_data_xy(XY1, self.ignore_existing_data_limits,
Expand Down Expand Up @@ -5278,7 +5320,7 @@ def fill_betweenx(self, y, x1, x2=0, where=None,
Setting *interpolate* to *True* will calculate the actual
intersection point and extend the filled region up to this point.

step : {'pre', 'post', 'mid'}, optional
step : {'pre', 'post', 'mid', 'between'}, optional
Define *step* if the filling should be a step function,
i.e. constant in between *y*. The value determines where the
step will occur:
Expand All @@ -5290,6 +5332,8 @@ def fill_betweenx(self, y, x1, x2=0, where=None,
every *x* position, i.e. the interval ``[x[i], x[i+1])`` has the
value ``y[i]``.
- 'mid': Steps occur half-way between the *x* positions.
- 'between': Expects abs(len(x)-len(y)) == 1, steps have value x[i]
on the interval ``[y[i], y[i+1])``

Other Parameters
----------------
Expand All @@ -5313,14 +5357,19 @@ def fill_betweenx(self, y, x1, x2=0, where=None,
.. [notes section required to get data note injection right]

"""

cbook._check_in_list((None, 'pre', 'post', 'mid', 'between'),
step=step)

if not rcParams['_internal.classic_mode']:
kwargs = cbook.normalize_kwargs(kwargs, mcoll.Collection)
if not any(c in kwargs for c in ('color', 'facecolor')):
kwargs['facecolor'] = \
self._get_patches_for_fill.get_next_color()

# Handle united data, such as dates
self._process_unit_info(ydata=y, xdata=x1, kwargs=kwargs)
self._process_unit_info(ydata=y, kwargs=kwargs)
self._process_unit_info(xdata=x1)
self._process_unit_info(xdata=x2)

# Convert the arrays so we can work with them
Expand All @@ -5337,20 +5386,34 @@ def fill_betweenx(self, y, x1, x2=0, where=None,
where = True
else:
where = np.asarray(where, dtype=bool)
if where.size != y.size:
if where.size != y.size and step != 'between':
cbook.warn_deprecated(
"3.2",
message="The parameter where must have the same size as y "
"in fill_between(). This will become an error in "
"future versions of Matplotlib.")
where = where & ~functools.reduce(np.logical_or,
map(np.ma.getmask, [y, x1, x2]))

y, x1, x2 = np.broadcast_arrays(np.atleast_1d(y), x1, x2)
if step == 'between':
pad_size = y.size - 1
else:
pad_size = y.size
x1 = np.broadcast_to(x1, pad_size, subok=True)
x2 = np.broadcast_to(x2, pad_size, subok=True)
where = np.broadcast_to(where, pad_size, subok=True)

get_masks = cbook.pad_arrays(list(map(np.atleast_1d,
map(np.ma.getmask,
[x1, x2]))), False)

where = where & ~functools.reduce(np.logical_or, get_masks)

polys = []
for ind0, ind1 in cbook.contiguous_regions(where):
yslice = y[ind0:ind1]
pad_where = cbook.pad_arrays([where, y], False)[0].astype(bool)
for ind0, ind1 in cbook.contiguous_regions(pad_where):
if step == 'between':
yslice = y[ind0:ind1+1]
else:
yslice = y[ind0:ind1]
x1slice = x1[ind0:ind1]
x2slice = x2[ind0:ind1]
if step is not None:
Expand Down Expand Up @@ -5405,6 +5468,12 @@ def get_interp_point(ind):
collection = mcoll.PolyCollection(polys, **kwargs)

# now update the datalim and autoscale
# For between pad with mean value
if step == 'between':
x1, x2 = cbook.pad_arrays([x1, x2, y],
np.mean([np.mean(x1.flatten()),
np.mean(x2.flatten())]))[:-1]
where = cbook.pad_arrays([where, y], 1)[0].astype(bool)
X1Y = np.array([x1[where], y[where]]).T
X2Y = np.array([x2[where], y[where]]).T
self.dataLim.update_from_data_xy(X1Y, self.ignore_existing_data_limits,
Expand Down
7 changes: 4 additions & 3 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,10 @@ def _plot_args(self, tup, kwargs):
if self.axes.yaxis is not None:
self.axes.yaxis.update_units(y)

if x.shape[0] != y.shape[0]:
raise ValueError(f"x and y must have same first dimension, but "
f"have shapes {x.shape} and {y.shape}")
if not kwargs.get('drawstyle') in ['steps-between', 'steps-edges']:
if x.shape[0] != y.shape[0]:
raise ValueError(f"x and y must have same first dimension, but"
f" have shapes {x.shape} and {y.shape}")
if x.ndim > 2 or y.ndim > 2:
raise ValueError(f"x and y can be no greater than 2-D, but have "
f"shapes {x.shape} and {y.shape}")
Expand Down
Loading