Skip to content

Commit 33f95db

Browse files
authored
Merge pull request #18275 from andrzejnovak/histline
feat: StepPatch
2 parents 1104326 + ecc54cb commit 33f95db

File tree

15 files changed

+492
-2
lines changed

15 files changed

+492
-2
lines changed

.flake8

+1
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ per-file-ignores =
163163
examples/lines_bars_and_markers/fill.py: E402
164164
examples/lines_bars_and_markers/fill_between_demo.py: E402
165165
examples/lines_bars_and_markers/filled_step.py: E402
166+
examples/lines_bars_and_markers/stairs_demo.py: E402
166167
examples/lines_bars_and_markers/horizontal_barchart_distribution.py: E402
167168
examples/lines_bars_and_markers/joinstyle.py: E402
168169
examples/lines_bars_and_markers/scatter_hist.py: E402

doc/api/artist_api.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
``matplotlib.artist``
55
*********************
66

7-
.. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.BrokenBarHCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ClabelText matplotlib.figure.Figure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.Wedge matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.CustomCell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text
7+
.. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.BrokenBarHCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ClabelText matplotlib.figure.Figure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.StepPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.Wedge matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.CustomCell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text
88
:parts: 1
99
:private-bases:
1010

doc/api/axes_api.rst

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ Binned
137137
Axes.hexbin
138138
Axes.hist
139139
Axes.hist2d
140+
Axes.stairs
140141

141142
Contours
142143
--------

doc/api/patches_api.rst

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Classes
2929
FancyBboxPatch
3030
Patch
3131
PathPatch
32+
StepPatch
3233
Polygon
3334
Rectangle
3435
RegularPolygon
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
New StepPatch artist and a stairs method
2+
----------------------------------------
3+
New `~.matplotlib.patches.StepPatch` artist and `.pyplot.stairs` method.
4+
For both the artist and the function, the x-like edges input is one
5+
longer than the y-like values input
6+
7+
.. plot::
8+
9+
import numpy as np
10+
import matplotlib.pyplot as plt
11+
12+
np.random.seed(0)
13+
h, bins = np.histogram(np.random.normal(5, 2, 5000),
14+
bins=np.linspace(0,10,20))
15+
16+
fig, ax = plt.subplots(constrained_layout=True)
17+
18+
ax.stairs(h, bins)
19+
20+
plt.show()
21+
22+
See :doc:`/gallery/lines_bars_and_markers/stairs_demo`
23+
for examples.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
===========
3+
Stairs Demo
4+
===========
5+
6+
This example demonstrates the use of `~.matplotlib.pyplot.stairs`
7+
for histogram and histogram-like data visualization and an associated
8+
underlying `.StepPatch` artist, which is a specialized version of
9+
`.PathPatch` specified by its bins and edges.
10+
11+
The primary difference to `~.matplotlib.pyplot.step` is that ``stairs``
12+
x-like edges input is one longer than its y-like values input.
13+
"""
14+
15+
import numpy as np
16+
import matplotlib.pyplot as plt
17+
from matplotlib.patches import StepPatch
18+
19+
np.random.seed(0)
20+
h, bins = np.histogram(np.random.normal(5, 3, 5000),
21+
bins=np.linspace(0, 10, 20))
22+
23+
fig, axs = plt.subplots(3, 1, figsize=(7, 15))
24+
axs[0].stairs(h, bins, label='Simple histogram')
25+
axs[0].stairs(h, bins+5, baseline=50, label='Modified baseline')
26+
axs[0].stairs(h, bins+10, baseline=None, label='No edges')
27+
axs[0].set_title("Step Histograms")
28+
29+
axs[1].stairs(np.arange(1, 6, 1), fill=True,
30+
label='Filled histogram\nw/ automatic edges')
31+
axs[1].stairs(np.arange(1, 6, 1)*0.3, np.arange(2, 8, 1),
32+
orientation='horizontal', hatch='//',
33+
label='Hatched histogram\nw/ horizontal orientation')
34+
axs[1].set_title("Filled histogram")
35+
36+
patch = StepPatch(values=[1, 2, 3, 2, 1],
37+
edges=range(1, 7),
38+
label=('Patch derived underlying object\n'
39+
'with default edge/facecolor behaviour'))
40+
axs[2].add_patch(patch)
41+
axs[2].set_xlim(0, 7)
42+
axs[2].set_ylim(-1, 5)
43+
axs[2].set_title("StepPatch artist")
44+
45+
for ax in axs:
46+
ax.legend()
47+
plt.show()
48+
49+
#############################################################################
50+
# Comparison of `.pyplot.step` and `.pyplot.stairs`.
51+
52+
bins = np.arange(14)
53+
centers = bins[:-1] + np.diff(bins)/2
54+
y = np.sin(centers / 2)
55+
56+
plt.step(bins[:-1], y, where='post', label='Step(where="post")')
57+
plt.plot(bins[:-1], y, 'o--', color='grey', alpha=0.3)
58+
59+
plt.stairs(y - 1, bins, baseline=None, label='Stairs')
60+
plt.plot(centers, y - 1, 'o--', color='grey', alpha=0.3)
61+
plt.plot(np.repeat(bins, 2), np.hstack([y[0], np.repeat(y, 2), y[-1]]) - 1,
62+
'o', color='red', alpha=0.2)
63+
64+
plt.legend()
65+
plt.title('plt.step vs plt.stairs')
66+
plt.show()
67+
68+
#############################################################################
69+
#
70+
# ------------
71+
#
72+
# References
73+
# """"""""""
74+
#
75+
# The use of the following functions, methods, classes and modules is shown
76+
# in this example:
77+
78+
import matplotlib
79+
matplotlib.axes.Axes.stairs
80+
matplotlib.pyplot.stairs
81+
matplotlib.patches.StepPatch

