diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index ee38cc91ab83..30bffd3cac02 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2598,13 +2598,25 @@ def bar_label(self, container, labels=None, *, fmt="%g", label_type="edge", **kwargs Any remaining keyword arguments are passed through to - `.Axes.annotate`. + `.Axes.annotate`. The alignment parameters ( + *horizontalalignment* / *ha*, *verticalalignment* / *va*) are + not supported because the labels are automatically aligned to + the bars. Returns ------- list of `.Text` A list of `.Text` instances for the labels. """ + for key in ['horizontalalignment', 'ha', 'verticalalignment', 'va']: + if key in kwargs: + raise ValueError( + f"Passing {key!r} to bar_label() is not supported.") + + a, b = self.yaxis.get_view_interval() + y_inverted = a > b + c, d = self.xaxis.get_view_interval() + x_inverted = c > d # want to know whether to put label on positive or negative direction # cannot use np.sign here because it will return 0 if x == 0 @@ -2633,7 +2645,7 @@ def sign(x): annotations = [] for bar, err, dat, lbl in itertools.zip_longest( - bars, errs, datavalues, labels + bars, errs, datavalues, labels ): (x0, y0), (x1, y1) = bar.get_bbox().get_points() xc, yc = (x0 + x1) / 2, (y0 + y1) / 2 @@ -2665,18 +2677,26 @@ def sign(x): xy = endpt, yc if orientation == "vertical": - xytext = 0, sign(dat) * padding + y_direction = -1 if y_inverted else 1 + xytext = 0, y_direction * sign(dat) * padding else: - xytext = sign(dat) * padding, 0 + x_direction = -1 if x_inverted else 1 + xytext = x_direction * sign(dat) * padding, 0 if label_type == "center": ha, va = "center", "center" elif label_type == "edge": if orientation == "vertical": ha = 'center' - va = 'top' if dat < 0 else 'bottom' # also handles NaN + if y_inverted: + va = 'top' if dat > 0 else 'bottom' # also handles NaN + else: + va = 'top' if dat < 0 else 'bottom' # also handles NaN elif orientation == "horizontal": - ha = 'right' if dat < 0 else 'left' # also handles NaN + if x_inverted: + ha = 'right' if dat > 0 else 'left' # also handles NaN + else: + ha = 'right' if dat < 0 else 'left' # also handles NaN va = 'center' if np.isnan(dat): diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 81b1b09ddd51..04d90d358d99 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -7343,6 +7343,20 @@ def test_bar_label_location_vertical(): assert labels[1].get_va() == 'top' +def test_bar_label_location_vertical_yinverted(): + ax = plt.gca() + ax.invert_yaxis() + xs, heights = [1, 2], [3, -4] + rects = ax.bar(xs, heights) + labels = ax.bar_label(rects) + assert labels[0].xy == (xs[0], heights[0]) + assert labels[0].get_ha() == 'center' + assert labels[0].get_va() == 'top' + assert labels[1].xy == (xs[1], heights[1]) + assert labels[1].get_ha() == 'center' + assert labels[1].get_va() == 'bottom' + + def test_bar_label_location_horizontal(): ax = plt.gca() ys, widths = [1, 2], [3, -4] @@ -7356,6 +7370,49 @@ def test_bar_label_location_horizontal(): assert labels[1].get_va() == 'center' +def test_bar_label_location_horizontal_yinverted(): + ax = plt.gca() + ax.invert_yaxis() + ys, widths = [1, 2], [3, -4] + rects = ax.barh(ys, widths) + labels = ax.bar_label(rects) + assert labels[0].xy == (widths[0], ys[0]) + assert labels[0].get_ha() == 'left' + assert labels[0].get_va() == 'center' + assert labels[1].xy == (widths[1], ys[1]) + assert labels[1].get_ha() == 'right' + assert labels[1].get_va() == 'center' + + +def test_bar_label_location_horizontal_xinverted(): + ax = plt.gca() + ax.invert_xaxis() + ys, widths = [1, 2], [3, -4] + rects = ax.barh(ys, widths) + labels = ax.bar_label(rects) + assert labels[0].xy == (widths[0], ys[0]) + assert labels[0].get_ha() == 'right' + assert labels[0].get_va() == 'center' + assert labels[1].xy == (widths[1], ys[1]) + assert labels[1].get_ha() == 'left' + assert labels[1].get_va() == 'center' + + +def test_bar_label_location_horizontal_xyinverted(): + ax = plt.gca() + ax.invert_xaxis() + ax.invert_yaxis() + ys, widths = [1, 2], [3, -4] + rects = ax.barh(ys, widths) + labels = ax.bar_label(rects) + assert labels[0].xy == (widths[0], ys[0]) + assert labels[0].get_ha() == 'right' + assert labels[0].get_va() == 'center' + assert labels[1].xy == (widths[1], ys[1]) + assert labels[1].get_ha() == 'left' + assert labels[1].get_va() == 'center' + + def test_bar_label_location_center(): ax = plt.gca() ys, widths = [1, 2], [3, -4] @@ -7407,6 +7464,16 @@ def test_bar_label_nan_ydata(): assert labels[0].get_va() == 'bottom' +def test_bar_label_nan_ydata_inverted(): + ax = plt.gca() + ax.yaxis_inverted() + bars = ax.bar([2, 3], [np.nan, 1]) + labels = ax.bar_label(bars) + assert [l.get_text() for l in labels] == ['', '1'] + assert labels[0].xy == (2, 0) + assert labels[0].get_va() == 'bottom' + + def test_patch_bounds(): # PR 19078 fig, ax = plt.subplots() ax.add_patch(mpatches.Wedge((0, -1), 1.05, 60, 120, 0.1))