Subplots

This section documents a variety of features related to proplot subplots, including a-b-c subplot labels, axis sharing between subplots, automatic “tight layout” spacing between subplots, and a unique feature where the figure width and/or height are automatically adjusted based on the subplot geometry.

Note

Proplot only supports one GridSpec per figure (see the section on adding subplots), and proplot does not officially support the “nested” matplotlib structures GridSpecFromSubplotSpec and SubFigure. These restrictions have the advantage of 1) considerably simplifying the tight layout and figure size algorithms and 2) reducing the ambiguity of a-b-c label assignment and automatic axis sharing between subplots. If you need the features associated with “nested” matplotlib structures, some are reproducible with proplot – including different spaces between distinct subplot rows and columns and different formatting for distinct groups of subplots.

A-b-c labels

Proplot can quickly add “a-b-c” labels using the number assigned to each subplot. If you add subplots one-by-one with add_subplot, you can manually specify the number with the number keyword. By default, 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 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' (note the number order also determines the list 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/subplots_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/subplots_3_0.svg

Figure width and height

Proplot automatically adjusts the figure width and height by default to respect the physical size of a “reference” subplot and the geometry of the gridspec. The “reference” subplot is the subplot whose number matches the refnum that was passed to Figure (the default refnum of 1 usually matches the subplot in the upper-left corner – see this section for more on subplot numbers). Alternatively, you can request a fixed figure width (height), and the algorithm will automatically adjusts the figure height (width) to respect the gridspec geometry.

This algorithm is extremely powerful and generally produces more aesthetically pleasing subplot grids out-of-the-box, especially when they contain images or map projections (see below). It is constrained by the following Figure keyword arguments:

  • refwidth and refheight set the physical width and height of the reference subplot (default is rc['subplots.refwidth'] = 2.5). If just the width (height) is specified, then the height (width) is automatically adjusted to satisfy the subplot spacing and the reference subplot aspect ratio refaspect (default is 1 unless the data aspect ratio is fixed – see below). If both the width and height are specified, then refaspect is ignored.

  • figwidth and figheight set the physical width and height of the figure. As in matplotlib, you can use figsize to set both at once. If just the width (height) is specified, then the height (width) is automatically adjusted, just like with refwidth and refheight. If both the width and height are specified (e.g., using figsize), then refaspect is ignored and the figure size is fixed. Note that figwidth and figheight always override refwidth and refheight.

  • journal sets the physical dimensions of the figure to meet requirements for submission to an academic journal. For example, journal='nat1' sets figwidth according to the *Nature* standard for single-column figures and journal='aaas2' sets figwidth according to the *Science* standard for dual-column figures. See this table for the currently available journal specifications (feel free to add to this list with a pull request).

The below examples demonstrate how different keyword arguments and subplot arrangements influence the figure size algorithm.

Important

  • If the data aspect ratio of the reference subplot is fixed (either due to calling set_aspect or filling the subplot with a geographic projection, imshow plot, or heatmap plot), then this is used as the default value for the reference aspect ratio refaspect. This helps minimize excess space between grids of subplots with fixed aspect ratios.

  • For the simplest subplot grids (e.g., those created by passing integers to add_subplot or passing ncols or nrows to add_subplots) the keyword arguments refaspect, refwidth, and refheight effectively apply to every subplot in the figure – not just the reference subplot.

  • The physical widths of proplot colorbars and panels are always independent of the figure size. GridSpec specifies their widths in physical units to help users avoid drawing colorbars and panels that look “too skinny” or “too fat” depending on the number of subplots in the figure.

