Why ProPlot?¶
ProPlot’s core mission is to improve upon the parts of matplotlib that tend to be cumbersome or repetitive for power users. This page enumerates the stickiest of these limitations and describes how ProPlot addresses them.
No more boilerplate¶
Problem
Power users often need to change lots of plot settings all at once. In matplotlib, this requires a bunch of one-liner setters and getters, like set_title
.
This workflow is verbose and often confusing. It can be unclear whether settings can be changed from a Figure
setter, an Axes
setter, an XAxis
or YAxis
setter, or a miscellaneous bulk function like tick_params
.
Solution
ProPlot introduces the format
method for changing arbitrary settings in bulk.
Think of this as an improved version of the Artist
update
method.
This massively reduces the amount of code needed to create highly customized figures. For example, it is trivial to see that
import proplot as plot
f, ax = plot.subplots()
ax.format(linewidth=1, color='gray')
ax.format(xticks=20, xtickminor=True, xlabel='x axis', ylabel='y axis')
…is much more succinct than
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from matplotlib import rcParams
rcParams['axes.linewidth'] = 1
rcParams['axes.color'] = 'gray'
fig, ax = plt.subplots()
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')
ax.set_ylabel('y axis', color='gray')
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 all of these extra class names and import statements can be a major drag.
Other parts of the matplotlib API were designed with this in mind. Native axes projections, axis scale classes, box style classes, arrow style classes, arc style classes, and backends are referenced with “registered” string names, as are basemap projection types. If these are already “registered”, why not “register” everything else?
Solution
In ProPlot, tick locators, tick formatters, axis scales, cartopy projections, colormaps, and property cyclers are all “registered”. This is done by creating several constructor functions and passing various keyword argument through the constructor functions. This may seem “unpythonic” but it is absolutely invaluable when making plots.
The constructor functions also accept other types of input: 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 all kinds of input. And if an instance of the corresponding class is passed to any constructor function, it is simply returned. See 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 |
Returns |
Used by |
Keyword argument(s) |
---|---|---|---|
Axis |
|
||
Axis |
|
||
Axis |
|
||
Property |
1d plotting methods |
|
|
|
2d plotting methods |
|
|
|
2d plotting methods |
|
|
|
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, while the dimensions of the individual subplots are more important:
The subplot aspect ratio is usually more relevant than the figure aspect ratio, e.g. for map projections.
The subplot width and height control the evident thickness of text and other content plotted inside the axes.
Matplotlib has a tight layout algorithm to keep you from having to “tweak” the spacing, but the algorithm cannot apply different amounts of spacing between different subplot row and column boundaries. This is a silly limitation that 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 existing aspect will be used instead.
Figure dimensions are calculated to accommodate subplot geometry and tight layout spacing as follows:
If
axwidth
oraxheight
are used, the figure width and height are calculated automatically.If
width
is used, the figure height is calculated automatically.If
height
is used, the figure width is calculated automatically.If
width
andheight
orfigsize
is used, the figure dimensions are fixed.
By default, 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 precise because:
The new
GridSpec
class permits variable spacing between rows and columns. It turns out this is critical for putting Colorbars and legends on the outside of subplots.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
, and it considerably simplifies the algorithm (see GH#50 for details).
See Subplots features for details.
Outer colorbars and legends¶
Problem
In matplotlib, it is difficult to draw colorbar
s and
legend
s on the outside of subplots. It is very easy to mess up the subplot aspect ratios and the colorbar widths. It is even more difficult to draw colorbar
s and legend
s that reference more than one subplot:
Matplotlib has no capacity for drawing colorbar axes that span multiple plots – you have to create the axes yourself. This requires so much tinkering that most users just add identical colorbars to every single subplot!
Legends that span multiple plots tend to require manual positioning and tinkering with the
GridSpec
spacing, just like legends placed outside of individual subplots.
Solution
ProPlot introduces a brand new engine for drawing colorbars and legends along the outside of individual subplots and along contiguous subplots on the edge of the figure:
Passing
loc='l'
,loc='r'
,loc='b'
, orloc='t'
toAxes
colorbar
orAxes
legend
draws the colorbar or legend along the outside of the axes.Passing
loc='l'
,loc='r'
,loc='b'
, orloc='t'
toFigure
colorbar
andlegend
draws the colorbar or legend along the edge of the figure, centered relative to the subplot grid rather than figure coordinates.Outer colorbars and legends don’t mess up the subplot layout or subplot aspect ratios, since
GridSpec
permits variable spacing between subplot rows and columns. This is critical e.g. if you have a colorbar between columns 1 and 2 but nothing between columns 2 and 3.Figure
andAxes
colorbar widths are specified in physical units rather than relative units. This makes colorbar thickness independent of figure size and easier to get just right.
The colorbar and legend commands also add several new features, like colorbars-from-lines and centered-row legends. And to make Axes
colorbar
consistent with Axes
legend
, you can also now draw inset colorbars. See Colorbars and legends for details.
The subplot container class¶
Problem
In matplotlib, subplots
returns a 2D ndarray
, a 1D ndarray
, or the axes itself. This inconsistent behavior can be confusing.
Solution
In ProPlot, subplots
returns a subplot_grid
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)
).
See The basics for details.
The subplot_grid
class also
unifies the behavior of the three possible matplotlib.pyplot.subplots
return values:
subplot_grid
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.Since
subplot_grid
is alist
subclass, it also supports 1d indexing, e.g.axs[1]
. The default order can be switched from row-major to column-major by passingorder='F'
tosubplots
.subplot_grid
behaves like a scalar when it contains just one element. So if you just made a single axes withf, axs = plot.subplots()
, callingaxs[0].command
is equivalent toaxs.command
.
New and improved plotting methods¶
Problem
Certain plotting tasks are quite difficult to accomplish
with 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 that none of these packages
address.
Solution
The ProPlot Axes
class
overrides various plotting methods to make
your life easier.
All 1d plotting methods accept a
cycle
keyword argument interpreted byCycle
and optionallegend
andcolorbar
keyword arguments for populating legends and colorbars at the specified location with the result of the plotting command. See Color cycles and Colorbars and legends.All 2d plotting methods accept a
cmap
keyword argument interpreted byColormap
, anorm
keyword argument interpreted byNorm
, and an optionalcolorbar
keyword argument for drawing on-the-fly colorbars with the resulting mappable. See Colormaps and Colorbars and legends.All 2d plotting methods now accept a
labels
keyword argument. This is used to draw contour labels or grid box labels on heatmap plots. See 2d plotting for details.ProPlot fixes the irritating white-lines-between-filled-contours, white-lines-between-pcolor-patches, and white-lines-between-colorbar-patches observed when saving vector graphics.
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:
If edges are passed to
contour
orcontourf
, centers are calculated from the edgesIf centers are passed to
pcolor
orpcolormesh
, edges are estimated from the centers.
ProPlot also duplicates various seaborn
, xarray
, and pandas
features
using new axes methods and by overriding old axes
methods. These changes are described in the following table.
Plotting method |
New? |
Description |
---|---|---|
✓ |
Alias for |
|
✓ |
Alias for |
|
✓ |
Draws parametric line plots, where the parametric coordinate is denoted with colormap colors. |
|
✗ |
Now accepts 2D arrays, stacks or groups successive columns. Soon will be able to use different colors for positive/negative data. |
|
✗ |
As with |
|
✗ |
Now accepts 2D arrays, stacks or overlays successive columns. Also can use different colors for positive/negative data. |
|
✗ |
As with |
|
✓ |
Invokes |
|
✗ |
Add contour labels by passing |
|
✗ |
As with |
|
✗ |
Add gridbox labels by passing |
|
✗ |
As with |
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.
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 introduce 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 most of the xarray.DataArray.plot
, pandas.DataFrame.plot
, and pandas.Series.plot
features on the Axes
plotting methods themselves.
Passing an xarray.DataArray
, pandas.DataFrame
, or pandas.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
.
And as described in New and improved 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 quite verbose and involve lots of boilerplate code,
while basemap is outdated and requires you to use plotting commands on a separate Basemap
object.
Also, cartopy
and basemap
plotting commands assume
map projection coordinates unless specified otherwise. For most of us, this
choice is very frustrating, since geophysical data are usually stored in
longitude-latitude or “Plate Carrée” coordinates.
Solution
ProPlot integrates various cartopy
and basemap
features
into the ProjAxes
format
method.
This lets you apply all kinds of geographic plot settings, like coastlines, continents, political boundaries, and meridian and parallel gridlines.
ProjAxes
also
overrides various plotting methods:
transform=ccrs.PlateCarree()
is the new default for allGeoAxes
plotting methods.latlon=True
is the new default for allBasemapAxes
plotting methods.globe=True
can be passed to any 2D plotting command to enforce global coverage over the poles and across the longitude boundaries.
See Geographic and polar plots 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 remove the basemap
integration features.
Colormaps and property cycles¶
Problem
In matplotlib, colormaps are implemented with the ListedColormap
and LinearSegmentedColormap
classes.
They are hard to edit and hard to create from scratch.
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 color cycles from colormaps! These cycles can be applied by passing thecycle
keyword argument to plotting commands or changing therc.cycle
setting. See Color cycles for details.The new
ListedColormap
andLinearSegmentedColormap
classes include several new methods, e.g.save
andconcatenate
, and have a much nicer REPL representation.The
PerceptuallyUniformColormap
class is used to make Perceptually uniform colormaps. These have smooth, aesthetically pleasing color transitions represent your data accurately.
Importing ProPlot also makes colormap names case-insensitive and reviersible by appending '_r'
tot he colormap name. This is powered by the CmapDict
dictionary, which replaces matplotlib’s native colormap database.
Smarter 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.
The problem is that matplotlib discretizes colormaps by generating a low-resolution lookup table (see LinearSegmentedColormap
for details).
This approach cannot be fine-tuned and creates an unnecessary copy of the colormap.
It is clear that the task discretizing colormap colors should be left to the normalizer, not the colormap itself. Matplotlib provides BoundaryNorm
for this purpose, but it is seldom used and its features are limited.
Solution
In ProPlot, all colormap visualizations are automatically discretized with the BinNorm
class. This reads the extend
property passed to your plotting command and chooses colormap indices so that your colorbar levels always traverse the full range of colormap colors.
BinNorm
also applies arbitrary continuous normalizer requested by the user, e.g. Normalize
or LogNorm
, before discretization. Think of BinNorm
as a “meta-normalizer” – other normalizers perform the continuous transformation step, while this performs the discretization step.
Bulk 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
or individual blocks of code 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.
For details, see Configuring proplot. The most notable bulk settings are described below.
Key |
Description |
Children |
---|---|---|
|
The color for axes bounds, ticks, and labels. |
|
|
The width of axes bounds and ticks. |
|
|
Font size for “small” labels. |
|
|
Font size for “large” labels. |
|
|
Padding between ticks and labels. |
|
|
Tick direction. |
|
|
Tick length. |
|
|
Ratio between major and minor tick lengths. |
|
|
Margin width when limits not explicitly set. |
|
Physical units engine¶
Problem
Matplotlib requires users to use
inches for the figure size figsize
. This must be confusing for users outside
of the U.S.
Matplotlib also 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 meaningless numbers until you find the
right one… and then if your figure size changes, you have to adjust them again.
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 (because why not?).
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 Configuring proplot for details.
Working with fonts¶
Problem
In matplotlib, the default font is DejaVu Sans. In this developer’s humble opinion, DejaVu Sans is fugly AF. It is also really tricky to work with custom fonts in matplotlib.
Solution
ProPlot comes packaged with several additional fonts. The new default font is Helvetica. Albeit somewhat overused, this is a tried and tested, aesthetically pleasing sans serif font.
ProPlot adds fonts to matplotlib by making use of a completely undocumented feature: the $TTFPATH
environment variable (matplotlib adds .ttf
and .otf
font files from folders listed in $TTFPATH
). You can also use your own font files by dropping them in ~/.proplot/fonts
.