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:
The same thing happens when using fig.add_subplot(projection='3d'):
Interestingly, a slightly different thing happens when using fig.add_axes([0, 0, 1, 1], projection='3d') and Axes3D(fig) (which is discouraged?):
Expected outcome
When using a "normal" 2D Axes, it looks as expected:
The output from fig.add_axes([0, 0, 1, 1]) is also different in this case:
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.
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:
And now without the bbox_inches='tight' setting:
And here's how "normal" 2D plots look without the bbox_inches='tight' setting:
I can't remember, but it might be that the tick labels for an Axes3D object
is stored in a separate location special for Axes3D. Therefore, the "tight"
algorithm doesn't know to check for those tick label's boxes.
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:
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.
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.
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:
Activity
jklymak commented on 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.
mgeier commented on 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 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:
And now without the bbox_inches='tight' setting:
And here's how "normal" 2D plots look without the bbox_inches='tight' setting:
WeatherGod commented on Feb 10, 2020
mgeier commented on 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?
kopytjuk commented on Feb 21, 2020
Hmm on my system everything works as expected:
Windows 10
Python 3.7.3
matplotlib-3.1.3
Seems to be a problem for newer releases, could reproduce it on 2bdf82b
WeatherGod commented on Feb 21, 2020
ImportanceOfBeingErnest commented on Feb 21, 2020
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


with bbox_inches="tight"
Outcome in current master
direct save

with bbox_inches="tight"

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:
jklymak commented on Feb 21, 2020
ping @eric-wieser
eric-wieser commented on Feb 21, 2020
What does bbox_inches="tight" do with axis("equal") in a 2d plot?
1 remaining item
anntzer commented on Feb 22, 2020
Should this block 3.2? Let's say yes for safety, but feel free to untag if you think not.
QuLogic commented on 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.
talaikis commented on Mar 24, 2020
For me, this bug is only in Linux (Ubuntu), Windows saves images ok.
tacaswell commented on May 26, 2020
ok, sorted out why this is happening.
In
matplotlib/lib/matplotlib/tight_bbox.py
Lines 28 to 36 in 9ccdb1d
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.
ENH/FIX: make "auto" aspect with Axes3D match Axes better
FIX: support bbox_inches='tight' for box_aspect Axes
FIX: support bbox_inches='tight' for box_aspect Axes
beasteers commented on Apr 21, 2022
Hmm I'm still seeing this in 3.5.1 (on mac, python 3.10) - reproduced with and without jupyter
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 commented on Apr 21, 2022
@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 commented on 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')