Skip to content

Commit 93c7651

Browse files
committed
Add group and bar spacing
1 parent f47d748 commit 93c7651

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+
ax[0, 0].grouped_bar(x, data)
143+
ax[0, 1].grouped_bar(x, data, group_spacing=0.5)
144+
ax[1, 0].grouped_bar(x, data, bar_spacing=0.2)
145+
ax[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
@@ -3013,7 +3013,8 @@ def broken_barh(self, xranges, yrange, **kwargs):
30133013

30143014
return col
30153015

3016-
def grouped_bar(self, x, heights, dataset_labels=None):
3016+
def grouped_bar(self, x, heights, *, group_spacing=1.5, bar_spacing=0,
3017+
dataset_labels=None, orientation="vertical"):
30173018
"""
30183019
Parameters
30193020
-----------
@@ -3069,9 +3070,20 @@ def grouped_bar(self, x, heights, dataset_labels=None):
30693070
30703071
An iterable of array-like: The iteration runs over the groups.
30713072
Each individual array-like is the list of label values for that group.
3073+
3074+
group_spacing : float
3075+
The space between two bar groups in units of bar width.
3076+
3077+
bar_spacing : float
3078+
The space between bars in units of bar width.
3079+
30723080
dataset_labels : array-like of str, optional
30733081
The labels of the datasets.
3082+
3083+
orientation : {"vertical", "horizontal"}, default: vertical
30743084
"""
3085+
_api.check_in_list(["vertical", "horizontal"], orientation=orientation)
3086+
30753087
if hasattr(heights, 'keys'):
30763088
if dataset_labels is not None:
30773089
raise ValueError(
@@ -3087,11 +3099,15 @@ def grouped_bar(self, x, heights, dataset_labels=None):
30873099
if isinstance(x[0], str):
30883100
tick_labels = x
30893101
group_centers = np.arange(num_groups)
3102+
group_distance = 1
30903103
else:
30913104
if num_groups > 1:
30923105
d = np.diff(x)
30933106
if not np.allclose(d, d.mean()):
30943107
raise ValueError("'x' must be equidistant")
3108+
group_distance = d[0]
3109+
else:
3110+
group_distance = 1
30953111
group_centers = np.asarray(x)
30963112
tick_labels = None
30973113

@@ -3102,19 +3118,33 @@ def grouped_bar(self, x, heights, dataset_labels=None):
31023118
f"has {len(dataset)} groups"
31033119
)
31043120

3105-
margin = 0.1
3106-
bar_width = (1 - 2 * margin) / num_datasets
3121+
bar_width = (group_distance /
3122+
(num_datasets + (num_datasets - 1) * bar_spacing + group_spacing))
3123+
bar_spacing_abs = bar_spacing * bar_width
3124+
margin_abs = 0.5 * group_spacing * bar_width
31073125

31083126
if dataset_labels is None:
31093127
dataset_labels = [None] * num_datasets
31103128
else:
31113129
assert len(dataset_labels) == num_datasets
31123130

3131+
# place the bars, but only use numerical positions, categorical tick labels
3132+
# are handled separately below
31133133
for i, (hs, dataset_label) in enumerate(zip(heights, dataset_labels)):
3114-
lefts = group_centers - 0.5 + margin + i * bar_width
3115-
self.bar(lefts, hs, width=bar_width, align="edge", label=dataset_label)
3134+
lefts = (group_centers - 0.5 * group_distance + margin_abs
3135+
+ i * (bar_width + bar_spacing_abs))
3136+
if orientation == "vertical":
3137+
self.bar(lefts, hs, width=bar_width, align="edge",
3138+
label=dataset_label)
3139+
else:
3140+
self.barh(lefts, hs, height=bar_width, align="edge",
3141+
label=dataset_label)
31163142

3117-
self.xaxis.set_ticks(group_centers, labels=tick_labels)
3143+
if tick_labels is not None:
3144+
if orientation == "vertical":
3145+
self.xaxis.set_ticks(group_centers, labels=tick_labels)
3146+
else:
3147+
self.yaxis.set_ticks(group_centers, labels=tick_labels)
31183148

31193149
# TODO: does not return anything for now
31203150

0 commit comments

Comments
 (0)