How to add border or frame around individual subplots - python

I want to create an image like this, but I'm unable to put the individual plots inside a frame.

Figures and axes have a patch attribute, which is the rectangle that makes up the background. Setting a figure frame is hence pretty straightforward:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 1)
# add a bit more breathing room around the axes for the frames
fig.subplots_adjust(top=0.85, bottom=0.15, left=0.2, hspace=0.8)
fig.patch.set_linewidth(10)
fig.patch.set_edgecolor('cornflowerblue')
# When saving the figure, the figure patch parameters are overwritten (WTF?).
# Hence we need to specify them again in the save command.
fig.savefig('test.png', edgecolor=fig.get_edgecolor())
Now the axes are a much tougher nut to crack. We could use the same approach as for the figure (which #jody-klymak I think is suggesting), however, the patch only corresponds to the area that is inside the axis limits, i.e. it does not include the tick labels, axis labels, nor the title.
However, axes have a get_tightbbox method, which is what we are after. However, using that also has some gotchas, as explained in the code comments.
# We want to use axis.get_tightbbox to determine the axis dimensions including all
# decorators, i.e. tick labels, axis labels, etc.
# However, get_tightbox requires the figure renderer, which is not initialized
# until the figure is drawn.
plt.ion()
fig.canvas.draw()
for ii, ax in enumerate(axes):
ax.set_title(f'Title {ii+1}')
ax.set_ylabel(f'Y-Label {ii+1}')
ax.set_xlabel(f'X-Label {ii+1}')
bbox = ax.get_tightbbox(fig.canvas.get_renderer())
x0, y0, width, height = bbox.transformed(fig.transFigure.inverted()).bounds
# slightly increase the very tight bounds:
xpad = 0.05 * width
ypad = 0.05 * height
fig.add_artist(plt.Rectangle((x0-xpad, y0-ypad), width+2*xpad, height+2*ypad, edgecolor='red', linewidth=3, fill=False))
fig.savefig('test2.png', edgecolor=fig.get_edgecolor())
plt.show()

I found something very similar and somehow configured it out what its doing .
autoAxis1 = ax8i[1].axis() #ax8i[1] is the axis where we want the border
import matplotlib.patches as ptch
rec = ptch.Rectangle((autoAxis1[0]-12,autoAxis1[2]-30),(autoAxis1[1]-
autoAxis1[0])+18,(autoAxis1[3]-
autoAxis1[2])+35,fill=False,lw=2,edgecolor='cyan')
rec = ax8i[1].add_patch(rec)
rec.set_clip_on(False)
The code is a bit complex but once we get to know what part of the bracket inside the Rectangle() is doing what its quite easy to get the code .

Related

Cannot set spine line style with matplotlib

I try to set the line style of matplotlib plot spines, but for some reason, it does not work. I can set them invisible or make them thinner, but I cannot change the line style.
My aim is to present one plot cut up into two to show outliers at the top. I would like to set the respective bottom/top spines to dotted so they clearly show that there is a break.
import numpy as np
import matplotlib.pyplot as plt
# Break ratio of the bottom/top plots respectively
ybreaks = [.25, .9]
figure, (ax1, ax2) = plt.subplots(
nrows=2, ncols=1,
sharex=True, figsize=(22, 10),
gridspec_kw = {'height_ratios':[1 - ybreaks[1], ybreaks[0]]}
)
d = np.random.random(100)
ax1.plot(d)
ax2.plot(d)
# Set the y axis limits
ori_ylim = ax1.get_ylim()
ax1.set_ylim(ori_ylim[1] * ybreaks[1], ori_ylim[1])
ax2.set_ylim(ori_ylim[0], ori_ylim[1] * ybreaks[0])
# Spine formatting
# ax1.spines['bottom'].set_visible(False) # This works
ax1.spines['bottom'].set_linewidth(.25) # This works
ax1.spines['bottom'].set_linestyle('dashed') # This does not work
ax2.spines['top'].set_linestyle('-') # Does not work
ax2.spines['top'].set_linewidth(.25) # Works
plt.subplots_adjust(hspace=0.05)
I would expect the above code to draw the top plot's bottom spine and the bottom plot's top spine dashed.
What do I miss?
First one should mention that if you do not change the linewidth, the dashed style shows fine.
ax1.spines['bottom'].set_linestyle("dashed")
However the spacing may be a bit too tight. This is due to the capstyle being set to "projecting" for spines by default.
One can hence set the capstyle to "butt" instead (which is also the default for normal lines in plots),
ax1.spines['bottom'].set_linestyle('dashed')
ax1.spines['bottom'].set_capstyle("butt")
Or, one can separate the dashes further. E.g.
ax1.spines['bottom'].set_linestyle((0,(4,4)))
Now, if you also set the linewidth so something smaller, then you would need proportionally more spacing. E.g.
ax1.spines['bottom'].set_linewidth(.2)
ax1.spines['bottom'].set_linestyle((0,(16,16)))
Note that the line does not actually become thinner on screen due to the antialiasing in use. It just washes out, such that it becomes lighter in color. So in total it may make sense to keep the lineswidth at some 0.72 points (0.72 points = 1 pixel at 100 dpi) and change the color to light gray instead.

How can I adjust Axes sizes in matplotlib polar plots? [duplicate]

I am starting to play around with creating polar plots in Matplotlib that do NOT encompass an entire circle - i.e. a "wedge" plot - by setting the thetamin and thetamax properties. This is something I was waiting for for a long time, and I am glad they have it done :)
However, I have noticed that the figure location inside the axes seem to change in a strange manner when using this feature; depending on the wedge angular aperture, it can be difficult to fine tune the figure so it looks nice.
Here's an example:
import numpy as np
import matplotlib.pyplot as plt
# get 4 polar axes in a row
fig, axes = plt.subplots(2, 2, subplot_kw={'projection': 'polar'},
figsize=(8, 8))
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig.set_facecolor('paleturquoise')
for i, theta_max in enumerate([2*np.pi, np.pi, 2*np.pi/3, np.pi/3]):
# define theta vector with varying end point and some data to plot
theta = np.linspace(0, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
axes[i//2, i%2].set_thetamin(0)
axes[i//2, i%2].set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
axes[i//2, i%2].plot(theta, data)
axes[i//2, i%2].set_ylim([0, 1])
axes[i//2, i%2].set_xlabel('Magnitude', fontsize=15)
axes[i//2, i%2].set_ylabel('Angles', fontsize=15)
fig.set_tight_layout(True)
#fig.savefig('fig.png', facecolor='skyblue')
The labels are in awkward locations and over the tick labels, but can be moved closer or further away from the axes by adding an extra labelpad parameter to set_xlabel, set_ylabel commands, so it's not a big issue.
Unfortunately, I have the impression that the plot is adjusted to fit inside the existing axes dimensions, which in turn lead to a very awkward white space above and below the half circle plot (which of course is the one I need to use).
It sounds like something that should be reasonably easy to get rid of - I mean, the wedge plots are doing it automatically - but I can't seem to figure it out how to do it for the half circle. Can anyone shed a light on this?
EDIT: Apologies, my question was not very clear; I want to create a half circle polar plot, but it seems that using set_thetamin() you end up with large amounts of white space around the image (especially above and below) which I would rather have removed, if possible.
It's the kind of stuff that normally tight_layout() takes care of, but it doesn't seem to be doing the trick here. I tried manually changing the figure window size after plotting, but the white space simply scales with the changes. Below is a minimum working example; I can get the xlabel closer to the image if I want to, but saved image file still contains tons of white space around it.
Does anyone knows how to remove this white space?
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, subplot_kw={'projection': 'polar'})
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig1.set_facecolor('skyblue')
theta_min = 0
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15)
ax1.set_ylabel('Angles', fontsize=15)
fig1.set_tight_layout(True)
#fig1.savefig('fig1.png', facecolor='skyblue')
EDIT 2: Added background color to figures to better show the boundaries, as suggested in ImportanteOfBeingErnest's answer.
It seems the wedge of the "truncated" polar axes is placed such that it sits in the middle of the original axes. There seems so be some constructs called LockedBBox and _WedgeBbox in the game, which I have never seen before and do not fully understand. Those seem to be created at draw time, such that manipulating them from the outside seems somewhere between hard and impossible.
One hack can be to manipulate the original axes such that the resulting wedge turns up at the desired position. This is not really deterministic, but rather looking for some good values by trial and error.
The parameters to adjust in this case are the figure size (figsize), the padding of the labels (labelpad, as already pointed out in the question) and finally the axes' position (ax.set_position([left, bottom, width, height])).
The result could then look like
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, figsize=(6,3.4), subplot_kw={'projection': 'polar'})
theta_min = 1.e-9
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6.)*np.abs(np.sin(3*theta)/np.sin(theta/2.))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180./np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15, labelpad=-60)
ax1.set_ylabel('Angles', fontsize=15)
ax1.set_position( [0.1, -0.45, 0.8, 2])
plt.show()
Here I've set some color to the background of the figure to better see the boundary.

imshow() subplots generate unwanted white spaces

When plotting two (or more) subplots, there is a large areas of white spaces within the plots (on all four sides) as seen here:
Following is the code which I used to plot it.
from pylab import *
from matplotlib import rc, rcParams
import matplotlib.pyplot as plt
for kk in range(57,58):
fn_i=str(kk)
image_file_1='RedshiftOutput00'+fn_i+'_Slice_z_RadioPowerDSA.png'
image_file_2='RedshiftOutput00'+fn_i+'_Slice_z_RadioPowerTRA.png'
image_file_3='RedshiftOutput00'+fn_i+'_Slice_z_RadioPowerDSA+TRA.png'
image_1 = plt.imread(image_file_1)
image_2 = plt.imread(image_file_2)
image_3 = plt.imread(image_file_3)
ax1 = subplot(131)
plt.imshow(image_1)
plt.axis('off') # clear x- and y-axes
ax2 = subplot(132)
plt.imshow(image_2)
plt.axis('off') # clear x- and y-axes
ax3 = subplot(133)
plt.imshow(image_3)
plt.axis('off') # clear x- and y-axes
plt.savefig('RedshiftOutput00'+fn_i+'_all.png')
I am also uploading the 3 images used in this code to making the code a Minimal Working Example
1) https://drive.google.com/file/d/0B6l5iRWTUbHWSTF2R3E1THBGeVk/view?usp=sharing
2) https://drive.google.com/file/d/0B6l5iRWTUbHWaFI4dHAzcWpiOEU/view?usp=sharing
3) https://drive.google.com/file/d/0B6l5iRWTUbHWaG8xclFlcGJNaUk/view?usp=sharing
How we can remove this white space ? I tried by fixing the whole plot size, still white space is comming.
Mel's comment above (use plt.tight_layout()) works in many situations, but sometimes you need a little more control. To manipulate axes more finely (useful, e.g., when you have lots of colorbars or twin-ned axes), you can use plt.subplots_adjust() or a GridSpec object.
GridSpec objects allow you to specify the horizontal and vertical extents of individual axes, as well as their proportional width and height & spacing. subplots_adjust() moves your axes around after you've already plotted stuff on them. I prefer using the first option, but both are documented well.
It also may help to fool around with the size of your figure. If you have lots of whitespace width-wise, make the width of the figure smaller.
Here's some example code that I used to set up a recent plot:
gs = gridspec.GridSpec(
nrows=1, ncols=3, left=0.1, bottom=0.25, right=0.95, top=0.95,
wspace=0.05, hspace=0., width_ratios=[1, 1, 1])
NII_ax = plt.subplot(gs[0])
SII_ax = plt.subplot(gs[1])
OI_ax = plt.subplot(gs[2])
And the result:
Then, if you need a colorbar, adjust the right argument in GridSpec to something like 0.85, and use fig.add_axes() with a list [left_lim, bottom, width, height] and use that as the axis argument for a fig.colorbar()

