Skip to content

Commit e68c1e8

Browse files
stefmolinQuLogic
andauthored
Add functionality to label individual bars with Axes.bar() (matplotlib#23525)
* Add labels argument to bar(). * Add what's new users entry. * Fix doc entry. * Add test for mismatch in label length and data length. * Switch to 'label' argument. * Handle duplicate bar labels in legend; address PR comments. * Add tests. * Update label description in docstring. * Remove duplicate label filtering. * Add note on repeated labels. * Update docstring per PR. Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
1 parent 83b97f9 commit e68c1e8

File tree

3 files changed

+68
-4
lines changed

3 files changed

+68
-4
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Easier labelling of bars in bar plot
2+
------------------------------------
3+
4+
The ``label`` argument of `~matplotlib.axes.Axes.bar` can now
5+
be passed a list of labels for the bars.
6+
7+
.. code-block:: python
8+
9+
import matplotlib.pyplot as plt
10+
11+
x = ["a", "b", "c"]
12+
y = [10, 20, 15]
13+
14+
fig, ax = plt.subplots()
15+
bar_container = ax.barh(x, y, label=x)
16+
[bar.get_label() for bar in bar_container]

lib/matplotlib/axes/_axes.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2256,6 +2256,14 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
22562256
The tick labels of the bars.
22572257
Default: None (Use default numeric labels.)
22582258
2259+
label : str or list of str, optional
2260+
A single label is attached to the resulting `.BarContainer` as a
2261+
label for the whole dataset.
2262+
If a list is provided, it must be the same length as *x* and
2263+
labels the individual bars. Repeated labels are not de-duplicated
2264+
and will cause repeated label entries, so this is best used when
2265+
bars also differ in style (e.g., by passing a list to *color*.)
2266+
22592267
xerr, yerr : float or array-like of shape(N,) or shape(2, N), optional
22602268
If not *None*, add horizontal / vertical errorbars to the bar tips.
22612269
The values are +/- sizes relative to the data:
@@ -2381,6 +2389,16 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
23812389
tick_label_axis = self.yaxis
23822390
tick_label_position = y
23832391

2392+
if not isinstance(label, str) and np.iterable(label):
2393+
bar_container_label = '_nolegend_'
2394+
patch_labels = label
2395+
else:
2396+
bar_container_label = label
2397+
patch_labels = ['_nolegend_'] * len(x)
2398+
if len(patch_labels) != len(x):
2399+
raise ValueError(f'number of labels ({len(patch_labels)}) '
2400+
f'does not match number of bars ({len(x)}).')
2401+
23842402
linewidth = itertools.cycle(np.atleast_1d(linewidth))
23852403
hatch = itertools.cycle(np.atleast_1d(hatch))
23862404
color = itertools.chain(itertools.cycle(mcolors.to_rgba_array(color)),
@@ -2420,14 +2438,14 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
24202438

24212439
patches = []
24222440
args = zip(left, bottom, width, height, color, edgecolor, linewidth,
2423-
hatch)
2424-
for l, b, w, h, c, e, lw, htch in args:
2441+
hatch, patch_labels)
2442+
for l, b, w, h, c, e, lw, htch, lbl in args:
24252443
r = mpatches.Rectangle(
24262444
xy=(l, b), width=w, height=h,
24272445
facecolor=c,
24282446
edgecolor=e,
24292447
linewidth=lw,
2430-
label='_nolegend_',
2448+
label=lbl,
24312449
hatch=htch,
24322450
)
24332451
r._internal_update(kwargs)
@@ -2466,7 +2484,8 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
24662484
datavalues = width
24672485

24682486
bar_container = BarContainer(patches, errorbar, datavalues=datavalues,
2469-
orientation=orientation, label=label)
2487+
orientation=orientation,
2488+
label=bar_container_label)
24702489
self.add_container(bar_container)
24712490

24722491
if tick_labels is not None:

lib/matplotlib/tests/test_axes.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,35 @@ def test_bar_hatches(fig_test, fig_ref):
18861886
ax_test.bar(x, y, hatch=hatches)
18871887

18881888

1889+
@pytest.mark.parametrize(
1890+
("x", "width", "label", "expected_labels", "container_label"),
1891+
[
1892+
("x", 1, "x", ["_nolegend_"], "x"),
1893+
(["a", "b", "c"], [10, 20, 15], ["A", "B", "C"],
1894+
["A", "B", "C"], "_nolegend_"),
1895+
(["a", "b", "c"], [10, 20, 15], ["R", "Y", "_nolegend_"],
1896+
["R", "Y", "_nolegend_"], "_nolegend_"),
1897+
(["a", "b", "c"], [10, 20, 15], "bars",
1898+
["_nolegend_", "_nolegend_", "_nolegend_"], "bars"),
1899+
]
1900+
)
1901+
def test_bar_labels(x, width, label, expected_labels, container_label):
1902+
_, ax = plt.subplots()
1903+
bar_container = ax.bar(x, width, label=label)
1904+
bar_labels = [bar.get_label() for bar in bar_container]
1905+
assert expected_labels == bar_labels
1906+
assert bar_container.get_label() == container_label
1907+
1908+
1909+
def test_bar_labels_length():
1910+
_, ax = plt.subplots()
1911+
with pytest.raises(ValueError):
1912+
ax.bar(["x", "y"], [1, 2], label=["X", "Y", "Z"])
1913+
_, ax = plt.subplots()
1914+
with pytest.raises(ValueError):
1915+
ax.bar(["x", "y"], [1, 2], label=["X"])
1916+
1917+
18891918
def test_pandas_minimal_plot(pd):
18901919
# smoke test that series and index objects do not warn
18911920
for x in [pd.Series([1, 2], dtype="float64"),

0 commit comments

Comments
 (0)