Why ProPlot?

Matplotlib is an extremely powerful plotting package used by academics, engineers, and data scientists far and wide. However, the default matplotlib API can be cumbersome or repetitive for users who…

  • …make very complex figures with multiple subplots.

  • …want to finely tune their figure annotations and aesthetics.

  • …need to make new figures nearly every day.

ProPlot’s core mission is to provide a smoother plotting experience for matplotlib’s heaviest users. We accomplish this by expanding upon the object-oriented matplotlib API. ProPlot makes changes that would be hard to justify or difficult to incorporate into matplotlib itself, owing to differing design choices and backwards compatibility considerations.

This page enumerates these changes and explains how they address the limitations of the matplotlib API.

Less typing, more plotting

Problem

Matplotlib users often need to change lots of plot settings all at once. With the default API, this requires calling a series of one-liner setter methods.

This workflow is quite verbose – it tends to require “boilerplate code” that gets copied and pasted a hundred times. It can also be confusing – it is often unclear whether properties are applied from an Axes setter (e.g. set_xlabel and set_xticks), an XAxis or YAxis setter (e.g. set_major_locator and set_major_formatter), a Spine setter (e.g. set_bounds), or a “bulk” property setter (e.g. tick_params), or whether one must dig into the figure architecture and apply settings to several different objects. While this is in the spirit of object-oriented design, it seems like there should be a more unified, straightforward way to change settings for day-to-day matplotlib usage.

Solution

ProPlot introduces the format method for changing arbitrary settings all at once. Think of this as an expanded and thoroughly documented version of the matplotlib.artist.Artist.update method. It can also be used to update so-called quick settings and various other rc settings for a particular subplot, and to concisely work with verbose classes using the constructor functions. Further, the subplots container class can be used to invoke format on several subplots at once.

Together, these features significantly reduce the amount of code needed to create highly customized figures. As an example, it is trivial to see that

import proplot as plot
fig, axs = plot.subplots(ncols=2)
axs.format(linewidth=1, color='gray')
axs.format(xlim=(0, 100), xticks=10, xtickminor=True, xlabel='x axis', ylabel='y axis')

…is much more succinct than

import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib as mpl
with mpl.rc_context(rc={'axes.linewidth': 1, 'axes.color': 'gray'}):
    fig, axs = plt.subplots(ncols=2, sharey=True)
    axs[0].set_ylabel('y axis', color='gray')
    for ax in axs:
        ax.set_xlim(0, 100)
        ax.xaxis.set_major_locator(mticker.MultipleLocator(10))
        ax.tick_params(width=1, color='gray', labelcolor='gray')
        ax.tick_params(axis='x', which='minor', bottom=True)
        ax.set_xlabel('x axis', color='gray')

Class constructor functions

Problem

Matplotlib and cartopy introduce a bunch of classes with verbose names like MultipleLocator, FormatStrFormatter, and LambertAzimuthalEqualArea. Since plotting code has a half life of about 30 seconds, typing out these extra class names and import statements can be a major drag.

Parts of the matplotlib API were actually designed with this in mind. Backend classes, native axes projections, axis scales, box styles, arrow styles, and arc styles are referenced with “registered” string names, as are basemap projection types. So, why not “register” everything else?

Solution

In ProPlot, tick locators, tick formatters, axis scales, cartopy projections, colormaps, and property cyclers are all “registered”. ProPlot does this by introducing several constructor functions and passing various keyword argument through the constructor functions. This may seem “unpythonic” but it is absolutely invaluable when writing plotting code.

The constructor functions also accept other input types for your convenience. For example, scalar numbers passed to Locator returns a MultipleLocator instance, lists of strings passed to Formatter returns a FixedFormatter instance, and Colormap and Cycle accept colormap names, individual colors, and lists of colors. Passing the relevant class instance to a constructor function simply returns the instance.

See the user guide sections on Cartesian axis settings, colormaps, and color cycles for details. The below table lists the constructor functions and the keyword arguments that use them.

Function

Return type

Used by

