From fb77685e7e6f3ad26fb24fb58923358fda040cec Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Fri, 7 Mar 2025 23:40:50 +0100 Subject: [PATCH] ENH: Add align parameter to broken_barh() In particular `align="center"` makes it easier to position bars centered on labels, as illustrated in the changed example. Note: This parameter is similar to `bar(..., align=...)`. --- .../next_whats_new/broken_barh_align.rst | 4 ++++ .../lines_bars_and_markers/broken_barh.py | 14 ++++++------ lib/matplotlib/axes/_axes.py | 22 ++++++++++++++++--- lib/matplotlib/axes/_axes.pyi | 1 + lib/matplotlib/pyplot.py | 7 +++++- lib/matplotlib/tests/test_axes.py | 15 +++++++++++++ 6 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 doc/users/next_whats_new/broken_barh_align.rst diff --git a/doc/users/next_whats_new/broken_barh_align.rst b/doc/users/next_whats_new/broken_barh_align.rst new file mode 100644 index 000000000000..5108ac5b0e9a --- /dev/null +++ b/doc/users/next_whats_new/broken_barh_align.rst @@ -0,0 +1,4 @@ +``broken_barh()`` vertical alignment though ``align`` parameter +--------------------------------------------------------------- +`~.Axes.broken_barh` now supports vertical alignment of the bars through the +``align`` parameter. diff --git a/galleries/examples/lines_bars_and_markers/broken_barh.py b/galleries/examples/lines_bars_and_markers/broken_barh.py index 3714ca7c748d..a709e911773d 100644 --- a/galleries/examples/lines_bars_and_markers/broken_barh.py +++ b/galleries/examples/lines_bars_and_markers/broken_barh.py @@ -18,13 +18,13 @@ network = np.column_stack([10*np.random.random(10), np.full(10, 0.05)]) fig, ax = plt.subplots() -# broken_barh(xranges, (ymin, height)) -ax.broken_barh(cpu_1, (-0.2, 0.4)) -ax.broken_barh(cpu_2, (0.8, 0.4)) -ax.broken_barh(cpu_3, (1.8, 0.4)) -ax.broken_barh(cpu_4, (2.8, 0.4)) -ax.broken_barh(disk, (3.8, 0.4), color="tab:orange") -ax.broken_barh(network, (4.8, 0.4), color="tab:green") +# broken_barh(xranges, (ypos, height)) +ax.broken_barh(cpu_1, (0, 0.4), align="center") +ax.broken_barh(cpu_2, (1, 0.4), align="center") +ax.broken_barh(cpu_3, (2, 0.4), align="center") +ax.broken_barh(cpu_4, (3, 0.4), align="center") +ax.broken_barh(disk, (4, 0.4), align="center", color="tab:orange") +ax.broken_barh(network, (5, 0.4), align="center", color="tab:green") ax.set_xlim(0, 10) ax.set_yticks(range(6), labels=["CPU 1", "CPU 2", "CPU 3", "CPU 4", "disk", "network"]) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a3c9abc96bae..cc10e72fb295 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2935,7 +2935,7 @@ def sign(x): @_preprocess_data() @_docstring.interpd - def broken_barh(self, xranges, yrange, **kwargs): + def broken_barh(self, xranges, yrange, align="bottom", **kwargs): """ Plot a horizontal sequence of rectangles. @@ -2948,8 +2948,16 @@ def broken_barh(self, xranges, yrange, **kwargs): The x-positions and extents of the rectangles. For each tuple (*xmin*, *xwidth*) a rectangle is drawn from *xmin* to *xmin* + *xwidth*. - yrange : (*ymin*, *yheight*) + yrange : (*ypos*, *yheight*) The y-position and extent for all the rectangles. + align : {"bottom", "center", "top"}, default: 'bottom' + The alignment of the yrange with respect to the y-position. One of: + + - "bottom": Resulting y-range [ypos, ypos + yheight] + - "center": Resulting y-range [ypos - yheight/2, ypos + yheight/2] + - "top": Resulting y-range [ypos - yheight, ypos] + + .. versionadded:: 3.11 Returns ------- @@ -2984,7 +2992,15 @@ def broken_barh(self, xranges, yrange, **kwargs): vertices = [] y0, dy = yrange - y0, y1 = self.convert_yunits((y0, y0 + dy)) + + _api.check_in_list(['bottom', 'center', 'top'], align=align) + if align == "bottom": + y0, y1 = self.convert_yunits((y0, y0 + dy)) + elif align == "center": + y0, y1 = self.convert_yunits((y0 - dy/2, y0 + dy/2)) + else: + y0, y1 = self.convert_yunits((y0 - dy, y0)) + for xr in xranges: # convert the absolute values, not the x and dx try: x0, dx = xr diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index e644860f8b1b..eb416be431ba 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -263,6 +263,7 @@ class Axes(_AxesBase): self, xranges: Sequence[tuple[float, float]], yrange: tuple[float, float], + align: Literal["bottom", "center", "top"] = ..., *, data=..., **kwargs diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index ab33146d2185..b29181452bc6 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3111,12 +3111,17 @@ def boxplot( def broken_barh( xranges: Sequence[tuple[float, float]], yrange: tuple[float, float], + align: Literal["bottom", "center", "top"] = "bottom", *, data=None, **kwargs, ) -> PolyCollection: return gca().broken_barh( - xranges, yrange, **({"data": data} if data is not None else {}), **kwargs + xranges, + yrange, + align=align, + **({"data": data} if data is not None else {}), + **kwargs, ) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 0a5a5ff23e80..ab3295b064ae 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -7134,6 +7134,21 @@ def test_broken_barh_timedelta(): assert pp.get_paths()[0].vertices[2, 0] == mdates.date2num(d0) + 1 / 24 +def test_broken_barh_align(): + fig, ax = plt.subplots() + pc = ax.broken_barh([(0, 10)], (0, 2)) + for path in pc.get_paths(): + assert_array_equal(path.get_extents().intervaly, [0, 2]) + + pc = ax.broken_barh([(0, 10)], (10, 2), align="center") + for path in pc.get_paths(): + assert_array_equal(path.get_extents().intervaly, [9, 11]) + + pc = ax.broken_barh([(0, 10)], (20, 2), align="top") + for path in pc.get_paths(): + assert_array_equal(path.get_extents().intervaly, [18, 20]) + + def test_pandas_pcolormesh(pd): time = pd.date_range('2000-01-01', periods=10) depth = np.arange(20)