Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6a322de

Browse files
authored
Merge pull request #15602 from immaxchen/bar-chart-auto-label-gh12386
Add an auto-labeling helper function for bar charts
2 parents ba1e063 + 9bb3161 commit 6a322de

File tree

11 files changed

+363
-26
lines changed

11 files changed

+363
-26
lines changed

doc/api/axes_api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Basic
6666

6767
Axes.bar
6868
Axes.barh
69+
Axes.bar_label
6970

7071
Axes.stem
7172
Axes.eventplot
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:orphan:
2+
3+
Bar charts auto-labeling
4+
------------------------
5+
A new `.Axes.bar_label` method has been added for auto-labeling bar charts.
6+
See :doc:`/gallery/lines_bars_and_markers/bar_label_demo` for examples.
7+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:orphan:
2+
3+
Setting BarContainer orientation
4+
--------------------------------
5+
`.BarContainer` now accepts a new string argument ``orientation``.
6+
It can be either ``vertical`` or ``horizontal``, default is ``None``.
7+
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
==============
3+
Bar Label Demo
4+
==============
5+
6+
This example shows how to use the `~.Axes.bar_label` helper function
7+
to create bar chart labels.
8+
9+
See also the :doc:`grouped bar
10+
</gallery/lines_bars_and_markers/barchart>`,
11+
:doc:`stacked bar
12+
</gallery/lines_bars_and_markers/bar_stacked>` and
13+
:doc:`horizontal bar chart
14+
</gallery/lines_bars_and_markers/barh>` examples.
15+
"""
16+
17+
import matplotlib
18+
import matplotlib.pyplot as plt
19+
import numpy as np
20+
21+
###############################################################################
22+
# Define the data
23+
24+
N = 5
25+
menMeans = (20, 35, 30, 35, -27)
26+
womenMeans = (25, 32, 34, 20, -25)
27+
menStd = (2, 3, 4, 1, 2)
28+
womenStd = (3, 5, 2, 3, 3)
29+
ind = np.arange(N) # the x locations for the groups
30+
width = 0.35 # the width of the bars: can also be len(x) sequence
31+
32+
###############################################################################
33+
# Stacked bar plot with error bars
34+
35+
fig, ax = plt.subplots()
36+
37+
p1 = ax.bar(ind, menMeans, width, yerr=menStd, label='Men')
38+
p2 = ax.bar(ind, womenMeans, width,
39+
bottom=menMeans, yerr=womenStd, label='Women')
40+
41+
ax.axhline(0, color='grey', linewidth=0.8)
42+
ax.set_ylabel('Scores')
43+
ax.set_title('Scores by group and gender')
44+
ax.set_xticks(ind)
45+
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
46+
ax.legend()
47+
48+
# Label with label_type 'center' instead of the default 'edge'
49+
ax.bar_label(p1, label_type='center')
50+
ax.bar_label(p2, label_type='center')
51+
ax.bar_label(p2)
52+
53+
plt.show()
54+
55+
###############################################################################
56+
# Horizontal bar chart
57+
58+
# Fixing random state for reproducibility
59+
np.random.seed(19680801)
60+
61+
# Example data
62+
people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim')
63+
y_pos = np.arange(len(people))
64+
performance = 3 + 10 * np.random.rand(len(people))
65+
error = np.random.rand(len(people))
66+
67+
fig, ax = plt.subplots()
68+
69+
hbars = ax.barh(y_pos, performance, xerr=error, align='center')
70+
ax.set_yticks(y_pos)
71+
ax.set_yticklabels(people)
72+
ax.invert_yaxis() # labels read top-to-bottom
73+
ax.set_xlabel('Performance')
74+
ax.set_title('How fast do you want to go today?')
75+
76+
# Label with specially formatted floats
77+
ax.bar_label(hbars, fmt='%.2f')
78+
ax.set_xlim(right=15) # adjust xlim to fit labels
79+
80+
plt.show()
81+
82+
###############################################################################
83+
# Some of the more advanced things that one can do with bar labels
84+
85+
fig, ax = plt.subplots()
86+
87+
hbars = ax.barh(y_pos, performance, xerr=error, align='center')
88+
ax.set_yticks(y_pos)
89+
ax.set_yticklabels(people)
90+
ax.invert_yaxis() # labels read top-to-bottom
91+
ax.set_xlabel('Performance')
92+
ax.set_title('How fast do you want to go today?')
93+
94+
# Label with given captions, custom padding and annotate options
95+
ax.bar_label(hbars, labels=['±%.2f' % e for e in error],
96+
padding=8, color='b', fontsize=14)
97+
ax.set_xlim(right=16)
98+
99+
plt.show()
100+
101+
#############################################################################
102+
#
103+
# ------------
104+
#
105+
# References
106+
# """"""""""
107+
#
108+
# The use of the following functions, methods and classes is shown
109+
# in this example:
110+
111+
matplotlib.axes.Axes.bar
112+
matplotlib.pyplot.bar
113+
matplotlib.axes.Axes.barh
114+
matplotlib.pyplot.barh
115+
matplotlib.axes.Axes.bar_label
116+
matplotlib.pyplot.bar_label

