Skip to content

Commit c8e5bf6

Browse files
committed
feat: StepPatch
1 parent 71de09a commit c8e5bf6

File tree

4 files changed

+181
-1
lines changed

4 files changed

+181
-1
lines changed

lib/matplotlib/axes/_axes.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6880,6 +6880,34 @@ def hist(self, x, bins=None, range=None, density=False, weights=None,
68806880
else "List[Polygon]")
68816881
return tops, bins, cbook.silent_list(patch_type, patches)
68826882

6883+
def histline(self, vals, bins=None, *,
6884+
orientation='horizontal', baseline=0, fill=False, **kwargs):
6885+
6886+
if 'color' not in kwargs:
6887+
kwargs['color'] = self._get_lines.get_next_color()
6888+
6889+
if bins is None:
6890+
bins = np.arange(len(vals)+1)
6891+
6892+
patch = mpatches.StepPatch(vals,
6893+
bins,
6894+
baseline=baseline,
6895+
orientation=orientation,
6896+
fill=fill,
6897+
**kwargs)
6898+
self.add_patch(patch)
6899+
6900+
if baseline is None:
6901+
baseline = 0
6902+
if orientation == 'horizontal':
6903+
patch.sticky_edges.y.append(baseline)
6904+
else:
6905+
patch.sticky_edges.x.append(baseline)
6906+
6907+
self._request_autoscale_view()
6908+
return patch
6909+
6910+
68836911
@_preprocess_data(replace_names=["x", "y", "weights"])
68846912
@docstring.dedent_interpd
68856913
def hist2d(self, x, y, bins=10, range=None, density=False, weights=None,

lib/matplotlib/legend.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
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, StepPatch
3737
from matplotlib.collections import (LineCollection, RegularPolyCollection,
3838
CircleCollection, PathCollection,
3939
PolyCollection)
@@ -623,6 +623,7 @@ def draw(self, renderer):
623623
ErrorbarContainer: legend_handler.HandlerErrorbar(),
624624
Line2D: legend_handler.HandlerLine2D(),
625625
Patch: legend_handler.HandlerPatch(),
626+
StepPatch: legend_handler.HandlerLinePatch(),
626627
LineCollection: legend_handler.HandlerLineCollection(),
627628
RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(),
628629
CircleCollection: legend_handler.HandlerCircleCollection(),

lib/matplotlib/legend_handler.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,71 @@ def create_artists(self, legend, orig_handle,
302302
return [p]
303303

304304

305+
class HandlerLinePatch(HandlerBase):
306+
"""
307+
Handler for `.HistLine` instances.
308+
"""
309+
def __init__(self, patch_func=None, **kw):
310+
"""
311+
Parameters
312+
----------
313+
patch_func : callable, optional
314+
The function that creates the legend key artist.
315+
*patch_func* should have the signature::
316+
317+
def patch_func(legend=legend, orig_handle=orig_handle,
318+
xdescent=xdescent, ydescent=ydescent,
319+
width=width, height=height, fontsize=fontsize)
320+
321+
Subsequently the created artist will have its ``update_prop``
322+
method called and the appropriate transform will be applied.
323+
324+
Notes
325+
-----
326+
Any other keyword arguments are given to `HandlerBase`.
327+
"""
328+
super().__init__(**kw)
329+
self._patch_func = patch_func
330+
331+
def _create_patch(self, legend, orig_handle,
332+
xdescent, ydescent, width, height, fontsize):
333+
if self._patch_func is None:
334+
p = Rectangle(xy=(-xdescent, -ydescent), color=orig_handle.get_facecolor(),
335+
width=width, height=height)
336+
else:
337+
p = self._patch_func(legend=legend, orig_handle=orig_handle,
338+
xdescent=xdescent, ydescent=ydescent,
339+
width=width, height=height, fontsize=fontsize)
340+
return p
341+
342+
def _create_line(self, legend, orig_handle,
343+
xdescent, ydescent, width, height, fontsize):
344+
345+
# Overwrite manually because patch and line properties don't mix
346+
legline = Line2D([0, width], [height/2, height/2],
347+
color=orig_handle.get_edgecolor(),
348+
linestyle=orig_handle.get_linestyle(),
349+
linewidth=orig_handle.get_linewidth(),
350+
)
351+
352+
legline.set_drawstyle('default')
353+
legline.set_marker("")
354+
return legline
355+
356+
def create_artists(self, legend, orig_handle,
357+
xdescent, ydescent, width, height, fontsize, trans):
358+
if orig_handle.get_fill():
359+
p = self._create_patch(legend, orig_handle,
360+
xdescent, ydescent, width, height, fontsize)
361+
self.update_prop(p, orig_handle, legend)
362+
else:
363+
p = self._create_line(legend, orig_handle,
364+
xdescent, ydescent, width, height, fontsize)
365+
p.set_transform(trans)
366+
367+
return [p]
368+
369+
305370
class HandlerLineCollection(HandlerLine2D):
306371
"""
307372
Handler for `.LineCollection` instances.

lib/matplotlib/patches.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,52 @@ def set_path(self, path):
989989
self._path = path
990990

991991

992+
class StepPatch(PathPatch):
993+
def __init__(self, vals, bins=None, *,
994+
orientation='horizontal', baseline=0, **kwargs):
995+
self.baseline = baseline
996+
self.orientation = orientation
997+
self._bins = bins
998+
self._vals = vals
999+
verts, codes = self._update_data()
1000+
path = Path(verts, codes)
1001+
super().__init__(path, **kwargs)
1002+
1003+
def _update_data(self):
1004+
if self._bins.size - 1 != self._vals.size:
1005+
raise ValueError('the length of the bins is wrong')
1006+
verts, codes = [], []
1007+
for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._vals)):
1008+
x = np.vstack((self._bins[idx0:idx1+1],
1009+
self._bins[idx0:idx1+1])).T.flatten()
1010+
y = np.vstack((self._vals[idx0:idx1],
1011+
self._vals[idx0:idx1])).T.flatten()
1012+
if self.baseline is not None:
1013+
y = np.hstack((self.baseline, y, self.baseline))
1014+
else:
1015+
y = np.hstack((y[0], y, y[-1]))
1016+
if self.orientation == 'horizontal':
1017+
xy = np.vstack([x, y]).T
1018+
else:
1019+
xy = np.vstack([y, x]).T
1020+
verts.append(xy)
1021+
codes.append(np.array([Path.MOVETO] + [Path.LINETO]*(len(xy)-1)))
1022+
return np.vstack(verts), np.hstack(codes)
1023+
1024+
def set_bins(self, bins):
1025+
self._bins = bins
1026+
self._update_data()
1027+
1028+
def set_vals(self, vals):
1029+
self._vals = vals
1030+
self._update_data()
1031+
1032+
def set_vals_bins(self, vals, bins):
1033+
self._vals = vals
1034+
self._bins = bins
1035+
self._update_data()
1036+
1037+
9921038
class Polygon(Patch):
9931039
"""A general polygon patch."""
9941040

@@ -1083,6 +1129,46 @@ def set_xy(self, xy):
10831129
doc='The vertices of the path as (N, 2) numpy array.')
10841130

10851131

1132+
class HistLine(Polygon):
1133+
1134+
def __init__(self, vals, bins=None, *, fill=False,
1135+
orientation='horizontal', baseline=0, **kwargs):
1136+
self.baseline = baseline
1137+
self.orientation = orientation
1138+
self._color = None
1139+
self._bins = bins
1140+
self._vals = vals
1141+
xy = self._update_data()
1142+
super(HistLine, self).__init__(xy, closed=False, fill=fill, **kwargs)
1143+
1144+
def _update_data(self):
1145+
if self._bins.size - 1 != self._vals.size:
1146+
raise ValueError('the length of the bins is wrong')
1147+
x = np.vstack((self._bins, self._bins)).T.flatten()
1148+
y = np.vstack((self._vals, self._vals)).T.flatten()
1149+
if self.baseline is not None:
1150+
y = np.hstack((self.baseline, y, self.baseline))
1151+
else:
1152+
y = np.hstack((y[0], y, y[-1]))
1153+
if self.orientation == 'horizontal':
1154+
return np.vstack([x, y]).T
1155+
else:
1156+
return np.vstack([y, x]).T
1157+
1158+
def set_bins(self, bins):
1159+
self._bins = bins
1160+
self._update_data()
1161+
1162+
def set_vals(self, vals):
1163+
self._vals = vals
1164+
self._update_data()
1165+
1166+
def set_vals_bins(self, vals, bins):
1167+
self._vals = vals
1168+
self._bins = bins
1169+
self._update_data()
1170+
1171+
10861172
class Wedge(Patch):
10871173
"""Wedge shaped patch."""
10881174

0 commit comments

Comments
 (0)