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) |
---|---|---|---|
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
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…
…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 mostimshow
plots.…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 withsubplots
(although in a future version, there will be aproplot.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
, andspany
subplots
keyword args.The new
Figure
colorbar
andlegend
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 legend
s
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.
To draw a colorbar or legend on the outside of a specific subplot, pass an “outer” location (e.g.
loc='l'
orloc='left'
) toproplot.axes.Axes.colorbar
orproplot.axes.Axes.legend
.To draw a colorbar or legend on the inside of a specific subplot, pass an “inner” location (e.g.
loc='ur'
orloc='upper right'
) toproplot.axes.Axes.colorbar
orproplot.axes.Axes.legend
.To draw a colorbar or legend along the edge of the figure, use
proplot.figure.Figure.colorbar
andproplot.figure.Figure.legend
. Thecol
,row
, andspan
keyword args control whichGridSpec
rows and columns are spanned by the colorbar or legend.
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.
The new
heatmap
method invokespcolormesh
and draws ticks at the center of each box. This is more convenient for things like covariance matrices.The new
parametric
method draws parametric line plots, where the parametric coordinate is denoted with a colorbar and colormap colors rather than text annotations.The
bar
andbarh
methods accept 2D arrays and can stack or group successive columns. Similarly, the newarea
andareax
methods (aliases forfill_between
andfill_betweenx
) also accept 2D arrays and can stack or overlay successive columns.The
bar
,barh
,vlines
,hlines
,area
, andareax
commands all accept anegpos
keyword argument that can be used to assign “negative” and “positive” colors to different regions.You can now add error bars or error shading to
bar
,barh
,plot
, andscatter
plots by passing special keyword arguments to these functions. You no longer have to work with theerrorbar
method directly.All 1D plotting methods accept a “cycle” keyword argument interpreted by
Cycle
and optional “colorbar” and “legend” keyword arguments for populating legends and colorbars at the specified location with the result of the plotting command.All 2D plotting methods methods accept “cmap” and “norm” keyword arguments interpreted by
Colormap
andNorm
, along with an optional “colorbar” keyword argument for drawing on-the-fly colorbars. They also accept a “labels” keyword argument used to draw contour labels or grid box labels on-the-fly, and labels are colored black or white according to the luminance of the underlying filled contour or grid box color.Matplotlib requires coordinate centers for contour plots and edges for pcolor plots. If you pass centers to pcolor, matplotlib treats them as edges and silently trims one row/column of your data. ProPlot changes this behavior so that your data is no longer trimmed.
ProPlot fixes an irritating issue with saved vector graphics where white lines appear between filled contours, pcolor patches, and colorbar patches.
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
andLinearSegmentedColormap
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]
. Sincesubplots
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 passingorder='F'
tosubplots
.When it is singleton,
SubplotsContainer
behaves like a scalar. So when you make a single axes withfig, axs = plot.subplots()
,axs[0].method(...)
is equivalent toaxs.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
.