-
-
Notifications
You must be signed in to change notification settings - Fork 8k
step-between as drawstyle [Alternative approach to #15019] #15065
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
c670099
f9d9d43
723dcdd
4e464eb
ce9fbe4
138d14c
028d618
aab6c1b
babca7e
5f70d07
46c2263
5264dea
7ed54a6
b0a6244
bf04d4c
f9634b2
65f0282
9cc16c1
814f180
ce92ba5
8b08a8e
3b73e4f
0d9dec7
ef10ca4
72598d6
bc3b19b
dbc3e92
80c348b
023ebe3
290be4a
46791b7
1989735
94799fd
c26aa11
b4cae0d
3fd3b43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
------- | ||
|
@@ -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) | ||
|
||
|
@@ -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: | ||
|
@@ -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] | ||
andrzejnovak marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
on the interval ``[x[i], x[i+1])`` | ||
|
||
Other Parameters | ||
---------------- | ||
|
@@ -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')): | ||
|
@@ -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)) | ||
|
@@ -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: | ||
|
||
pad_size = x.size | ||
y1 = np.broadcast_to(y1, pad_size, subok=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
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, | ||
andrzejnovak marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
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: | ||
|
@@ -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, | ||
|
@@ -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: | ||
|
@@ -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 | ||
---------------- | ||
|
@@ -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 | ||
|
@@ -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: | ||
|
@@ -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, | ||
|
Uh oh!
There was an error while loading. Please reload this page.