examples/lines_bars_and_markers/barchart.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,8 @@
3030
ax.set_xticklabels(labels)
3131
ax.legend()
3232

33-
34-
def autolabel(rects):
35-
"""Attach a text label above each bar in *rects*, displaying its height."""
36-
for rect in rects:
37-
height = rect.get_height()
38-
ax.annotate('{}'.format(height),
39-
xy=(rect.get_x() + rect.get_width() / 2, height),
40-
xytext=(0, 3), # 3 points vertical offset
41-
textcoords="offset points",
42-
ha='center', va='bottom')
43-
44-
45-
autolabel(rects1)
46-
autolabel(rects2)
33+
ax.bar_label(rects1, padding=3)
34+
ax.bar_label(rects2, padding=3)
4735

4836
fig.tight_layout()
4937

@@ -61,5 +49,5 @@ def autolabel(rects):
6149

6250
matplotlib.axes.Axes.bar
6351
matplotlib.pyplot.bar
64-
matplotlib.axes.Axes.annotate
65-
matplotlib.pyplot.annotate
52+
matplotlib.axes.Axes.bar_label
53+
matplotlib.pyplot.bar_label

examples/lines_bars_and_markers/horizontal_barchart_distribution.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,12 @@ def survey(results, category_names):
5454
for i, (colname, color) in enumerate(zip(category_names, category_colors)):
5555
widths = data[:, i]
5656
starts = data_cum[:, i] - widths
57-
ax.barh(labels, widths, left=starts, height=0.5,
58-
label=colname, color=color)
59-
xcenters = starts + widths / 2
57+
rects = ax.barh(labels, widths, left=starts, height=0.5,
58+
label=colname, color=color)
6059

6160
r, g, b, _ = color
6261
text_color = 'white' if r * g * b < 0.5 else 'darkgrey'
63-
for y, (x, c) in enumerate(zip(xcenters, widths)):
64-
ax.text(x, y, str(int(c)), ha='center', va='center',
65-
color=text_color)
62+
ax.bar_label(rects, label_type='center', color=text_color)
6663
ax.legend(ncol=len(category_names), bbox_to_anchor=(0, 1),
6764
loc='lower left', fontsize='small')
6865

@@ -85,7 +82,7 @@ def survey(results, category_names):
8582
import matplotlib
8683
matplotlib.axes.Axes.barh
8784
matplotlib.pyplot.barh
88-
matplotlib.axes.Axes.text
89-
matplotlib.pyplot.text
85+
matplotlib.axes.Axes.bar_label
86+
matplotlib.pyplot.bar_label
9087
matplotlib.axes.Axes.legend
9188
matplotlib.pyplot.legend

lib/matplotlib/axes/_axes.py

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2385,7 +2385,13 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
23852385

23862386
self._request_autoscale_view()
23872387

2388-
bar_container = BarContainer(patches, errorbar, label=label)
2388+
if orientation == 'vertical':
2389+
datavalues = height
2390+
elif orientation == 'horizontal':
2391+
datavalues = width
2392+
2393+
bar_container = BarContainer(patches, errorbar, datavalues=datavalues,
2394+
orientation=orientation, label=label)
23892395
self.add_container(bar_container)
23902396

23912397
if tick_labels is not None:
@@ -2501,6 +2507,132 @@ def barh(self, y, width, height=0.8, left=None, *, align="center",
25012507
align=align, **kwargs)
25022508
return patches
25032509

