Figure layout

A-b-c labels

ProPlot can quickly add “a-b-c” labels to subplots. This is possible because ProPlot assigns a unique number to each subplot. The subplot number can be manually controlled by passing a number keyword to add_subplot. Otherwise, the subplot number is incremented by 1 each time you call add_subplot.

If you draw all of your subplots at once with add_subplots, the subplot numbers depend on the input arguments. If you passed an array, the subplot numbers correspond to the numbers in the array. But if you used the ncols and nrows keyword arguments, the number order is row-major by default and can be switched to column-major by passing order='F'. The number order also determines the subplot order in the SubplotGrid returned by add_subplots.

To turn on “a-b-c” labels, set rc.abc to True or pass abc=True to format (see the format command for details). To change the label style, set rc.abc to e.g. 'A.' or pass e.g. abc='A.' to format. You can also modify the “a-b-c” label location, weight, and size with the rc['abc.loc'], rc['abc.weight'], and rc['abc.size'] settings. Also note that if the an “a-b-c” label and title are in the same position, they are automatically offset away from each other.

Note

“Inner” a-b-c labels and titles are surrounded with a white border when rc['abc.border'] and rc['title.border'] are True (the default). White boxes can be used instead by setting rc['abc.bbox'] and rc['title.bbox'] to True. These options help labels stand out against plotted content. Any text can be given “borders” or “boxes” by passing border=True or bbox=True to proplot.axes.Axes.text.

[1]:
import proplot as pplt
fig = pplt.figure(space=0, refwidth='10em')
axs = fig.subplots(nrows=3, ncols=3)
axs.format(
    abc='A.', abcloc='ul',
    xticks='null', yticks='null', facecolor='gray5',
    xlabel='x axis', ylabel='y axis',
    suptitle='A-b-c label offsetting, borders, and boxes',
)
axs[:3].format(abcloc='l', titleloc='l', title='Title')
axs[-3:].format(abcbbox=True)  # also disables abcborder
# axs[:-3].format(abcborder=True)  # this is already the default
_images/layout_2_0.svg
[2]:
import proplot as pplt
fig = pplt.figure(space=0, refwidth=0.7)
axs = fig.subplots(nrows=8, ncols=8)
axs.format(
    abc=True, abcloc='ur',
    xlabel='x axis', ylabel='y axis', xticks=[], yticks=[],
    suptitle='A-b-c label stress test'
)
_images/layout_3_0.svg

Automatic size

By default, ProPlot determines the suitable figure size given the geometry of the subplot grid and the size of a “reference” subplot. This “reference” subplot is specified with the Figure keyword refnum (default is 1, i.e. the first subplot added to the figure or the subplot in the upper-left corner when generated with subplots). ProPlot can also determine the suitable figure height given a fixed figure width, and the suitable figure width given a fixed figure height.

The figure size is ultimately controlled by the following Figure keyword arguments:

  • refwidth and refheight set the physical dimensions of the reference subplot (default is rc['subplots.refwidth'] = 2.5). If one is specified, the other is calculated to satisfy the subplot aspect ratio refaspect (default is 1). If both are specified, refaspect is ignored. When these keyword arguments are used, the width and height of the figure are both determined automatically.

  • figwidth and figheight set the physical dimensions of the figure. If one is specified, the other is calculated to satisfy refaspect and the subplot spacing. If both are specified, or if the figsize parameter is specified, the figure size is fixed and refaspect is ignored.

  • journal constrains the physical dimensions of the figure to meet requirements for submission to an academic journal. For example, journal='nat1' results in a width suitable for single-column Nature figures. See this table for the list of available journal specifications (feel free to add to this table by submitting a pull request).

The below examples show how these keyword arguments affect the figure size.

Important

The automatic figure size algorithm has the following notable properties:

  • For very simple subplot grids (e.g., subplots created with the ncols and nrows arguments), the arguments refaspect, refwidth, and refheight effectively apply to every subplot in the figure – not just the reference subplot.

  • When the reference subplot aspect ratio has been fixed (e.g., using ax.set_aspect(1)) or is set to 'equal' (as with geographic projections and imshow images), the fixed aspect ratio is used and the subplots refaspect parameter is ignored. This is critical for getting the figure size right when working with grids of images and geographic projections.

  • The physical widths of colorbars and panels are always preserved during figure resizing. ProPlot specifies their widths in physical units to help avoid colorbars and panels that look “too skinny” or “too fat”.

[3]:
import proplot as pplt
import numpy as np

# Grid of images (note the square pixels)
state = np.random.RandomState(51423)
colors = np.tile(state.rand(8, 12, 1), (1, 1, 3))
fig, axs = pplt.subplots(ncols=3, nrows=2, refwidth=1.7)
fig.format(suptitle='Auto figure size for grid of images')
for ax in axs:
    ax.imshow(colors)

