69

I'm trying to adjust a suptitle above a multi-panel figure and am having trouble figuring out how to adjust the figsize and subsequently position the suptitle.

The problem is that calling plt.suptitle("my title", y=...) to adjust the position of the suptitle also adjusts the figure dimensions. A few questions:

  1. where does suptitle(..., y=1.1) actually put the title? As far as I can tell, the documentation for the y parameter of suptitle points to matplotlib.text.Text, but I don't know what figure coordinates mean when you have multiple subplots.

  2. what is the effect on figure size when specifying y to suptitle?

  3. how do I manually adjust figure size and spacing (subplots_adjust?) to add a figure title per panel and a suptitle for the entire figure, maintaining the size of each ax in the figure?

An example:

data = np.random.random(size=100)
f, a = plt.subplots(2, 2, figsize=(10, 5))

a[0,0].plot(data)
a[0,0].set_title("this is a really long title\n"*2)
a[0,1].plot(data)
a[1,1].plot(data)

plt.suptitle("a big long suptitle that runs into the title\n"*2, y=1.05);

enter image description here

Obviously I can tweak y each time I make a figure, but I need a solution that generally works without manual intervention. I've tried both constrained layout and tight layout; neither works reliably with figures of any complexity.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Noah
  • 21,451
  • 8
  • 63
  • 71
  • " I've tried both constrained layout and tight layout; neither works reliably with figures of any complexity." tight layout doesn't do `suptitle` at all. constrained_layout should, so if you actually have a case where it doesn't work, please open an issue at GitHub with a minimal example that shows the problem. – Jody Klymak Apr 21 '19 at 15:36
  • 1
    See [Matplotlib tight_layout() doesn't take into account figure suptitle](https://stackoverflow.com/questions/8248467/matplotlib-tight-layout-doesnt-take-into-account-figure-suptitle) `fig.tight_layout(rect=[0, 0.03, 1, 0.95])` – Avi Vajpeyi Jun 22 '20 at 10:02

3 Answers3

99

1. What do figure coordinates mean?

Figure coordinates go 0 to 1, where (0,0) is the lower left corner and (1,1) is the upper right corner. A coordinate of y=1.05 is hence slightly outside the figure.

enter image description here

2. what is the effect on figure size when specifying y to suptitle?

Specifying y to suptitle has no effect whatsoever on the figure size.

3a. How do I manually adjust figure size and spacing to add a figure title per panel and a suptitle for the entire figure?

First, one would not add an additional linebreak. I.e. if you want to have 2 lines, don't use 3 linebreaks (\n). Then one can adjust the subplot parameters as desired to leave space for the titles. E.g. fig.subplots_adjust(top=0.8) and use a y <= 1 for the title to be inside the figure.

import matplotlib.pyplot as plt
import numpy as np

data = np.random.random(size=100)
fig, axes = plt.subplots(2, 2, figsize=(10, 5))
fig.subplots_adjust(top=0.8)

axes[0,0].plot(data)
axes[0,0].set_title("\n".join(["this is a really long title"]*2))
axes[0,1].plot(data)
axes[1,1].plot(data)

fig.suptitle("\n".join(["a big long suptitle that runs into the title"]*2), y=0.98)

plt.show()

enter image description here

3b. ... while maintaining the size of each ax in the figure?

Maintaining the size of the axes and still have enough space for the titles is only possible by changing the overall figure size.

This could look as follows, where we define a function make_space_above which takes the array of axes as input, as well as the newly desired top margin in units of inches. So for example, you come to the conclusion that you need 1 inch of margin on top to host your titles:

import matplotlib.pyplot as plt
import numpy as np

data = np.random.random(size=100)
fig, axes = plt.subplots(2, 2, figsize=(10, 5), squeeze = False)

axes[0,0].plot(data)
axes[0,0].set_title("\n".join(["this is a really long title"]*2))
axes[0,1].plot(data)
axes[1,1].plot(data)

fig.suptitle("\n".join(["a big long suptitle that runs into the title"]*2), y=0.98)


def make_space_above(axes, topmargin=1):
    """ increase figure size to make topmargin (in inches) space for 
        titles, without changing the axes sizes"""
    fig = axes.flatten()[0].figure
    s = fig.subplotpars
    w, h = fig.get_size_inches()

    figh = h - (1-s.top)*h  + topmargin
    fig.subplots_adjust(bottom=s.bottom*h/figh, top=1-topmargin/figh)
    fig.set_figheight(figh)


make_space_above(axes, topmargin=1)    

plt.show()

enter image description here

(left: without calling make_space_above; right: with call to make_space_above(axes, topmargin=1))

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • This is an excellent start, thank you! Your answer to 2. seems to be correct for some backends, but not correct when using `%matplotlib inline` in jupyter -- a `suptitle(...,y=1.5)` gets cropped when I `plt.savefig()` but the figure gets adjusted to include that at the top when shown in jupyter. – Noah Apr 20 '19 at 02:22
  • Is there documentation on the figure coordinates, or what the values in `subplots_adjust` actually mean? (The docs just say "the bottom of the subplots of the figure" which I probably could have gathered from the name of the parameter). Or is this an instance of matplotlib documentation growing from stackoverflow? :) – Noah Apr 20 '19 at 02:27
  • 1
    Concerning the shown image in jupyter with inline backend, see [this answer](https://stackoverflow.com/questions/37864735/matplotlib-and-ipython-notebook-displaying-exactly-the-figure-that-will-be-save). The point is that the image you see is cropped or expanded, but the size of the figure stays the same. Concerning figure coordinates, it's really as simple as it sounds. `bottom=0.2` means the subplots start at y=0.2 in figure coordinates, which range from 0 to 1. – ImportanceOfBeingErnest Apr 20 '19 at 11:26
  • Great answer! Is there a way to make your solution 3b work with a tight layout? – Vivek Subramanian May 08 '20 at 19:33
  • @VivekSubramanian "Maintaining the size of the axes.." and using tight layout is contradictory, right? So I don't know what you mean. – ImportanceOfBeingErnest May 09 '20 at 10:26
  • Sorry, I meant, is there a way to make it so the figure is cropped on all four sides (right above the suptitle and right beside the subplot axes) such that there is no additional white space? – Vivek Subramanian May 09 '20 at 16:59
  • @VivekSubramanian This question asks for a way to not have the figure size be cropped. Are we still on the same page here? – ImportanceOfBeingErnest May 10 '20 at 17:23
  • Frankly, considering matplotlib is at version 3.4, this mess for placing a figure title is simply unbelievable. – Antonio Sesto Nov 14 '21 at 17:35
59

Short Answer

For those coming from Google for adjusting the title position on a scatter matrix, you can simply set the y parameter to a value slightly lower than 1:

plt.suptitle('My Title', y=0.92)

Fernando Wittmann
  • 1,991
  • 20
  • 16
17

... or use constrained_layout:

import matplotlib.pyplot as plt
import numpy as np

data = np.random.random(size=100)
f, a = plt.subplots(2, 2, figsize=(10, 5), constrained_layout=True)

a[0,0].plot(data)
a[0,0].set_title("this is a really long title\n"*2)
a[0,1].plot(data)
a[1,1].plot(data)

plt.suptitle("a big long suptitle that runs into the title\n"*2);

enter image description here

Jody Klymak
  • 4,979
  • 2
  • 15
  • 31