2510+
def bar_label(self, container, labels=None, *, fmt="%g", label_type="edge",
2511+
padding=0, **kwargs):
2512+
"""
2513+
Label a bar plot.
2514+
2515+
Adds labels to bars in the given `.BarContainer`.
2516+
You may need to adjust the axis limits to fit the labels.
2517+
2518+
Parameters
2519+
----------
2520+
container : `.BarContainer`
2521+
Container with all the bars and optionally errorbars, likely
2522+
returned from `.bar` or `.barh`.
2523+
2524+
labels : array-like, optional
2525+
A list of label texts, that should be displayed. If not given, the
2526+
label texts will be the data values formatted with *fmt*.
2527+
2528+
fmt : str, default: '%g'
2529+
A format string for the label.
2530+
2531+
label_type : {'edge', 'center'}, default: 'edge'
2532+
The label type. Possible values:
2533+
2534+
- 'edge': label placed at the end-point of the bar segment, and the
2535+
value displayed will be the position of that end-point.
2536+
- 'center': label placed in the center of the bar segment, and the
2537+
value displayed will be the length of that segment.
2538+
(useful for stacked bars, i.e.
2539+
:doc:`/gallery/lines_bars_and_markers/bar_label_demo`)
2540+
2541+
padding : float, default: 0
2542+
Distance of label from the end of the bar.
2543+
2544+
**kwargs
2545+
Any remaining keyword arguments are passed through to
2546+
`.Axes.annotate`.
2547+
2548+
Returns
2549+
-------
2550+
list of `.Text`
2551+
A list of `.Text` instances for the labels.
2552+
"""
2553+
2554+
# want to know whether to put label on positive or negative direction
2555+
# cannot use np.sign here because it will return 0 if x == 0
2556+
def sign(x):
2557+
return 1 if x >= 0 else -1
2558+
2559+
_api.check_in_list(['edge', 'center'], label_type=label_type)
2560+
2561+
bars = container.patches
2562+
errorbar = container.errorbar
2563+
datavalues = container.datavalues
2564+
orientation = container.orientation
2565+
2566+
if errorbar:
2567+
# check "ErrorbarContainer" for the definition of these elements
2568+
lines = errorbar.lines # attribute of "ErrorbarContainer" (tuple)
2569+
barlinecols = lines[2] # 0: data_line, 1: caplines, 2: barlinecols
2570+
barlinecol = barlinecols[0] # the "LineCollection" of error bars
2571+
errs = barlinecol.get_segments()
2572+
else:
2573+
errs = []
2574+
2575+
if labels is None:
2576+
labels = []
2577+
2578+
annotations = []
2579+
2580+
for bar, err, dat, lbl in itertools.zip_longest(
2581+
bars, errs, datavalues, labels
2582+
):
2583+
(x0, y0), (x1, y1) = bar.get_bbox().get_points()
2584+
xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
2585+
2586+
if orientation == "vertical":
2587+
extrema = max(y0, y1) if dat >= 0 else min(y0, y1)
2588+
length = abs(y0 - y1)
2589+
elif orientation == "horizontal":
2590+
extrema = max(x0, x1) if dat >= 0 else min(x0, x1)
2591+
length = abs(x0 - x1)
2592+
2593+
if err is None:
2594+
endpt = extrema
2595+
elif orientation == "vertical":
2596+
endpt = err[:, 1].max() if dat >= 0 else err[:, 1].min()
2597+
elif orientation == "horizontal":
2598+
endpt = err[:, 0].max() if dat >= 0 else err[:, 0].min()
2599+
2600+
if label_type == "center":
2601+
value = sign(dat) * length
2602+
elif label_type == "edge":
2603+
value = extrema
2604+
2605+
if label_type == "center":
2606+
xy = xc, yc
2607+
elif label_type == "edge" and orientation == "vertical":
2608+
xy = xc, endpt
2609+
elif label_type == "edge" and orientation == "horizontal":
2610+
xy = endpt, yc
2611+
2612+
if orientation == "vertical":
2613+
xytext = 0, sign(dat) * padding
2614+
else:
2615+
xytext = sign(dat) * padding, 0
2616+
2617+
if label_type == "center":
2618+
ha, va = "center", "center"
2619+
elif label_type == "edge":
2620+
if orientation == "vertical" and dat >= 0:
2621+
ha, va = "center", "bottom"
2622+
elif orientation == "vertical" and dat < 0:
2623+
ha, va = "center", "top"
2624+
elif orientation == "horizontal" and dat >= 0:
2625+
ha, va = "left", "center"
2626+
elif orientation == "horizontal" and dat < 0:
2627+
ha, va = "right", "center"
2628+
2629+
annotation = self.annotate(fmt % value if lbl is None else lbl,
2630+
xy, xytext, textcoords="offset points",
2631+
ha=ha, va=va, **kwargs)
2632+
annotations.append(annotation)
2633+
2634+
return annotations
2635+
25042636
@_preprocess_data()
25052637
@docstring.dedent_interpd
25062638
def broken_barh(self, xranges, yrange, **kwargs):

lib/matplotlib/container.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,21 @@ class BarContainer(Container):
5757
A container for the error bar artists if error bars are present.
5858
*None* otherwise.
5959
60+
datavalues : None or array-like
61+
The underlying data values corresponding to the bars.
62+
63+
orientation : {'vertical', 'horizontal'}, default: None
64+
If 'vertical', the bars are assumed to be vertical.
65+
If 'horizontal', the bars are assumed to be horizontal.
66+
6067
"""
6168

62-
def __init__(self, patches, errorbar=None, **kwargs):
69+
def __init__(self, patches, errorbar=None, *, datavalues=None,
70+
orientation=None, **kwargs):
6371
self.patches = patches
6472
self.errorbar = errorbar
73+
self.datavalues = datavalues
74+
self.orientation = orientation
6575
super().__init__(patches, **kwargs)
6676

6777

lib/matplotlib/pyplot.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2596,6 +2596,16 @@ def barh(y, width, height=0.8, left=None, *, align='center', **kwargs):
25962596
y, width, height=height, left=left, align=align, **kwargs)
25972597

25982598

2599+
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
2600+
@_copy_docstring_and_deprecators(Axes.bar_label)
2601+
def bar_label(
2602+
container, labels=None, *, fmt='%g', label_type='edge',
2603+
padding=0, **kwargs):
2604+
return gca().bar_label(
2605+
container, labels=labels, fmt=fmt, label_type=label_type,
2606+
padding=padding, **kwargs)
2607+
2608+
25992609
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
26002610
@_copy_docstring_and_deprecators(Axes.boxplot)
26012611
def boxplot(

0 commit comments

Comments
 (0)