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

Skip to content

Commit 4735755

Browse files
authored
Merge pull request #18511 from andrzejnovak/baselinearr
feat: StepPatch to take array as baseline
2 parents 6970023 + 998c648 commit 4735755

File tree

5 files changed

+69
-67
lines changed

5 files changed

+69
-67
lines changed

examples/lines_bars_and_markers/stairs_demo.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@
4343
ax.legend()
4444
plt.show()
4545

46+
#############################################################################
47+
# *baseline* can take an array to allow for stacked histogram plots
48+
A = [[0, 0, 0],
49+
[1, 2, 3],
50+
[2, 4, 6],
51+
[3, 6, 9]]
52+
53+
for i in range(len(A) - 1):
54+
plt.stairs(A[i+1], baseline=A[i], fill=True)
55+
4656
#############################################################################
4757
# Comparison of `.pyplot.step` and `.pyplot.stairs`
4858
# -------------------------------------------------

lib/matplotlib/axes/_axes.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6730,7 +6730,8 @@ def hist(self, x, bins=None, range=None, density=False, weights=None,
67306730
def stairs(self, values, edges=None, *,
67316731
orientation='vertical', baseline=0, fill=False, **kwargs):
67326732
"""
6733-
A stepwise constant line or filled plot.
6733+
A stepwise constant function as a line with bounding edges
6734+
or a filled plot.
67346735
67356736
Parameters
67366737
----------
@@ -6745,9 +6746,11 @@ def stairs(self, values, edges=None, *,
67456746
The direction of the steps. Vertical means that *values* are along
67466747
the y-axis, and edges are along the x-axis.
67476748
6748-
baseline : float or None, default: 0
6749-
Determines starting value of the bounding edges or when
6750-
``fill=True``, position of lower edge.
6749+
baseline : float, array-like or None, default: 0
6750+
The bottom value of the bounding edges or when
6751+
``fill=True``, position of lower edge. If *fill* is
6752+
True or an array is passed to *baseline*, a closed
6753+
path is drawn.
67516754
67526755
fill : bool, default: False
67536756
Whether the area under the step curve should be filled.
@@ -6776,8 +6779,8 @@ def stairs(self, values, edges=None, *,
67766779
if edges is None:
67776780
edges = np.arange(len(values) + 1)
67786781

6779-
edges, values = self._process_unit_info(
6780-
[("x", edges), ("y", values)], kwargs)
6782+
edges, values, baseline = self._process_unit_info(
6783+
[("x", edges), ("y", values), ("y", baseline)], kwargs)
67816784

67826785
patch = mpatches.StepPatch(values,
67836786
edges,
@@ -6789,9 +6792,9 @@ def stairs(self, values, edges=None, *,
67896792
if baseline is None:
67906793
baseline = 0
67916794
if orientation == 'vertical':
6792-
patch.sticky_edges.y.append(baseline)
6795+
patch.sticky_edges.y.append(np.min(baseline))
67936796
else:
6794-
patch.sticky_edges.x.append(baseline)
6797+
patch.sticky_edges.x.append(np.min(baseline))
67956798
self._request_autoscale_view()
67966799
return patch
67976800

lib/matplotlib/patches.py

Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import math
55
from numbers import Number
66
import textwrap
7+
from collections import namedtuple
78

89
import numpy as np
910

@@ -926,7 +927,8 @@ class StepPatch(PathPatch):
926927
"""
927928
A path patch describing a stepwise constant function.
928929
929-
The path is unclosed. It starts and stops at baseline.
930+
By default the path is not closed and starts and stops at
931+
baseline value.
930932
"""
931933

932934
_edge_default = False
@@ -945,21 +947,23 @@ def __init__(self, values, edges, *,
945947
between which the curve takes on vals values.
946948
947949
orientation : {'vertical', 'horizontal'}, default: 'vertical'
948-
The direction of the steps. Vertical means that *values* are along
949-
the y-axis, and edges are along the x-axis.
950+
The direction of the steps. Vertical means that *values* are
951+
along the y-axis, and edges are along the x-axis.
950952
951-
baseline : float or None, default: 0
952-
Determines starting value of the bounding edges or when
953-
``fill=True``, position of lower edge.
953+
baseline : float, array-like or None, default: 0
954+
The bottom value of the bounding edges or when
955+
``fill=True``, position of lower edge. If *fill* is
956+
True or an array is passed to *baseline*, a closed
957+
path is drawn.
954958
955959
Other valid keyword arguments are:
956960
957961
%(Patch)s
958962
"""
959-
self.baseline = baseline
960963
self.orientation = orientation
961964
self._edges = np.asarray(edges)
962965
self._values = np.asarray(values)
966+
self._baseline = np.asarray(baseline) if baseline is not None else None
963967
self._update_path()
964968
super().__init__(self._path, **kwargs)
965969

@@ -972,13 +976,24 @@ def _update_path(self):
972976
f"`len(values) = {self._values.size}` and "
973977
f"`len(edges) = {self._edges.size}`.")
974978
verts, codes = [], []
975-
for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._values)):
979+
980+
_nan_mask = np.isnan(self._values)
981+
if self._baseline is not None:
982+
_nan_mask |= np.isnan(self._baseline)
983+
for idx0, idx1 in cbook.contiguous_regions(~_nan_mask):
976984
x = np.repeat(self._edges[idx0:idx1+1], 2)
977985
y = np.repeat(self._values[idx0:idx1], 2)
978-
if self.baseline is not None:
979-
y = np.hstack((self.baseline, y, self.baseline))
980-
else:
986+
if self._baseline is None:
981987
y = np.hstack((y[0], y, y[-1]))
988+
elif self._baseline.ndim == 0: # single baseline value
989+
y = np.hstack((self._baseline, y, self._baseline))
990+
elif self._baseline.ndim == 1: # baseline array
991+
base = np.repeat(self._baseline[idx0:idx1], 2)[::-1]
992+
x = np.concatenate([x, x[::-1]])
993+
y = np.concatenate([np.hstack((base[-1], y, base[0],
994+
base[0], base, base[-1]))])
995+
else: # no baseline
996+
raise ValueError('Invalid `baseline` specified')
982997
if self.orientation == 'vertical':
983998
xy = np.column_stack([x, y])
984999
else:
@@ -988,59 +1003,29 @@ def _update_path(self):
9881003
self._path = Path(np.vstack(verts), np.hstack(codes))
9891004