Keyword argument(s)

Locator

Locator

format and colorbar

locator=, xlocator=, ylocator=, minorlocator=, xminorlocator=, yminorlocator=, ticks=, xticks=, yticks=, minorticks=, xminorticks=, yminorticks=

Formatter

Formatter

format and colorbar

formatter=, xformatter=, yformatter=, ticklabels=, xticklabels=, yticklabels=

Scale

ScaleBase

format

xscale=, yscale=

Cycle

Cycler

1D plotting methods

cycle=

Colormap

Colormap

2D plotting methods

cmap=

Norm

Normalize

2D plotting methods

norm=

Proj

Projection or Basemap

subplots

proj=

Note that set_xscale and set_yscale now accept instances of ScaleBase thanks to a monkey patch applied by ProPlot.

Automatic dimensions and spacing

Problem

Matplotlib plots tend to require lots of “tweaking” when you have more than one subplot in the figure. This is partly because you must specify the physical dimensions of the figure, despite the fact that…

  1. …the subplot aspect ratio is generally more relevant than the figure aspect ratio. An aspect ratio of 1 is desirable for most plots, and the aspect ratio must be held fixed for geographic and polar projections and most imshow plots.

  2. …the physical width and height of the subplot controls the “evident” thickness of text, lines, and other content plotted inside the subplot. The effect of the figure size on this “evident” thickness depends on the number of subplot tiles in the figure.

Also, while matplotlib has a tight layout algorithm to keep you from having to “tweak” the spacing, the algorithm cannot apply different amounts of spacing between different subplot row and column boundaries.

Solution

In ProPlot, you can specify the physical dimensions of a reference subplot instead of the figure by passing axwidth, axheight, and/or aspect to Figure. The default behavior is aspect=1 and axwidth=2 (inches). If the aspect ratio mode for the reference subplot is set to 'equal', as with geographic and polar plots and imshow plots, the imposed aspect ratio will be used instead. The width or height of the figure can also be constrained with the width and height parameters, and the journal parameter lets you create figures with suitable size for various publications.

ProPlot also uses its own “tight layout” algorithm to automatically determine the left, right, bottom, top, wspace, and hspace GridSpec parameters. This algorithm has the following advantages:

  • Spacing between rows and columns is variable thanks to the new GridSpec class. This is critical for putting colorbars and legends outside of subplots without “stealing space” from the parent subplot.

  • The “tight layout” is calculated quickly and simply because figures are restricted to have only one GridSpec per figure. This is done by requiring users to draw all of their subplots at once with subplots (although in a future version, there will be a proplot.figure function that allows users to add subplots one-by-one while retaining the single-gridspec restriction).

See the user guide for details.

Eliminating redundancies

Problem

For many academics, figures with just one subplot are a rarity. We tend to need multiple subplots for comparing different datasets and illustrating complex concepts. Unfortunately, it is easy to end up with redundant figure elements when drawing multiple subplots, namely…

  • …repeated axis tick labels.

  • …repeated axis labels.

  • …repeated colorbars.

  • …repeated legends.

These sorts of redundancies are very common even in publications, where they waste valuable page space. They arise because this is often the path of least resistance – removing redundancies tends to require extra work.

Solution

ProPlot seeks to eliminate redundant elements to help you make clear, concise figures. We tackle this issue using shared and spanning axis labels and figure-spanning colorbars and legends.

  • Axis tick labels and axis labels are shared between subplots in the same row or column by default. This is controlled by the sharex, sharey, spanx, and spany subplots keyword args.

  • The new Figure colorbar and legend methods make it easy to draw colorbars and legends intended to reference more than one subplot. For details, see the next section.

Outer colorbars and legends

Problem

In matplotlib, it can be difficult to draw legends along the outside of subplots. Generally, you need to position the legend manually and adjust various GridSpec spacing properties to make room for the legend. And while colorbars can be drawn along the outside of subplots with fig.colorbar(..., ax=ax), this can cause asymmetry in plots with more than one subplot, since the space allocated for the colorbar is “stolen” from the parent axes.