lib/matplotlib/axes/_axes.py

+67
Original file line numberDiff line numberDiff line change
@@ -6740,6 +6740,73 @@ def hist(self, x, bins=None, range=None, density=False, weights=None,
67406740
else "List[Polygon]")
67416741
return tops, bins, cbook.silent_list(patch_type, patches)
67426742

6743+
@_preprocess_data()
6744+
def stairs(self, values, edges=None, *,
6745+
orientation='vertical', baseline=0, fill=False, **kwargs):
6746+
"""
6747+
A histogram-like line or filled plot.
6748+
6749+
Parameters
6750+
----------
6751+
values : array-like
6752+
An array of y-values.
6753+
6754+
edges : array-like, default: ``range(len(vals)+1)``
6755+
A array of x-values, with ``len(edges) == len(vals) + 1``,
6756+
between which the curve takes on vals values.
6757+
6758+
orientation : {'vertical', 'horizontal'}, default: 'vertical'
6759+
6760+
baseline : float or None, default: 0
6761+
Determines starting value of the bounding edges or when
6762+
``fill=True``, position of lower edge.
6763+
6764+
fill : bool, default: False
6765+
6766+
Returns
6767+
-------
6768+
StepPatch : `matplotlib.patches.StepPatch`
6769+
6770+
Other Parameters
6771+
----------------
6772+
**kwargs
6773+
`~matplotlib.patches.StepPatch` properties
6774+
6775+
"""
6776+
6777+
if 'color' in kwargs:
6778+
_color = kwargs.pop('color')
6779+
else:
6780+
_color = self._get_lines.get_next_color()
6781+
if fill:
6782+
kwargs.setdefault('edgecolor', 'none')
6783+
kwargs.setdefault('facecolor', _color)
6784+
else:
6785+
kwargs.setdefault('edgecolor', _color)
6786+
6787+
if edges is None:
6788+
edges = np.arange(len(values) + 1)
6789+
6790+
self._process_unit_info(xdata=edges, ydata=values, kwargs=kwargs)
6791+
edges = self.convert_xunits(edges)
6792+
values = self.convert_yunits(values)
6793+
6794+
patch = mpatches.StepPatch(values,
6795+
edges,
6796+
baseline=baseline,
6797+
orientation=orientation,
6798+
fill=fill,
6799+
**kwargs)
6800+
self.add_patch(patch)
6801+
if baseline is None:
6802+
baseline = 0
6803+
if orientation == 'vertical':
6804+
patch.sticky_edges.y.append(baseline)
6805+
else:
6806+
patch.sticky_edges.x.append(baseline)
6807+
self._request_autoscale_view()
6808+
return patch
6809+
67436810
@_preprocess_data(replace_names=["x", "y", "weights"])
67446811
@docstring.dedent_interpd
67456812
def hist2d(self, x, y, bins=10, range=None, density=False, weights=None,

lib/matplotlib/legend.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
from matplotlib.cbook import silent_list
3434
from matplotlib.font_manager import FontProperties
3535
from matplotlib.lines import Line2D
36-
from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch
36+
from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch,
37+
StepPatch)
3738
from matplotlib.collections import (LineCollection, RegularPolyCollection,
3839
CircleCollection, PathCollection,
3940
PolyCollection)
@@ -623,6 +624,7 @@ def draw(self, renderer):
623624
ErrorbarContainer: legend_handler.HandlerErrorbar(),
624625
Line2D: legend_handler.HandlerLine2D(),
625626
Patch: legend_handler.HandlerPatch(),
627+
StepPatch: legend_handler.HandlerStepPatch(),
626628
LineCollection: legend_handler.HandlerLineCollection(),
627629
RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(),
628630
CircleCollection: legend_handler.HandlerCircleCollection(),