[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 dimensions 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 dimensions for grid of cartopy projections')
_images/subplots_5_0.svg
_images/subplots_5_1.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/subplots_6_0.svg
_images/subplots_6_1.svg
_images/subplots_6_2.svg
_images/subplots_6_3.svg
_images/subplots_6_4.svg
_images/subplots_6_5.svg
_images/subplots_6_6.svg
_images/subplots_6_7.svg

Spacing and tight layout

Proplot automatically adjusts the spacing between subplots by default to accomadate labels using its own “tight layout” algorithm. In contrast to matplotlib’s algorithm, proplot’s algorithm can change the figure size and permits variable spacing between each subplot row and column (see proplot.gridspec.GridSpec for details). This algorithm can be disabled entirely by passing tight=False to Figure or by setting rc['subplots.tight'] to False, or it can be partly overridden by passing any of the spacing arguments left, right, top, bottom, wspace, or hspace to Figure or GridSpec. For example:

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

  • wspace=1 fixes the space between subplot columns at 1 em-width, while the space between subplot rows is determined by the tight layout algorithm.

  • 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 by the tight layout algorithm.

The padding between the tight layout extents (rather than the absolute spaces between subplot edges) can also be changed by passing outerpad, innerpad, or panelpad to Figure or GridSpec. This padding can be set locally by passing an array of values to wpad and hpad (analogous to wspace and hspace), or by passing the pad keyword when creating panel axes or outer colorbars or legends (analogous to space).

All the subplot spacing arguments can be specified with a unit string interpreted by units. The default unit assumed for numeric arguments is an “em-width” (i.e., a rc['font.size'] width – see the units table for details).

Note

The core behavior of the tight layout algorithm can be modified with a few keyword arguments and settings. Using wequal=True, hequal=True, or equal=True (or setting rc['subplots.equalspace'] to True) constrains the tight layout algorithm to produce equal spacing between main subplot columns or rows (note that equal spacing is the default behavior when tight layout is disabled). Similarly, using wgroup=False, hgroup=False, or group=False (or setting rc['subplots.groupspace'] to False) disables the default behavior of only comparing subplot extent between adjacent subplot “groups” and instead compares subplot extents across entire columns and rows (note the spacing between the first and second row in the below example).

[5]:
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/subplots_8_0.svg
[6]:
import proplot as pplt

# Stress test of the tight layout algorithm
# Add large labels along the edge of one subplot
equals = [('unequal', False), ('unequal', False), ('equal', True)]
groups = [('grouped', True), ('ungrouped', False), ('grouped', True)]
for (name1, equal), (name2, group) in zip(equals, groups):
    suffix = ' (default)' if group and not equal else ''
    suptitle = f'Tight layout with "{name1}" and "{name2}" row-column spacing{suffix}'
    fig, axs = pplt.subplots(
        nrows=3, ncols=3, refwidth=1.1, share=False, equal=equal, group=group,
    )
    axs[1].format(
        xlabel='xlabel\nxlabel',
        ylabel='ylabel\nylabel\nylabel\nylabel'
    )
    axs[3:6:2].format(
        title='Title\nTitle',
        titlesize='med',
    )
    axs.format(
        grid=False,
        toplabels=('Column 1', 'Column 2', 'Column 3'),
        leftlabels=('Row 1', 'Row 2', 'Row 3'),
        suptitle=suptitle,
    )
_images/subplots_9_0.svg
_images/subplots_9_1.svg
_images/subplots_9_2.svg

Axis label sharing

Figures with lots of subplots often have redundant labels. To help address this, the matplotlib command matplotlib.pyplot.subplots includes sharex and sharey keywords that permit sharing axis limits and ticks between like rows and columns of subplots. Proplot builds on this feature by:

  1. Automatically sharing axes between subplots and panels occupying the same rows or columns of the GridSpec. This works for aribtrarily complex subplot grids. It also works for subplots generated one-by-one with add_subplot rather than subplots. It is controlled by the sharex and sharey Figure keywords (default is rc['subplots.share'] = True). Use the share keyword as a shorthand to set both sharex and sharey.

  2. Automatically sharing labels across subplots and panels with edges along the same row or column of the GridSpec. This also works for complex subplot grids and subplots generated one-by-one. It is controlled by the spanx and spany Figure keywords (default is rc['subplots.span'] = True). Use the span keyword as a shorthand to set both spanx and spany. Note that unlike supxlabel and supylabel, these labels are aligned between gridspec edges rather than figure edges.

  3. Supporting five sharing “levels”. These values can be passed to sharex, sharey, or share, or assigned to rc['subplots.share']. The levels are defined as follows:

    • False or 0: Axis sharing is disabled.

    • 'labels', 'labs', or 1: Axis labels are shared, but nothing else. Labels will appear on the leftmost and bottommost subplots.

    • 'limits', 'lims', or 2: Same as 1, but axis limits, axis scales, and major and minor tick locations and formatting are also shared.

    • True or 3 (default): Same as 2, but axis tick labels are also shared. Tick labels will appear on the leftmost and bottommost subplots.

    • 'all' or 4: Same as 3, but axis limits, axis scales, and axis ticks are shared even between subplots not in the same row or column.

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)

