10000 [Bug]: RuntimeError when bar_label of stacked bar chart comes to rest outside of plot's Y limit · Issue #25625 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content
8000

[Bug]: RuntimeError when bar_label of stacked bar chart comes to rest outside of plot's Y limit #25625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Lavode opened this issue Apr 5, 2023 · 7 comments
Milestone

Comments

@Lavode
Copy link
Lavode commented Apr 5, 2023

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/
8000
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

@Lavode Lavode changed the title [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 Apr 5, 2023
@oscargus oscargus added topic: geometry manager LayoutEngine, Constrained layout, Tight layout topic: text and removed topic: geometry manager LayoutEngine, Constrained layout, Tight layout labels Apr 5, 2023
@jklymak
Copy link
Member
jklymak commented 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
Copy link
Member
QuLogic commented Apr 5, 2023

The bar labels have a custom coordinate:

kwargs["xycoords"] = (
lambda r, b=bar:
mtransforms.Bbox.intersection(
b.get_window_extent(r), b.get_clip_box()
)
)

However, Bbox.intersection returns None when there's no overlap, and not some sort of empty Bbox, and so the Annotation locator breaks since it doesn't understand that type:
elif callable(s):
tr = s(renderer)
if isinstance(tr, BboxBase):
return BboxTransformTo(tr)
elif isinstance(tr, Transform):
return tr
else:
raise RuntimeError("Unknown return type")

@QuLogic
Copy link
Member
QuLogic commented Apr 5, 2023

Somewhat related to #24104 (comment)

@erichege
Copy link

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
Copy link
Contributor

I believe I have a solution thanks to @QuLogic's comments.

@mrgransky
Copy link

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 tacaswell added this to the v3.7.2 milestone Apr 20, 2023
@tacaswell
Copy link
Member

This was closed by #25681 and will be release in 3.7.2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants
0