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 x and y 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. By comparison, the effect of the figure size on the evident thickness depends on the number of subplots 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. This limitation often results in unnecessary whitespace, and can be a major problem when you want to put e.g. a legend on the outside of a subplot.

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. Figure dimensions are constrained as follows:

  • When axwidth or axheight are specified, the figure dimensions are determined automatically.

  • When width is specified, the figure height is determined automatically.

  • When height is specified, the figure width is determined automatically.

  • When width and height or figsize is specified, the figure dimensions are fixed.

ProPlot also uses a custom tight layout algorithm that automatically determines the left, right, bottom, top, wspace, and hspace GridSpec parameters. This algorithm is simpler and more accurate for the following reasons:

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

  • 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 figure function that allows users to add subplots one-by-one while retaining the gridspec restriction).

See the user guide for details.

Eliminating redundancies

Problem

For many of us, 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.

It is also notoriously difficult to make colorbars that span multiple subplots in matplotlib. You have to supply colorbar with an axes that you drew yourself, generally using an elaborate GridSpec specification. This requires so much tinkering that most users just add identical colorbars to every single subplot!

Solution

ProPlot introduces a brand new framework for drawing colorbars and legends referencing individual subplots and multiple contiguous subplots. This framework makes the process of drawing colorbars and legends much cleaner.

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 greater detail 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. There is also room for improvement of the native matplotlib plotting methods that none of these packages address.

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.

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

Solution

ProPlot integrates various cartopy and basemap features into the proplot.axes.GeoAxes.format method. This lets you apply all kinds of geographic plot settings, like continents, coastlines, political boundaries, and meridian and parallel gridlines.

GeoAxes also overrides various plotting methods as follows:

  • The new default for all CartopyAxes plotting methods is transform=ccrs.PlateCarree().

  • The new default for all BasemapAxes plotting methods is latlon=True.

  • Global coverage over the poles and across the matrix longitude boundaries can be enforced by passing globe=True to any 2D plotting command, e.g. pcolormesh and contourf.

See the user guide for details. Note that active development on basemap will halt after 2020. For now, cartopy is missing several features offered by basemap – namely, flexible meridian and parallel gridline labels, drawing physical map scales, and convenience features for adding background images like the “blue marble”. But once these are added to cartopy, ProPlot may deprecate the basemap integration features.

Colormaps and property cycles

Problem

In matplotlib, colormaps are implemented with the ListedColormap and LinearSegmentedColormap classes. They are generally difficult 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 colormap database.

Cleaner colormap normalization

Problem

In matplotlib, when extend='min', extend='max', or extend='neither' is passed to colorbar , the colormap colors reserved for “out-of-bounds” values are truncated. This can be irritating for plots with very few colormap levels, which are often more desirable (see the user guide for an example).

The problem is that matplotlib “discretizes” colormaps by generating low-resolution lookup tables (see LinearSegmentedColormap). While straightforward, this approach has limitations and results in unnecessary plot-specific copies of the colormap. Ideally, the task of discretizing colormap colors should be left to the normalizer. While matplotlib provides BoundaryNorm for this purpose, it is seldom used and its features are limited.

Solution

In ProPlot, all colormaps retain a high-resolution lookup table and the DiscreteNorm class is used to make distinct “levels”. DiscreteNorm first normalizes the levels with a “continuous” normalizer like Normalize or LogNorm, then restricts the plot to a subset of lookup table colors at the indices of the normalized level centers. It also adjusts the indices such that the colorbar colors always traverse the full range of the colormap, regardless of the extend setting, and ensures that the end colors on cyclic colormaps are distinct.

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 also 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.

The most notable bulk settings are described below. See the user guide for details.

Key

Description

Children

color

The color for axes bounds, ticks, and labels.

axes.edgecolor, geoaxes.edgecolor, axes.labelcolor, tick.labelcolor, hatch.color, xtick.color, ytick.color

linewidth

The width of axes bounds and ticks.

axes.linewidth, geoaxes.linewidth, hatch.linewidth, xtick.major.width, ytick.major.width

small

Font size for “small” labels.

font.size, tick.labelsize, xtick.labelsize, ytick.labelsize, axes.labelsize, legend.fontsize, geogrid.labelsize

large

Font size for “large” labels.

abc.size, figure.titlesize, axes.titlesize, suptitle.size, title.size, leftlabel.size, toplabel.size, rightlabel.size, bottomlabel.size

tickpad

Padding between ticks and labels.

xtick.major.pad, xtick.minor.pad, ytick.major.pad, ytick.minor.pad

tickdir

Tick direction.

xtick.direction, ytick.direction

ticklen

Tick length.

xtick.major.size, ytick.major.size, ytick.minor.size * tickratio, xtick.minor.size * tickratio

tickratio

Ratio between major and minor tick lengths.

xtick.major.size, ytick.major.size, ytick.minor.size * tickratio, xtick.minor.size * tickratio

margin

Margin width when limits not explicitly set.

axes.xmargin, axes.ymargin

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, em-heights, and light years. 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.linewidth, rc.ticklen, rc[‘axes.titlesize’], and rc[‘axes.titlepad’]. See the user guide for details.

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.