# Grid of cartopy projections
fig, axs = pplt.subplots(ncols=2, nrows=3, proj='robin')
axs.format(land=True, landcolor='k')
fig.format(suptitle='Auto figure size for grid of cartopy projections')
_images/layout_5_0.svg
/home/docs/checkouts/readthedocs.org/user_builds/proplot/conda/v0.8.0/lib/python3.8/site-packages/cartopy/io/__init__.py:260: DownloadWarning: Downloading: https://naciscdn.org/naturalearth/110m/physical/ne_110m_land.zip
  warnings.warn('Downloading: {}'.format(url), DownloadWarning)
_images/layout_5_2.svg
[4]:
import proplot as pplt
pplt.rc.update(grid=False, titleloc='uc', titleweight='bold', titlecolor='red9')

# Change the reference subplot width
suptitle = 'Effect of subplot width on figure size'
for refwidth in ('3cm', '5cm'):
    fig, axs = pplt.subplots(ncols=2, refwidth=refwidth,)
    axs[0].format(title=f'refwidth = {refwidth}', suptitle=suptitle)

# Change the reference subplot aspect ratio
suptitle = 'Effect of subplot aspect ratio on figure size'
for refaspect in (1, 2):
    fig, axs = pplt.subplots(ncols=2, refwidth=1.6, refaspect=refaspect)
    axs[0].format(title=f'refaspect = {refaspect}', suptitle=suptitle)

# Change the reference subplot
suptitle = 'Effect of reference subplot on figure size'
for ref in (1, 2):  # with different width ratios
    fig, axs = pplt.subplots(ncols=3, wratios=(3, 2, 2), ref=ref, refwidth=1.1)
    axs[ref - 1].format(title='reference', suptitle=suptitle)
for ref in (1, 2):  # with complex subplot grid
    fig, axs = pplt.subplots([[1, 2], [1, 3]], refnum=ref, refwidth=1.8)
    axs[ref - 1].format(title='reference', suptitle=suptitle)

pplt.rc.reset()
_images/layout_6_0.svg
_images/layout_6_1.svg
_images/layout_6_2.svg
_images/layout_6_3.svg
_images/layout_6_4.svg
_images/layout_6_5.svg
_images/layout_6_6.svg
_images/layout_6_7.svg

Automatic spacing

In addition the figure size, by default, ProPlot automatically determines the suitable space between subplots using a tight layout algorithm. This algorithm automatically expands or contracts the space between subplots to accommodate labels. It can be disabled by passing tight=False to subplots or setting rc['subplots.tight'] to False. While matplotlib has its own tight layout algorithm, ProPlot’s algorithm may change the figure size to accommodate the correct spacing and permits variable spacing between subsequent subplot rows and columns (see proplot.gridspec.GridSpec for details).

The tight layout algorithm can also be completely or partly overridden. When you pass any of the spacing arguments left, right, top, bottom, wspace, or hspace to figure, subplots, or GridSpec, that value is always respected. For example:

  • left=2 fixes the left margin at 2 em-widths, while the right, bottom, and top margin widths are determined automatically.

  • wspace=1 fixes the spaces between subplot columns at 1 em-width, while the spaces between subplot rows are determined automatically.

  • wspace=(3, None) fixes the space between the first two columns of a three-column plot at 3 em-widths, while the space between the second two columns is determined automatically.

Alternatively, the padding used by the tight layout algorithm can be changed by passing outerpad, innerpad, panelpad, wpad, or hpad to figure or subplots. All spacing parameters can be specified with a unit string interpreted by units, but the default unit is an “em-width” (i.e. a rc['font.size'] width – see the units table for details).

[5]:
import proplot as pplt

# Stress test of the tight layout algorithm
# Add large labels along the edge of one subplot
fig, axs = pplt.subplots(nrows=3, ncols=3, refwidth=1.1, share=False)
axs[1].format(
    xlabel='xlabel\nxlabel',
    ylabel='ylabel\nylabel\nylabel\nylabel'
)
axs.format(
    grid=False,
    toplabels=('Column 1', 'Column 2', 'Column 3'),
    leftlabels=('Row 1', 'Row 2', 'Row 3'),
    suptitle='Tight layout with variable row-column spacing',
)
_images/layout_8_0.svg
[6]:
import proplot as pplt

# Stress test of the tight layout algorithm
# This time override the algorithm between selected subplot rows/columns
fig, axs = pplt.subplots(
    ncols=4, nrows=3, refwidth=1.1, span=False,
    bottom='5em', right='5em',  # margin spacing overrides
    wspace=(0, 0, None), hspace=(0, None),  # column and row spacing overrides
)
axs.format(
    grid=False,
    xlocator=1, ylocator=1, tickdir='inout',
    xlim=(-1.5, 1.5), ylim=(-1.5, 1.5),
    suptitle='Tight layout with user overrides',
    toplabels=('Column 1', 'Column 2', 'Column 3', 'Column 4'),
    leftlabels=('Row 1', 'Row 2', 'Row 3'),
)
axs[0, :].format(xtickloc='top')
axs[2, :].format(xtickloc='both')
axs[:, 1].format(ytickloc='neither')
axs[:, 2].format(ytickloc='right')
axs[:, 3].format(ytickloc='both')
axs[-1, :].format(xlabel='xlabel', title='Title\nTitle\nTitle')
axs[:, 0].format(ylabel='ylabel')
_images/layout_9_0.svg

