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

Jupyter "inline" backend seems to misinterpret "figsize" with Axes3D #16463

Closed
mgeier opened this issue Feb 10, 2020 · 17 comments · Fixed by #17515
Closed

Jupyter "inline" backend seems to misinterpret "figsize" with Axes3D #16463

mgeier opened this issue Feb 10, 2020 · 17 comments · Fixed by #17515
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. topic: geometry manager LayoutEngine, Constrained layout, Tight layout topic: mplot3d
Milestone

Comments

@mgeier
Copy link
Contributor

mgeier commented Feb 10, 2020

I don't know if this is a bug in Jupyter/IPython or in Matplotlib ...

Bug report

Bug summary

When creating 3D plots (using Axes3D) with the "inline" backend (which is the default) in a Jupyter notebook, the argument figsize doesn't seem to work as intended. It looks like the width is ignored when calculating the figure size (but it doesn't seem to be ignored when calculating the size of the "axes" object?).

Actual outcome

First, when using fig.gca(projection='3d'), which is used in the official Matplotlib examples:

image

The same thing happens when using fig.add_subplot(projection='3d'):

image

Interestingly, a slightly different thing happens when using fig.add_axes([0, 0, 1, 1], projection='3d') and Axes3D(fig) (which is discouraged?):

image

Expected outcome

When using a "normal" 2D Axes, it looks as expected:

image

The output from fig.add_axes([0, 0, 1, 1]) is also different in this case:

image

Matplotlib version

  • Operating system: Debian Linux
  • Matplotlib version: master
  • Matplotlib backend (print(matplotlib.get_backend())): module://ipykernel.pylab.backend_inline
  • Python version: 3.7.6
  • Jupyter version (if applicable): JupyterLab master
@jklymak
Copy link
Member

jklymak commented Feb 10, 2020

inline use bbox_inches='tight' and hence clips the figure to the size of the drawn objects. That will lead to different sized figures if your drawn axes are different sizes.

add_axes([0, 0, 1, 1]) says to fill the figure with the axes. bbox_inches='tight' then expands the figure to include the axes.

I'm going to close this as its understood behaviour and not a bug per-se, though I agree the decision to use bbox_inches='tight' can cause confusion.

@jklymak jklymak closed this as completed Feb 10, 2020
@jklymak jklymak added the Community support Users in need of help. label Feb 10, 2020
@mgeier
Copy link
Contributor Author

mgeier commented Feb 10, 2020

