Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Bug]: compressed layout setting can be forgotten on second save #24954

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
rcomer opened this issue Jan 12, 2023 · 7 comments · Fixed by #24971
Closed

[Bug]: compressed layout setting can be forgotten on second save #24954

rcomer opened this issue Jan 12, 2023 · 7 comments · Fixed by #24971
Labels
topic: geometry manager LayoutEngine, Constrained layout, Tight layout
Milestone

Comments

@rcomer
Copy link
Member

rcomer commented Jan 12, 2023

Bug summary

I'm not sure whether this is really a bug or I'm just using an inconsistent combination of options. Under some specific circumstances (see below) compressed layout is not applied the second time a figure is saved.

Code for reproduction

import matplotlib
import matplotlib.pyplot as plt
import numpy as np

arr = np.arange(100).reshape((10, 10))

matplotlib.rcParams['figure.constrained_layout.use'] = True

fig, ax_dict = plt.subplot_mosaic('AB;AC', figsize=(6, 9), width_ratios=[3, 2],
                                  layout='compressed')

for key in ["B", "C"]:
    ax_dict[key].imshow(arr)
    
fig.savefig("test1.png", bbox_inches="tight")
fig.savefig("test2.png", bbox_inches="tight")

Actual outcome

test1.png
test1

test2.png
test2

Expected outcome

Both images should look like the first.

Additional information

If I do not set the rcParams, all is well. If I do not set bbox_inches="tight" in my calls to savefig, the images are identical (but I have too much white space top and bottom). Maybe there is a better option than bbox_inches="tight" when using compressed layout?

For context, my real example is a script that makes several figures. For most of them I want constrained layout, so I set that once in the rcParams for convenience. Only one figure needs "compressed", and I am saving twice because I want both a png and a pdf. Fixed it in my current example by just reverting the rcParams setting for the one figure.

Operating system

RHEL7

Matplotlib Version

3.6.2 and main

Matplotlib Backend

QtAgg

Python version

3.9 and 3.11

Jupyter version

No response

Installation

conda

@rcomer rcomer added the topic: geometry manager LayoutEngine, Constrained layout, Tight layout label Jan 12, 2023
@jklymak
Copy link
Member

jklymak commented Jan 12, 2023

Yeah we do some dancing around when we save with bbox inches - so this seems to get caught in that. I tried to track it down, but the figure-saving stack is full of context managers, and I can't see where the layout manager gets reset. Hopefully someone more cognizant of that part of the codebase can explain.

@rcomer
Copy link
Member Author

rcomer commented Jan 12, 2023

Thanks for looking @jklymak 🙂

@QuLogic
Copy link
Member

QuLogic commented Jan 13, 2023

I think it is set (temporarily) here;

# we have already done layout above, so turn it off:
stack.enter_context(self.figure._cm_set(layout_engine='none'))

@jklymak
Copy link
Member

jklymak commented Jan 13, 2023

It is, but I don't understand what _cm_set does to reset the layout engine after this. Somehow it is dropping the old layout engine and making a new one, and the new one doesn't know that the old one was a 'compressed' engine.

@QuLogic
Copy link
Member

QuLogic commented Jan 13, 2023

It calls get_{kwarg} and after running calls set({kwarg}={old value}). So here it calls oldvalue = figure.get_layout_engine() and figure.set(layout_engine=oldvalue). Is figure.set_layout_engine(figure.get_layout_engine()) working correctly?

@rcomer
Copy link
Member Author

rcomer commented Jan 13, 2023

I am way out of my depth here but

import matplotlib.pyplot as plt

plt.rcParams['figure.constrained_layout.use'] = True
fig = plt.figure(layout="compressed")

print(fig.get_layout_engine()._compress)
fig.set_layout_engine(fig.get_layout_engine())
print(fig.get_layout_engine()._compress)

fig.savefig('foo.png', bbox_inches='tight')
print(fig.get_layout_engine()._compress)
True
True
False

Without the rcParams line, fig.get_layout_engine() returns None after the savefig.

@rcomer
Copy link
Member Author

rcomer commented Jan 13, 2023

I think the problem is the call to adjust_bbox

restore_bbox = _tight_bbox.adjust_bbox(
self.figure, bbox_inches, self.figure.canvas.fixed_dpi)

which explicity calls

fig.set_layout_engine(None)

which will use the default constrained layout engine if the rcParams is set

if layout is None:
if mpl.rcParams['figure.autolayout']:
layout = 'tight'
elif mpl.rcParams['figure.constrained_layout.use']:
layout = 'constrained'
else:
self._layout_engine = None
return
if layout == 'tight':
new_layout_engine = TightLayoutEngine(**kwargs)
elif layout == 'constrained':
new_layout_engine = ConstrainedLayoutEngine(**kwargs)

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

Successfully merging a pull request may close this issue.

4 participants