Axis sharing

Redundant labels are a common issue for figures with lots of subplots. To address this, matplotlib.pyplot.subplots includes the sharex and sharey keyword arguments that permit sharing axis limits, ticks, and tick labels between like rows and columns of subplots.

ProPlot builds on this feature by…

  1. Automatically sharing axes between subplot and panel axes spanning the same rows or columns of the GridSpec. This works for aribtrarily complex subplot grids. It also works if subplots were generated one-by-one with add_subplot rather than subplots.

  2. Adding four axis-sharing “levels”, controlled by the sharex and sharey keywords (default is rc['subplots.share'] = True). You can use the share keyword as a shorthand to set both sharex and sharey. The axis-sharing “levels” are defined as follows:

    • share=False or share=0 disables axis sharing.

    • share='labels', share='labs', or share=1 automatically shares the axis labels, but nothing else.

    • share='limits', share='lims', or share=2 is the same as 1, but also shares the axis limits, axis scales, and tick locations.

    • share=True or share=3 is the same as 2, but also shares the axis tick labels.

  3. Adding an option to automatically share labels between axes spanning the same row or column of the subplot grid, controlled by the spanx and spany keywords (default is rc['subplots.span'] = True). Use the span keyword as a shorthand to set both spanx and spany.

The below examples demonstrate the effect of various axis and label sharing settings on the appearance of several subplot grids.

[7]:
import proplot as pplt
import numpy as np
N = 50
M = 40
state = np.random.RandomState(51423)
cycle = pplt.Cycle('grays_r', M, left=0.1, right=0.8)
datas = []
for scale in (1, 3, 7, 0.2):
    data = scale * (state.rand(N, M) - 0.5).cumsum(axis=0)[N // 2:, :]
    datas.append(data)

# Same plot with different sharing and spanning settings
for i, share in enumerate((False, 'labels', 'limits', True)):
    fig = pplt.figure(refaspect=1, refwidth=1.06, sharey=share, spanx=i // 2)
    axs = fig.subplots(ncols=4)
    for ax, data in zip(axs, datas):
        on = ('off', 'on')[i // 2]
        ax.plot(data, cycle=cycle)
        ax.format(
            suptitle=f'Sharing mode {share!r} (level {i}) with spanning labels {on}',
            grid=False, xlabel='spanning axis', ylabel='shared axis'
        )
_images/layout_11_0.svg
_images/layout_11_1.svg
_images/layout_11_2.svg
_images/layout_11_3.svg
[8]:
import proplot as pplt
import numpy as np
pplt.rc.reset()
pplt.rc.cycle = 'Set3'
state = np.random.RandomState(51423)

# Same plot with and without default sharing settings
titles = ('With redundant labels', 'Without redundant labels')
for b in (False, True):
    fig = pplt.figure(refwidth=1, share=b, span=b)
    axs = fig.subplots(nrows=4, ncols=4)
    for ax in axs:
        ax.plot((state.rand(100, 20) - 0.4).cumsum(axis=0))
    axs.format(
        abc=True, abcloc='ul', suptitle=titles[b],
        xlabel='xlabel', ylabel='ylabel',
        grid=False, xticks=25, yticks=5
    )
_images/layout_12_0.svg
_images/layout_12_1.svg

Physical units

ProPlot supports arbitrary physical units for controlling the figure figwidth and figheight, the reference subplot refwidth and refheight, the gridspec spacing and tight layout padding values left, right, bottom, top, wspace, hspace, outerpad, innerpad, panelpad, wpad, and hpad, the panel_axes and colorbar widths, and all applicable rc settings (e.g., settings controlling legend spacing, label padding, and font size). This feature is powered by the units function.

A table of acceptable physical units is found here. They include centimeters, millimeters, pixels, em-heights, en-heights, and points. The default physical unit (assumed when an argument is numeric) depends on the context. For legend and gridspec spaces, it is em-widths. For subplot and figure sizes, it is inches. For text padding and font sizes, it is points. See the relevant documentation in the API reference for details.

[9]:
import proplot as pplt
import numpy as np
with pplt.rc.context(fontsize='12px'):
    fig, axs = pplt.subplots(
        ncols=3, figwidth='15cm', figheight='3in',
        wspace=('10pt', '20pt'), right='10mm',
    )
    cmap = pplt.Colormap('Mono')
    cb = fig.colorbar(
        cmap, loc='b', extend='both', label='colorbar',
        width='2em', extendsize='3em', shrink=0.8,
    )
    pax = axs[2].panel_axes('r', width='5en')
axs.format(
    suptitle='Arguments with arbitrary units',
    xlabel='x axis', ylabel='y axis',
    xlim=(0, 1), ylim=(0, 1),
)
_images/layout_14_0.svg