Repository navigation
Description
Bug summary
When creating a stacked bar chart and annotating its components by use of the bar_label method, a RuntimeError("Unknown return type") exception is raised the moment one of the bar labels comes to rest outside the specified Y limits.
Code for reproduction
#!/usr/bin/env python3
from matplotlib import pyplot
import numpy as np
def main():
categories = ["Foo", "Bar", "Baz", "Barbaz"]
data = {
"Apples": [0, 1, 2, 3],
"Oranges": [2, 5, 27, 1],
"Pears": [17, 22, 15, 10],
}
grid = (1, 1)
fig = pyplot.figure(figsize=(9, 9), dpi=300, layout="constrained")
ax = pyplot.subplot2grid(grid, (0, 0), fig=fig)
bottom = np.zeros(len(categories))
width = 0.5
for fruit, values in data.items():
p = ax.bar(
categories,
values,
width,
label=fruit,
bottom=bottom,
)
ax.bar_label(p, label_type='center')
bottom += values
# The exception happens if the Y-limit of the plot is chosen too low.
# This limit is fine, the whole graph fits
# ax.set(ylim=(0, 50))
# This limit is fine as well, even though it cuts off the top of the
# topmost bar.
# ax.set(ylim=(0, 35))
# This limit is fine still, with the topmost label ('15' of the 'Baz'
# category) being on the top border of the plot.
# ax.set(ylim=(0, 29))
# This limit produces an exception, likely as the topmost label ('15' of
# the 'Baz' category) cannot be placed anymore, since its bar's lower
# end starts at 2+27 = 29.
ax.set(ylim=(0, 28))
fig.savefig("/tmp/matplotlib_example.png", format="png")
if __name__ == '__main__':
main()Actual outcome
(matplotlib-bar-label-mre)matplotlib-bar-label-mre › python demo.py
Traceback (most recent call last):
File "/home/michael/dev/python/matplotlib-bar-label-mre/demo.py", line 56, in <module>
main()
File "/home/michael/dev/python/matplotlib-bar-label-mre/demo.py", line 53, in main
fig.savefig("/tmp/matplotlib_example.png", format="png")
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/figure.py", line 3343, in savefig
self.canvas.print_figure(fname, **kwargs)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/backend_bases.py", line 2342, in print_figure
self.figure.draw(renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/artist.py", line 95, in draw_wrapper
result = draw(artist, renderer, *args, **kwargs)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/artist.py", line 72, in draw_wrapper
return draw(artist, renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/figure.py", line 3134, in draw
self.get_layout_engine().execute(self)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/layout_engine.py", line 253, in execute
return do_constrained_layout(fig, w_pad=w_pad, h_pad=h_pad,
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/_constrained_layout.py", line 119, in do_constrained_layout
make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/_constrained_layout.py", line 369, in make_layout_margins
pos, bbox = get_pos_and_bbox(ax, renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/_constrained_layout.py", line 617, in get_pos_and_bbox
tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/artist.py", line 1415, in _get_tightbbox_for_layout_only
return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/axes/_base.py", line 4408, in get_tightbbox
bbox = a.get_tightbbox(renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/text.py", line 2064, in get_tightbbox
return super().get_tightbbox(renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/artist.py", line 367, in get_tightbbox
bbox = self.get_window_extent(renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/text.py", line 2050, in get_window_extent
self.update_positions(self._renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/text.py", line 1954, in update_positions
self.set_transform(self._get_xy_transform(renderer, self.anncoords))
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/text.py", line 1538, in _get_xy_transform
xy0 = self._get_ref_xy(renderer)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/text.py", line 1569, in _get_ref_xy
return self._get_xy(renderer, *self.xy, self.xycoords)
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/text.py", line 1478, in _get_xy
return self._get_xy_transform(renderer, s).transform((x, y))
File "/home/michael/.local/share/virtualenvs/matplotlib-bar-label-mre/lib/python3.10/site-packages/matplotlib/text.py", line 1496, in _get_xy_transform
raise RuntimeError("Unknown return type")
RuntimeError: Unknown return type
Expected outcome
I'd expect the corresponding section of the bar, along with its label, to simply be outside the visible plot area. Or at least a more descriptive error message.
Additional information
The exception seems to happen the moment one of the bar labels cannot fit in the visible plot area anymore. This can be seen by starting with a large Y limit, and slowly lowering it.
As the Y limit approaches the position of the topmost label, it will be shifted down on its own. However the moment the Y limit is set low enough that the topmost bar's component is fully outside the visible area, the exception arises.
The (mostly) minimal reproducible example above contains several Y limits to indicate this happening.
Operating system
Arch Linux, Linux 6.2.5
Matplotlib Version
3.7.1
Matplotlib Backend
TkAgg
Python version
3.10.9
Jupyter version
Not applicable
Installation
pip
Activity
[-][Bug]: RuntimeError when bar_label of stacked bar char comes to rest outside of plot's Y limit[/-][+][Bug]: RuntimeError when bar_label of stacked bar chart comes to rest outside of plot's Y limit[/+]jklymak commentedon Apr 5, 2023
The layout manager is a red herring as it happens w/o the layout manager too. I suspect #23688 is not behaving if the label is off the page. ping @stefmolin @immaxchen
QuLogic commentedon Apr 5, 2023
The bar labels have a custom coordinate:
matplotlib/lib/matplotlib/axes/_axes.py
Lines 2769 to 2774 in 8ca75e4
However,
Bbox.intersectionreturnsNonewhen there's no overlap, and not some sort of emptyBbox, and so the Annotation locator breaks since it doesn't understand that type:matplotlib/lib/matplotlib/text.py
Lines 1453 to 1460 in 8ca75e4
QuLogic commentedon Apr 5, 2023
Somewhat related to #24104 (comment)
erichege commentedon Apr 13, 2023
I am running into this exact same issue, but it seems to be as of the result of a semi recent change. I had a google colab notebook to auto convert a standardized excel output into a labeled bargraph. Bar_label worked as intended without the runtime error as of 1/26/2023. I recently revisited this notebook on 4/6/2023 and it no longer was working due to the issue referenced in the original post. I tested out the notebook on old data that I knew had worked previously and was still facing this error.
stefmolin commentedon Apr 14, 2023
I believe I have a solution thanks to @QuLogic's comments.
mrgransky commentedon Apr 20, 2023
one dirty and temporary solution which worked for me was to apply some margin:
ax.set_xlim(right=ax.get_xlim()[1]+1.0, auto=True)it might worth a shot!
tacaswell commentedon Apr 20, 2023
This was closed by #25681 and will be release in 3.7.2.