9901005
def get_data(self):
991-
"""Get `.StepPatch` values and edges."""
992-
return self._values, self._edges
1006+
"""Get `.StepPatch` values, edges and baseline as namedtuple."""
1007+
StairData = namedtuple('StairData', 'values edges baseline')
1008+
return StairData(self._values, self._edges, self._baseline)
9931009

994-
def set_data(self, values, edges=None):
1010+
def set_data(self, values=None, edges=None, baseline=None):
9951011
"""
996-
Set `.StepPatch` values and optionally edges.
1012+
Set `.StepPatch` values, edges and baseline.
9971013
9981014
Parameters
9991015
----------
10001016
values : 1D array-like or None
10011017
Will not update values, if passing None
10021018
edges : 1D array-like, optional
1019+
baseline : float, 1D array-like or None
10031020
"""
1021+
if values is None and edges is None and baseline is None:
1022+
raise ValueError("Must set *values*, *edges* or *baseline*.")
10041023
if values is not None:
10051024
self._values = np.asarray(values)
10061025
if edges is not None:
10071026
self._edges = np.asarray(edges)
1008-
self._update_path()
1009-
self.stale = True
1010-
1011-
def set_values(self, values):
1012-
"""
1013-
Set `.StepPatch` values.
1014-
1015-
Parameters
1016-
----------
1017-
values : 1D array-like
1018-
"""
1019-
self.set_data(values, edges=None)
1020-
1021-
def set_edges(self, edges):
1022-
"""
1023-
Set `.StepPatch` edges.
1024-
1025-
Parameters
1026-
----------
1027-
edges : 1D array-like
1028-
"""
1029-
self.set_data(None, edges=edges)
1030-
1031-
def get_baseline(self):
1032-
"""Get `.StepPatch` baseline value."""
1033-
return self.baseline
1034-
1035-
def set_baseline(self, baseline):
1036-
"""
1037-
Set `.StepPatch` baseline value.
1038-
1039-
Parameters
1040-
----------
1041-
baseline : float or None
1042-
"""
1043-
self.baseline = baseline
1027+
if baseline is not None:
1028+
self._baseline = np.asarray(baseline)
10441029
self._update_path()
10451030
self.stale = True
10461031

