Why ProPlot?¶
Matplotlib is an extremely powerful plotting package used by scientists and engineers far and wide. However, matplotlib can be cumbersome or repetitive for users who…
Make highly complex figures with many subplots.
Want to finely tune their 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 most demanding users. We accomplish this by expanding upon matplotlib’s object-oriented interface. 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 matplotlib’s default interface. To start using these features, see the usage introduction and the user guide.
Less typing, more plotting¶
Limitation
Matplotlib users often need to change lots of plot settings all at once. With the default interface, 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.
It seems like there should be a more unified, straightforward way to change
settings without sacrificing the advantages of object-oriented design.
Solution
ProPlot introduces the proplot.axes.Axes.format
command to resolve this.
Think of this as an expanded and thoroughly documented version of the
matplotlib.artist.Artist.update
command. format
can modify things
like axis labels and titles and apply new “rc” settings to existing
axes. It also integrates with various constructor functions
to help keep things succinct. Further, the proplot.figure.Figure.format
and proplot.gridspec.SubplotGrid.format
commands can be used to
format
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 pplt
fig, axs = pplt.subplots(ncols=2)
axs.format(linewidth=1, edgecolor='gray')
axs.format(xlim=(0, 100), xticks=10, xtickminor=True, xlabel='foo', ylabel='bar')
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.edgecolor': 'gray'}):
fig, axs = plt.subplots(ncols=2, sharey=True)
axs[0].set_ylabel('bar', 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('foo', color='gray')
Class constructor functions¶
Limitation
Matplotlib and cartopy define several classes with verbose names like
MultipleLocator
, FormatStrFormatter
,
and LambertAzimuthalEqualArea
. They also keep them out of the top-level
package namespace. 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 matplotlib’s interface were actually designed with this in mind. Backend classes, native axes projections, axis scales, colormaps, box styles, arrow styles, and arc styles are referenced with “registered” string names, as are basemap projections. So, why not “register” everything else?
Solution
In ProPlot, tick locators, tick formatters, axis scales, property cycles, colormaps, normalizers, and cartopy projections are all “registered”. This is accomplished by defining “constructor functions” and passing various keyword arguments through these functions.
The constructor functions also accept intuitive inputs alongside “registered”
names. For example, a scalar passed to Locator
returns a MultipleLocator
, a
lists of strings passed to Formatter
returns a
FixedFormatter
, and Cycle
and Colormap
accept colormap names, individual colors, and
lists of colors. Passing the relevant class instance to a constructor function
simply returns it, and all the registered classes are available in the top-level
namespace – so class instances can be directly created with e.g.
pplt.MultipleLocator(...)
or pplt.LogNorm(...)
rather than
relying on constructor functions.
For details, see the user guide sections on Cartesian plots,
color cycles and colormaps. The below table
lists the constructor functions and the keyword arguments that use them. Note that
set_xscale
and set_yscale
accept
instances of ScaleBase
thanks to a patch applied by ProPlot.
Function |
Return type |
Used by |
Keyword argument(s) |
---|---|---|---|
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
Automatic dimensions and spacing¶
Limitation
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’s tight layout algorithm can help you avoid tweaking 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 by
passing refwidth
, refheight
, and/or refaspect
to figure
or
subplots
. The dimensions of the figure are calculated automatically.
The default behavior is refaspect=1
and refwidth=2.5
(inches). If the
aspect ratio mode
for the reference subplot is set to 'equal'
, as with geographic,
polar, imshow
, and heatmap
plots, the or data aspect ratio is used instead.
You can also independently specify the width or height of the figure with the
figwidth
and figheight
parameters. If only one is specified, the other will be
calculated to preserve subplot aspect ratios. You can also select a figwidth
and/or
figheight
suitable for submission to various publications
using the journal
parameter.
ProPlot also uses its own tight layout algorithm to
automatically determine the left
, right
, bottom
, top
, wspace
, and
hspace
spacing parameters. This algorithm has the following advantages:
The tight layout algorithm can produce variable spacing between rows and columns using the
proplot.gridspec.GridSpec
subclass ofmatplotlib.gridspec.GridSpec
. This is critical for making outer colorbars and legends and axes panels without “stealing space” from the parent subplot – these objects usually need to be spaced closer to their parents than other subplots.The tight layout calculations are simplified by permitting only one
GridSpec
per figure. This restriction is possible by requiring successiveadd_subplot
calls to imply the same geometry and include only subplot specs generated from the sameGridSpec
.
See the user guide for details.
Working with multiple subplots¶
Limitation
When working with multiple subplots in matplotlib, the path of least resistance often leads to redundant figure elements. 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. It is also generally necessary to add “a-b-c” labels to figures with multiple subplots before submitting them to publications, but matplotlib has no built-in way of doing this.
Solution
ProPlot makes it easier to work with multiple subplots and create clear, concise figures.
Axis tick labels and axis labels are automatically shared and aligned between subplots in the same
GridSpec
row or column. This is controlled by thesharex
,sharey
,spanx
,spany
,alignx
, andaligny
figure keywords.The figure
proplot.figure.Figure.colorbar
andproplot.figure.Figure.legend
commands can easily draw colorbars and legends intended to reference more than one subplot in arbitrary contiguous rows and columns. See the next section for details.The
panel_axes
(shorthandpanel
) commands can draw thin panels along the edges of subplots. This can be useful for plotting 1D summary statistics alongside 2D plots.A-b-c labels can be added to subplots simply using the
rc.abc
setting – for example,pplt.rc['abc'] = 'A.'
oraxs.format(abc='A.')
. This is possible becauseadd_subplot
assigns a uniquenumber
to every added subplot.The
proplot.gridspec.SubplotGrid.format
command can easily format multiple subplots at once or add colorbars, legends, panels, twin axes, or inset axes to multiple subplots at once. ASubplotGrid
is returned byproplot.figure.Figure.subplots
, and can be indexed like a list or like a 2D array (in which case the indices match the subplot grid extents). See the user guide for details.
Simpler colorbars and legends¶
Limitation
In matplotlib, it can be difficult to draw legend
s
along the outside of subplots. Generally, you need to position the legend
manually and tweak the spacing to make room for the legend.
Also, colorbar
s drawn along the outside of subplots
with e.g. fig.colorbar(..., ax=ax)
need to “steal” space from the parent subplot.
This can cause asymmetry in figures with more than one subplot. It is also generally
difficult to draw “inset” colorbars in matplotlib and to generate outer colorbars
with consistent widths (i.e., not too “skinny” or “fat”).
Solution
ProPlot includes a simple framework for drawing colorbars and legends that reference 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 alter subplot
spacing or add 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 useful new colorbar and legend features described in the user guide.
Improved plotting commands¶
Limitation
A few common plotting tasks take a lot of work using matplotlib alone. The seaborn, xarray, and pandas packages offer improvements, but it would be nice to have this functionality built right into matplotlib’s interface.
Solution
ProPlot uses the PlotAxes
subclass to add various seaborn,
xarray, and pandas features to existing matplotlib plotting commands
along with several additional features designed to make your life easier.
The following features are relevant for the 1D plotting commands like
line
(equivalent to plot
)
and scatter
:
The
cycle
keyword is interpreted by theCycle
constructor function and applies property cyclers on-the-fly. This permits succinct and flexible property cycler declaration.The
legend
andcolorbar
keywords draw on-the-fly legends and colorbars using the result of the plotting command. Note that colorbars can be drawn from lists of artists (seelegend
).The default
ylim
(xlim
) in the presence of a fixedxlim
(ylim
) is now adjusted to exclude out-of-bounds data. This can be useful when “zooming in” on a dependent variable axis but can be disabled by settingrc['axes.inbounds']
toFalse
or passinginbounds=False
to plot commands.The
bar
andbarh
commands accept 2D arrays and can stack or group successive columns. Likewise, thearea
andareax
commands (shorthands forfill_between
andfill_betweenx
) accept 2D arrays and can stack or overlay successive columns.The
bar
,barh
,vlines
,hlines
,area
, andareax
commands accept anegpos
keyword argument that assigns different colors colors to “negative” and “positive” regions.The
linex
andscatterx
commands are just likeline
andscatter
, but positional arguments are interpreted as x coordinates or (y, x) pairs. There are also the related commandsstemx
,stepx
,boxh
(shorthand forboxploth
), andviolinh
(shorthand forviolinploth
).The
line
,linex
,scatter
,scatterx
,bar
, andbarh
commands can quickly draw vertical or horizontal error bars or “shading” using a variety of keyword arguments. This is often more convenient than working directly witherrorbar
.The
parametric
command draws clean-looking parametric lines by encoding the parametric coordinate using colormap colors rather than text annotations.
The following features are relevant for the 2D plotting commands like
pcolor
and contour
:
The
cmap
andnorm
keyword arguments are interpreted by theColormap
andNorm
constructor functions. This permits succinct and flexible colormap and normalizer application.The
colorbar
keyword draws on-the-fly colorbars using the result of the plotting command. Note that “inset” colorbars can also be drawn, analogous to “inset” legends (seecolorbar
).The
contour
,contourf
,pcolormesh
, andpcolor
commands all accept alabels
keyword. This draws contour and grid box labels on-the-fly. Labels are automatically colored black or white according to the luminance of the underlying grid box or filled contour.The default
vmin
andvmax
used to normalize colormaps now excludes data outside the x and y axis boundsxlim
andylim
if they were explicitly fixed. This can be disabled by settingrc['image.inbounds']
toFalse
or by passinginbounds=False
to plot commands.The
DiscreteNorm
normalizer is paired with most colormaps by default. It can easily divide colormaps into distinct levels, similar to contour plots. This can be disabled by settingrc['image.discrete']
toFalse
or by passingdiscrete=False
to plot commands.The
DivergingNorm
normalizer is perfect for data with a natural midpoint and offers both “fair” and “unfair” scaling. TheSegmentedNorm
normalizer can generate uneven color gradations useful for unusual data distributions.The
heatmap
command invokespcolormesh
then applies an equal axes apect ratio, adds ticks to the center of each gridbox, and disables minor ticks and gridlines. This can be convenient for things like covariance matrices.Coordinate centers passed to commands like
pcolor
are automatically translated to “edges”, and coordinate edges passed to commands likecontour
are automatically translated to “centers”. In matplotlib,pcolor
simply truncates the data when it receives centers.Commands like
pcolor
,contourf
andcolorbar
automatically fix an irritating issue where saved vector graphics appear to have thin white lines between filled contours, grid boxes, and colorbar segments. This can be disabled by passingedgefix=False
to plot commands.
Cartopy and basemap integration¶
Limitation
There are two widely-used engines for working with geographic data in
matplotlib: cartopy and basemap. Using cartopy tends to be
verbose and involve boilerplate code, while using basemap requires plotting
with a separate Basemap
object rather than the
Axes
. They both require separate import statements and extra
lines of code to configure the projection.
Furthermore, when you use cartopy and basemap plotting commands, “map projection” coordinates are the default coordinate system rather than longitude-latitude coordinates. This choice is confusing for many users, since the vast majority of geophysical data are stored with longitude-latitude (i.e., “Plate Carrée”) coordinates.
Solution
ProPlot can succinctly create detailed geographic plots using either cartopy
or basemap as “backends”. By default, cartopy is used, but basemap can be used
by passing basemap=True
to axes-creation commands or by setting rc.basemap
to True
. To create a geographic plot, simply pass the PROJ
name to an axes-creation command, e.g. fig, ax = pplt.subplots(proj='pcarree')
or fig.add_subplot(proj='pcarree')
. Alternatively, use the
Proj
constructor function to quickly generate
a cartopy.crs.Projection
or Basemap
instance.
Requesting geographic projections creates a proplot.axes.GeoAxes
with unified support for cartopy and basemap features via the
proplot.axes.GeoAxes.format
command. This lets you quickly modify geographic
plot features like latitude and longitude gridlines, gridline labels, continents,
coastlines, and political boundaries. The syntax is conveniently analogous to the
syntax used for proplot.axes.CartesianAxes.format
and proplot.axes.PolarAxes.format
.
The GeoAxes
subclass also makes longitude-latitude coordinates
the “default” coordinate system by passing transform=ccrs.PlateCarree()
or latlon=True
to plotting commands (depending on whether cartopy or basemap
is the backend). And to enforce global coverage over the poles and across longitude
seams, you can pass globe=True
to 2D plotting commands like
contour
and pcolormesh
.
See the user guide for details.
Pandas and xarray integration¶
Limitation
Scientific data is commonly stored in array-like containers
that include metadata – namely, xarray.DataArray
s, pandas.DataFrame
s,
and pandas.Series
. When matplotlib receives these objects, it simply ignores
the associated metadata. To create plots that are labeled with the metadata,
you must use the xarray.DataArray.plot
, pandas.DataFrame.plot
,
and pandas.Series.plot
commands 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
interface rather than the object-oriented interface. The
plot
commands also include features that would be useful additions to matplotlib
in their own right, without requiring special containers and a separate interface.
Solution
ProPlot reproduces many of the xarray.DataArray.plot
,
pandas.DataFrame.plot
, and pandas.Series.plot
features on the
Axes
plotting commands themselves. Passing a
DataArray
, DataFrame
, or Series
through any
plotting command updates the axis tick labels, axis labels, subplot title, and
colorbar and legend labels from the metadata. This feature can be disabled
by setting rc.autoformat
to False
or passing autoformat=False
to any plotting command.
ProPlot also supports pint.Quantity
positional arguments by auto-calling
setup_matplotlib
when a pint.Quantity
is detected and by
extracting magnitudes from z coordinates (e.g., the data passed to contour
)
to avoid the stripped-units warning message. It also adds a unit string formatted
with rc.unitformat
as the default x and y axis label when rc.autoformat
is enabled and supports DataArray
containers with pint.Quantity
arrays.
Finally, as described above, ProPlot implements features
that were originally only available from the xarray.DataArray.plot
,
pandas.DataFrame.plot
, and pandas.Series.plot
commands – like grouped
bar plots, layered area plots, and on-the-fly colorbars and legends –
directly within the Axes
plotting commands.
Aesthetic colors and fonts¶
Limitation
A common problem with scientific visualizations is the use of “misleading”
colormaps like 'jet'
. These colormaps have jarring jumps in
hue, saturation, and luminance that can trick the human eye into seeing
non-existing patterns. It is important to use “perceptually uniform” colormaps
instead. Matplotlib comes packaged with a few of its own, plus
the ColorBrewer colormap series, but external projects
offer a larger variety of aesthetically pleasing “perceptually uniform” colormaps.
Matplotlib also “registers” the X11/CSS4 color names, but these are relatively
limited. The more intuitive and more numerous XKCD color survey names
can be accessed with the 'xkcd:'
prefix, but this is cumbersome, and external
projects like open color offer even more useful names.
Finally, matplotlib comes packaged with DejaVu Sans
as the default font.
This font is open source and include glyphs for a huge variety of characters,
but unfortunately (in our opinion) it is not very aesthetically pleasing. It
can also be difficult to change the default matplotlib font.
Solution
ProPlot adds new colormaps, colors, and fonts to help you make more aesthetically pleasing figures.
ProPlot adds colormaps from the seaborn, cmocean, SciVisColor, and Scientific Colour Maps projects. It also defines a few default perceptually uniform colormaps and includes a
PerceptualColormap
class for generating new ones. A table of colormap and color cycles can be shown usingshow_cmaps
andshow_cycles
. Colormaps like'jet'
can still be accessed, but this is discouraged.ProPlot adds colors from the open color project and adds XKCD color survey names without the
'xkcd:'
prefix after filtering them to exclude perceptually-similar colors and normalizing the naming pattern to make them more self-consistent. Old X11/CSS4 colors can still be accessed, but this is discouraged. A table of color names can be shown usingshow_colors
.ProPlot adds the entire TeX Gyre font family to matplotlib. These are open-source fonts designed to resemble more popular, commonly-used fonts like Helvetica and Century. They are used as the new default serif, sans-serif, monospace, cursive, and “fantasy” fonts, and they are available on all workstations. A table of font names can be shown using
show_fonts
.
For details on adding new colormaps, colors, and fonts, see the .proplot folder section.
Manipulating colormaps¶
Limitation
In matplotlib, colormaps are implemented with the
LinearSegmentedColormap
class (representing “smooth”
color gradations) and the ListedColormap
class (representing
“categorical” color sets). They are generally cumbersome to modify or create from
scratch. Meanwhile, property cycles used for individual plot elements are implemented
with the Cycler
class. They are easier to modify but they cannot be
“registered” by name like colormaps.
The seaborn package introduces “color palettes” to make working with colormaps and property cycles easier, but it would be nice to have similar features integrated more closely with matplotlib.
Solution
In ProPlot, it is easy to manipulate colormaps and property cycles.
All colormaps in ProPlot are replaced with the
ContinuousColormap
andDiscreteColormap
subclasses ofLinearSegmentedColormap
andListedColormap
. These classes include several useful features leveraged by the constructor functionsColormap
andCycle
.The
Colormap
function can merge, truncate, and modify existing colormaps or generate brand new colormaps. It can also create newPerceptualColormap
s – a type ofproplot.colors.ContinuousColormap
with linear transitions in the perceptually uniform-like hue, saturation, and luminance channels rather then the red, blue, and green channels.The
Cycle
function can make property cycles from scratch or retrieve “registered” color cycles from their associatedDiscreteColormap
instances. It can also make property cycles by splitting up the colors from registered or on-the-flyContinuousColormap
s andPerceptualColormap
s.
ProPlot also makes all colormap and color cycle names case-insensitive, and
colormaps are automatically reversed or cyclically shifted 180 degrees if you
append '_r'
or '_s'
to any colormap name. These features are powered by
ColormapDatabase
, which replaces matplotlib’s native
colormap database.
Physical units engine¶
Limitation
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
, figwidth
, figheight
, refwidth
, refheight
,
left
, right
, top
, bottom
, wspace
, hspace
, and keyword arguments in a
few other places. Acceptable units include inches, centimeters, millimeters,
pixels, points, picas, and em-heights (a table of acceptable units
is found here). Em-heights are particularly useful, as the
figure text can be a useful “ruler” when figuring out the amount of space you
need. The units
function also translates rc settings assigned
to rc_matplotlib
and rc_proplot
, e.g.
rc['axes.labelpad']
, rc['legend.handlelength']
, and
rc['subplot.refwidth']
. See the user guide for details.
Flexible global settings¶
Limitation
In matplotlib, there are several rcParams
that would be
useful to set all at once, like spine and label colors. It might also
be useful to change these settings for individual subplots rather
than globally.
Solution
In ProPlot, you can use the rc
object to change both native
matplotlib settings (found in rc_matplotlib
) and added ProPlot
settings (found in rc_proplot
). Assigned settings are always
validated, and special settings like meta.edgecolor
, meta.linewidth
, and
font.smallsize
can be used to update many settings all at once. Settings can
be changed with pplt.rc.key = value
, pplt.rc[key] = value
,
pplt.rc.update(key=value)
, using proplot.axes.Axes.format
, or using
proplot.config.Configurator.context
. Settings that have changed during the
python session can be saved to a file with proplot.config.Configurator.save
(see changed
), and settings can be loaded from
files with proplot.config.Configurator.load
. See the
user guide for details.
Loading saved settings¶
Limitation
Matplotlib rcParams
can be changed persistently by placing
matplotlibrc
files in the same directory as your python script. But it
can be difficult to design and store your own colormaps and color cycles for
future use. It is also difficult to get matplotlib to use custom .ttf
and
.otf
font files, which may be desirable when you are working on
Linux servers with limited font selections.
Solution
ProPlot settings can be changed persistently by editing the default proplotrc
file in the location given by user_file
(this is
usually $HOME/.proplot/proplotrc
) or by adding proplotrc
files to either
the current directory or any parent directory. Adding files to parent directories
can be useful when working in projects with lots of subfolders. See the
user guide for details.
ProPlot also automatically registers colormaps, color cycles, colors, and font
files stored in the cmaps
, cycles
, colors
, and fonts
folders in
the location given by user_folder
(this is usually
$HOME/.proplot
). You can save colormaps and color cycles to these
folders simply by passing save=True
to Colormap
and
Cycle
. To explicitly register objects stored in these folders,
or to register arbitrary input arguments, you can use register_cmaps
,
register_cycles
, register_colors
, or
register_fonts
.