Solution

ProPlot introduces a new framework for drawing colorbars and legends referencing individual subplots and multiple contiguous subplots.

Since GridSpec permits variable spacing between subplot rows and columns, “outer” colorbars and legends do not mess up subplot spacing or add extra whitespace. This is critical e.g. if you have a colorbar between columns 1 and 2 but nothing between columns 2 and 3. Also, Figure and Axes colorbar widths are now specified in physical units rather than relative units, which makes colorbar thickness independent of subplot size and easier to get just right.

There are also several new colorbar and legend features described in the user guide.

Enhanced plotting methods

Problem

Certain common plotting tasks take a lot of work when using the default matplotlib API. The seaborn, xarray, and pandas packages offer improvements, but it would be nice to have this functionality build right into matplotlib.

Solution

ProPlot adds various seaborn, xarray, and pandas features to the Axes plotting methods along with several brand new features designed to make your life easier.

Xarray and pandas integration

Problem

When you pass the array-like xarray.DataArray, pandas.DataFrame, and pandas.Series containers to matplotlib plotting commands, the metadata is ignored. To create plots that are automatically labeled with this metadata, you must use the dedicated xarray.DataArray.plot, pandas.DataFrame.plot, and pandas.Series.plot tools instead.

This approach is fine for quick plots, but not ideal for complex ones. It requires learning a different syntax from matplotlib, and tends to encourage using the pyplot API rather than the object-oriented API. These tools also include features that would be useful additions to matplotlib in their own right, without requiring special data containers and an entirely separate API.

Solution

ProPlot reproduces many of the xarray.DataArray.plot, pandas.DataFrame.plot, and pandas.Series.plot features on the Axes plotting methods themselves. Passing a DataArray, DataFrame, or Series through any plotting method automatically updates the axis tick labels, axis labels, subplot titles, and colorbar and legend labels from the metadata. This can be disabled by passing autoformat=False to the plotting method or to subplots.

Also, as described in the section on plotting methods, ProPlot implements certain features like grouped bar plots, layered area plots, heatmap plots, and on-the-fly colorbars and legends from the xarray and pandas APIs directly on the Axes class.

Cartopy and basemap integration

Problem

There are two widely-used engines for plotting geophysical data with matplotlib: cartopy and basemap. Using cartopy tends to be verbose and involve boilerplate code, while using basemap requires you to use plotting commands on a separate Basemap object rather than an axes object. They both require separate import statements and extra lines of code to configure the projection.

Furthermore, when you use cartopy and basemap plotting commands, the assumed coordinate system is map projection coordinates rather than longitude-latitude coordinates. This choice is confusing for many users, since the vast majority of geophysical data are stored in longitude-latitude or “Plate Carrée” coordinates.

Solution

ProPlot lets you specify geographic projections by simply passing the PROJ name to subplots with e.g. fig, ax = plot.subplots(proj='pcarree'). Alternatively, the Proj constructor function can be used to quickly generate cartopy.crs.Projection and Basemap instances.

ProPlot also gives you access to various cartopy and basemap features via the proplot.axes.GeoAxes.format method. This lets you quickly modify geographic plot settings like latitude and longitude gridlines, gridline labels, continents, coastlines, and political boundaries.

Finally, GeoAxes makes longitude-latitude coordinates the “default” coordinate system by passing transform=ccrs.PlateCarree() to CartopyAxes plotting methods and latlon=True to BasemapAxes plotting methods. And to enforce global coverage over the poles and across longitude seams, you can pass globe=True to any 2D plotting command, e.g. pcolormesh and contourf.

See the user guide for details.

Colormaps and property cycles

Problem

In matplotlib, colormaps are implemented with the ListedColormap and LinearSegmentedColormap classes. They are generally cumbersome to edit or create from scratch. The seaborn package introduces “color palettes” to make this easier, but it would be nice to have similar features built right into the matplotlib API.

Solution

