Skip to content

Commit d82409a

Browse files
committed
Add an auto-labeling helper function for bar charts
For GH matplotlib#12386
1 parent dd15dc0 commit d82409a

File tree

4 files changed

+271
-2
lines changed

4 files changed

+271
-2
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""
2+
==============
3+
Bar Label Demo
4+
==============
5+
6+
This example shows how to use `blabel` helper function
7+
to create bar chart labels.
8+
9+
See also the :doc:`grouped bar chart
10+
</gallery/lines_bars_and_markers/barchart>`,
11+
:doc:`stacked bar graph
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+
# Grouped bar chart
34+
35+
fig1, ax1 = plt.subplots()
36+
37+
rects1 = ax1.bar(ind - width/2, menMeans, width, label='Men')
38+
rects2 = ax1.bar(ind + width/2, womenMeans, width, label='Women')
39+
40+
ax1.set_ylabel('Scores')
41+
ax1.set_title('Scores by group and gender')
42+
ax1.set_xticks(ind)
43+
ax1.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
44+
ax1.legend()
45+
46+
# Basic labels
47+
ax1.blabel(rects1)
48+
ax1.blabel(rects2)
49+
50+
plt.show()
51+
52+
###############################################################################
53+
# Stacked bar plot with error bars
54+
55+
fig2, ax2 = plt.subplots()
56+
57+
p1 = ax2.bar(ind, menMeans, width, yerr=menStd, label='Men')
58+
p2 = ax2.bar(ind, womenMeans, width,
59+
bottom=menMeans, yerr=womenStd, label='Women')
60+
61+
ax2.set_ylabel('Scores')
62+
ax2.set_title('Scores by group and gender')
63+
ax2.set_xticks(ind)
64+
ax2.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
65+
ax2.legend()
66+
67+
# Label with 'center' mode instead of the default 'edge' mode
68+
ax2.blabel(p1, mode='center')
69+
ax2.blabel(p2, mode='center')
70+
ax2.blabel(p2)
71+
72+
plt.show()
73+
74+
###############################################################################
75+
# Horizontal bar chart
76+
77+
# Fixing random state for reproducibility
78+
np.random.seed(19680801)
79+
80+
# Example data
81+
people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim')
82+
y_pos = np.arange(len(people))
83+
performance = 3 + 10 * np.random.rand(len(people))
84+
error = np.random.rand(len(people))
85+
86+
fig3, ax3 = plt.subplots()
87+
88+
hbars1 = ax3.barh(y_pos, performance, xerr=error, align='center')
89+
ax3.set_yticks(y_pos)
90+
ax3.set_yticklabels(people)
91+
ax3.invert_yaxis() # labels read top-to-bottom
92+
ax3.set_xlabel('Performance')
93+
ax3.set_title('How fast do you want to go today?')
94+
95+
# Label with specially formatted floats
96+
ax3.blabel(hbars1, fmt='%.2f')
97+
98+
plt.show()
99+
100+
###############################################################################
101+
# Some of the more advanced things that one can do with bar labels
102+
103+
fig4, ax4 = plt.subplots()
104+
105+
hbars2 = ax4.barh(y_pos, performance, xerr=error, align='center')
106+
ax4.set_yticks(y_pos)
107+
ax4.set_yticklabels(people)
108+
ax4.invert_yaxis() # labels read top-to-bottom
109+
ax4.set_xlabel('Performance')
110+
ax4.set_title('How fast do you want to go today?')
111+
112+
# Label with given captions, custom padding, shifting and annotate option
113+
arrowprops = dict(color='b', arrowstyle="-|>",
114+
connectionstyle="angle,angleA=0,angleB=90,rad=20")
115+
116+
ax4.blabel(hbars2, captions=['±%.2f' % e for e in error],
117+
padding=30, shifting=20, arrowprops=arrowprops, color='b')
118+
119+
plt.show()
120+
121+
#############################################################################
122+
#
123+
# ------------
124+
#
125+
# References
126+
# """"""""""
127+
#
128+
# The use of the following functions, methods and classes is shown
129+
# in this example:
130+
131+
matplotlib.axes.Axes.bar
132+
matplotlib.pyplot.bar
133+
matplotlib.axes.Axes.barh
134+
matplotlib.pyplot.barh
135+
matplotlib.axes.Axes.blabel
136+
matplotlib.pyplot.blabel

lib/matplotlib/axes/_axes.py

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

23822382
self._request_autoscale_view()
23832383

2384-
bar_container = BarContainer(patches, errorbar, label=label)
2384+
bar_container = BarContainer(patches, errorbar, orientation,
2385+
label=label)
23852386
self.add_container(bar_container)
23862387

23872388
if tick_labels is not None:
@@ -2497,6 +2498,136 @@ def barh(self, y, width, height=0.8, left=None, *, align="center",
24972498
align=align, **kwargs)
24982499
return patches
24992500

2501+
def blabel(self, container, captions=[], fmt="%g", mode="edge",
2502+
padding=None, shifting=0, autoscale=True, **kwargs):
2503+
"""
2504+
Label a bar plot.
2505+
2506+
Adds labels to bars in the given `BarContainer`.
2507+
2508+
Parameters
2509+
----------
2510+
container : `.BarContainer`
2511+
Container with all the bars and optionally errorbars.
2512+
2513+
captions : array-like, optional
2514+
A list of label texts, that should be displayed. If not given, the
2515+
label texts will be the data values formatted with *fmt*.
2516+
2517+
fmt : str, optional
2518+
A format string for the label. Default is '%g'
2519+
2520+
mode : {'edge', 'center'}, optional, default: 'edge'
2521+
Position of the label relative to the bar:
2522+
2523+
- 'edge': Placed at edge, the cumulative values will be shown.
2524+
- 'center': Placed at center, the individual values will be shown.
2525+
2526+
padding : float, optional
2527+
Space in points for label to leave the edge of the bar.
2528+
2529+
shifting : float, optional
2530+
Translation in points for label to leave the center of the bar.
2531+
2532+
autoscale : bool, optional
2533+
If ``True``, try to rescale to fit the labels. Default is ``True``.
2534+
2535+
Returns
2536+
-------
2537+
annotations
2538+
A list of `.Text` instances for the labels.
2539+
"""
2540+
def nonefill(a, n):
2541+
return list(a) + [None] * (n - len(a))
2542+
2543+
def sign(x):
2544+
return 1 if x >= 0 else -1
2545+
2546+
if mode == "edge":
2547+
padding = padding or 3
2548+
elif mode == "center":
2549+
padding = padding or 0
2550+
2551+
bars = container.patches
2552+
errorbar = container.errorbar
2553+
orientation = container.orientation
2554+
2555+
N = len(bars)
2556+
if errorbar:
2557+
lines = errorbar.lines
2558+
barlinecols = lines[2]
2559+
barlinecol = barlinecols[0]
2560+
segments = barlinecol.get_segments()
2561+
errs = nonefill(segments, N)
2562+
else:
2563+
errs = nonefill([], N)
2564+
caps = nonefill(captions, N)
2565+
2566+
annotations = []
2567+
for bar, err, cap in zip(bars, errs, caps):
2568+
2569+
(x0, y0), (x1, y1) = bar.get_bbox().get_points()
2570+
xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
2571+
2572+
if orientation == "vertical":
2573+
extrema = max(y0, y1) if yc >= 0 else min(y0, y1)
2574+
length = abs(y0 - y1)
2575+
elif orientation == "horizontal":
2576+
extrema = max(x0, x1) if xc >= 0 else min(x0, x1)
2577+
length = abs(x0 - x1)
2578+
2579+
if err is None:
2580+
endpt = extrema
2581+
elif orientation == "vertical":
2582+
endpt = err[:, 1].max() if yc >= 0 else err[:, 1].min()
2583+
elif orientation == "horizontal":
2584+
endpt = err[:, 0].max() if xc >= 0 else err[:, 0].min()
2585+
2586+
if mode == "center":
2587+
value = sign(extrema) * length
2588+
elif mode == "edge":
2589+
value = extrema
2590+
2591+
if mode == "center":
2592+
xy = xc, yc
2593+
elif mode == "edge" and orientation == "vertical":
2594+
xy = xc, endpt
2595+
elif mode == "edge" and orientation == "horizontal":
2596+
xy = endpt, yc
2597+
2598+
if orientation == "vertical":
2599+
xytext = shifting, sign(extrema) * padding
2600+
else:
2601+
xytext = sign(extrema) * padding, shifting
2602+
2603+
if mode == "center":
2604+
ha, va = "center", "center"
2605+
elif mode == "edge" and orientation == "vertical" and yc >= 0:
2606+
ha, va = "center", "bottom"
2607+
elif mode == "edge" and orientation == "vertical" and yc < 0:
2608+
ha, va = "center", "top"
2609+
elif mode == "edge" and orientation == "horizontal" and xc >= 0:
2610+
ha, va = "left", "center"
2611+
elif mode == "edge" and orientation == "horizontal" and xc < 0:
2612+
ha, va = "right", "center"
2613+
2614+
annotation = self.annotate(cap or fmt % value, xy, xytext,
2615+
textcoords="offset points",
2616+
ha=ha, va=va, **kwargs)
2617+
annotations.append(annotation)
2618+
2619+
if autoscale:
2620+
transform = self.transData.inverted()
2621+
renderer = self.figure.canvas.get_renderer()
2622+
corners = []
2623+
for text in annotations:
2624+
points = text.get_window_extent(renderer).get_points()
2625+
corners.append(transform.transform(points))
2626+
self.update_datalim(np.vstack(corners))
2627+
self.autoscale_view()
2628+
2629+
return annotations
2630+
25002631
@_preprocess_data()
25012632
@docstring.dedent_interpd
25022633
def broken_barh(self, xranges, yrange, **kwargs):

lib/matplotlib/container.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ class BarContainer(Container):
6262
6363
"""
6464

65-
def __init__(self, patches, errorbar=None, **kwargs):
65+
def __init__(self, patches, errorbar=None, orientation=None, **kwargs):
6666
self.patches = patches
6767
self.errorbar = errorbar
68+
self.orientation = orientation
6869
super().__init__(patches, **kwargs)
6970

7071

tools/boilerplate.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ def boilerplate_gen():
199199
'bar',
200200
'barbs',
201201
'barh',
202+
'blabel',
202203
'boxplot',
203204
'broken_barh',
204205
'cla',

0 commit comments

Comments
 (0)