Size, aspect & layout¶
The layout of figures and sub-figures is certainly one of the most frustrating aspect of matplotlib and for new user, it is generally difficult to obtain the desired layout without a lot of trials and errors.. But this is not specific to matplotlib, it is actually equally difficult with any software (and even worse for some). To understand why it is difficult, it is necessary to gain a better understanding of the underlying machinery.
Figure and axes aspect¶
When you create a new figure, this figure comes with a specific size, either implicitly using defaults (as explained in the previous chapter) or explicitly through the figsize keyword. If we take the height divided by the width of the figure we obtain the figure aspect ratio. When you create an axes, you can also specify an aspect that will be enforced by matplotlib. And here, we hit the first difficulty. You have a container with a given aspect ratio and you want to put inside an item with a possibly different aspect ratio and matplotlib has to solve these constrains. This is illustrated on figure figure-aspects with different cases:
A: figure aspect is 1, axes aspect is 1, x and y range are equal
fig = plt.figure(figsize=(6,6)) ax = plt.subplot(1,1,1, aspect=1) ax.set_xlim(0,1), ax.set_ylim(0,1)
B: figure aspect is 1/2, axes aspect is 1, x and y range are equal
fig = plt.figure(figsize=(6,3)) ax = plt.subplot(1,1,1, aspect=1) ax.set_xlim(0,1), ax.set_ylim(0,1)
C: figure aspect is 1/2, axes aspect is 1, x and y range are different
fig = plt.figure(figsize=(6,3)) ax = plt.subplot(1,1,1, aspect=1) ax.set_xlim(0,2), ax.set_ylim(0,1)
D: figure aspect is 2, axes aspect is 1, x and y range are equal
fig = plt.figure(figsize=(3,6)) ax = plt.subplot(1,1,1, aspect=1) ax.set_xlim(0,1), ax.set_ylim(0,1)
E: figure aspect is 1, axes aspect is 1, x and y range are different
fig = plt.figure(figsize=(6,6)) ax = plt.subplot(1,1,1, aspect=1) ax.set_xlim(0,2), ax.set_ylim(0,1)
F: figure aspect is 1, axes aspect is 0.5, x and y range are equal
fig = plt.figure(figsize=(6,6)) ax = plt.subplot(1,1,1, aspect=0.5) ax.set_xlim(0,1), ax.set_ylim(0,1)
G: figure aspect is 1/2, axes aspect is 1, x and y range are different
fig = plt.figure(figsize=(3,6)) ax = plt.subplot(1,1,1, aspect=1) ax.set_xlim(0,1), ax.set_ylim(0,2)
H: figure aspect is 1, axes aspect is 1, x and y range are different
fig = plt.figure(figsize=(6,6)) ax = plt.subplot(1,1,1, aspect=1) ax.set_xlim(0,1), ax.set_ylim(0,2)
I: figure aspect is 1, axes aspect is 2, x and y range are equal
fig = plt.figure(figsize=(6,6)) ax = plt.subplot(1,1,1, aspect=2) ax.set_xlim(0,1), ax.set_ylim(0,1)
Combination of figure and axes aspect ratio figure-aspects¶
The final layout of a figure results from a set of constraints that makes it difficult to predict the end result. This is actually even more acute when you combine several axes on the same figure as shown on figures figure-layout-aspect-1, figure-layout-aspect-2 & figure-layout-aspect-3. Depending on what is important in your figure (aspect, range or size), you’ll privilege one of these layout. In any case, you should now have realized that if you over-constrained your layout, it might be unsolvable and matplotlib will try to find the best compromise.
Same size, same range, different aspect (sources layout/layout-aspect.py) figure-layout-aspect-1¶
Same range, same aspect, different size (sources layout/layout-aspect.py) figure-layout-aspect-2¶
Same size, same aspect, different range (sources layout/layout-aspect.py) figure-layout-aspect-3¶
Axes layout¶
Now that we know how figure and axes aspect may interact, it’s time to organize our figure into subfigures. We’ve already seen one example in the previous section, but let’s now look at the details. There exist indeed several different methods to create subfigures and each have their pros and cons. Let’s take a very simple example where we want to have two axes side by side. To do so, we can use add_subplot, add_axes, GridSpec and subplot_mosaic:
fig = plt.figure(figsize=(6,2))
# Using subplots
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
# Using gridspecs
G = GridSpec(1,2)
ax1 = fig.add_subplot(G[0,0])
ax2 = fig.add_subplot(G[0,1])
# Using axes
ax1 = fig.add_axes([0.1, 0.1, 0.35, 0.8])
ax2 = fig.add_axes([0.6, 0.1, 0.35, 0.8])
# Using mosaic
ax1, ax2 = fig.add_mosaic("AB")
As a general advice, I would encourage users to use the gridspec approach because if offers a lot of flexibility compared to the classical approach. For example, figure figure-layout-classical shows a nice and simple 3x3 layout but does not offer much control over the relative aspect of each axes whereas in figure figure-layout-gridspec, we can very easily specify different sizes for each axes.
Subplots using classical layout. (source layout/layout-classical.py) figure-layout-classical¶
Subplots using gridspec layout (source layout/layout-gridspec.py) figure-layout-gridspec¶
The biggest difficulty with gridspec is to get axes right, that is, at the right position with the right size and this depends on the initial partition of the grid spec, taking into account the different height & width ratios. Let’s consider for example figure figure-complex-layout that display a moderately complex layout. The question is: how do we partition it? Do we need a single axis for the small image of individual axes ? Are the text on the left part of axes or do they use their own axes. There are indeed several solutions and figure figure-complex-layout-bare shows the solution I chose to design this figure. There are individual axes for subplots and left label. There is also a small line of axes for titles in order to ensure that subplots have all the same size. If I had used a title on the first row of subplots, this would have modified their relative size compared to others. The legend on the top is using two axes, one axes for the color legend and another for detailed explanation. In this case, I use the central axes and write the text outside the axes, specifying this does not need to be clipped.
Complex layout (source layout/complex-layout.py) figure-complex-layout¶
Complex layout structure (source layout/complex-layout-bare.py) figure-complex-layout-bare¶
Exercices¶
Standard layout 1 Using gridspec, the goal is to reproduce figure figure-standard-layout-1 where the colorbar is the same size as the main axes and its width is one tenth of main axis width.
Image and colorbar (sources layout/standard-layout-1.py) figure-standard-layout-1¶
Standard layout 2 Using gridspec, the goal is to reproduce figure figure-standard-layout-2 with top and right histograms aligned with the main axes.
Scatter plot and histograms (sources layout/standard-layout-2.py) figure-standard-layout-2¶