Matplotlib legend vertical rotation

Does someone perhaps know if it is possible to rotate a legend on a plot in matplotlib? I made a simple plot with the below code, and edited the graph in paint to show what I want.
plt.plot([4,5,6], label = 'test')
ax = plt.gca()
ax.legend()
plt.show()
I went to a similar problem and solved it by writing the function legendAsLatex that generates a latex code to be used as the label of the y-axis. The function gathers the color, the marker, the line style, and the label provided to the plot function. It requires enabling the latex and loading the required packages. Here is the code to generate your plot with extra curves that use both vertical axis.
from matplotlib import pyplot as plt
import matplotlib.colors as cor
plt.rc('text', usetex=True)
plt.rc('text.latex', preamble=r'\usepackage{amsmath} \usepackage{wasysym}'+
r'\usepackage[dvipsnames]{xcolor} \usepackage{MnSymbol} \usepackage{txfonts}')
def legendAsLatex(axes, rotation=90) :
'''Generate a latex code to be used instead of the legend.
Uses the label, color, marker and linestyle provided to the pyplot.plot.
The marker and the linestyle must be defined using the one or two character
abreviations shown in the help of pyplot.plot.
Rotation of the markers must be multiple of 90.
'''
latexLine = {'-':'\\textbf{\Large ---}',
'-.':'\\textbf{\Large --\:\!$\\boldsymbol{\cdot}$\:\!--}',
'--':'\\textbf{\Large --\,--}',':':'\\textbf{\Large -\:\!-}'}
latexSymbol = {'o':'medbullet', 'd':'diamond', 's':'filledmedsquare',
'D':'Diamondblack', '*':'bigstar', '+':'boldsymbol{\plus}',
'x':'boldsymbol{\\times}', 'p':'pentagon', 'h':'hexagon',
',':'boldsymbol{\cdot}', '_':'boldsymbol{\minus}','<':'LHD',
'>':'RHD','v':'blacktriangledown', '^':'blacktriangle'}
rot90=['^','<','v','>']
di = [0,-1,2,1][rotation%360//90]
latexSymbol.update({rot90[i]:latexSymbol[rot90[(i+di)%4]] for i in range(4)})
return ', '.join(['\\textcolor[rgb]{'\
+ ','.join([str(x) for x in cor.to_rgb(handle.get_color())]) +'}{'
+ '$\\'+latexSymbol.get(handle.get_marker(),';')+'$'
+ latexLine.get(handle.get_linestyle(),'') + '} ' + label
for handle,label in zip(*axes.get_legend_handles_labels())])
ax = plt.axes()
ax.plot(range(0,10), 'b-', label = 'Blue line')
ax.plot(range(10,0,-1), 'sm', label = 'Magenta squares')
ax.set_ylabel(legendAsLatex(ax))
ax2 = plt.twinx()
ax2.plot([x**0.5 for x in range(0,10)], 'ro', label = 'Red circles')
ax2.plot([x**0.5 for x in range(10,0,-1)],'g--', label = 'Green dashed line')
ax2.set_ylabel(legendAsLatex(ax2))
plt.savefig('legend.eps')
plt.close()
Figure generated by the code:
I spent a few hours chipping away at this yesterday, and made a bit of progress so I'll share that below along with some suggestions moving forward.
First, it seems that we can certainly rotate and translate the bounding box (bbox) or frame around the legend. In the first example below you can see that a transform can be applied, albeit requiring some oddly large translation numbers after applying the 90 degree rotation. But, there are actually problems saving the translated legend frame to an image file so I had to take a screenshot from the IPython notebook. I've added some comments as well.
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import matplotlib.transforms
fig = plt.figure()
ax = fig.add_subplot('121') #make room for second subplot, where we are actually placing the legend
ax2 = fig.add_subplot('122') #blank subplot to make space for legend
ax2.axis('off')
ax.plot([4,5,6], label = 'test')
transform = matplotlib.transforms.Affine2D(matrix=np.eye(3)) #start with the identity transform, which does nothing
transform.rotate_deg(90) #add the desired 90 degree rotation
transform.translate(410,11) #for some reason we need to play with some pretty extreme translation values to position the rotated legend
legend = ax.legend(bbox_to_anchor=[1.5,1.0])
legend.set_title('test title')
legend.get_frame().set_transform(transform) #This actually works! But, only for the frame of the legend (see below)
frame = legend.get_frame()
fig.subplots_adjust(wspace = 0.4, right = 0.9)
fig.savefig('rotate_legend_1.png',bbox_extra_artists=(legend,frame),bbox_inches='tight', dpi = 300) #even with the extra bbox parameters the legend frame is still getting clipped
Next, I thought it would be smart to explore the get_methods() of other legend components. You can sort of dig through these things with dir(legend) and legend.__dict__ and so on. In particular, I noticed that you can do this: legend.get_title().set_transform(transform), which would seem to imply that we could translate the legend text (and not just the frame as above). Let's see what happens when I tried that:
fig2 = plt.figure()
ax = fig2.add_subplot('121')
ax2 = fig2.add_subplot('122')
ax2.axis('off')
ax.plot([4,5,6], label = 'test')
transform = matplotlib.transforms.Affine2D(matrix=np.eye(3))
transform.rotate_deg(90)
transform.translate(410,11)
legend = ax.legend(bbox_to_anchor=[1.5,1.0])
legend.set_title('test title')
legend.get_frame().set_transform(transform)
legend.get_title().set_transform(transform) #one would expect this to apply the same transformation to the title text in the legend, rotating it 90 degrees and translating it
frame = legend.get_frame()
fig2.subplots_adjust(wspace = 0.4, right = 0.9)
fig2.savefig('rotate_legend_1.png',bbox_extra_artists=(legend,frame),bbox_inches='tight', dpi = 300)
The legend title seems to have disappeared in the screenshot from the IPython notebook. But, if we look at the saved file the legend title is now in the bottom left corner and seems to have ignored the rotation component of the transformation (why?):
I had similar technical difficulties with this type of approach:
bbox = matplotlib.transforms.Bbox([[0.,1],[1,1]])
trans_bbox = matplotlib.transforms.TransformedBbox(bbox, transform)
legend.set_bbox_to_anchor(trans_bbox)
Other notes and suggestions:
It might be a sensible idea to dig into the differences in behaviour between the legend title and frame objects--why do they both accept transforms, but only the frame accepts a rotation? Perhaps it would be possible to subclass the legend object in the source code and make some adjustments.
We also need to find a solution for the rotated / translated legend frame not being saved to output, even after following various related suggestion on SO (i.e., Matplotlib savefig with a legend outside the plot).

Subplots: tight_layout changes figure size

Changing the vertical distance between two subplot using tight_layout(h_pad=-1) changes the total figuresize. How can I define the figuresize using tight_layout?
Here is the code:
#define figure
pl.figure(figsize=(10, 6.25))
ax1=subplot(211)
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
subplot(212)
x=linspace(0,50)
pl.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
And here is the results:
If I write
pl.tight_layout(h_pad=-2)
in the last line, then I get this:
As you can see, the figure is bigger...
You can use a GridSpec object to control precisely width and height ratios, as answered on this thread and documented here.
Experimenting with your code, I could produce something like what you want, by using a height_ratio that assigns twice the space to the upper subplot, and increasing the h_pad parameter to the tight_layout call. This does not sound completely right, but maybe you can adjust this further ...
import numpy as np
from matplotlib.pyplot import *
import matplotlib.pyplot as pl
import matplotlib.gridspec as gridspec
#define figure
fig = pl.figure(figsize=(10, 6.25))
gs = gridspec.GridSpec(2, 1, height_ratios=[2,1])
ax1=subplot(gs[0])
img=pl.imshow(np.random.random((10,50)), interpolation='none')
ax1.set_xticklabels(()) #hides the tickslabels of the first plot
ax2=subplot(gs[1])
x=np.linspace(0,50)
ax2.plot(x,x,'k-')
xlim( ax1.get_xlim() ) #same x-axis for both plots
fig.tight_layout(h_pad=-5)
show()
There were other issues, like correcting the imports, adding numpy, and plotting to ax2 instead of directly with pl. The output I see is this:
This case is peculiar because of the fact that the default aspect ratios of images and plots are not the same. So it is worth noting for people looking to remove the spaces in a grid of subplots consisting of images only or of plots only that you may find an appropriate solution among the answers to this question (and those linked to it): How to remove the space between subplots in matplotlib.pyplot?.
The aspect ratios of the subplots in this particular example are as follows:
# Default aspect ratio of images:
ax1.get_aspect()
# 1.0
# Which is as it is expected based on the default settings in rcParams file:
matplotlib.rcParams['image.aspect']
# 'equal'
# Default aspect ratio of plots:
ax2.get_aspect()
# 'auto'
The size of ax1 and the space beneath it are adjusted automatically based on the number of pixels along the x-axis (i.e. width) so as to preserve the 'equal' aspect ratio while fitting both subplots within the figure. As you mentioned, using fig.tight_layout(h_pad=xxx) or the similar fig.set_constrained_layout_pads(hspace=xxx) is not a good option as this makes the figure larger.
To remove the gap while preserving the original figure size, you can use fig.subplots_adjust(hspace=xxx) or the equivalent plt.subplots(gridspec_kw=dict(hspace=xxx)), as shown in the following example:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
np.random.seed(1)
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6.25),
gridspec_kw=dict(hspace=-0.206))
# For those not using plt.subplots, you can use this instead:
# fig.subplots_adjust(hspace=-0.206)
size = 50
ax1.imshow(np.random.random((10, size)))
ax1.xaxis.set_visible(False)
# Create plot of a line that is aligned with the image above
x = np.arange(0, size)
ax2.plot(x, x, 'k-')
ax2.set_xlim(ax1.get_xlim())
plt.show()
I am not aware of any way to define the appropriate hspace automatically so that the gap can be removed for any image width. As stated in the docstring for fig.subplots_adjust(), it corresponds to the height of the padding between subplots, as a fraction of the average axes height. So I attempted to compute hspace by dividing the gap between the subplots by the average height of both subplots like this:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Compute negative hspace to close the vertical gap between subplots
ax1_h = ax1_y1-ax1_y0
ax2_h = ax2_y1-ax2_y0
avg_h = (ax1_h+ax2_h)/2
gap = ax1_y0-ax2_y1
hspace=-(gap/avg_h) # this divided by 2 also does not work
fig.subplots_adjust(hspace=hspace)
Unfortunately, this does not work. Maybe someone else has a solution for this.
It is also worth mentioning that I tried removing the gap between subplots by editing the y positions like in this example:
# Extract axes positions in figure coordinates
ax1_x0, ax1_y0, ax1_x1, ax1_y1 = np.ravel(ax1.get_position())
ax2_x0, ax2_y0, ax2_x1, ax2_y1 = np.ravel(ax2.get_position())
# Set new y positions: shift ax1 down over gap
gap = ax1_y0-ax2_y1
ax1.set_position([ax1_x0, ax1_y0-gap, ax1_x1, ax1_y1-gap])
ax2.set_position([ax2_x0, ax2_y0, ax2_x1, ax2_y1])
Unfortunately, this (and variations of this) produces seemingly unpredictable results, including a figure resizing similar to when using fig.tight_layout(). Maybe someone else has an explanation for what is happening here behind the scenes.

Categories