# Plots with different sharing and spanning settings
# Note that span=True and share=True are the defaults
spans = (False, False, True, True)
shares = (False, 'labels', 'limits', True)
for i, (span, share) in enumerate(zip(spans, shares)):
    fig = pplt.figure(refaspect=1, refwidth=1.06, spanx=span, sharey=share)
    axs = fig.subplots(ncols=4)
    for ax, data in zip(axs, datas):
        on = ('off', 'on')[int(span)]
        ax.plot(data, cycle=cycle)
        ax.format(
            grid=False, xlabel='spanning axis', ylabel='shared axis',
            suptitle=f'Sharing mode {share!r} (level {i}) with spanning labels {on}'
        )
_images/subplots_11_0.svg
_images/subplots_11_1.svg
_images/subplots_11_2.svg
_images/subplots_11_3.svg
[8]:
import proplot as pplt
import numpy as np
state = np.random.RandomState(51423)

# Plots with minimum and maximum sharing settings
# Note that all x and y axis limits and ticks are identical
spans = (False, True)
shares = (False, 'all')
titles = ('Minimum sharing', 'Maximum sharing')
for span, share, title in zip(spans, shares, titles):
    fig = pplt.figure(refwidth=1, span=span, share=share)
    axs = fig.subplots(nrows=4, ncols=4)
    for ax in axs:
        data = (state.rand(100, 20) - 0.4).cumsum(axis=0)
        ax.plot(data, cycle='Set3')
    axs.format(
        abc=True, abcloc='ul', suptitle=title,
        xlabel='xlabel', ylabel='ylabel',
        grid=False, xticks=25, yticks=5
    )
_images/subplots_12_0.svg
_images/subplots_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 keywords left, right, bottom, top, wspace, hspace, outerpad, innerpad, panelpad, wpad, and hpad; the colorbar and panel widths; various legend spacing and padding arguments; various format font size and padding arguments; the line width and marker size arguments passed to PlotAxes commands; and all applicable rc settings, e.g. rc['subplots.refwidth'], rc['legend.columnspacing'], and rc['axes.labelpad']. This feature is powered by the physical units engine units.

When one of these keyword arguments is numeric, a default physical unit is used. For subplot and figure sizes, the defult unit is inches. For gridspec and legend spaces, the default unit is em-widths. For font sizes, text padding, and line widths, the default unit is points. See the relevant documentation in the API reference for details. A table of acceptable physical units is found here – they include centimeters, millimeters, pixels, em-widths, en-heights, and points.

[9]:
import proplot as pplt
import numpy as np
with pplt.rc.context(fontsize='12px'):  # depends on rc['figure.dpi']
    fig, axs = pplt.subplots(
        ncols=3, figwidth='15cm', figheight='3in',
        wspace=('10pt', '20pt'), right='10mm',
    )
    cb = fig.colorbar(
        'Mono', 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',
)
_images/subplots_14_0.svg