diff --git a/doc/users/next_whats_new/barcontainer_properties.rst b/doc/users/next_whats_new/barcontainer_properties.rst new file mode 100644 index 000000000000..0efe4ee00e4f --- /dev/null +++ b/doc/users/next_whats_new/barcontainer_properties.rst @@ -0,0 +1,7 @@ +``BarContainer`` properties +--------------------------- +`.BarContainer` gained new properties to easily access coordinates of the bars: + +- `~.BarContainer.bottoms` +- `~.BarContainer.tops` +- `~.BarContainer.position_centers` diff --git a/lib/matplotlib/container.py b/lib/matplotlib/container.py index b6dd43724f34..fcf2e6016db9 100644 --- a/lib/matplotlib/container.py +++ b/lib/matplotlib/container.py @@ -73,6 +73,48 @@ def __init__(self, patches, errorbar=None, *, datavalues=None, self.orientation = orientation super().__init__(patches, **kwargs) + @property + def bottoms(self): + """ + Return the values at the lower end of the bars. + + .. versionadded:: 3.11 + """ + if self.orientation == 'vertical': + return [p.get_y() for p in self.patches] + elif self.orientation == 'horizontal': + return [p.get_x() for p in self.patches] + else: + raise ValueError("orientation must be 'vertical' or 'horizontal'.") + + @property + def tops(self): + """ + Return the values at the upper end of the bars. + + .. versionadded:: 3.11 + """ + if self.orientation == 'vertical': + return [p.get_y() + p.get_height() for p in self.patches] + elif self.orientation == 'horizontal': + return [p.get_x() + p.get_width() for p in self.patches] + else: + raise ValueError("orientation must be 'vertical' or 'horizontal'.") + + @property + def position_centers(self): + """ + Return the centers of bar positions. + + .. versionadded:: 3.11 + """ + if self.orientation == 'vertical': + return [p.get_x() + p.get_width() / 2 for p in self.patches] + elif self.orientation == 'horizontal': + return [p.get_y() + p.get_height() / 2 for p in self.patches] + else: + raise ValueError("orientation must be 'vertical' or 'horizontal'.") + class ErrorbarContainer(Container): """ diff --git a/lib/matplotlib/container.pyi b/lib/matplotlib/container.pyi index c66e7ba4b4c3..ff11830c544c 100644 --- a/lib/matplotlib/container.pyi +++ b/lib/matplotlib/container.pyi @@ -32,6 +32,12 @@ class BarContainer(Container): orientation: Literal["vertical", "horizontal"] | None = ..., **kwargs ) -> None: ... + @property + def bottoms(self) -> list[float]: ... + @property + def tops(self) -> list[float]: ... + @property + def position_centers(self) -> list[float]: ... class ErrorbarContainer(Container): lines: tuple[Line2D, tuple[Line2D, ...], tuple[LineCollection, ...]] diff --git a/lib/matplotlib/tests/test_container.py b/lib/matplotlib/tests/test_container.py index 1e4577c518ae..6998101dd755 100644 --- a/lib/matplotlib/tests/test_container.py +++ b/lib/matplotlib/tests/test_container.py @@ -1,4 +1,5 @@ import numpy as np +from numpy.testing import assert_array_equal import matplotlib.pyplot as plt @@ -35,3 +36,20 @@ def test_nonstring_label(): # Test for #26824 plt.bar(np.arange(10), np.random.rand(10), label=1) plt.legend() + + +def test_barcontainer_position_centers__bottoms__tops(): + fig, ax = plt.subplots() + pos = [1, 2, 4] + bottoms = np.array([1, 5, 3]) + heights = np.array([2, 3, 4]) + + container = ax.bar(pos, heights, bottom=bottoms) + assert_array_equal(container.position_centers, pos) + assert_array_equal(container.bottoms, bottoms) + assert_array_equal(container.tops, bottoms + heights) + + container = ax.barh(pos, heights, left=bottoms) + assert_array_equal(container.position_centers, pos) + assert_array_equal(container.bottoms, bottoms) + assert_array_equal(container.tops, bottoms + heights)