Possible to matplotlib's constrained_layout ignore axis tick labels? - python

I have a reasonably complicated grid of subplots that involves two sets (one on the left and another on the right) of columns plotting a set of quantities for each row, separated by a common legend to label the entries in each row.
Here is a sample of what I want to accomplish
Using matplotlib with constrained_layout = True works 95% perfectly for applying the out optimal sizes & spacing for the columns, down to the tricky case of having the legend run down the middle. The remaining 5% is highlighted in red, where the wordy x-axis tick labels seem to push away the columns: it would be perfect if there was a way to make the layout engine "ignore" the tick labels in determining the spacing.
Methods using other libraries are also appreciated. Thank you in advance.
What I tried:
subplots_adjust
GridSpec
The main difficulty with those attempts:
constrained_layout is incompatible with those settings, so one must sacrifice the optimized legend spacing at the cost of getting the column spacing right, or vice versa.

Related

Matplotlib savefig bbox_inches='tight' along a single direction only?

I have two plots, each with multiple subplots (panels), i.e. multiple rows and columns. Each row shows the same type of data as images with colorbars on the right side. The first plot has, say, 8 rows and 4 columns. The second one has, say, 3 rows and 4 columns. The two plots are inserted into a LaTeX pdf document on two consecutive pages, with the same width (\includegraphics[width=\hsize]{fig1.pdf}). For layout reasons, I want the panels to have exactly the same width and height when flipping between pages in a pdf reader. To guarantee this, I thus used the same subplot layout of 8 x 4 panels for the second plot and made the panels (axes) for the 5 rows where there are no data invisible.
Since the second plot has only 3 rows of data, I use fig.savefig(bbox_inches='tight') to clip the white space below those rows. Unfortunately, as the ticks on the colorbar on the right side of the last column have a different maximum number of digits (on the first and second plot, say 1 and 2 decimal digits), with bbox_inches='tight' the resulting figure width (after saving and thus also when included into the LaTeX pdf) becomes different between the two plots.
I would like to not have to use a different layout of 3 x 4 subplots for the second plot, where I have to manually adjust the figure height (and likely still don't get exactly the same panel sizes). I could adjust both the tick formatters and tick locators on the colorbars to the same maximum number of digits for the two plots, but I would prefer to let matplotlib use the default ScalarFormatter() and AutoLocator().
Optimally, I would like to clip the white space below the 3 rows for the y-direction using bbox_inches='tight', but leave the bbox untouched for the x-direction and manually adjust the right figure border via fig.subplot_params(fig_right) to the same value for the two plots.
Questions: Is it possible to use bbox_inches='tight' only for the y-direction of a figure, but use the default bbox_inches (rcParams['savefig.bbox'], defaults to None) for the x-direction? Should such a feature (accepted values 'tight_x' and 'tight_y' for bbox_inches) maybe be implemented to matplotlib (I assume (but don't know) that this would not be too difficult)? Do you have alternative ideas how I can obtain exactly the same panel sizes for the two plots?
Update from original poster: As noted by #Jody Klymak, it is not possible to use a tight bounding box for one direction (or one border) only, but custom bounding boxes may be used. In my case, I pass bbox_inches=None for the first plot, and bbox_inches=Bbox([[0,fig.get_tightbbox(fig.canvas.get_renderer())._bbox.y0/fig.dpi - 0.1],[fig.get_figwidth(),fig.get_figheight()]]) for the second plot. This solves the question.

How to adjust subplots borders in matplotlib automatically?

When plotting multiple plots using plt.subplots, most of the time the spacing between subplots is not ideal so the the xtick labels of the top plot would overlap with the title of the bottom plots. There is a way to fix this manually by calling say plt.subplots_adjust(hspace=0.5) and changing the parameters interactively to obtain a decent looking plot. Is there a way to calculate the subplot_adjust parameter automatically? Meaning finding the minimum hspace and wspace so that there is not overlap between texts of the plots.
You can use tight_layout https://matplotlib.org/stable/tutorials/intermediate/tight_layout_guide.html or constrained_layout https://matplotlib.org/stable/tutorials/intermediate/constrainedlayout_guide.html
I'm pretty certain that the closest your going to find to an inbuilt calculation method is:
plt.tight_layout()
or
figure.Figure.tight_layout() #if you are using the object version of the code

How to ajust the location of the labels in legend?

I plotted a figure as the attached picture.
There are 3 labels in the legend.
As you can see, the label on the second row is quite long, so I'd like to set the right of “BS” label align with the end of "Farinotti et al. (2019)" label.
How should I do this?
Basically, you're thinking "these labels form a 2x2 grid, and I want the bottom row to span 2 columns"?
To be honest, I don't think that's necessary; to my eyes your legend looks fine. This won't save as much white space, but you could save some by just swapping the positions of "BS" and "Farinotti". Saving more white space than that would be inconsequential here. You can have a look at this other answer for tips on how to flip the label positions:
Matplotlib legend, add items across columns instead of down
But if you want to go down the path of creating column-spanning labels anyway, for a more extreme case or out of curiosity, what you want to do is define your own legend handler. Required reading:
https://matplotlib.org/1.3.1/users/legend_guide.html
https://matplotlib.org/3.1.0/api/legend_handler_api.html
Or here's a somewhat hacky answer I found that combines 2 legends into 1, which should give you the effect that you're looking for.
pyplot: change ncols within a legend

How to set the physical length from an axis in matplotlib?

Is there a command to set the length of an axis? I do not mean the range! Independently from the values, the range from the axis or other factors, I want to set its length. How can I do that?
Something like plt.yaxislenght(20)?
I'm not sure of a specific way to set an axis length of axes generated by e.g. plt.subplots. You can use ax.set_aspect(num), but this adjusts the aspect ratio, and therefore will change both axes in a dependent way.
You can however use ax = plt.axes([left,bottom,width,height]) to add individual subplots in whatever positions you like. This should allow you to achieve what you want, but will be tedious because you need to place each subplot manually.
What you want to do is tricky due to the way that mpl works underneath. Most of the artist are specified in units that are not on-screen units (data, axes, or figure space: see transfrom tutorial). This gives you a good deal of power/flexibility as most of the time you want to work in one of the relative sets of coordinates, however the cost is if you want to set 'absolute' sizes of things you end up having to do it indirectly.
If you want you axis to be a fixed length (in display units) between figures, then you need to control the size of you axes (in figure units) by hand (via fig.add_axes) and then use fig.set_size_inches to set the size of your over-all figure. By tweaking these values you can get what you want.

On adjusting margins in matplotlib

I am trying to minimize margins around a 1X2 figure, a figure which are two stacked subplots. I searched a lot and came up with commands like:
self.figure.subplots_adjust(left=0.01, bottom=0.01, top=0.99, right=0.99)
Which leaves a large gap on top and between the subplots. Playing with these parameters, much less understanding them was tough (things like ValueError: bottom cannot be >= top)
My questions :
What is the command to completely minimize the margins?
What do these numbers mean, and what coordinate system does this follow (the non-standard percent thing and origin point of this coordinate system)? What are the special rules on top of this coordinate system?
Where is the exact point this command needs to be called? From experiment, I figured out it works after you create subplots. What if you need to call it repeatedly after you resize a window and need to resize the figure to fit inside?
What are the other methods of adjusting layouts, especially for a single subplot?
They're in figure coordinates: http://matplotlib.sourceforge.net/users/transforms_tutorial.html
To remove gaps between subplots, use the wspace and hspace keywords to subplots_adjust.
If you want to have things adjusted automatically, have a look at tight_layout
Gridspec: http://matplotlib.sourceforge.net/users/gridspec.html

Categories