Skip to content

Commit d7f7238

Browse files
committed
Add group and bar spacing and orientation
1 parent 9ca2508 commit d7f7238

File tree

2 files changed

+76
-6
lines changed

2 files changed

+76
-6
lines changed

galleries/examples/lines_bars_and_markers/grouped_bar_chart.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,43 @@
118118

119119
fig, ax = plt.subplots()
120120
ax.grouped_bar(x, data)
121+
122+
123+
# %%
124+
# Bar width and spacing
125+
# ---------------------
126+
# The center positions of the bar groups are given by x. We can still choose
127+
# two of the following properties: bar width, spacing between groups, and
128+
# spacing between bars.
129+
#
130+
# We believe the most convenient approach is defining spacing between groups
131+
# and spacing between bars as fraction of the bar width.
132+
133+
x = ['A', 'B', 'C']
134+
data = {
135+
'data1': [1, 2, 3],
136+
'data2': [1.2, 2.2, 3.2],
137+
'data3': [1.4, 2.4, 3.4],
138+
'data4': [1.6, 2.6, 3.6],
139+
}
140+
141+
fig, axs = plt.subplots(2, 2)
142+
axs[0, 0].grouped_bar(x, data)
143+
axs[0, 1].grouped_bar(x, data, group_spacing=0.5)
144+
axs[1, 0].grouped_bar(x, data, bar_spacing=0.2)
145+
axs[1, 1].grouped_bar(x, data, group_spacing=0.5, bar_spacing=0.1)
146+
147+
148+
# %%
149+
# Horizontal grouped bars
150+
# -----------------------
151+
# Use ``orientation="horizontal"`` to create horizontal grouped bar charts.
152+
153+
x = ['A', 'B', 'C']
154+
data = {
155+
'data1': [1, 2, 3],
156+
'data2': [1.2, 2.2, 3.2],
157+
}
158+
159+
fig, ax = plt.subplots()
160+
ax.grouped_bar(x, data, orientation="horizontal")

lib/matplotlib/axes/_axes.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,7 +3048,8 @@ def broken_barh(self, xranges, yrange, **kwargs):
30483048

30493049
return col
30503050

3051-
def grouped_bar(self, x, heights, dataset_labels=None):
3051+
def grouped_bar(self, x, heights, *, group_spacing=1.5, bar_spacing=0,
3052+
dataset_labels=None, orientation="vertical"):
30523053
"""
30533054
Parameters
30543055
-----------
@@ -3104,9 +3105,20 @@ def grouped_bar(self, x, heights, dataset_labels=None):
31043105
31053106
An iterable of array-like: The iteration runs over the groups.
31063107
Each individual array-like is the list of label values for that group.
3108+
3109+
group_spacing : float
3110+
The space between two bar groups in units of bar width.
3111+
3112+
bar_spacing : float
3113+
The space between bars in units of bar width.
3114+
31073115
dataset_labels : array-like of str, optional
31083116
The labels of the datasets.
3117+
3118+
orientation : {"vertical", "horizontal"}, default: vertical
31093119
"""
3120+
_api.check_in_list(["vertical", "horizontal"], orientation=orientation)
3121+
31103122
if hasattr(heights, 'keys'):
31113123
if dataset_labels is not None:
31123124
raise ValueError(
@@ -3122,11 +3134,15 @@ def grouped_bar(self, x, heights, dataset_labels=None):
31223134
if isinstance(x[0], str):
31233135
tick_labels = x
31243136
group_centers = np.arange(num_groups)
3137+
group_distance = 1
31253138
else:
31263139
if num_groups > 1:
31273140
d = np.diff(x)
31283141
if not np.allclose(d, d.mean()):
31293142
raise ValueError("'x' must be equidistant")
3143+
group_distance = d[0]
3144+
else:
3145+
group_distance = 1
31303146
group_centers = np.asarray(x)
31313147
tick_labels = None
31323148

@@ -3137,19 +3153,33 @@ def grouped_bar(self, x, heights, dataset_labels=None):
31373153
f"has {len(dataset)} groups"
31383154
)
31393155

3140-
margin = 0.1
3141-
bar_width = (1 - 2 * margin) / num_datasets
3156+
bar_width = (group_distance /
3157+
(num_datasets + (num_datasets - 1) * bar_spacing + group_spacing))
3158+
bar_spacing_abs = bar_spacing * bar_width
3159+
margin_abs = 0.5 * group_spacing * bar_width
31423160

31433161
if dataset_labels is None:
31443162
dataset_labels = [None] * num_datasets
31453163
else:
31463164
assert len(dataset_labels) == num_datasets
31473165

3166+
# place the bars, but only use numerical positions, categorical tick labels
3167+
# are handled separately below
31483168
for i, (hs, dataset_label) in enumerate(zip(heights, dataset_labels)):
3149-
lefts = group_centers - 0.5 + margin + i * bar_width
3150-
self.bar(lefts, hs, width=bar_width, align="edge", label=dataset_label)
3169+
lefts = (group_centers - 0.5 * group_distance + margin_abs
3170+
+ i * (bar_width + bar_spacing_abs))
3171+
if orientation == "vertical":
3172+
self.bar(lefts, hs, width=bar_width, align="edge",
3173+
label=dataset_label)
3174+
else:
3175+
self.barh(lefts, hs, height=bar_width, align="edge",
3176+
label=dataset_label)
31513177

3152-
self.xaxis.set_ticks(group_centers, labels=tick_labels)
3178+
if tick_labels is not None:
3179+
if orientation == "vertical":
3180+
self.xaxis.set_ticks(group_centers, labels=tick_labels)
3181+
else:
3182+
self.yaxis.set_ticks(group_centers, labels=tick_labels)
31533183

31543184
# TODO: does not return anything for now
31553185

0 commit comments

Comments
 (0)