In ProPlot, it is easy to manipulate colormaps and property cycles:

  • The Colormap constructor function can be used to slice and merge existing colormaps and/or generate brand new ones.

  • The Cycle constructor function can be used to make property cycles from colormaps! Property cycles can be applied to plots in a variety of ways – see the user guide for details.

  • The new ListedColormap and LinearSegmentedColormap classes include several convenient methods and have a much nicer REPL string representation.

  • The PerceptuallyUniformColormap class is used to make perceptually uniform colormaps. These have smooth, aesthetically pleasing color transitions that represent your data accurately.

Importing ProPlot also makes all colormap names case-insensitive, and colormaps can be reversed or cyclically shifted by 180 degrees simply by appending '_r' or '_s' to the colormap name. This is powered by the ColormapDatabase dictionary, which replaces matplotlib’s native database.

The subplot container class

Problem

In matplotlib, subplots returns a 2D ndarray for figures with more than one column and row, a 1D ndarray for single-row or single-column figures, or just an Axes instance for single-subplot figures.

Solution

In ProPlot, subplots returns a SubplotsContainer filled with Axes instances. This container lets you call arbitrary methods on arbitrary subplots all at once, which can be useful when you want to style your subplots identically (e.g. axs.format(tickminor=False)). The SubplotsContainer class also unifies the behavior of the three possible matplotlib.pyplot.subplots return values:

  • SubplotsContainer permits 2D indexing, e.g. axs[1, 0]. Since subplots can generate figures with arbitrarily complex subplot geometry, this 2D indexing is useful only when the arrangement happens to be a clean 2D matrix.

  • SubplotsContainer permits 1D indexing, e.g. axs[0]. The default order can be switched from row-major to column-major by passing order='F' to subplots.

  • When it is singleton, SubplotsContainer behaves like a scalar. So when you make a single axes with fig, axs = plot.subplots(), axs[0].method(...) is equivalent to axs.method(...).

See the user guide for details.

Quick global settings

Problem

In matplotlib, there are several rcParams that you often want to set all at once, like the tick lengths and spine colors. It is also often desirable to change these settings for individual subplots rather than globally.

Solution

In ProPlot, you can use the rc object to change lots of settings at once with convenient shorthands. This is meant to replace matplotlib’s rcParams dictionary. Settings can be changed with plot.rc.key = value, plot.rc[key] = value, plot.rc.update(...), with the format method, or with the context method. See the user guide for details.

Physical units engine

Problem

Matplotlib uses figure-relative units for the margins left, right, bottom, and top, and axes-relative units for the column and row spacing wspace and hspace. Relative units tend to require “tinkering” with numbers until you find the right one. And since they are relative, if you decide to change your figure size or add a subplot, they will have to be readjusted.

Matplotlib also requires users to set the figure size figsize in inches. This may be confusing for users outside of the United States.

Solution

ProPlot introduces the physical units engine units for interpreting figsize, width, height, axwidth, axheight, left, right, top, bottom, wspace, hspace, and arguments in a few other places. Acceptable units include inches, centimeters, millimeters, pixels, points, picas, and em-heights. Em-heights are particularly useful, as labels already present can be useful “rulers” for figuring out the amount of space needed.

units is also used to convert settings passed to rc from arbitrary physical units to points – for example rc.ticklen, rc[‘title.size’], and rc[‘title.pad’]. See the user guide for

The .proplot folder

Problem

In matplotlib, it can be difficult to design your own colormaps and color cycles, and there is no builtin way to save them for future use. It is also difficult to get matplotlib to use custom .ttc, .ttf, and .otf font files, which may be desirable when you are working on Linux servers with limited font selections.

Solution

ProPlot automatically adds colormaps, color cycles, and font files saved in the .proplot/cmaps, .proplot/cycles, and .proplot/fonts folders in your home directory. You can save colormaps and color cycles to these folders simply by passing save=True to Colormap and Cycle. To manually load from these folders, e.g. if you have added files to these folders but you do not want to restart your ipython session, simply call register_cmaps, register_cycles, and register_fonts.