lib/matplotlib/legend_handler.py

+45
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,51 @@ def create_artists(self, legend, orig_handle,
302302
return [p]
303303

304304

305+
class HandlerStepPatch(HandlerBase):
306+
"""
307+
Handler for `~.matplotlib.patches.StepPatch` instances.
308+
"""
309+
def __init__(self, **kw):
310+
"""
311+
Any other keyword arguments are given to `HandlerBase`.
312+
"""
313+
super().__init__(**kw)
314+
315+
def _create_patch(self, legend, orig_handle,
316+
xdescent, ydescent, width, height, fontsize):
317+
p = Rectangle(xy=(-xdescent, -ydescent),
318+
color=orig_handle.get_facecolor(),
319+
width=width, height=height)
320+
return p
321+
322+
# Unfilled StepPatch should show as a line
323+
def _create_line(self, legend, orig_handle,
324+
xdescent, ydescent, width, height, fontsize):
325+
326+
# Overwrite manually because patch and line properties don't mix
327+
legline = Line2D([0, width], [height/2, height/2],
328+
color=orig_handle.get_edgecolor(),
329+
linestyle=orig_handle.get_linestyle(),
330+
linewidth=orig_handle.get_linewidth(),
331+
)
332+
333+
legline.set_drawstyle('default')
334+
legline.set_marker("")
335+
return legline
336+
337+
def create_artists(self, legend, orig_handle,
338+
xdescent, ydescent, width, height, fontsize, trans):
339+
if orig_handle.get_fill() or (orig_handle.get_hatch() is not None):
340+
p = self._create_patch(legend, orig_handle,
341+
xdescent, ydescent, width, height, fontsize)
342+
self.update_prop(p, orig_handle, legend)
343+
else:
344+
p = self._create_line(legend, orig_handle,
345+
xdescent, ydescent, width, height, fontsize)
346+
p.set_transform(trans)
347+
return [p]
348+
349+
305350
class HandlerLineCollection(HandlerLine2D):
306351
"""
307352
Handler for `.LineCollection` instances.

0 commit comments

Comments
 (0)