Thanks for the quick answer @jklymak!
I was actually already aware of the bbox_inches='tight' problem (I've even mentioned it in my pamphlet about wrong default values in the inline backend), but I didn't realize that it was also the culprit in this case!

I'm going to close this as its understood behaviour and not a bug per-se, though I agree the decision to use bbox_inches='tight' can cause confusion.

I was still surprised that the aspect ratio of the figure was completely changed. I would understand if some empty space were removed from all sides, but in this case a lot of "empty" space was removed from the left and right sides, but apparently not from the top and bottom?

Are you sure that's not a bug in the bbox_inches='tight' algorithm?

Here are a few updated screenshots. First, for comparison, two cases repeated from above:

image

And now without the bbox_inches='tight' setting:

image

And here's how "normal" 2D plots look without the bbox_inches='tight' setting:

image

@WeatherGod
Copy link
Member

WeatherGod commented Feb 10, 2020 via email

@mgeier
Copy link
Contributor Author

mgeier commented Feb 14, 2020

Thanks @WeatherGod, so are you saying this could be a bug?

If yes, could you (or @jklymak) please re-open this issue?

@jklymak jklymak reopened this Feb 14, 2020
@ImportanceOfBeingErnest ImportanceOfBeingErnest added topic: geometry manager LayoutEngine, Constrained layout, Tight layout topic: mplot3d and removed Community support Users in need of help. labels Feb 14, 2020
@kopytjuk
Copy link
Contributor

kopytjuk commented Feb 21, 2020

Hmm on my system everything works as expected:

image

Windows 10
Python 3.7.3
matplotlib-3.1.3

Seems to be a problem for newer releases, could reproduce it on 2bdf82b

@WeatherGod
Copy link
Member

WeatherGod commented Feb 21, 2020 via email

@ImportanceOfBeingErnest
Copy link
Member

This bisects to 64f197b which has apparently been recreated via #16472.
So it's not visible in any released version yet.

To make sure we're all on the same page here, let's take jupyter (lab) out of the equation and create a clear bug report here:

import matplotlib.pyplot as plt
from  mpl_toolkits.mplot3d import Axes3D

plt.rcParams["axes.facecolor"] = "yellow"

plt.subplots(dpi=72, figsize=(6,3), subplot_kw={"projection" : "3d"})

plt.savefig("test1.png", facecolor="cyan")
plt.savefig("test2.png", facecolor="cyan", bbox_inches="tight")
plt.show()

Actual outcome in 3.1.3

direct save
test1
with bbox_inches="tight"
test2

Outcome in current master

direct save
test1

with bbox_inches="tight"
test2

Expected outcome

The direct save is expected. What's unexpected is the aspect ratio changing in the with bbox_inches="tight" case. The expected outcome would be something like this instead:

image

@jklymak
Copy link
Member

jklymak commented Feb 21, 2020

ping @eric-wieser

@eric-wieser
Copy link
Contributor

What does bbox_inches="tight" do with axis("equal") in a 2d plot?

@jklymak
Copy link
Member

jklymak commented Feb 21, 2020

It trims the figure size to encompass just the 2d-plot.

@anntzer anntzer added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Feb 22, 2020
@anntzer anntzer added this to the v3.2.0 milestone Feb 22, 2020
@anntzer
Copy link
Contributor

anntzer commented Feb 22, 2020

Should this block 3.2? Let's say yes for safety, but feel free to untag if you think not.

@QuLogic
Copy link
Member

QuLogic commented Feb 24, 2020

That PR is only in master, so I don't think we need to block 3.2. That is, the outcome of @ImportanceOfBeingErnest's test on v3.2.x is the same as the 3.1.3 result.

@QuLogic QuLogic modified the milestones: v3.2.0, v3.3.0 Feb 24, 2020
@talaikis
Copy link

For me, this bug is only in Linux (Ubuntu), Windows saves images ok.

@tacaswell
Copy link
Member

ok, sorted out why this is happening.

In

for ax in fig.axes:
pos = ax.get_position(original=False).frozen()
locator_list.append(ax.get_axes_locator())
asp_list.append(ax.get_aspect())
def _l(a, r, pos=pos):
return pos
ax.set_axes_locator(_l)
ax.set_aspect("auto")

we freeze all of the axes postiions and set the aspect to auto for all of the axes having relied on a previous draw to have put them all in the right place. The next time we draw (for the render we actually emit) none of the axes adjust their positions / limits / etc and the transforms all progate through as we expect. Very previously Axes3D would do something about set_aspect('equal') that was sometimes right but sometimes very wrong so we disabled (to some notable protest). Previously (as of 3.2) Axes3D was always in auto mode. On master, we now treat Axes3D like it always has a fixed aspect ratio (which is not 1:1:1 but 4:4:3, see #17172). Thus on master when we set the aspect to "auto" in tight_bbox.adjust_bbox that effectively gets ignored and in the second draw we do adjust the position of the axes, the transforms all propgate and we get this issue. The pragmatic solution is to special case Axes3D and make sure that we suppress adjusting the limits is this case.

tacaswell added a commit to tacaswell/matplotlib that referenced this issue May 27, 2020
To make bbox_inches='tight' work correctly, we cache the positions of
all of the axes, change their aspect to 'auto', adjust the transforms
on the figure, render the output, and then restore the previous
setting of aspect to each of the figures.  This prevent the Axes from
trying to adjust their size on draw. This matters because for the
render that is emitted the figure transforms are out of sync with the
figure size.

As part of cleaning fixing the rendering of Axes3D in matplotlib#8896 / matplotlib#16472
we started to use apply_aspect to re-size the area the Axes3D takes up
to maintain a fixed ratio between the axes when the aspect is "auto".
However this conflicts with the expectation in tight_bbox.adjust_bbox
as it assumes setting the aspect to "auto" will prevent any axes
resizing.

This commit addresses this by:
 - exiting the Axes3D.apply_aspect early if aspect is auto
 - adding a new aspect mode 'auto_pb' which is the default
   for Axes3D which maintains the current master branch behavior
   of maintaining a fixed ratio between the sizes of the x, y z axis
   independent of the data limits.
 - re implement several functions on Axes3D to make sure they handle
   sharez correctly.

closes matplotlib#16463.
tacaswell added a commit to tacaswell/matplotlib that referenced this issue May 30, 2020
The way that bbox_inches='tight' is implemented we need to ensure that
we do not try to adjust the aspect during the draw (because we have
temporarily de-coupled the reported figure size from the transforms
which results in the being distorted).  Previously we did not have a
way to fix the aspect ratio in screen space of the Axes (only the
aspect ratio in dataspace) however in 3.3 we gained this ability for
both Axes (matplotlib#14917) and Axes3D (matplotlib#8896 / matplotlib#16472).

Rather than add an aspect value to `set_aspect` to handle this case,
in the tight_bbox code we monkey-patch the `apply_aspect` method with
a no-op function and then restore it when we are done.  Previously we
would set the aspect to "auto" and restore it in the same places.

closes matplotlib#16463.
tacaswell added a commit to tacaswell/matplotlib that referenced this issue Jun 4, 2020
The way that bbox_inches='tight' is implemented we need to ensure that
we do not try to adjust the aspect during the draw (because we have
temporarily de-coupled the reported figure size from the transforms
which results in the being distorted).  Previously we did not have a
way to fix the aspect ratio in screen space of the Axes (only the
aspect ratio in dataspace) however in 3.3 we gained this ability for
both Axes (matplotlib#14917) and Axes3D (matplotlib#8896 / matplotlib#16472).

Rather than add an aspect value to `set_aspect` to handle this case,
in the tight_bbox code we monkey-patch the `apply_aspect` method with
a no-op function and then restore it when we are done.  Previously we
would set the aspect to "auto" and restore it in the same places.

closes matplotlib#16463.
@beasteers
Copy link

beasteers commented Apr 21, 2022

Hmm I'm still seeing this in 3.5.1 (on mac, python 3.10) - reproduced with and without jupyter

fig = plt.figure(figsize=(20, 6))
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, wspace=0, hspace=0)
ax = fig.add_subplot(111, projection="3d", facecolor='#000000', elev=70., azim=0, aspect='auto')
ax.axis('off')
ax.plot_surface(X, Y, S / 10, cmap=plt.get_cmap('magma'))

image

No matter what I try I can't change the axis aspect ratio to fill the figure.

fig.subplots_adjust(left=-1, right=2) doesn't stretch it, it just moves it back and forth

@tacaswell
Copy link
Member

@beasteers I think you want https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.html#mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect

Could you please post this at https://discourse.matplotlib.org (if you think it is a usage question) or open a new issue (if you think it is a bug)?

@diplodocuslongus
Copy link

diplodocuslongus commented May 10, 2024

I was observing exactly the same as @beasteers , Linux, python 3.10, Matplotlib 3.7.1.
The trick to fill the figure is to use the proper combination of aspect and zoom (parameters of Axes3D.set_box_aspect), and figsize.
An example:

fig,ax1 = plt.subplots(1,1,figsize=(10, 6), constrained_layout=False,subplot_kw=dict(projection='3d'))
azi = 30
ax1.view_init(elev = 10, azim = azi)
ax1.set_box_aspect((3,7,3),zoom = 1.5)
fig.savefig('test.png')

test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. topic: geometry manager LayoutEngine, Constrained layout, Tight layout topic: mplot3d
Projects
None yet