Resizing Axes#

matplotlib provides multiple ways to maximize the size of the axes such that everything still fits. This includes:

These work all within the figure, meaning that the size of the figure is overall not changed. For a fixed aspect ratio of the axes, however, the height of the figure depends on the width. None of the methods listed above adjust the height of the figure to match a desired aspect ratio of the axes and maximize the used area of the figure.

This may have some shortcomings, as the following example showcases:

import matplotlib.pyplot as plt

plt.rcParams["figure.constrained_layout.use"] = True
plt.rcParams["figure.facecolor"] = "grey"

fig, axs = plt.subplots(2, 3, figsize=(3.2*3, 3.2*2))

for ax in axs.flatten():
    ax.set_box_aspect(1/1.618)

(Source code, png, hires.png, pdf)

../../../_images/constrained_layout_shortcomings.png

As one can see, since the height of the figure is fixed, a lot of space is wasted. One could manually adjust the height to a more appropriate value, but that is tedious.

Compare this to the atompy way:

import matplotlib.pyplot as plt
import atompy as ap

plt.rcParams["figure.facecolor"] = "grey"

fig, axs = plt.subplots(2, 3, figsize=(3.2*3, 3.2*2))

for ax in axs.flatten():
    ax.set_box_aspect(1/1.618)

ap.make_me_nice()

(Source code, png, hires.png, pdf)

../../../_images/constrained_layout_atompy_solution.png

Of course, make_me_nice() has its own shortcomings. It can only handle n \(\times\) m grids (i.e., axes cannot span multiple rows or columns). But for most use-cases, it should suffice.

Additionaly, it handles colorbars much nicer. See Colormaps and Colorbars for more information.

See the documentation page of make_me_nice() for the full functionality.

Unexpected bahaviour and workarounds#

Fixed position and fixed aspect ratios#

Let’s say you have the following setup: The top-row axes is narrower than the bottom row axes and you want to align it to the edges. You can do this using the matplotlib.axes.Axes.set_position and matplotlib.axes.Axes.get_position.

import matplotlib.pyplot as plt
import atompy as ap

plt.rcParams["figure.facecolor"] = "grey"

fig, axs = plt.subplots(2, 1, figsize=(3.2, 3.2))

axs[0].set_box_aspect(1.)

axs[1].set_box_aspect(1./1.618)

# align top left axes to left edge
tmp = axs[0].get_position()
axs[0].set_position([axs[1].get_position().x0,
                       tmp.y0,
                       tmp.width,
                       tmp.height])

(Source code, png, hires.png, pdf)

../../../_images/multi_plots_multi_aspects_1.png

If you now add ap.make_me_nice(), you get the following:

(Source code, png, hires.png, pdf)

../../../_images/multi_plots_multi_aspects_2.png

As one can see, the top axes is not aligned propperly any more. One unfortunately has to re-align the top row for it to work. As this is a necessity, atompy provides some methods to streamline the process, namely align_axes_horizontally() and align_axes_vertically().

Let’s adjust the code:

import matplotlib.pyplot as plt
import atompy as ap

plt.rcParams["figure.facecolor"] = "grey"

fig, axs = plt.subplots(2, 1, figsize=(3.2, 3.2))

axs[0].set_box_aspect(1.0)
axs[1].set_box_aspect(1./1.618)

ap.make_me_nice()

ap.align_axes_horizontally(axs[0], axs[1], alignment="right")

plt.show()

(Source code, png, hires.png, pdf)

../../../_images/multi_plots_multi_aspects_3.png

Proper alignment!

… unless, of course, a situation like this arises:

(Source code, png, hires.png, pdf)

../../../_images/multi_plots_multi_aspects_4.png

Note that the required x-padding is larger for the upper axes. However, since the axes is re-aligned after the call to make_me_nice(), the required x-padding for the lower axes is used. Of course, that is wrong.

The solution: Throw align_axes_horizontally() around more liberally:

import matplotlib.pyplot as plt
import atompy as ap

plt.rcParams["figure.facecolor"] = "grey"

fig, axs = plt.subplots(2, 1, figsize=(3.2, 3.2))

axs[0].set_box_aspect(1.0)
axs[1].set_box_aspect(1./1.618)

axs[0].set_ylim(0.0, 0.1)

ap.align_axes_horizontally(axs[0], axs[1], alignment="right")

ap.make_me_nice()

ap.align_axes_horizontally(axs[0], axs[1], alignment="right")

(Source code, png, hires.png, pdf)

../../../_images/multi_plots_multi_aspects_5.png

And yet, since we live in a volatile universe where the depths of infitity reach out into the pits of that-what-shall-not and nothing is certain but the uncertainty of life, this still does not result in propper alignment.

If all fails, you can always do minor corrections using the margin_pad_pts keyword:

import matplotlib.pyplot as plt
import atompy as ap

plt.rcParams["figure.facecolor"] = "grey"

fig, axs = plt.subplots(2, 1, figsize=(3.2, 3.2))

axs[0].set_box_aspect(1.0)
axs[1].set_box_aspect(1./1.618)

axs[0].set_ylim(0.0, 0.1)

ap.align_axes_horizontally(axs[0], axs[1], alignment="right")

ap.make_me_nice(margin_pad_pts=(8.0, 5.0, 5.0, 5.0))  # left, right, top, bottom

ap.align_axes_horizontally(axs[0], axs[1], alignment="right")

(Source code, png, hires.png, pdf)

../../../_images/multi_plots_multi_aspects_6.png

Alternatively, one can use a fixed axes size (instead of a fixed figure size). Then, these problems should not arise.

import matplotlib.pyplot as plt
import atompy as ap

plt.rcParams["figure.facecolor"] = "grey"

fh, ah = 3.2, 1.5

fig, axs = plt.subplots(2, 1, figsize=(fh, fh))

ap.set_axes_size(ah, ah, axs[0], anchor="left")
ap.set_axes_size(ah*1.618, ah, axs[1], anchor="left")

axs[0].set_ylim(0.0, 0.1)

ap.make_me_nice(fix_figwidth=False, max_figwidth=fh)

(Source code, png, hires.png, pdf)

../../../_images/multi_plots_multi_aspects_7.png

Using the optional keyword max_figwidth=True, one ensures that the new figure width is not larger than the desired figure width, which, in turn, ensures that the resulting graphic will not reach into the margins of a text (in the example, the text width would be specified by fh).