From 157aecf3b320d277376877c6ae9577fd1f92d696 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 21 Aug 2020 00:18:20 +0200 Subject: [PATCH 01/12] feat: StepPatch --- lib/matplotlib/axes/_axes.py | 28 +++++++++++ lib/matplotlib/legend.py | 3 +- lib/matplotlib/legend_handler.py | 65 ++++++++++++++++++++++++ lib/matplotlib/patches.py | 86 ++++++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 500ef4365092..6c71e7f69017 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6871,6 +6871,34 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, else "List[Polygon]") return tops, bins, cbook.silent_list(patch_type, patches) + def histline(self, vals, bins=None, *, + orientation='horizontal', baseline=0, fill=False, **kwargs): + + if 'color' not in kwargs: + kwargs['color'] = self._get_lines.get_next_color() + + if bins is None: + bins = np.arange(len(vals)+1) + + patch = mpatches.StepPatch(vals, + bins, + baseline=baseline, + orientation=orientation, + fill=fill, + **kwargs) + self.add_patch(patch) + + if baseline is None: + baseline = 0 + if orientation == 'horizontal': + patch.sticky_edges.y.append(baseline) + else: + patch.sticky_edges.x.append(baseline) + + self._request_autoscale_view() + return patch + + @_preprocess_data(replace_names=["x", "y", "weights"]) @docstring.dedent_interpd def hist2d(self, x, y, bins=10, range=None, density=False, weights=None, diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 25f790893961..f49deadfcb37 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -33,7 +33,7 @@ from matplotlib.cbook import silent_list from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D -from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch +from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch, StepPatch from matplotlib.collections import (LineCollection, RegularPolyCollection, CircleCollection, PathCollection, PolyCollection) @@ -623,6 +623,7 @@ def draw(self, renderer): ErrorbarContainer: legend_handler.HandlerErrorbar(), Line2D: legend_handler.HandlerLine2D(), Patch: legend_handler.HandlerPatch(), + StepPatch: legend_handler.HandlerLinePatch(), LineCollection: legend_handler.HandlerLineCollection(), RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(), CircleCollection: legend_handler.HandlerCircleCollection(), diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index 4b96dd4289c5..ca79e9bdd63e 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -302,6 +302,71 @@ def create_artists(self, legend, orig_handle, return [p] +class HandlerLinePatch(HandlerBase): + """ + Handler for `.HistLine` instances. + """ + def __init__(self, patch_func=None, **kw): + """ + Parameters + ---------- + patch_func : callable, optional + The function that creates the legend key artist. + *patch_func* should have the signature:: + + def patch_func(legend=legend, orig_handle=orig_handle, + xdescent=xdescent, ydescent=ydescent, + width=width, height=height, fontsize=fontsize) + + Subsequently the created artist will have its ``update_prop`` + method called and the appropriate transform will be applied. + + Notes + ----- + Any other keyword arguments are given to `HandlerBase`. + """ + super().__init__(**kw) + self._patch_func = patch_func + + def _create_patch(self, legend, orig_handle, + xdescent, ydescent, width, height, fontsize): + if self._patch_func is None: + p = Rectangle(xy=(-xdescent, -ydescent), color=orig_handle.get_facecolor(), + width=width, height=height) + else: + p = self._patch_func(legend=legend, orig_handle=orig_handle, + xdescent=xdescent, ydescent=ydescent, + width=width, height=height, fontsize=fontsize) + return p + + def _create_line(self, legend, orig_handle, + xdescent, ydescent, width, height, fontsize): + + # Overwrite manually because patch and line properties don't mix + legline = Line2D([0, width], [height/2, height/2], + color=orig_handle.get_edgecolor(), + linestyle=orig_handle.get_linestyle(), + linewidth=orig_handle.get_linewidth(), + ) + + legline.set_drawstyle('default') + legline.set_marker("") + return legline + + def create_artists(self, legend, orig_handle, + xdescent, ydescent, width, height, fontsize, trans): + if orig_handle.get_fill(): + p = self._create_patch(legend, orig_handle, + xdescent, ydescent, width, height, fontsize) + self.update_prop(p, orig_handle, legend) + else: + p = self._create_line(legend, orig_handle, + xdescent, ydescent, width, height, fontsize) + p.set_transform(trans) + + return [p] + + class HandlerLineCollection(HandlerLine2D): """ Handler for `.LineCollection` instances. diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 859d495111cb..7f41edc67eee 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -989,6 +989,52 @@ def set_path(self, path): self._path = path +class StepPatch(PathPatch): + def __init__(self, vals, bins=None, *, + orientation='horizontal', baseline=0, **kwargs): + self.baseline = baseline + self.orientation = orientation + self._bins = bins + self._vals = vals + verts, codes = self._update_data() + path = Path(verts, codes) + super().__init__(path, **kwargs) + + def _update_data(self): + if self._bins.size - 1 != self._vals.size: + raise ValueError('the length of the bins is wrong') + verts, codes = [], [] + for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._vals)): + x = np.vstack((self._bins[idx0:idx1+1], + self._bins[idx0:idx1+1])).T.flatten() + y = np.vstack((self._vals[idx0:idx1], + self._vals[idx0:idx1])).T.flatten() + if self.baseline is not None: + y = np.hstack((self.baseline, y, self.baseline)) + else: + y = np.hstack((y[0], y, y[-1])) + if self.orientation == 'horizontal': + xy = np.vstack([x, y]).T + else: + xy = np.vstack([y, x]).T + verts.append(xy) + codes.append(np.array([Path.MOVETO] + [Path.LINETO]*(len(xy)-1))) + return np.vstack(verts), np.hstack(codes) + + def set_bins(self, bins): + self._bins = bins + self._update_data() + + def set_vals(self, vals): + self._vals = vals + self._update_data() + + def set_vals_bins(self, vals, bins): + self._vals = vals + self._bins = bins + self._update_data() + + class Polygon(Patch): """A general polygon patch.""" @@ -1083,6 +1129,46 @@ def set_xy(self, xy): doc='The vertices of the path as (N, 2) numpy array.') +class HistLine(Polygon): + + def __init__(self, vals, bins=None, *, fill=False, + orientation='horizontal', baseline=0, **kwargs): + self.baseline = baseline + self.orientation = orientation + self._color = None + self._bins = bins + self._vals = vals + xy = self._update_data() + super(HistLine, self).__init__(xy, closed=False, fill=fill, **kwargs) + + def _update_data(self): + if self._bins.size - 1 != self._vals.size: + raise ValueError('the length of the bins is wrong') + x = np.vstack((self._bins, self._bins)).T.flatten() + y = np.vstack((self._vals, self._vals)).T.flatten() + if self.baseline is not None: + y = np.hstack((self.baseline, y, self.baseline)) + else: + y = np.hstack((y[0], y, y[-1])) + if self.orientation == 'horizontal': + return np.vstack([x, y]).T + else: + return np.vstack([y, x]).T + + def set_bins(self, bins): + self._bins = bins + self._update_data() + + def set_vals(self, vals): + self._vals = vals + self._update_data() + + def set_vals_bins(self, vals, bins): + self._vals = vals + self._bins = bins + self._update_data() + + class Wedge(Patch): """Wedge shaped patch.""" From 467683d402c36cd67192e3b84e3ca9eb0b201821 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 21 Aug 2020 00:31:55 +0200 Subject: [PATCH 02/12] feat: histline color handling --- lib/matplotlib/axes/_axes.py | 8 +++- lib/matplotlib/legend.py | 3 +- lib/matplotlib/legend_handler.py | 3 +- lib/matplotlib/patches.py | 68 ++++++++++++-------------------- 4 files changed, 36 insertions(+), 46 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 6c71e7f69017..78ac3c4a77d0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6874,8 +6874,12 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, def histline(self, vals, bins=None, *, orientation='horizontal', baseline=0, fill=False, **kwargs): - if 'color' not in kwargs: - kwargs['color'] = self._get_lines.get_next_color() + _color = self._get_lines.get_next_color() + if not fill: + kwargs.setdefault('edgecolor', _color) + else: + kwargs.setdefault('edgecolor', 'none') + kwargs.setdefault('facecolor', _color) if bins is None: bins = np.arange(len(vals)+1) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index f49deadfcb37..b44cb39d1864 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -33,7 +33,8 @@ from matplotlib.cbook import silent_list from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D -from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch, StepPatch +from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch, + StepPatch) from matplotlib.collections import (LineCollection, RegularPolyCollection, CircleCollection, PathCollection, PolyCollection) diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index ca79e9bdd63e..60d91a8dd6da 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -331,7 +331,8 @@ def patch_func(legend=legend, orig_handle=orig_handle, def _create_patch(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize): if self._patch_func is None: - p = Rectangle(xy=(-xdescent, -ydescent), color=orig_handle.get_facecolor(), + p = Rectangle(xy=(-xdescent, -ydescent), + color=orig_handle.get_facecolor(), width=width, height=height) else: p = self._patch_func(legend=legend, orig_handle=orig_handle, diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 7f41edc67eee..8fe2bf58f65f 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -990,8 +990,32 @@ def set_path(self, path): class StepPatch(PathPatch): + """A stepline path patch.""" + + @docstring.dedent_interpd def __init__(self, vals, bins=None, *, orientation='horizontal', baseline=0, **kwargs): + """ + Parameters + ---------- + vals : array, len N + An array of y-values. + + bins : array, len N+1 + A array of x-values, between which the curve takes on + vals values. + + orientation : {'vertical', 'horizontal'}, default: 'vertical' + + baseline : float or None, default: 0 + Determines starting value of the bounding edges or when + "fill" == True, position of lower edge. + + **kwargs + `Patch` properties: + + %(Patch)s + """ self.baseline = baseline self.orientation = orientation self._bins = bins @@ -1005,9 +1029,9 @@ def _update_data(self): raise ValueError('the length of the bins is wrong') verts, codes = [], [] for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._vals)): - x = np.vstack((self._bins[idx0:idx1+1], + x = np.vstack((self._bins[idx0:idx1+1], self._bins[idx0:idx1+1])).T.flatten() - y = np.vstack((self._vals[idx0:idx1], + y = np.vstack((self._vals[idx0:idx1], self._vals[idx0:idx1])).T.flatten() if self.baseline is not None: y = np.hstack((self.baseline, y, self.baseline)) @@ -1129,46 +1153,6 @@ def set_xy(self, xy): doc='The vertices of the path as (N, 2) numpy array.') -class HistLine(Polygon): - - def __init__(self, vals, bins=None, *, fill=False, - orientation='horizontal', baseline=0, **kwargs): - self.baseline = baseline - self.orientation = orientation - self._color = None - self._bins = bins - self._vals = vals - xy = self._update_data() - super(HistLine, self).__init__(xy, closed=False, fill=fill, **kwargs) - - def _update_data(self): - if self._bins.size - 1 != self._vals.size: - raise ValueError('the length of the bins is wrong') - x = np.vstack((self._bins, self._bins)).T.flatten() - y = np.vstack((self._vals, self._vals)).T.flatten() - if self.baseline is not None: - y = np.hstack((self.baseline, y, self.baseline)) - else: - y = np.hstack((y[0], y, y[-1])) - if self.orientation == 'horizontal': - return np.vstack([x, y]).T - else: - return np.vstack([y, x]).T - - def set_bins(self, bins): - self._bins = bins - self._update_data() - - def set_vals(self, vals): - self._vals = vals - self._update_data() - - def set_vals_bins(self, vals, bins): - self._vals = vals - self._bins = bins - self._update_data() - - class Wedge(Patch): """Wedge shaped patch.""" From 67c15ecedc8ab0c653d10ef9d25c9b884b45e4b1 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 21 Aug 2020 14:27:37 +0200 Subject: [PATCH 03/12] feat: Add histline tests + cleanup --- lib/matplotlib/axes/_axes.py | 49 +- lib/matplotlib/legend.py | 2 +- lib/matplotlib/legend_handler.py | 37 +- lib/matplotlib/patches.py | 43 +- .../test_axes/test_histline_options.svg | 556 ++++++++++++++++++ lib/matplotlib/tests/test_axes.py | 91 +++ 6 files changed, 717 insertions(+), 61 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.svg diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 78ac3c4a77d0..82a5fc0a8b96 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6871,18 +6871,52 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, else "List[Polygon]") return tops, bins, cbook.silent_list(patch_type, patches) + @_preprocess_data() def histline(self, vals, bins=None, *, - orientation='horizontal', baseline=0, fill=False, **kwargs): + orientation='vertical', baseline=0, fill=False, **kwargs): + """ + A histogram-like line or filled plot. - _color = self._get_lines.get_next_color() - if not fill: - kwargs.setdefault('edgecolor', _color) + Parameters + ---------- + vals : array + An array of y-values. + + bins : array, default: ``range(len(vals)+1)`` + A array of x-values, with ``len(bins) == len(vals) + 1``, + between which the curve takes on vals values. + + orientation : {'vertical', 'horizontal'}, default: 'vertical' + + baseline : float or None, default: 0 + Determines starting value of the bounding edges or when + "fill" == True, position of lower edge. + + fill : bool, default: False + + Returns + ------- + patch : `.StepPatch` + + Other Parameters + ---------------- + **kwargs + `~matplotlib.patches.Patch` properties + + """ + + if 'color' in kwargs: + _color = kwargs.pop('color') else: + _color = self._get_lines.get_next_color() + if fill: kwargs.setdefault('edgecolor', 'none') kwargs.setdefault('facecolor', _color) + else: + kwargs.setdefault('edgecolor', _color) if bins is None: - bins = np.arange(len(vals)+1) + bins = np.arange(len(vals) + 1) patch = mpatches.StepPatch(vals, bins, @@ -6891,18 +6925,15 @@ def histline(self, vals, bins=None, *, fill=fill, **kwargs) self.add_patch(patch) - if baseline is None: baseline = 0 - if orientation == 'horizontal': + if orientation == 'vertical': patch.sticky_edges.y.append(baseline) else: patch.sticky_edges.x.append(baseline) - self._request_autoscale_view() return patch - @_preprocess_data(replace_names=["x", "y", "weights"]) @docstring.dedent_interpd def hist2d(self, x, y, bins=10, range=None, density=False, weights=None, diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index b44cb39d1864..e30d8056eb08 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -624,7 +624,7 @@ def draw(self, renderer): ErrorbarContainer: legend_handler.HandlerErrorbar(), Line2D: legend_handler.HandlerLine2D(), Patch: legend_handler.HandlerPatch(), - StepPatch: legend_handler.HandlerLinePatch(), + StepPatch: legend_handler.HandlerStepPatch(), LineCollection: legend_handler.HandlerLineCollection(), RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(), CircleCollection: legend_handler.HandlerCircleCollection(), diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index 60d91a8dd6da..accb6ce514c6 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -302,44 +302,24 @@ def create_artists(self, legend, orig_handle, return [p] -class HandlerLinePatch(HandlerBase): +class HandlerStepPatch(HandlerBase): """ - Handler for `.HistLine` instances. + Handler for `.StepPatch` instances. """ - def __init__(self, patch_func=None, **kw): + def __init__(self, **kw): """ - Parameters - ---------- - patch_func : callable, optional - The function that creates the legend key artist. - *patch_func* should have the signature:: - - def patch_func(legend=legend, orig_handle=orig_handle, - xdescent=xdescent, ydescent=ydescent, - width=width, height=height, fontsize=fontsize) - - Subsequently the created artist will have its ``update_prop`` - method called and the appropriate transform will be applied. - - Notes - ----- Any other keyword arguments are given to `HandlerBase`. """ super().__init__(**kw) - self._patch_func = patch_func def _create_patch(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize): - if self._patch_func is None: - p = Rectangle(xy=(-xdescent, -ydescent), - color=orig_handle.get_facecolor(), - width=width, height=height) - else: - p = self._patch_func(legend=legend, orig_handle=orig_handle, - xdescent=xdescent, ydescent=ydescent, - width=width, height=height, fontsize=fontsize) + p = Rectangle(xy=(-xdescent, -ydescent), + color=orig_handle.get_facecolor(), + width=width, height=height) return p + # Unfilled StepPatch should show as a line def _create_line(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize): @@ -356,7 +336,7 @@ def _create_line(self, legend, orig_handle, def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): - if orig_handle.get_fill(): + if orig_handle.get_fill() or (orig_handle.get_hatch() is not None): p = self._create_patch(legend, orig_handle, xdescent, ydescent, width, height, fontsize) self.update_prop(p, orig_handle, legend) @@ -364,7 +344,6 @@ def create_artists(self, legend, orig_handle, p = self._create_line(legend, orig_handle, xdescent, ydescent, width, height, fontsize) p.set_transform(trans) - return [p] diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 8fe2bf58f65f..a8a6e4b27617 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -990,20 +990,20 @@ def set_path(self, path): class StepPatch(PathPatch): - """A stepline path patch.""" + """An unclosed stepline path patch.""" @docstring.dedent_interpd - def __init__(self, vals, bins=None, *, + def __init__(self, vals, edges, *, orientation='horizontal', baseline=0, **kwargs): """ Parameters ---------- - vals : array, len N + vals : array An array of y-values. - bins : array, len N+1 - A array of x-values, between which the curve takes on - vals values. + edges : array + A array of x-value edges, with ``len(edges) == len(vals) + 1``, + between which the curve takes on vals values. orientation : {'vertical', 'horizontal'}, default: 'vertical' @@ -1011,33 +1011,32 @@ def __init__(self, vals, bins=None, *, Determines starting value of the bounding edges or when "fill" == True, position of lower edge. - **kwargs - `Patch` properties: + Other valid keyword arguments are: - %(Patch)s + %(Patch)s """ self.baseline = baseline self.orientation = orientation - self._bins = bins - self._vals = vals + self._edges = np.asarray(edges) + self._vals = np.asarray(vals) verts, codes = self._update_data() path = Path(verts, codes) super().__init__(path, **kwargs) def _update_data(self): - if self._bins.size - 1 != self._vals.size: - raise ValueError('the length of the bins is wrong') + if self._edges.size - 1 != self._vals.size: + raise ValueError('Size mismatch between "vals" and "edges"') verts, codes = [], [] for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._vals)): - x = np.vstack((self._bins[idx0:idx1+1], - self._bins[idx0:idx1+1])).T.flatten() + x = np.vstack((self._edges[idx0:idx1+1], + self._edges[idx0:idx1+1])).T.flatten() y = np.vstack((self._vals[idx0:idx1], self._vals[idx0:idx1])).T.flatten() if self.baseline is not None: y = np.hstack((self.baseline, y, self.baseline)) else: y = np.hstack((y[0], y, y[-1])) - if self.orientation == 'horizontal': + if self.orientation == 'vertical': xy = np.vstack([x, y]).T else: xy = np.vstack([y, x]).T @@ -1045,17 +1044,17 @@ def _update_data(self): codes.append(np.array([Path.MOVETO] + [Path.LINETO]*(len(xy)-1))) return np.vstack(verts), np.hstack(codes) - def set_bins(self, bins): - self._bins = bins + def set_edges(self, edges): + self._edges = np.asarray(edges) self._update_data() def set_vals(self, vals): - self._vals = vals + self._vals = np.asarray(vals) self._update_data() - def set_vals_bins(self, vals, bins): - self._vals = vals - self._bins = bins + def set_vals_edges(self, vals, edges): + self._vals = np.asarray(vals) + self._edegs = np.asarray(edges) self._update_data() diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.svg b/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.svg new file mode 100644 index 000000000000..e542a554b68a --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.svg @@ -0,0 +1,556 @@ + + + + + + + + 2020-08-21T14:13:57.484861 + image/svg+xml + + + Matplotlib v3.3.0rc1.post775.dev0+g23fea799e, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a35f55a55db9..67fa64256a12 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1744,6 +1744,97 @@ def test_hist_zorder(histtype, zorder): assert patch.get_zorder() == zorder +@check_figures_equal() +def test_histline(fig_test, fig_ref): + import matplotlib.lines as mlines + y = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist + x = np.array([1., 2., 3., 4., 5., 6., 7., 8., 9.]) # bins + + fig_test, test_axes = plt.subplots(3, 2) + test_axes = test_axes.flatten() + test_axes[0].histline(y, x, baseline=None) + test_axes[1].histline(y, x, baseline=None, orientation='horizontal') + test_axes[2].histline(y, x) + test_axes[3].histline(y, x, orientation='horizontal') + test_axes[4].histline(y, x) + test_axes[4].semilogy() + test_axes[5].histline(y, x, orientation='horizontal') + test_axes[5].semilogy() + + fig_ref, ref_axes = plt.subplots(3, 2) + ref_axes = ref_axes.flatten() + ref_axes[0].plot(x, np.append(y, y[-1]), drawstyle='steps-post') + ref_axes[1].plot(np.append(y[0], y), x, drawstyle='steps-post') + + ref_axes[2].plot(x, np.append(y, y[-1]), drawstyle='steps-post') + ref_axes[2].add_line(mlines.Line2D([x[0], x[0]], [0, y[0]])) + ref_axes[2].add_line(mlines.Line2D([x[-1], x[-1]], [0, y[-1]])) + ref_axes[2].set_ylim(0, None) + + ref_axes[3].plot(np.append(y[0], y), x, drawstyle='steps-post') + ref_axes[3].add_line(mlines.Line2D([0, y[0]], [x[0], x[0]])) + ref_axes[3].add_line(mlines.Line2D([0, y[-1]], [x[-1], x[-1]])) + ref_axes[3].set_xlim(0, None) + + ref_axes[4].plot(x, np.append(y, y[-1]), drawstyle='steps-post') + ref_axes[4].add_line(mlines.Line2D([x[0], x[0]], [0, y[0]])) + ref_axes[4].add_line(mlines.Line2D([x[-1], x[-1]], [0, y[-1]])) + ref_axes[4].semilogy() + + ref_axes[5].plot(np.append(y[0], y), x, drawstyle='steps-post') + ref_axes[5].add_line(mlines.Line2D([0, y[0]], [x[0], x[0]])) + ref_axes[5].add_line(mlines.Line2D([0, y[-1]], [x[-1], x[-1]])) + ref_axes[5].semilogx() + + +@check_figures_equal() +def test_histline_fill(fig_test, fig_ref): + h, bins = [1, 2, 3, 4, 2], [0, 1, 2, 3, 4, 5] + bs = -2 + # Test + fig_test, test_axes = plt.subplots(2, 2) + test_axes = test_axes.flatten() + test_axes[0].histline(h, bins, fill=True) + test_axes[1].histline(h, bins, orientation='horizontal', fill=True) + test_axes[2].histline(h, bins, baseline=bs, fill=True) + test_axes[3].histline(h, bins, baseline=bs, orientation='horizontal', + fill=True) + + # # Ref + fig_ref, ref_axes = plt.subplots(2, 2) + ref_axes = ref_axes.flatten() + ref_axes[0].fill_between(bins, np.append(h, h[-1]), step='post') + ref_axes[0].set_ylim(0, None) + ref_axes[1].fill_betweenx(bins, np.append(h, h[-1]), step='post') + ref_axes[1].set_xlim(0, None) + ref_axes[2].fill_between(bins, np.append(h, h[-1]), + np.ones(len(h)+1)*bs, step='post') + ref_axes[2].set_ylim(bs, None) + ref_axes[3].fill_betweenx(bins, np.append(h, h[-1]), + np.ones(len(h)+1)*bs, step='post') + ref_axes[3].set_xlim(bs, None) + + +@image_comparison(['test_histline_options.svg'], remove_text=True) +def test_histline_options(): + x, y = np.array([1, 2, 3, 4, 5]), np.array([1, 2, 3, 4]).astype(float) + yn = y.copy() + yn[1] = np.nan + + fig, ax = plt.subplots() + ax.histline(y*3, x, color='green', fill=True, label="A") + ax.histline(y, x*3-3, color='red', fill=True, + orientation='horizontal', label="B") + ax.histline(yn, x, color='orange', ls='--', lw=2, label="C") + ax.histline(yn/3, x*3-2, color='purple', ls='--', lw=2, baseline=0.5, + orientation='horizontal', label="D") + ax.histline(y[::-1]*3+12, x, color='red', ls='--', lw=2, baseline=None, + label="E") + ax.histline(y[:-1][::-1]*2+11, x[:-1]+0.5, color='blue', ls='--', lw=2, + baseline=12, hatch='//', label="F") + ax.legend(loc=0) + + def contour_dat(): x = np.linspace(-3, 5, 150) y = np.linspace(-3, 5, 120) From 48c206839d00943161a210a7a2544e34f7c078ef Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 21 Aug 2020 16:27:49 +0200 Subject: [PATCH 04/12] fix: add histline to pyplot --- lib/matplotlib/pyplot.py | 11 +++++++++++ tools/boilerplate.py | 1 + 2 files changed, 12 insertions(+) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index ba82e3433716..ba463b3162a1 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2777,6 +2777,17 @@ def hist( **({"data": data} if data is not None else {}), **kwargs) +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@_copy_docstring_and_deprecators(Axes.histline) +def histline( + vals, bins=None, *, orientation='vertical', baseline=0, + fill=False, data=None, **kwargs): + return gca().histline( + vals, bins=bins, orientation=orientation, baseline=baseline, + fill=fill, **({"data": data} if data is not None else {}), + **kwargs) + + # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.hist2d) def hist2d( diff --git a/tools/boilerplate.py b/tools/boilerplate.py index 98840d1f09f2..dae00a961b4e 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -215,6 +215,7 @@ def boilerplate_gen(): 'grid', 'hexbin', 'hist', + 'histline', 'hist2d', 'hlines', 'imshow', From e24ec9376e0046f3e95adf9a96e97fd06f2fe42e Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Fri, 21 Aug 2020 17:09:40 +0200 Subject: [PATCH 05/12] feat: Add histline/StepPatch demo and what's new --- .flake8 | 1 + .../next_whats_new/steppatch_and_histline.rst | 22 + .../lines_bars_and_markers/histline_demo.py | 60 ++ lib/matplotlib/axes/_axes.py | 16 +- lib/matplotlib/legend_handler.py | 2 +- lib/matplotlib/patches.py | 35 +- lib/matplotlib/pyplot.py | 4 +- .../test_axes/test_histline_options.png | Bin 0 -> 12896 bytes .../test_axes/test_histline_options.svg | 556 ------------------ lib/matplotlib/tests/test_axes.py | 2 +- 10 files changed, 112 insertions(+), 586 deletions(-) create mode 100644 doc/users/next_whats_new/steppatch_and_histline.rst create mode 100644 examples/lines_bars_and_markers/histline_demo.py create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.png delete mode 100644 lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.svg diff --git a/.flake8 b/.flake8 index 20f404db4104..0ab6c997577b 100644 --- a/.flake8 +++ b/.flake8 @@ -162,6 +162,7 @@ per-file-ignores = examples/lines_bars_and_markers/fill.py: E402 examples/lines_bars_and_markers/fill_between_demo.py: E402 examples/lines_bars_and_markers/filled_step.py: E402 + examples/lines_bars_and_markers/histline_demo.py: E402 examples/lines_bars_and_markers/horizontal_barchart_distribution.py: E402 examples/lines_bars_and_markers/joinstyle.py: E402 examples/lines_bars_and_markers/scatter_hist.py: E402 diff --git a/doc/users/next_whats_new/steppatch_and_histline.rst b/doc/users/next_whats_new/steppatch_and_histline.rst new file mode 100644 index 000000000000..44a753e2290c --- /dev/null +++ b/doc/users/next_whats_new/steppatch_and_histline.rst @@ -0,0 +1,22 @@ +New `~.matplotlib.patches.StepPatch` artist and a `.pyplot.histline` method +--------------------------------------------------------------------------- +These take inputs of asymmetric lengths with y-like values and +x-like edges, between which the values lie. + + .. plot:: + + import numpy as np + import matplotlib.pyplot as plt + + np.random.seed(0) + h, bins = np.histogram(np.random.normal(5, 2, 5000), + bins=np.linspace(0,10,20)) + + fig, ax = plt.subplots(constrained_layout=True) + + ax.histline(h, bins) + + plt.show() + +See :doc:`/gallery/lines_bars_and_markers/histline_demo` +for examples. \ No newline at end of file diff --git a/examples/lines_bars_and_markers/histline_demo.py b/examples/lines_bars_and_markers/histline_demo.py new file mode 100644 index 000000000000..7f8118012ab6 --- /dev/null +++ b/examples/lines_bars_and_markers/histline_demo.py @@ -0,0 +1,60 @@ +""" +============= +Histline Demo +============= + +This example demonstrates the use of `~.matplotlib.pyplot.histline` +for histogram and histogram-like data visualization and an associated +underlying `~.matplotlib.patches.StepPatch` artist, which is +a contrained version of `.PathPatch` specified by its bins and edges. +""" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import StepPatch + +np.random.seed(0) +h, bins = np.histogram(np.random.normal(5, 3, 5000), + bins=np.linspace(0, 10, 20)) + +fig, axs = plt.subplots(3, 1, figsize=(7, 15)) +axs[0].histline(h, bins, label='Simple histogram') +axs[0].histline(h, bins+5, baseline=50, label='--//-- w/ modified baseline') +axs[0].histline(h, bins+10, baseline=None, label='--//-- w/ no edges') +axs[0].set_title("Step Histograms") + +axs[1].histline(np.arange(1, 6, 1), fill=True, + label='Filled histogram\nw/ automatatic edges') +axs[1].histline(np.arange(1, 6, 1)*0.3, np.arange(2, 8, 1), + orientation='horizontal', hatch='//', + label='Hatched histogram\nw/ horizontal orientation') +axs[1].set_title("Filled histogram") + +patch = StepPatch(values=[1, 2, 3, 2, 1], + edges=range(1, 7), + label=('Patch derived underlying object\n' + 'with default edge/facecolor behaviour')) +axs[2].add_patch(patch) +axs[2].set_xlim(0, 7) +axs[2].set_ylim(-1, 5) +axs[2].set_title("StepPatch artist") + +for ax in axs: + ax.legend() +plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.histline +matplotlib.pyplot.histline +matplotlib.patches.StepPatch diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 82a5fc0a8b96..f41fecda0872 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6872,17 +6872,17 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, return tops, bins, cbook.silent_list(patch_type, patches) @_preprocess_data() - def histline(self, vals, bins=None, *, + def histline(self, values, bins=None, *, orientation='vertical', baseline=0, fill=False, **kwargs): """ A histogram-like line or filled plot. Parameters ---------- - vals : array + values : array-like An array of y-values. - bins : array, default: ``range(len(vals)+1)`` + bins : array-like, default: ``range(len(vals)+1)`` A array of x-values, with ``len(bins) == len(vals) + 1``, between which the curve takes on vals values. @@ -6890,18 +6890,18 @@ def histline(self, vals, bins=None, *, baseline : float or None, default: 0 Determines starting value of the bounding edges or when - "fill" == True, position of lower edge. + ``fill=True``, position of lower edge. fill : bool, default: False Returns ------- - patch : `.StepPatch` + StepPatch : `.patches.StepPatch` Other Parameters ---------------- **kwargs - `~matplotlib.patches.Patch` properties + `~.matplotlib.patches.StepPatch` properties """ @@ -6916,9 +6916,9 @@ def histline(self, vals, bins=None, *, kwargs.setdefault('edgecolor', _color) if bins is None: - bins = np.arange(len(vals) + 1) + bins = np.arange(len(values) + 1) - patch = mpatches.StepPatch(vals, + patch = mpatches.StepPatch(values, bins, baseline=baseline, orientation=orientation, diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index accb6ce514c6..45fb759d7b23 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -304,7 +304,7 @@ def create_artists(self, legend, orig_handle, class HandlerStepPatch(HandlerBase): """ - Handler for `.StepPatch` instances. + Handler for `~.matplotlib.patches.StepPatch` instances. """ def __init__(self, **kw): """ diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index a8a6e4b27617..230d24acaacf 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -993,15 +993,15 @@ class StepPatch(PathPatch): """An unclosed stepline path patch.""" @docstring.dedent_interpd - def __init__(self, vals, edges, *, - orientation='horizontal', baseline=0, **kwargs): + def __init__(self, values, edges, *, + orientation='vertical', baseline=0, **kwargs): """ Parameters ---------- - vals : array + values : array-like An array of y-values. - edges : array + edges : array-like A array of x-value edges, with ``len(edges) == len(vals) + 1``, between which the curve takes on vals values. @@ -1009,7 +1009,7 @@ def __init__(self, vals, edges, *, baseline : float or None, default: 0 Determines starting value of the bounding edges or when - "fill" == True, position of lower edge. + ``fill=True``, position of lower edge. Other valid keyword arguments are: @@ -1018,20 +1018,24 @@ def __init__(self, vals, edges, *, self.baseline = baseline self.orientation = orientation self._edges = np.asarray(edges) - self._vals = np.asarray(vals) + self._values = np.asarray(values) verts, codes = self._update_data() path = Path(verts, codes) super().__init__(path, **kwargs) def _update_data(self): - if self._edges.size - 1 != self._vals.size: - raise ValueError('Size mismatch between "vals" and "edges"') + if self._edges.size - 1 != self._values.size: + raise ValueError('Size mismatch between "values" and "edges". ' + "Expected `len(values) + 1 == len(edges)`, but " + "they are or lengths {} and {}".format( + self._edges.size, self._values.size) + ) verts, codes = [], [] - for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._vals)): + for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._values)): x = np.vstack((self._edges[idx0:idx1+1], self._edges[idx0:idx1+1])).T.flatten() - y = np.vstack((self._vals[idx0:idx1], - self._vals[idx0:idx1])).T.flatten() + y = np.vstack((self._values[idx0:idx1], + self._values[idx0:idx1])).T.flatten() if self.baseline is not None: y = np.hstack((self.baseline, y, self.baseline)) else: @@ -1048,13 +1052,8 @@ def set_edges(self, edges): self._edges = np.asarray(edges) self._update_data() - def set_vals(self, vals): - self._vals = np.asarray(vals) - self._update_data() - - def set_vals_edges(self, vals, edges): - self._vals = np.asarray(vals) - self._edegs = np.asarray(edges) + def set_values(self, values): + self._values = np.asarray(values) self._update_data() diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index ba463b3162a1..8e9b6c814bc5 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2780,10 +2780,10 @@ def hist( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.histline) def histline( - vals, bins=None, *, orientation='vertical', baseline=0, + values, bins=None, *, orientation='vertical', baseline=0, fill=False, data=None, **kwargs): return gca().histline( - vals, bins=bins, orientation=orientation, baseline=baseline, + values, bins=bins, orientation=orientation, baseline=baseline, fill=fill, **({"data": data} if data is not None else {}), **kwargs) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.png b/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.png new file mode 100644 index 0000000000000000000000000000000000000000..8ef7e7bc9920eaa49db7b0621d2d8917b603f3bf GIT binary patch literal 12896 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 AH2?qr literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.svg b/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.svg deleted file mode 100644 index e542a554b68a..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.svg +++ /dev/null @@ -1,556 +0,0 @@ - - - - - - - - 2020-08-21T14:13:57.484861 - image/svg+xml - - - Matplotlib v3.3.0rc1.post775.dev0+g23fea799e, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 67fa64256a12..769b1ba41786 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1815,7 +1815,7 @@ def test_histline_fill(fig_test, fig_ref): ref_axes[3].set_xlim(bs, None) -@image_comparison(['test_histline_options.svg'], remove_text=True) +@image_comparison(['test_histline_options.png'], remove_text=True) def test_histline_options(): x, y = np.array([1, 2, 3, 4, 5]), np.array([1, 2, 3, 4]).astype(float) yn = y.copy() From 4a111ce36e26df6e77efbb78d90fd5b9bdf7962f Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Tue, 1 Sep 2020 10:45:49 +0200 Subject: [PATCH 06/12] feat: rename histline --- .flake8 | 2 +- doc/api/artist_api.rst | 2 +- doc/api/patches_api.rst | 1 + .../next_whats_new/steppatch_and_histline.rst | 6 +- .../lines_bars_and_markers/histline_demo.py | 60 ------------------ .../lines_bars_and_markers/levels_demo.py | 60 ++++++++++++++++++ lib/matplotlib/axes/_axes.py | 30 ++++----- lib/matplotlib/legend.py | 4 +- lib/matplotlib/legend_handler.py | 6 +- lib/matplotlib/patches.py | 16 ++--- lib/matplotlib/pyplot.py | 14 ++-- ...ne_options.png => test_levels_options.png} | Bin lib/matplotlib/tests/test_axes.py | 40 ++++++------ tools/boilerplate.py | 2 +- 14 files changed, 122 insertions(+), 121 deletions(-) delete mode 100644 examples/lines_bars_and_markers/histline_demo.py create mode 100644 examples/lines_bars_and_markers/levels_demo.py rename lib/matplotlib/tests/baseline_images/test_axes/{test_histline_options.png => test_levels_options.png} (100%) diff --git a/.flake8 b/.flake8 index 0ab6c997577b..7c676656cb75 100644 --- a/.flake8 +++ b/.flake8 @@ -162,7 +162,7 @@ per-file-ignores = examples/lines_bars_and_markers/fill.py: E402 examples/lines_bars_and_markers/fill_between_demo.py: E402 examples/lines_bars_and_markers/filled_step.py: E402 - examples/lines_bars_and_markers/histline_demo.py: E402 + examples/lines_bars_and_markers/levels_demo.py: E402 examples/lines_bars_and_markers/horizontal_barchart_distribution.py: E402 examples/lines_bars_and_markers/joinstyle.py: E402 examples/lines_bars_and_markers/scatter_hist.py: E402 diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index 184a16d4be61..2215a7210423 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -4,7 +4,7 @@ ``matplotlib.artist`` ********************* -.. 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 +.. 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.LevelsPatch 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 :parts: 1 :private-bases: diff --git a/doc/api/patches_api.rst b/doc/api/patches_api.rst index e183fde84c12..95884ec50fc7 100644 --- a/doc/api/patches_api.rst +++ b/doc/api/patches_api.rst @@ -29,6 +29,7 @@ Classes FancyBboxPatch Patch PathPatch + LevelsPatch Polygon Rectangle RegularPolygon diff --git a/doc/users/next_whats_new/steppatch_and_histline.rst b/doc/users/next_whats_new/steppatch_and_histline.rst index 44a753e2290c..157d8184d1c2 100644 --- a/doc/users/next_whats_new/steppatch_and_histline.rst +++ b/doc/users/next_whats_new/steppatch_and_histline.rst @@ -1,4 +1,4 @@ -New `~.matplotlib.patches.StepPatch` artist and a `.pyplot.histline` method +New `~.matplotlib.patches.LevelsPatch` artist and a `.pyplot.levels` method --------------------------------------------------------------------------- These take inputs of asymmetric lengths with y-like values and x-like edges, between which the values lie. @@ -14,9 +14,9 @@ x-like edges, between which the values lie. fig, ax = plt.subplots(constrained_layout=True) - ax.histline(h, bins) + ax.levels(h, bins) plt.show() -See :doc:`/gallery/lines_bars_and_markers/histline_demo` +See :doc:`/gallery/lines_bars_and_markers/levels_demo` for examples. \ No newline at end of file diff --git a/examples/lines_bars_and_markers/histline_demo.py b/examples/lines_bars_and_markers/histline_demo.py deleted file mode 100644 index 7f8118012ab6..000000000000 --- a/examples/lines_bars_and_markers/histline_demo.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -============= -Histline Demo -============= - -This example demonstrates the use of `~.matplotlib.pyplot.histline` -for histogram and histogram-like data visualization and an associated -underlying `~.matplotlib.patches.StepPatch` artist, which is -a contrained version of `.PathPatch` specified by its bins and edges. -""" - -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.patches import StepPatch - -np.random.seed(0) -h, bins = np.histogram(np.random.normal(5, 3, 5000), - bins=np.linspace(0, 10, 20)) - -fig, axs = plt.subplots(3, 1, figsize=(7, 15)) -axs[0].histline(h, bins, label='Simple histogram') -axs[0].histline(h, bins+5, baseline=50, label='--//-- w/ modified baseline') -axs[0].histline(h, bins+10, baseline=None, label='--//-- w/ no edges') -axs[0].set_title("Step Histograms") - -axs[1].histline(np.arange(1, 6, 1), fill=True, - label='Filled histogram\nw/ automatatic edges') -axs[1].histline(np.arange(1, 6, 1)*0.3, np.arange(2, 8, 1), - orientation='horizontal', hatch='//', - label='Hatched histogram\nw/ horizontal orientation') -axs[1].set_title("Filled histogram") - -patch = StepPatch(values=[1, 2, 3, 2, 1], - edges=range(1, 7), - label=('Patch derived underlying object\n' - 'with default edge/facecolor behaviour')) -axs[2].add_patch(patch) -axs[2].set_xlim(0, 7) -axs[2].set_ylim(-1, 5) -axs[2].set_title("StepPatch artist") - -for ax in axs: - ax.legend() -plt.show() - - -############################################################################# -# -# ------------ -# -# References -# """""""""" -# -# The use of the following functions, methods, classes and modules is shown -# in this example: - -import matplotlib -matplotlib.axes.Axes.histline -matplotlib.pyplot.histline -matplotlib.patches.StepPatch diff --git a/examples/lines_bars_and_markers/levels_demo.py b/examples/lines_bars_and_markers/levels_demo.py new file mode 100644 index 000000000000..cf9e9922422b --- /dev/null +++ b/examples/lines_bars_and_markers/levels_demo.py @@ -0,0 +1,60 @@ +""" +============= +Histline Demo +============= + +This example demonstrates the use of `~.matplotlib.pyplot.levels` +for histogram and histogram-like data visualization and an associated +underlying `.LevelsPatch` artist, which is +a contrained version of `.PathPatch` specified by its bins and edges. +""" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import LevelsPatch + +np.random.seed(0) +h, bins = np.histogram(np.random.normal(5, 3, 5000), + bins=np.linspace(0, 10, 20)) + +fig, axs = plt.subplots(3, 1, figsize=(7, 15)) +axs[0].levels(h, bins, label='Simple histogram') +axs[0].levels(h, bins+5, baseline=50, label='--//-- w/ modified baseline') +axs[0].levels(h, bins+10, baseline=None, label='--//-- w/ no edges') +axs[0].set_title("Step Histograms") + +axs[1].levels(np.arange(1, 6, 1), fill=True, + label='Filled histogram\nw/ automatatic edges') +axs[1].levels(np.arange(1, 6, 1)*0.3, np.arange(2, 8, 1), + orientation='horizontal', hatch='//', + label='Hatched histogram\nw/ horizontal orientation') +axs[1].set_title("Filled histogram") + +patch = LevelsPatch(values=[1, 2, 3, 2, 1], + edges=range(1, 7), + label=('Patch derived underlying object\n' + 'with default edge/facecolor behaviour')) +axs[2].add_patch(patch) +axs[2].set_xlim(0, 7) +axs[2].set_ylim(-1, 5) +axs[2].set_title("LevelsPatch artist") + +for ax in axs: + ax.legend() +plt.show() + + +############################################################################# +# +# ------------ +# +# References +# """""""""" +# +# The use of the following functions, methods, classes and modules is shown +# in this example: + +import matplotlib +matplotlib.axes.Axes.levels +matplotlib.pyplot.levels +matplotlib.patches.LevelsPatch diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f41fecda0872..71508179854f 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6872,8 +6872,8 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, return tops, bins, cbook.silent_list(patch_type, patches) @_preprocess_data() - def histline(self, values, bins=None, *, - orientation='vertical', baseline=0, fill=False, **kwargs): + def levels(self, values, edges=None, *, + orientation='vertical', baseline=0, fill=False, **kwargs): """ A histogram-like line or filled plot. @@ -6882,8 +6882,8 @@ def histline(self, values, bins=None, *, values : array-like An array of y-values. - bins : array-like, default: ``range(len(vals)+1)`` - A array of x-values, with ``len(bins) == len(vals) + 1``, + edges : array-like, default: ``range(len(vals)+1)`` + A array of x-values, with ``len(edges) == len(vals) + 1``, between which the curve takes on vals values. orientation : {'vertical', 'horizontal'}, default: 'vertical' @@ -6896,12 +6896,12 @@ def histline(self, values, bins=None, *, Returns ------- - StepPatch : `.patches.StepPatch` + LevelsPatch : `matplotlib.patches.LevelsPatch` Other Parameters ---------------- **kwargs - `~.matplotlib.patches.StepPatch` properties + `~matplotlib.patches.LevelsPatch` properties """ @@ -6915,15 +6915,15 @@ def histline(self, values, bins=None, *, else: kwargs.setdefault('edgecolor', _color) - if bins is None: - bins = np.arange(len(values) + 1) - - patch = mpatches.StepPatch(values, - bins, - baseline=baseline, - orientation=orientation, - fill=fill, - **kwargs) + if edges is None: + edges = np.arange(len(values) + 1) + + patch = mpatches.LevelsPatch(values, + edges, + baseline=baseline, + orientation=orientation, + fill=fill, + **kwargs) self.add_patch(patch) if baseline is None: baseline = 0 diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index e30d8056eb08..afb6347928be 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -34,7 +34,7 @@ from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch, - StepPatch) + LevelsPatch) from matplotlib.collections import (LineCollection, RegularPolyCollection, CircleCollection, PathCollection, PolyCollection) @@ -624,7 +624,7 @@ def draw(self, renderer): ErrorbarContainer: legend_handler.HandlerErrorbar(), Line2D: legend_handler.HandlerLine2D(), Patch: legend_handler.HandlerPatch(), - StepPatch: legend_handler.HandlerStepPatch(), + LevelsPatch: legend_handler.HandlerLevelsPatch(), LineCollection: legend_handler.HandlerLineCollection(), RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(), CircleCollection: legend_handler.HandlerCircleCollection(), diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index 45fb759d7b23..1e59b66bbb39 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -302,9 +302,9 @@ def create_artists(self, legend, orig_handle, return [p] -class HandlerStepPatch(HandlerBase): +class HandlerLevelsPatch(HandlerBase): """ - Handler for `~.matplotlib.patches.StepPatch` instances. + Handler for `~.matplotlib.patches.LevelsPatch` instances. """ def __init__(self, **kw): """ @@ -319,7 +319,7 @@ def _create_patch(self, legend, orig_handle, width=width, height=height) return p - # Unfilled StepPatch should show as a line + # Unfilled LevelsPatch should show as a line def _create_line(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize): diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 230d24acaacf..94ad8413d84f 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -989,8 +989,8 @@ def set_path(self, path): self._path = path -class StepPatch(PathPatch): - """An unclosed stepline path patch.""" +class LevelsPatch(PathPatch): + """An unclosed levels path patch.""" @docstring.dedent_interpd def __init__(self, values, edges, *, @@ -1024,6 +1024,8 @@ def __init__(self, values, edges, *, super().__init__(path, **kwargs) def _update_data(self): + if np.isnan(np.sum(self._edges)): + raise ValueError('Nan values in "edges" are disallowed') if self._edges.size - 1 != self._values.size: raise ValueError('Size mismatch between "values" and "edges". ' "Expected `len(values) + 1 == len(edges)`, but " @@ -1032,18 +1034,16 @@ def _update_data(self): ) verts, codes = [], [] for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._values)): - x = np.vstack((self._edges[idx0:idx1+1], - self._edges[idx0:idx1+1])).T.flatten() - y = np.vstack((self._values[idx0:idx1], - self._values[idx0:idx1])).T.flatten() + x = np.repeat(self._edges[idx0:idx1+1], 2) + y = np.repeat(self._values[idx0:idx1], 2) if self.baseline is not None: y = np.hstack((self.baseline, y, self.baseline)) else: y = np.hstack((y[0], y, y[-1])) if self.orientation == 'vertical': - xy = np.vstack([x, y]).T + xy = np.column_stack([x, y]) else: - xy = np.vstack([y, x]).T + xy = np.column_stack([y, x]) verts.append(xy) codes.append(np.array([Path.MOVETO] + [Path.LINETO]*(len(xy)-1))) return np.vstack(verts), np.hstack(codes) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 8e9b6c814bc5..34c1bd856213 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2778,14 +2778,14 @@ def hist( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_copy_docstring_and_deprecators(Axes.histline) -def histline( - values, bins=None, *, orientation='vertical', baseline=0, +@_copy_docstring_and_deprecators(Axes.levels) +def levels( + values, edges=None, *, orientation='vertical', baseline=0, fill=False, data=None, **kwargs): - return gca().histline( - values, bins=bins, orientation=orientation, baseline=baseline, - fill=fill, **({"data": data} if data is not None else {}), - **kwargs) + return gca().levels( + values, edges=edges, orientation=orientation, + baseline=baseline, fill=fill, + **({"data": data} if data is not None else {}), **kwargs) # Autogenerated by boilerplate.py. Do not edit as changes will be lost. diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.png b/lib/matplotlib/tests/baseline_images/test_axes/test_levels_options.png similarity index 100% rename from lib/matplotlib/tests/baseline_images/test_axes/test_histline_options.png rename to lib/matplotlib/tests/baseline_images/test_axes/test_levels_options.png diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 769b1ba41786..862de4dd1d7b 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1745,20 +1745,20 @@ def test_hist_zorder(histtype, zorder): @check_figures_equal() -def test_histline(fig_test, fig_ref): +def test_levels(fig_test, fig_ref): import matplotlib.lines as mlines y = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist x = np.array([1., 2., 3., 4., 5., 6., 7., 8., 9.]) # bins fig_test, test_axes = plt.subplots(3, 2) test_axes = test_axes.flatten() - test_axes[0].histline(y, x, baseline=None) - test_axes[1].histline(y, x, baseline=None, orientation='horizontal') - test_axes[2].histline(y, x) - test_axes[3].histline(y, x, orientation='horizontal') - test_axes[4].histline(y, x) + test_axes[0].levels(y, x, baseline=None) + test_axes[1].levels(y, x, baseline=None, orientation='horizontal') + test_axes[2].levels(y, x) + test_axes[3].levels(y, x, orientation='horizontal') + test_axes[4].levels(y, x) test_axes[4].semilogy() - test_axes[5].histline(y, x, orientation='horizontal') + test_axes[5].levels(y, x, orientation='horizontal') test_axes[5].semilogy() fig_ref, ref_axes = plt.subplots(3, 2) @@ -1788,16 +1788,16 @@ def test_histline(fig_test, fig_ref): @check_figures_equal() -def test_histline_fill(fig_test, fig_ref): +def test_levels_fill(fig_test, fig_ref): h, bins = [1, 2, 3, 4, 2], [0, 1, 2, 3, 4, 5] bs = -2 # Test fig_test, test_axes = plt.subplots(2, 2) test_axes = test_axes.flatten() - test_axes[0].histline(h, bins, fill=True) - test_axes[1].histline(h, bins, orientation='horizontal', fill=True) - test_axes[2].histline(h, bins, baseline=bs, fill=True) - test_axes[3].histline(h, bins, baseline=bs, orientation='horizontal', + test_axes[0].levels(h, bins, fill=True) + test_axes[1].levels(h, bins, orientation='horizontal', fill=True) + test_axes[2].levels(h, bins, baseline=bs, fill=True) + test_axes[3].levels(h, bins, baseline=bs, orientation='horizontal', fill=True) # # Ref @@ -1815,22 +1815,22 @@ def test_histline_fill(fig_test, fig_ref): ref_axes[3].set_xlim(bs, None) -@image_comparison(['test_histline_options.png'], remove_text=True) -def test_histline_options(): +@image_comparison(['test_levels_options.png'], remove_text=True) +def test_levels_options(): x, y = np.array([1, 2, 3, 4, 5]), np.array([1, 2, 3, 4]).astype(float) yn = y.copy() yn[1] = np.nan fig, ax = plt.subplots() - ax.histline(y*3, x, color='green', fill=True, label="A") - ax.histline(y, x*3-3, color='red', fill=True, + ax.levels(y*3, x, color='green', fill=True, label="A") + ax.levels(y, x*3-3, color='red', fill=True, orientation='horizontal', label="B") - ax.histline(yn, x, color='orange', ls='--', lw=2, label="C") - ax.histline(yn/3, x*3-2, color='purple', ls='--', lw=2, baseline=0.5, + ax.levels(yn, x, color='orange', ls='--', lw=2, label="C") + ax.levels(yn/3, x*3-2, color='purple', ls='--', lw=2, baseline=0.5, orientation='horizontal', label="D") - ax.histline(y[::-1]*3+12, x, color='red', ls='--', lw=2, baseline=None, + ax.levels(y[::-1]*3+12, x, color='red', ls='--', lw=2, baseline=None, label="E") - ax.histline(y[:-1][::-1]*2+11, x[:-1]+0.5, color='blue', ls='--', lw=2, + ax.levels(y[:-1][::-1]*2+11, x[:-1]+0.5, color='blue', ls='--', lw=2, baseline=12, hatch='//', label="F") ax.legend(loc=0) diff --git a/tools/boilerplate.py b/tools/boilerplate.py index dae00a961b4e..5b1027a630c9 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -215,7 +215,7 @@ def boilerplate_gen(): 'grid', 'hexbin', 'hist', - 'histline', + 'levels', 'hist2d', 'hlines', 'imshow', From aa905acd787e6cf3cc20ad112cc9e7a10b66c51f Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Mon, 7 Sep 2020 21:57:32 +0200 Subject: [PATCH 07/12] feat: rename levels to stairs --- doc/api/artist_api.rst | 2 +- doc/api/patches_api.rst | 2 +- ..._histline.rst => steppatch_and_stairs.rst} | 8 ++-- .../{levels_demo.py => stairs_demo.py} | 32 +++++++------- lib/matplotlib/axes/_axes.py | 18 ++++---- lib/matplotlib/legend.py | 4 +- lib/matplotlib/legend_handler.py | 6 +-- lib/matplotlib/patches.py | 4 +- lib/matplotlib/pyplot.py | 6 +-- ...ls_options.png => test_stairs_options.png} | Bin lib/matplotlib/tests/test_axes.py | 40 +++++++++--------- tools/boilerplate.py | 2 +- 12 files changed, 62 insertions(+), 62 deletions(-) rename doc/users/next_whats_new/{steppatch_and_histline.rst => steppatch_and_stairs.rst} (73%) rename examples/lines_bars_and_markers/{levels_demo.py => stairs_demo.py} (57%) rename lib/matplotlib/tests/baseline_images/test_axes/{test_levels_options.png => test_stairs_options.png} (100%) diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index 2215a7210423..d77f27f0960f 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -4,7 +4,7 @@ ``matplotlib.artist`` ********************* -.. 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.LevelsPatch 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 +.. 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 :parts: 1 :private-bases: diff --git a/doc/api/patches_api.rst b/doc/api/patches_api.rst index 95884ec50fc7..ac21a644ccab 100644 --- a/doc/api/patches_api.rst +++ b/doc/api/patches_api.rst @@ -29,7 +29,7 @@ Classes FancyBboxPatch Patch PathPatch - LevelsPatch + StepPatch Polygon Rectangle RegularPolygon diff --git a/doc/users/next_whats_new/steppatch_and_histline.rst b/doc/users/next_whats_new/steppatch_and_stairs.rst similarity index 73% rename from doc/users/next_whats_new/steppatch_and_histline.rst rename to doc/users/next_whats_new/steppatch_and_stairs.rst index 157d8184d1c2..ec598a6d49ca 100644 --- a/doc/users/next_whats_new/steppatch_and_histline.rst +++ b/doc/users/next_whats_new/steppatch_and_stairs.rst @@ -1,5 +1,5 @@ -New `~.matplotlib.patches.LevelsPatch` artist and a `.pyplot.levels` method ---------------------------------------------------------------------------- +New `~.matplotlib.patches.StepPatch` artist and a `.pyplot.stairs` method +------------------------------------------------------------------------- These take inputs of asymmetric lengths with y-like values and x-like edges, between which the values lie. @@ -14,9 +14,9 @@ x-like edges, between which the values lie. fig, ax = plt.subplots(constrained_layout=True) - ax.levels(h, bins) + ax.stairs(h, bins) plt.show() -See :doc:`/gallery/lines_bars_and_markers/levels_demo` +See :doc:`/gallery/lines_bars_and_markers/stairs_demo` for examples. \ No newline at end of file diff --git a/examples/lines_bars_and_markers/levels_demo.py b/examples/lines_bars_and_markers/stairs_demo.py similarity index 57% rename from examples/lines_bars_and_markers/levels_demo.py rename to examples/lines_bars_and_markers/stairs_demo.py index cf9e9922422b..54fef8f005ae 100644 --- a/examples/lines_bars_and_markers/levels_demo.py +++ b/examples/lines_bars_and_markers/stairs_demo.py @@ -3,41 +3,41 @@ Histline Demo ============= -This example demonstrates the use of `~.matplotlib.pyplot.levels` +This example demonstrates the use of `~.matplotlib.pyplot.stairs` for histogram and histogram-like data visualization and an associated -underlying `.LevelsPatch` artist, which is +underlying `.StepPatch` artist, which is a contrained version of `.PathPatch` specified by its bins and edges. """ import numpy as np import matplotlib.pyplot as plt -from matplotlib.patches import LevelsPatch +from matplotlib.patches import StepPatch np.random.seed(0) h, bins = np.histogram(np.random.normal(5, 3, 5000), bins=np.linspace(0, 10, 20)) fig, axs = plt.subplots(3, 1, figsize=(7, 15)) -axs[0].levels(h, bins, label='Simple histogram') -axs[0].levels(h, bins+5, baseline=50, label='--//-- w/ modified baseline') -axs[0].levels(h, bins+10, baseline=None, label='--//-- w/ no edges') +axs[0].stairs(h, bins, label='Simple histogram') +axs[0].stairs(h, bins+5, baseline=50, label='--//-- w/ modified baseline') +axs[0].stairs(h, bins+10, baseline=None, label='--//-- w/ no edges') axs[0].set_title("Step Histograms") -axs[1].levels(np.arange(1, 6, 1), fill=True, +axs[1].stairs(np.arange(1, 6, 1), fill=True, label='Filled histogram\nw/ automatatic edges') -axs[1].levels(np.arange(1, 6, 1)*0.3, np.arange(2, 8, 1), +axs[1].stairs(np.arange(1, 6, 1)*0.3, np.arange(2, 8, 1), orientation='horizontal', hatch='//', label='Hatched histogram\nw/ horizontal orientation') axs[1].set_title("Filled histogram") -patch = LevelsPatch(values=[1, 2, 3, 2, 1], - edges=range(1, 7), - label=('Patch derived underlying object\n' - 'with default edge/facecolor behaviour')) +patch = StepPatch(values=[1, 2, 3, 2, 1], + edges=range(1, 7), + label=('Patch derived underlying object\n' + 'with default edge/facecolor behaviour')) axs[2].add_patch(patch) axs[2].set_xlim(0, 7) axs[2].set_ylim(-1, 5) -axs[2].set_title("LevelsPatch artist") +axs[2].set_title("StepPatch artist") for ax in axs: ax.legend() @@ -55,6 +55,6 @@ # in this example: import matplotlib -matplotlib.axes.Axes.levels -matplotlib.pyplot.levels -matplotlib.patches.LevelsPatch +matplotlib.axes.Axes.stairs +matplotlib.pyplot.stairs +matplotlib.patches.StepPatch diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 71508179854f..fc58cbe824eb 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6872,7 +6872,7 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, return tops, bins, cbook.silent_list(patch_type, patches) @_preprocess_data() - def levels(self, values, edges=None, *, + def stairs(self, values, edges=None, *, orientation='vertical', baseline=0, fill=False, **kwargs): """ A histogram-like line or filled plot. @@ -6896,12 +6896,12 @@ def levels(self, values, edges=None, *, Returns ------- - LevelsPatch : `matplotlib.patches.LevelsPatch` + StepPatch : `matplotlib.patches.StepPatch` Other Parameters ---------------- **kwargs - `~matplotlib.patches.LevelsPatch` properties + `~matplotlib.patches.StepPatch` properties """ @@ -6918,12 +6918,12 @@ def levels(self, values, edges=None, *, if edges is None: edges = np.arange(len(values) + 1) - patch = mpatches.LevelsPatch(values, - edges, - baseline=baseline, - orientation=orientation, - fill=fill, - **kwargs) + patch = mpatches.StepPatch(values, + edges, + baseline=baseline, + orientation=orientation, + fill=fill, + **kwargs) self.add_patch(patch) if baseline is None: baseline = 0 diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index afb6347928be..e30d8056eb08 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -34,7 +34,7 @@ from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch, - LevelsPatch) + StepPatch) from matplotlib.collections import (LineCollection, RegularPolyCollection, CircleCollection, PathCollection, PolyCollection) @@ -624,7 +624,7 @@ def draw(self, renderer): ErrorbarContainer: legend_handler.HandlerErrorbar(), Line2D: legend_handler.HandlerLine2D(), Patch: legend_handler.HandlerPatch(), - LevelsPatch: legend_handler.HandlerLevelsPatch(), + StepPatch: legend_handler.HandlerStepPatch(), LineCollection: legend_handler.HandlerLineCollection(), RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(), CircleCollection: legend_handler.HandlerCircleCollection(), diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py index 1e59b66bbb39..45fb759d7b23 100644 --- a/lib/matplotlib/legend_handler.py +++ b/lib/matplotlib/legend_handler.py @@ -302,9 +302,9 @@ def create_artists(self, legend, orig_handle, return [p] -class HandlerLevelsPatch(HandlerBase): +class HandlerStepPatch(HandlerBase): """ - Handler for `~.matplotlib.patches.LevelsPatch` instances. + Handler for `~.matplotlib.patches.StepPatch` instances. """ def __init__(self, **kw): """ @@ -319,7 +319,7 @@ def _create_patch(self, legend, orig_handle, width=width, height=height) return p - # Unfilled LevelsPatch should show as a line + # Unfilled StepPatch should show as a line def _create_line(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize): diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 94ad8413d84f..edb3b5afaeab 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -989,8 +989,8 @@ def set_path(self, path): self._path = path -class LevelsPatch(PathPatch): - """An unclosed levels path patch.""" +class StepPatch(PathPatch): + """An unclosed step path patch.""" @docstring.dedent_interpd def __init__(self, values, edges, *, diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 34c1bd856213..c5c047471aa8 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2778,11 +2778,11 @@ def hist( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. -@_copy_docstring_and_deprecators(Axes.levels) -def levels( +@_copy_docstring_and_deprecators(Axes.stairs) +def stairs( values, edges=None, *, orientation='vertical', baseline=0, fill=False, data=None, **kwargs): - return gca().levels( + return gca().stairs( values, edges=edges, orientation=orientation, baseline=baseline, fill=fill, **({"data": data} if data is not None else {}), **kwargs) diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_levels_options.png b/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_options.png similarity index 100% rename from lib/matplotlib/tests/baseline_images/test_axes/test_levels_options.png rename to lib/matplotlib/tests/baseline_images/test_axes/test_stairs_options.png diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 862de4dd1d7b..f98afe119b31 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1745,20 +1745,20 @@ def test_hist_zorder(histtype, zorder): @check_figures_equal() -def test_levels(fig_test, fig_ref): +def test_stairs(fig_test, fig_ref): import matplotlib.lines as mlines y = np.array([6, 14, 32, 37, 48, 32, 21, 4]) # hist x = np.array([1., 2., 3., 4., 5., 6., 7., 8., 9.]) # bins fig_test, test_axes = plt.subplots(3, 2) test_axes = test_axes.flatten() - test_axes[0].levels(y, x, baseline=None) - test_axes[1].levels(y, x, baseline=None, orientation='horizontal') - test_axes[2].levels(y, x) - test_axes[3].levels(y, x, orientation='horizontal') - test_axes[4].levels(y, x) + test_axes[0].stairs(y, x, baseline=None) + test_axes[1].stairs(y, x, baseline=None, orientation='horizontal') + test_axes[2].stairs(y, x) + test_axes[3].stairs(y, x, orientation='horizontal') + test_axes[4].stairs(y, x) test_axes[4].semilogy() - test_axes[5].levels(y, x, orientation='horizontal') + test_axes[5].stairs(y, x, orientation='horizontal') test_axes[5].semilogy() fig_ref, ref_axes = plt.subplots(3, 2) @@ -1788,16 +1788,16 @@ def test_levels(fig_test, fig_ref): @check_figures_equal() -def test_levels_fill(fig_test, fig_ref): +def test_stairs_fill(fig_test, fig_ref): h, bins = [1, 2, 3, 4, 2], [0, 1, 2, 3, 4, 5] bs = -2 # Test fig_test, test_axes = plt.subplots(2, 2) test_axes = test_axes.flatten() - test_axes[0].levels(h, bins, fill=True) - test_axes[1].levels(h, bins, orientation='horizontal', fill=True) - test_axes[2].levels(h, bins, baseline=bs, fill=True) - test_axes[3].levels(h, bins, baseline=bs, orientation='horizontal', + test_axes[0].stairs(h, bins, fill=True) + test_axes[1].stairs(h, bins, orientation='horizontal', fill=True) + test_axes[2].stairs(h, bins, baseline=bs, fill=True) + test_axes[3].stairs(h, bins, baseline=bs, orientation='horizontal', fill=True) # # Ref @@ -1815,22 +1815,22 @@ def test_levels_fill(fig_test, fig_ref): ref_axes[3].set_xlim(bs, None) -@image_comparison(['test_levels_options.png'], remove_text=True) -def test_levels_options(): +@image_comparison(['test_stairs_options.png'], remove_text=True) +def test_stairs_options(): x, y = np.array([1, 2, 3, 4, 5]), np.array([1, 2, 3, 4]).astype(float) yn = y.copy() yn[1] = np.nan fig, ax = plt.subplots() - ax.levels(y*3, x, color='green', fill=True, label="A") - ax.levels(y, x*3-3, color='red', fill=True, + ax.stairs(y*3, x, color='green', fill=True, label="A") + ax.stairs(y, x*3-3, color='red', fill=True, orientation='horizontal', label="B") - ax.levels(yn, x, color='orange', ls='--', lw=2, label="C") - ax.levels(yn/3, x*3-2, color='purple', ls='--', lw=2, baseline=0.5, + ax.stairs(yn, x, color='orange', ls='--', lw=2, label="C") + ax.stairs(yn/3, x*3-2, color='purple', ls='--', lw=2, baseline=0.5, orientation='horizontal', label="D") - ax.levels(y[::-1]*3+12, x, color='red', ls='--', lw=2, baseline=None, + ax.stairs(y[::-1]*3+12, x, color='red', ls='--', lw=2, baseline=None, label="E") - ax.levels(y[:-1][::-1]*2+11, x[:-1]+0.5, color='blue', ls='--', lw=2, + ax.stairs(y[:-1][::-1]*2+11, x[:-1]+0.5, color='blue', ls='--', lw=2, baseline=12, hatch='//', label="F") ax.legend(loc=0) diff --git a/tools/boilerplate.py b/tools/boilerplate.py index 5b1027a630c9..5035db1c3db2 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -215,7 +215,7 @@ def boilerplate_gen(): 'grid', 'hexbin', 'hist', - 'levels', + 'stairs', 'hist2d', 'hlines', 'imshow', From f2499b9c50f168865f2a2f8cc6977e3efc41af06 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Mon, 7 Sep 2020 22:45:11 +0200 Subject: [PATCH 08/12] feat: update stair tests --- .flake8 | 2 +- lib/matplotlib/patches.py | 8 +++- .../test_axes/test_stairs_options.png | Bin 12896 -> 13278 bytes lib/matplotlib/tests/test_axes.py | 35 +++++++++++++++--- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.flake8 b/.flake8 index 7c676656cb75..111f14d349e8 100644 --- a/.flake8 +++ b/.flake8 @@ -162,7 +162,7 @@ per-file-ignores = examples/lines_bars_and_markers/fill.py: E402 examples/lines_bars_and_markers/fill_between_demo.py: E402 examples/lines_bars_and_markers/filled_step.py: E402 - examples/lines_bars_and_markers/levels_demo.py: E402 + examples/lines_bars_and_markers/stairs_demo.py: E402 examples/lines_bars_and_markers/horizontal_barchart_distribution.py: E402 examples/lines_bars_and_markers/joinstyle.py: E402 examples/lines_bars_and_markers/scatter_hist.py: E402 diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index edb3b5afaeab..5d908a5db7ae 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1050,11 +1050,15 @@ def _update_data(self): def set_edges(self, edges): self._edges = np.asarray(edges) - self._update_data() + verts, codes = self._update_data() + self.set_path(Path(verts, codes)) + self.stale = True def set_values(self, values): self._values = np.asarray(values) - self._update_data() + verts, codes = self._update_data() + self.set_path(Path(verts, codes)) + self.stale = True class Polygon(Patch): diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_options.png b/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_options.png index 8ef7e7bc9920eaa49db7b0621d2d8917b603f3bf..0f750bc421d94a454ccb0f83e7e9e233003ce59a 100644 GIT binary patch literal 13278 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 AH2?qr diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f98afe119b31..cd7b48f88dc7 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1815,6 +1815,29 @@ def test_stairs_fill(fig_test, fig_ref): ref_axes[3].set_xlim(bs, None) +@check_figures_equal() +def test_stairs_update(fig_test, fig_ref): + # Test + fig_test, test_ax = plt.subplots() + h = test_ax.stairs([1, 2]) + h.set_values([2, 1]) + h.set_edges([0, 1, 1.5]) + + # # Ref + fig_ref, ref_ax = plt.subplots() + h = ref_ax.stairs([2, 1], [0, 1, 1.5]) + + +@pytest.mark.xfail +def test_stairs_invalid_nan(): + plt.stairs([1, 2], [0, np.nan, 1]) + + +@pytest.mark.xfail +def test_stairs_invalid_mismatch(): + plt.stairs([1, 2], [0, 1]) + + @image_comparison(['test_stairs_options.png'], remove_text=True) def test_stairs_options(): x, y = np.array([1, 2, 3, 4, 5]), np.array([1, 2, 3, 4]).astype(float) @@ -1824,14 +1847,14 @@ def test_stairs_options(): fig, ax = plt.subplots() ax.stairs(y*3, x, color='green', fill=True, label="A") ax.stairs(y, x*3-3, color='red', fill=True, - orientation='horizontal', label="B") + orientation='horizontal', label="B") ax.stairs(yn, x, color='orange', ls='--', lw=2, label="C") - ax.stairs(yn/3, x*3-2, color='purple', ls='--', lw=2, baseline=0.5, - orientation='horizontal', label="D") + ax.stairs(yn/3, x*3-2, ls='--', lw=2, baseline=0.5, + orientation='horizontal', label="D") ax.stairs(y[::-1]*3+12, x, color='red', ls='--', lw=2, baseline=None, - label="E") - ax.stairs(y[:-1][::-1]*2+11, x[:-1]+0.5, color='blue', ls='--', lw=2, - baseline=12, hatch='//', label="F") + label="E") + ax.stairs(y[:-1][::-1]*2+11, x[:-1]+0.5, color='black', ls='--', lw=2, + baseline=12, hatch='//', label="F") ax.legend(loc=0) From e5af734dbef8e4ef6828cb2f0b6ca0e539c865b6 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 9 Sep 2020 03:01:41 +0200 Subject: [PATCH 09/12] fix: stairs/steppatch cleanup --- doc/api/axes_api.rst | 1 + .../next_whats_new/steppatch_and_stairs.rst | 4 +- .../lines_bars_and_markers/stairs_demo.py | 37 +++++++++++++++---- lib/matplotlib/axes/_axes.py | 4 ++ lib/matplotlib/patches.py | 33 ++++++++++------- lib/matplotlib/tests/test_axes.py | 11 +++--- 6 files changed, 61 insertions(+), 29 deletions(-) diff --git a/doc/api/axes_api.rst b/doc/api/axes_api.rst index aa10872ddd35..cf0f0fdac8d7 100644 --- a/doc/api/axes_api.rst +++ b/doc/api/axes_api.rst @@ -137,6 +137,7 @@ Binned Axes.hexbin Axes.hist Axes.hist2d + Axes.stairs Contours -------- diff --git a/doc/users/next_whats_new/steppatch_and_stairs.rst b/doc/users/next_whats_new/steppatch_and_stairs.rst index ec598a6d49ca..e196bb6cf66a 100644 --- a/doc/users/next_whats_new/steppatch_and_stairs.rst +++ b/doc/users/next_whats_new/steppatch_and_stairs.rst @@ -1,7 +1,7 @@ New `~.matplotlib.patches.StepPatch` artist and a `.pyplot.stairs` method ------------------------------------------------------------------------- -These take inputs of asymmetric lengths with y-like values and -x-like edges, between which the values lie. +For both the artist and the function, the x-like edges input is one +longer than the y-like values input .. plot:: diff --git a/examples/lines_bars_and_markers/stairs_demo.py b/examples/lines_bars_and_markers/stairs_demo.py index 54fef8f005ae..d9f8cce98326 100644 --- a/examples/lines_bars_and_markers/stairs_demo.py +++ b/examples/lines_bars_and_markers/stairs_demo.py @@ -1,12 +1,15 @@ """ -============= -Histline Demo -============= +=========== +Stairs Demo +=========== This example demonstrates the use of `~.matplotlib.pyplot.stairs` for histogram and histogram-like data visualization and an associated -underlying `.StepPatch` artist, which is -a contrained version of `.PathPatch` specified by its bins and edges. +underlying `.StepPatch` artist, which is a specialized version of +`.PathPatch` specified by its bins and edges. + +The primary difference to `~.matplotlib.pyplot.step` is that ``stairs`` +x-like edges input is one longer than its y-like values input. """ import numpy as np @@ -19,12 +22,12 @@ fig, axs = plt.subplots(3, 1, figsize=(7, 15)) axs[0].stairs(h, bins, label='Simple histogram') -axs[0].stairs(h, bins+5, baseline=50, label='--//-- w/ modified baseline') -axs[0].stairs(h, bins+10, baseline=None, label='--//-- w/ no edges') +axs[0].stairs(h, bins+5, baseline=50, label='Modified baseline') +axs[0].stairs(h, bins+10, baseline=None, label='No edges') axs[0].set_title("Step Histograms") axs[1].stairs(np.arange(1, 6, 1), fill=True, - label='Filled histogram\nw/ automatatic edges') + label='Filled histogram\nw/ automatic edges') axs[1].stairs(np.arange(1, 6, 1)*0.3, np.arange(2, 8, 1), orientation='horizontal', hatch='//', label='Hatched histogram\nw/ horizontal orientation') @@ -43,6 +46,24 @@ ax.legend() plt.show() +############################################################################# +# Comparison of `.pyplot.step` and `.pyplot.stairs`. + +bins = np.arange(14) +centers = bins[:-1] + np.diff(bins)/2 +y = np.sin(centers / 2) + +plt.step(bins[:-1], y, where='post', label='Step(where="post")') +plt.plot(bins[:-1], y, 'o--', color='grey', alpha=0.3) + +plt.stairs(y - 1, bins, baseline=None, label='Stairs') +plt.plot(centers, y - 1, 'o--', color='grey', alpha=0.3) +plt.plot(np.repeat(bins, 2), np.hstack([y[0], np.repeat(y, 2), y[-1]]) - 1, + 'o', color='red', alpha=0.2) + +plt.legend() +plt.title('plt.step vs plt.stairs') +plt.show() ############################################################################# # diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index fc58cbe824eb..206d63f7dfc8 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6918,6 +6918,10 @@ def stairs(self, values, edges=None, *, if edges is None: edges = np.arange(len(values) + 1) + self._process_unit_info(xdata=edges, ydata=values, kwargs=kwargs) + edges = self.convert_xunits(edges) + values = self.convert_yunits(values) + patch = mpatches.StepPatch(values, edges, baseline=baseline, diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 5d908a5db7ae..eb700758666f 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1019,11 +1019,10 @@ def __init__(self, values, edges, *, self.orientation = orientation self._edges = np.asarray(edges) self._values = np.asarray(values) - verts, codes = self._update_data() - path = Path(verts, codes) - super().__init__(path, **kwargs) + self._update_path() + super().__init__(self._path, **kwargs) - def _update_data(self): + def _update_path(self): if np.isnan(np.sum(self._edges)): raise ValueError('Nan values in "edges" are disallowed') if self._edges.size - 1 != self._values.size: @@ -1046,18 +1045,24 @@ def _update_data(self): xy = np.column_stack([y, x]) verts.append(xy) codes.append(np.array([Path.MOVETO] + [Path.LINETO]*(len(xy)-1))) - return np.vstack(verts), np.hstack(codes) + self._path = Path(np.vstack(verts), np.hstack(codes)) - def set_edges(self, edges): - self._edges = np.asarray(edges) - verts, codes = self._update_data() - self.set_path(Path(verts, codes)) - self.stale = True + def set_data(self, values, edges=None): + """ + Set the values and optionally edges of the + StepPatch artist. - def set_values(self, values): - self._values = np.asarray(values) - verts, codes = self._update_data() - self.set_path(Path(verts, codes)) + Parameters + ---------- + values : 1D array-like or None + Will not update values, if passing None + edges : 1D array-like, optional + """ + if values is not None: + self._values = np.asarray(values) + if edges is not None: + self._edges = np.asarray(edges) + self._update_path() self.stale = True diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index cd7b48f88dc7..b88c2aee5dff 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1819,13 +1819,14 @@ def test_stairs_fill(fig_test, fig_ref): def test_stairs_update(fig_test, fig_ref): # Test fig_test, test_ax = plt.subplots() - h = test_ax.stairs([1, 2]) - h.set_values([2, 1]) - h.set_edges([0, 1, 1.5]) - + h = test_ax.stairs([1, 2, 3]) + h.set_values([3, 2, 1]) + h.set_data([1, 2, 1], np.arange(4)/2) + h.set_data([1, 2, 3]) + h.set_data(None, np.arange(4)) # # Ref fig_ref, ref_ax = plt.subplots() - h = ref_ax.stairs([2, 1], [0, 1, 1.5]) + h = ref_ax.stairs([1, 2, 3]) @pytest.mark.xfail From 6d177614ecdd969dec5a91295a899a94c61df166 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 9 Sep 2020 16:03:30 +0200 Subject: [PATCH 10/12] feat: extend StepPatch set/get methods, tests --- lib/matplotlib/patches.py | 43 +++++++++++++++++++++++++++++-- lib/matplotlib/tests/test_axes.py | 20 +++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index eb700758666f..707cd37c1c06 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1047,10 +1047,13 @@ def _update_path(self): codes.append(np.array([Path.MOVETO] + [Path.LINETO]*(len(xy)-1))) self._path = Path(np.vstack(verts), np.hstack(codes)) + def get_data(self): + """Get `.StepPatch` values and edges.""" + return self._values, self._edges + def set_data(self, values, edges=None): """ - Set the values and optionally edges of the - StepPatch artist. + Set `.StepPatch` values and optionally edges. Parameters ---------- @@ -1065,6 +1068,42 @@ def set_data(self, values, edges=None): self._update_path() self.stale = True + def set_values(self, values): + """ + Set `.StepPatch` values. + + Parameters + ---------- + values : 1D array-like + """ + self.set_data(values, edges=None) + + def set_edges(self, edges): + """ + Set `.StepPatch` edges. + + Parameters + ---------- + edges : 1D array-like + """ + self.set_data(None, edges=edges) + + def get_baseline(self): + """Get `.StepPatch` baseline value.""" + return self.baseline + + def set_baseline(self, baseline): + """ + Set `.StepPatch` baseline value. + + Parameters + ---------- + baseline : float or None + """ + self.baseline = baseline + self._update_path() + self.stale = True + class Polygon(Patch): """A general polygon patch.""" diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index b88c2aee5dff..a117292f1718 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1821,12 +1821,18 @@ def test_stairs_update(fig_test, fig_ref): fig_test, test_ax = plt.subplots() h = test_ax.stairs([1, 2, 3]) h.set_values([3, 2, 1]) + h.set_edges(np.arange(4)+2) h.set_data([1, 2, 1], np.arange(4)/2) h.set_data([1, 2, 3]) h.set_data(None, np.arange(4)) + assert np.allclose(h.get_data()[0], np.arange(1, 4)) + assert np.allclose(h.get_data()[1], np.arange(4)) + h.set_baseline(-2) + assert h.get_baseline() == -2 + # # Ref fig_ref, ref_ax = plt.subplots() - h = ref_ax.stairs([1, 2, 3]) + h = ref_ax.stairs([1, 2, 3], baseline=-2) @pytest.mark.xfail @@ -1839,6 +1845,18 @@ def test_stairs_invalid_mismatch(): plt.stairs([1, 2], [0, 1]) +@pytest.mark.xfail +def test_stairs_invalid_update(): + h = plt.stairs([1, 2], [0, 1]) + h.set_edges(np.arange(5)) + + +@pytest.mark.xfail +def test_stairs_invalid_update2(): + h = plt.stairs([1, 2], [0, 1]) + h.set_edges([1, np.nan, 2]) + + @image_comparison(['test_stairs_options.png'], remove_text=True) def test_stairs_options(): x, y = np.array([1, 2, 3, 4, 5]), np.array([1, 2, 3, 4]).astype(float) From 4018a62515947685f2b2fabfe1ae95247b18ed33 Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 9 Sep 2020 16:24:50 +0200 Subject: [PATCH 11/12] feat: add datetime test for StepPatch --- .../test_axes/test_stairs_datetime.png | Bin 0 -> 25157 bytes lib/matplotlib/tests/test_axes.py | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/test_stairs_datetime.png diff --git a/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_datetime.png b/lib/matplotlib/tests/baseline_images/test_axes/test_stairs_datetime.png new file mode 100644 index 0000000000000000000000000000000000000000..fa499047b0f865aea8aa1afacdf8cce50a73923a GIT binary patch literal 25157 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 b_h{6adDDV>R=&Gh$!BDx6eJTQPPqO*!F&^} literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a117292f1718..437f99f56ab3 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1877,6 +1877,15 @@ def test_stairs_options(): ax.legend(loc=0) +@image_comparison(['test_stairs_datetime.png']) +def test_stairs_datetime(): + f, ax = plt.subplots(constrained_layout=True) + ax.stairs(np.arange(36), + np.arange(np.datetime64('2001-12-27'), + np.datetime64('2002-02-02'))) + plt.xticks(rotation=30) + + def contour_dat(): x = np.linspace(-3, 5, 150) y = np.linspace(-3, 5, 120) From ecc54cb11180fc466f812df37d9bbc7fff94c39d Mon Sep 17 00:00:00 2001 From: andrzejnovak Date: Wed, 16 Sep 2020 06:56:33 +0200 Subject: [PATCH 12/12] fix: minor StepPatch adjustments --- doc/users/next_whats_new/steppatch_and_stairs.rst | 5 +++-- lib/matplotlib/patches.py | 5 ++--- lib/matplotlib/tests/test_axes.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/users/next_whats_new/steppatch_and_stairs.rst b/doc/users/next_whats_new/steppatch_and_stairs.rst index e196bb6cf66a..268130ff9d1e 100644 --- a/doc/users/next_whats_new/steppatch_and_stairs.rst +++ b/doc/users/next_whats_new/steppatch_and_stairs.rst @@ -1,5 +1,6 @@ -New `~.matplotlib.patches.StepPatch` artist and a `.pyplot.stairs` method -------------------------------------------------------------------------- +New StepPatch artist and a stairs method +---------------------------------------- +New `~.matplotlib.patches.StepPatch` artist and `.pyplot.stairs` method. For both the artist and the function, the x-like edges input is one longer than the y-like values input diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 707cd37c1c06..a616e3d77281 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1028,9 +1028,8 @@ def _update_path(self): if self._edges.size - 1 != self._values.size: raise ValueError('Size mismatch between "values" and "edges". ' "Expected `len(values) + 1 == len(edges)`, but " - "they are or lengths {} and {}".format( - self._edges.size, self._values.size) - ) + f"`len(values) = {self._values.size}` and " + f"`len(edges) = {self._edges.size}`.") verts, codes = [], [] for idx0, idx1 in cbook.contiguous_regions(~np.isnan(self._values)): x = np.repeat(self._edges[idx0:idx1+1], 2) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 437f99f56ab3..801294158551 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1847,14 +1847,14 @@ def test_stairs_invalid_mismatch(): @pytest.mark.xfail def test_stairs_invalid_update(): - h = plt.stairs([1, 2], [0, 1]) - h.set_edges(np.arange(5)) + h = plt.stairs([1, 2], [0, 1, 2]) + h.set_edges([1, np.nan, 2]) @pytest.mark.xfail def test_stairs_invalid_update2(): - h = plt.stairs([1, 2], [0, 1]) - h.set_edges([1, np.nan, 2]) + h = plt.stairs([1, 2], [0, 1, 2]) + h.set_edges(np.arange(5)) @image_comparison(['test_stairs_options.png'], remove_text=True)