Skip to content
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

[Bug]: constrained layout clips y-label above y-axis #27877

Open
pank opened this issue Mar 7, 2024 · 6 comments
Open

[Bug]: constrained layout clips y-label above y-axis #27877

pank opened this issue Mar 7, 2024 · 6 comments
Labels
topic: geometry manager LayoutEngine, Constrained layout, Tight layout

Comments

@pank
Copy link

pank commented Mar 7, 2024

Bug summary

When placing the y-label above the y-axis/spine, it gets clipped with constrained layout. It works OK with tight layout and on the left in constrained layout.

Code for reproduction

import matplotlib.pyplot as plt
x = range(10)
lab = "foo bar baz"

# with extra space it works, but the second plot with two lines gets
# clipped.
fig0, axes0 = plt.subplots(1, 2, constrained_layout=True)
[ax.plot(x, x) for ax in axes0]
[ax.set_ylabel("\n".join([lab]*(i+1)),
               y=1.0,
               rotation="horizontal",
               horizontalalignment="left",
               multialignment="left",)
 for i, ax in enumerate(axes0)]

# single plot with one line also gets clipped
fig1, ax1 = plt.subplots(constrained_layout=True)
ax1.plot(x, x)
ax1.set_ylabel(lab,
               y=1.0,
               rotation="horizontal",
               horizontalalignment="left",
               multialignment="left",)

# For reference it works fine when placing the ylab on the 'side'
# as a conventional matplotlib plot
fig2, ax2 = plt.subplots(constrained_layout=True)
ax2.plot(x, x)
ax2.set_ylabel(f"{lab}\n{lab}",
               rotation="horizontal",
               horizontalalignment="right",
               multialignment="right",)

for i, fig in enumerate((fig0, fig1, fig2)):
    fig.savefig(f"fig{i}.png")

Actual outcome

The y-label gets clipped. The second plot should have a two line long y label.
fig0

It also happens with a y-label that is just one line:
fig1

Expected outcome

I would expect the y label to be included, even when putting it above the axis. Example with tight layout (fig1.tight_layout())

fig1_tight

Additional information

I think this is a bug(?).
I have tried various vertical alignments, and it doesn't make much of a difference. I also tried to follow the legend set_in_layout-example in the constrained layout guide without much luck.

A temporary fix is to set the title to ' ' or increasing the constrained layout padding, I guess.

Note that constrained layout works as expected with long y-labels on the left:

fig2

Operating system

Windows

Matplotlib Version

3.8.0

Matplotlib Backend

module://matplotlib_inline.backend_inline, but present in saved figures (savefig)

Python version

3.12.1

Jupyter version

Jupyter console 6.6.3

Installation

conda

@pank pank changed the title [Bug]: constrained layout clip ylabel on top [Bug]: constrained layout clips ylabel on top Mar 7, 2024
@pank pank changed the title [Bug]: constrained layout clips ylabel on top [Bug]: constrained layout clips y-label on top Mar 7, 2024
@pank pank changed the title [Bug]: constrained layout clips y-label on top [Bug]: constrained layout clips y-label above y-axis Mar 7, 2024
@dstansby dstansby added the topic: geometry manager LayoutEngine, Constrained layout, Tight layout label Mar 7, 2024
@jklymak
Copy link
Member

jklymak commented Mar 8, 2024

This was a conscious decision to avoid the case of a really long vertical ylabel driving the layout to make a tiny axes. For this situation I don't see the reason to use ylabel instead of text since you are doing all the placement manually anyway?

@pank
Copy link
Author

pank commented Mar 8, 2024

That makes sense for a usual y-axis label.

I think the second example (with a single line title) is not out of the ordinary and imho it should work as with tight layout.

I want to provide a utility to match an existing template made for excel. I want to be as close to plain mpl as possible. So one would do ax.set_ylabel('foo') and then call excelify(ax) or something similar (excelify places the label in a more sophisticated way). [For a one-off drawing I would probably just hijack the axes title tbh].

I would rather not replace it with a Text or a hijack the title as it would break ax.yaxis.label. [And I would have to explain people that they can't use ax.set_ylabel, but should use excelify.set_ylabel(label, ax) instead, which isn't great]

Anyway, can I tell constrained_layout that I have made a conscious decision re the y-label's placement? And if so could that flagged if the y-label is moved manually? I would be happy to provide a patch.

@jklymak
Copy link
Member

jklymak commented Mar 9, 2024

I'm a little leery of too many toggles, but adding one is not out of the question.

However, excelify could just as easily take whatever text is in the ylabel and make a new text object. Or as you suggest, add a dummy text object to force space for your ylabel. The user need no know this is happening.

@pank
Copy link
Author

pank commented Mar 9, 2024 via email

@jklymak
Copy link
Member

jklymak commented Mar 10, 2024

Finally dug into this. This was a purposeful change that affected both tight_layout and constrained_layout. #17222. The reason it doesn't look like a problem with tight_layout is that it has a (much) larger margins - if you put a three-line ylabel in with tight_layout it is similarly cutoff.

I'm not sure checking of the user manually specified y is a very good heuristic for toggling off this behaviour. Someone could have a tweaked y to be 0.2 or 0.7 for whatever reason and would want to keep the behaviour as-is

So things are working as expected, and this is not a bug per-se. A toggle could likely be added if someone were motivated and the API could be clean enough.

@pank
Copy link
Author

pank commented Mar 11, 2024

Thanks for the the reference and fair enough.
Do you have any ideas on an acceptable heuristic for detecting when not to cut off long labels?

Last question re my problem at hand, if you will permit. As I read the constrained layout guide, I can only change padding between subplots, not around plots. Is that correct? I am leaning towards "hijacking" ax.title now... edit: fig.get_layout_engine().set(rect=(0,0,1,.97)) # (left, right, width, height) seems to work for setting the margin in constrained layout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: geometry manager LayoutEngine, Constrained layout, Tight layout
Projects
None yet
Development

No branches or pull requests

3 participants