Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/next_api_changes/behavior/empty_bar_legend.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Legend entry of an empty bar plot reflects the requested style
--------------------------------------------------------------

Calling `~matplotlib.axes.Axes.bar` with empty data previously produced a
legend entry with the default style and the first color of the property cycle.
The legend entry now reflects the requested bar style (e.g. *color*, *alpha*),
consistent with `~matplotlib.axes.Axes.plot` and `~matplotlib.axes.Axes.scatter`.
17 changes: 17 additions & 0 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2646,6 +2646,23 @@ def bar(self, x, height, width=0.8, bottom=None, *, align="center",
bar_container = BarContainer(patches, errorbar, datavalues=datavalues,
orientation=orientation,
label=bar_container_label)
if not patches:
# No bars were created (e.g. empty data), so there is no child
# artist to carry the requested style into the legend. Stash a
# non-drawn sample Rectangle with the resolved style so the legend
# entry still reflects it, consistent with plot()/scatter().
# facecolor/edgecolor are not broadcast against the (empty) data,
# so they still carry the resolved style; linewidth/hatch were
# broadcast and may be exhausted, so fall back to the defaults.
sample = mpatches.Rectangle(
xy=(0, 0), width=0, height=0,
facecolor=next(facecolor),
edgecolor=next(edgecolor),
linewidth=next(linewidth, None),
hatch=next(hatch, None),
)
sample._internal_update(kwargs)
bar_container._sample_patch = sample
self.add_container(bar_container)

if tick_labels is not None:
Expand Down
4 changes: 4 additions & 0 deletions lib/matplotlib/legend_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def legend_artist(self, legend, orig_handle, fontsize, handlebox)

def update_from_first_child(tgt, src):
first_child = next(iter(src.get_children()), None)
if first_child is None:
# Containers with no children (e.g. a bar plot of empty data) may carry
# a non-drawn sample artist describing the intended style.
first_child = getattr(src, '_sample_patch', None)
if first_child is not None:
tgt.update_from(first_child)

Expand Down
10 changes: 10 additions & 0 deletions lib/matplotlib/tests/test_legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,16 @@ def test_empty_bar_chart_with_legend():
plt.legend()


def test_empty_bar_chart_legend_handle_style():
# Issue #21506: a bar plot of empty data should still produce a legend
# entry styled like the requested bars (consistent with plot()/scatter()),
# rather than falling back to the default style and color.
fig, ax = plt.subplots()
ax.bar([], [], color='red', alpha=0.3, label='empty')
handle = ax.legend().legend_handles[0]
assert_allclose(handle.get_facecolor(), mpl.colors.to_rgba('red', 0.3))


@image_comparison(['shadow_argument_types.png'], remove_text=True, style='mpl20',
tol=0 if platform.machine() == 'x86_64' else 0.028)
def test_shadow_argument_types():
Expand Down
Loading