lib/matplotlib/tests/test_axes.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,15 +1892,15 @@ def test_stairs_update(fig_test, fig_ref):
18921892
test_ax = fig_test.add_subplot()
18931893
h = test_ax.stairs([1, 2, 3])
18941894
test_ax.set_ylim(ylim)
1895-
h.set_values([3, 2, 1])
1896-
h.set_edges(np.arange(4)+2)
1895+
h.set_data([3, 2, 1])
1896+
h.set_data(edges=np.arange(4)+2)
18971897
h.set_data([1, 2, 1], np.arange(4)/2)
18981898
h.set_data([1, 2, 3])
18991899
h.set_data(None, np.arange(4))
19001900
assert np.allclose(h.get_data()[0], np.arange(1, 4))
19011901
assert np.allclose(h.get_data()[1], np.arange(4))
1902-
h.set_baseline(-2)
1903-
assert h.get_baseline() == -2
1902+
h.set_data(baseline=-2)
1903+
assert h.get_data().baseline == -2
19041904

19051905
# Ref
19061906
ref_ax = fig_ref.add_subplot()
@@ -1921,13 +1921,13 @@ def test_stairs_invalid_mismatch():
19211921
def test_stairs_invalid_update():
19221922
h = plt.stairs([1, 2], [0, 1, 2])
19231923
with pytest.raises(ValueError, match='Nan values in "edges"'):
1924-
h.set_edges([1, np.nan, 2])
1924+
h.set_data(edges=[1, np.nan, 2])
19251925

19261926

19271927
def test_stairs_invalid_update2():
19281928
h = plt.stairs([1, 2], [0, 1, 2])
19291929
with pytest.raises(ValueError, match='Size mismatch'):
1930-
h.set_edges(np.arange(5))
1930+
h.set_data(edges=np.arange(5))
19311931

19321932

19331933
@image_comparison(['test_stairs_options.png'], remove_text=True)
@@ -1943,10 +1943,14 @@ def test_stairs_options():
19431943
ax.stairs(yn, x, color='orange', ls='--', lw=2, label="C")
19441944
ax.stairs(yn/3, x*3-2, ls='--', lw=2, baseline=0.5,
19451945
orientation='horizontal', label="D")
1946-
ax.stairs(y[::-1]*3+12, x, color='red', ls='--', lw=2, baseline=None,
1946+
ax.stairs(y[::-1]*3+13, x-1, color='red', ls='--', lw=2, baseline=None,
19471947
label="E")
1948+
ax.stairs(y[::-1]*3+14, x, baseline=26,
1949+
color='purple', ls='--', lw=2, label="F")
1950+
ax.stairs(yn[::-1]*3+15, x+1, baseline=np.linspace(27, 25, len(y)),
1951+
color='blue', ls='--', lw=2, label="G", fill=True)
19481952
ax.stairs(y[:-1][::-1]*2+11, x[:-1]+0.5, color='black', ls='--', lw=2,
1949-
baseline=12, hatch='//', label="F")
1953+
baseline=12, hatch='//', label="H")
19501954
ax.legend(loc=0)
19511955

19521956

0 commit comments

Comments
 (0)