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]: Matplotlib don't take into account savefig.pad_inches when plt.plot(... transform=fig.dpi_scale_trans) #29224

Open
sindzicat opened this issue Dec 3, 2024 · 12 comments

Comments

@sindzicat
Copy link

Bug summary

Hello! When I draw line from bottom left figure corner up to top right figure corner, I see then that there some figure paddings:

fig = plt.figure(facecolor='#ccc')
ax = fig.gca()
ax.set_axis_off()
line_1, = plt.plot([0, 1], [0, 1], transform=fig.transFigure, clip_on=False, lw=2, c='blue')
plt.show()

image

fig.get_size_inches() gives me 6.4×4.8

When I use plt.plot with transform=fig.dpi_scale_trans, I see that line starts from the left bottom corner:

fig = plt.figure(facecolor='#ccc')
ax = fig.gca()
ax.set_axis_off()
line_1, = plt.plot([0, 1], [0, 1], transform=fig.transFigure, clip_on=False, lw=2, c='blue')
line_2, = plt.plot([0,6.4], [0,4.8], transform=fig.dpi_scale_trans, clip_on=False, lw=2, c='black')
plt.show()

image

plt.rcParams['savefig.pad_inches'] gives me 0.1. When I add 0.1 to line_2 coordinates, line_2 will be placed the same as line_1:

fig = plt.figure(facecolor='#ccc')
ax = fig.gca()
ax.set_axis_off()
line_1, = plt.plot([0, 1], [0, 1], transform=fig.transFigure, clip_on=False, lw=2, c='blue')
ofs = plt.rcParams['savefig.pad_inches']
line_2, = plt.plot([0+ofs,6.4+ofs], [0+ofs,4.8+ofs], transform=fig.dpi_scale_trans, clip_on=False, lw=2, c='black')
plt.show()

image

It seems to me this is a bug.

Code for reproduction

fig = plt.figure(facecolor='#ccc')
ax = fig.gca()
ax.set_axis_off()
line_1, = plt.plot([0, 1], [0, 1], transform=fig.transFigure, clip_on=False, lw=2, c='blue')
ofs = 0
# uncomment line below for workaround:
# ofs = plt.rcParams['savefig.pad_inches']
fig_w, fig_h = fig.get_size_inches()
line_2, = plt.plot([0+ofs,fig_w+ofs], [0+ofs,fig_h+ofs], transform=fig.dpi_scale_trans, clip_on=False, lw=2, c='black')
plt.show()

Actual outcome

image

Expected outcome

image

Additional information

No response

Operating system

Windows 10

Matplotlib Version

3.9.3

Matplotlib Backend

inline

Python version

3.12.5

Jupyter version

ms-toolsai.jupyter v2024.10.0

Installation

pip

@rcomer
Copy link
Member

rcomer commented Dec 4, 2024

This is because you are using a notebook and the inline backend is expanding the figure to fit around the line with the padding you found added on. See https://matplotlib.org/devdocs/users/explain/figure/figure_intro.html#notebooks-and-ides. I guess dpi_scale_trans is working relative to the expanded figure and transFigure is working relative to the original.

If instead you save to a file with

plt.savefig('test.png')

you get

test

Note that, unlike your "expected outcome" above, here the line goes right to the corners.

@sindzicat
Copy link
Author

@rcomer, thanks for your answer!

I tested a lot and found that matplotlib inline uses savefig with an option bbox_inches='tight'. I tested this:

fig = plt.figure(facecolor='#ccc')
ax = fig.gca()
ax.set_axis_off()
line_1, = plt.plot([0, 1], [0, 1], transform=fig.transFigure, clip_on=False, lw=2, c='blue')
ofs = 0
# uncomment line below for workaround:
# ofs = plt.rcParams['savefig.pad_inches']
fig_w, fig_h = fig.get_size_inches()
line_2, = plt.plot([0+ofs,fig_w+ofs], [0+ofs,fig_h+ofs], transform=fig.dpi_scale_trans, clip_on=False, lw=2, c='black')
#plt.show()
plt.savefig('test.png', bbox_inches='tight')

And test.png now is:

image

So now I suggest this is definitely is a Matplotlib bug.

@jklymak
Copy link
Member

jklymak commented Dec 5, 2024

@sindzicat
Copy link
Author

@jklymak, sorry, I don't know how this should resolve this problem. Please explain this in more detail if you could.

I already use these pads in my code as a workaround. But I wonder, why fig.transFigure don't require to shift plotted line.

@jklymak
Copy link
Member

jklymak commented Dec 5, 2024

Because bboxinches=tight changes the size of the figure and adds a pad around the visible artists. The line in figure space then gets stretched.

@rcomer
Copy link
Member

rcomer commented Dec 5, 2024

The problem is that you are asking for two mutually exclusive things: the plot commands ask for the line to start right in the corner of the figure whereas saving with bbox_inches='tight' and the default pad asks for a 0.1 inch gap between the line and the edge of the figure.

@sindzicat
Copy link
Author

@rcomer, I'm just wondering now, why this default pad make line_1 with fig.transFigure transform to be shifted from the left bottom corner, but not line_2 with ig.dpi_scale_trans transform. If you could explain this in more detail, that would be great. I couldn't find anything about it in the documentation, so this seems like an unexpected Matplotlib behavior.

@sindzicat
Copy link
Author

@jklymak, Okay, but why is only one of the lines displaced relative to the lower left corner? I couldn't find anything about this in the Matplotlib documentation.

@rcomer
Copy link
Member

rcomer commented Dec 5, 2024

@sindzicat if you are really keen, the code that makes the adjustment for bbox_inches is here. There is some explicit handling for transFigure, but not for dpi_scale_trans. I do not claim to understand everything this function does.

@sindzicat
Copy link
Author

@rcomer, many thanks! I tried to find this myself, but without success.

I found a way to make code work:

ax.plot([0, 6.4], [0, 4.8], transform=(fig.dpi_scale_trans+mpl.transforms.ScaledTranslation(0,0,fig.transFigure)), clip_on=False, c='blue')

I've added ScaledTranslation to fig.dpi_scale_trans. I'm not sure why this works, though. I feel like there are a few things missing from the Matplotlib documentation.

@jklymak
Copy link
Member

jklymak commented Dec 5, 2024

In my opinion, if you want to mess with transfigure and trans_fig_dpi you should avoid using bbox_inches="tight" which changes the size of the figure on you.

@jklymak
Copy link
Member

jklymak commented Dec 5, 2024

I should add that it's iPython that makes the inline backend use bbox_inches='tight' by default.

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

No branches or pull requests

3 participants