2d plotting¶
ProPlot adds new features to various Axes
plotting methods thanks to a set of wrapper functions. These features are a strict superset of the existing matplotlib API – if you want, you can use plotting commands exactly as you always have.
Standardized input¶
The standardize_2d
wrapper is used to standardize the input of a bunch of different plotting functions.
It allows you to optionally omit x and y coordinates, in which case they are inffered from the data array; guesses graticule edges for pcolor
and pcolormesh
plots; and optionally enforces global data coverage when plotting in map projections. See the documentation for details.
Pandas and xarray integration¶
The standardize_2d
wrapper also supports integration with pandas DataFrame
s and xarray DataArray
s. When you pass a DataFrame or DataArray to any plotting command, the x-axis label, y-axis label, legend label, colorbar label, and/or title are configured from the metadata. This restores some of the convenience you get with the builtin pandas and xarray plotting functions. This feature is optional, and does not require that pandas
or xarray
is installed.
[1]:
import xarray as xr
import numpy as np
import pandas as pd
from string import ascii_lowercase
# DataArray
state = np.random.RandomState(51423)
data = 50*(np.sin(np.linspace(0, 2*np.pi, 20) + 0)**2) * \
np.cos(np.linspace(0, np.pi, 20) + np.pi/2)[:, None]**2
da = xr.DataArray(data, dims=('plev', 'lat'), coords={
'plev': xr.DataArray(np.linspace(1000, 0, 20), dims=('plev',), attrs={'long_name': 'pressure', 'units': 'hPa'}),
'lat': xr.DataArray(np.linspace(-90, 90, 20), dims=('lat',), attrs={'units': 'degN'}),
}, name='u', attrs={'long_name': 'zonal wind', 'units': 'm/s'})
# DataFrame
data = state.rand(20, 20)
df = pd.DataFrame(data.cumsum(axis=0).cumsum(
axis=1), index=[*'JFMAMJJASONDJFMAMJJA'])
df.name = 'temporal data'
df.index.name = 'index'
df.columns.name = 'time (days)'
[2]:
import proplot as plot
f, axs = plot.subplots(nrows=2, axwidth=2.2, share=0)
axs.format(collabels=['Automatic subplot formatting'])
# Plot DataArray
axs[0].contourf(da, cmap='Greens', cmap_kw={
'left': 0.05}, colorbar='l', linewidth=0.7, color='gray7')
axs[0].format(yreverse=True)
# Plot DataFrame
axs[1].contourf(df, cmap='Blues', colorbar='r', linewidth=0.7, color='gray7')
axs[1].format(xtickminor=False)
On-the-fly colormaps¶
The cmap_changer
wrapper allows you to create and apply new colormaps on-the-fly. Just pass the cmap
keyword argument to any supported plotting method. See Making new colormaps for details. This wrapper also implements the features described in Colormap levels, Colormap normalizers, and Pcolor and contour labels below.
Colormap levels¶
cmap_changer
applies the BinNorm
“meta normalizer” for all plotting commands involving colormaps. BinNorm
applies any arbitrary continuous normalizer, e.g. LogNorm
, then discretizes the colormap levels. This permits distinct levels
even for commands like pcolor
and pcolormesh
.
Distinct levels can help the reader discern numeric values and qualitative structure in the figure. Distinct levels are also critical for users that would prefer contours, but have complex 2D coordinate matrices that trip up the contouring algorithm.
[3]:
import proplot as plot
import numpy as np
f, axs = plot.subplots(ncols=2, axwidth=2)
cmap = 'spectral'
state = np.random.RandomState(51423)
data = (state.normal(0, 1, size=(33, 33))).cumsum(axis=0).cumsum(axis=1)
axs.format(suptitle='Pcolor with levels demo')
ax = axs[0]
ax.pcolor(data, cmap=cmap, N=200, symmetric=True, colorbar='l')
ax.format(title='Ambiguous values', yformatter='null')
ax = axs[1]
ax.pcolor(data, cmap=cmap, N=10, symmetric=True, colorbar='r')
ax.format(title='Discernible levels')
BinNorm
also standardizes your colormap end colors. It ensures the following conditions are met:
Colormaps always span the entire color range, independent of the
extend
setting.Cyclic colormaps always have distinct color levels on either end of the colorbar.
This may seem nitpicky, but it is very important when the number of color levels is small.
[4]:
import proplot as plot
import numpy as np
f, axs = plot.subplots([[0, 0, 1, 1, 0, 0], [2, 3, 3, 4, 4, 5]],
wratios=(1.5, 0.5, 1, 1, 0.5, 1.5), axwidth=1.7, ref=1, right='2em')
axs.format(suptitle='Demo of colorbar color-range standardization')
levels = plot.arange(0, 360, 45)
state = np.random.RandomState(51423)
data = (20*(state.rand(20, 20) - 0.4).cumsum(axis=0).cumsum(axis=1)) % 360
# Show cyclic colorbar with distinct end colors
ax = axs[0]
ax.pcolormesh(data, levels=levels, cmap='phase',
extend='neither', colorbar='b')
ax.format(title='cyclic colormap\nwith distinct end colors')
# Show colorbars with different extend values
for ax, extend in zip(axs[1:], ('min', 'max', 'neither', 'both')):
ax.pcolormesh(data[:, :10], levels=levels, cmap='oxy',
extend=extend, colorbar='b', colorbar_kw={'locator': 90})
ax.format(title=f'extend={extend!r}')
Colormap normalizers¶
If you pass unevenly spaced levels
to a plotting command, the LinearSegmentedNorm
normalizer is applied by default. This results in even color gradations across indices of the level list, no matter their spacing. To use an arbitrary colormap normalizer, just pass norm
and optionally norm_kw
to a command wrapped by cmap_changer
. These arguments are passed to the Norm
constructor.
[5]:
import proplot as plot
import numpy as np
f, axs = plot.subplots(ncols=2, axwidth=2.5, aspect=1.5)
state = np.random.RandomState(51423)
data = 10**(2*state.rand(20, 20).cumsum(axis=0)/7)
ticks = [5, 10, 20, 50, 100, 200, 500, 1000]
for i, (norm, title) in enumerate(zip(('linear', 'segments'), ('Linear normalizer', 'LinearSegmentedNorm'))):
m = axs[i].contourf(data, levels=ticks, extend='both',
cmap='Mako', norm=norm, colorbar='b')
axs[i].format(title=title)
axs.format(suptitle='Level normalizers demo')
Finally, there is a new MidpointNorm
class that warps your colormap so that its midpoint lies on some central data value, no matter the minimum and maximum colormap colors. Again, to use an arbitrary colormap normalizer, just pass norm
and optionally norm_kw
to a command wrapped by cmap_changer
. These arguments are passed to the Norm
constructor.
[6]:
import proplot as plot
import numpy as np
state = np.random.RandomState(51423)
data1 = (state.rand(20, 20) - 0.43).cumsum(axis=0)
data2 = (state.rand(20, 20) - 0.57).cumsum(axis=0)
f, axs = plot.subplots(ncols=2, axwidth=2.5, aspect=1.5)
cmap = plot.Colormap('Moisture', cut=0.1)
axs.format(suptitle='Midpoint normalizer demo')
axs[0].contourf(data1, norm='midpoint', cmap=cmap, colorbar='b')
axs[0].format(title='Skewed positive data')
axs[1].contourf(data2, norm='midpoint', cmap=cmap, colorbar='b')
axs[1].format(title='Skewed negative data')
Pcolor and contour labels¶
Thanks to cmap_changer
, you can now add labels to heatmap
, pcolor
, pcolormesh
, contour
, and contourf
plots by simply passing labels=True
. ProPlot draws contour labels with clabel
or grid box labels with text
. Label colors are automatically chosen based on the luminance of the underlying box or contour color. The label text objects can be changed with the labels_kw
dictionary keyword arg and the precision
keyword arg. See cmap_changer
for details.
[7]:
import proplot as plot
import pandas as pd
import numpy as np
# Heatmap with labels
f, axs = plot.subplots([[1, 1, 2, 2], [0, 3, 3, 0]],
axwidth=2, share=1, span=False, hratios=(1, 0.9))
state = np.random.RandomState(51423)
data = state.rand(6, 6)
data = pd.DataFrame(data, index=pd.Index(['a', 'b', 'c', 'd', 'e', 'f']))
axs.format(xlabel='xlabel', ylabel='ylabel', suptitle='Labels demo')
ax = axs[0]
m = ax.heatmap(data, cmap='rocket', labels=True,
precision=2, labels_kw={'weight': 'bold'})
ax.format(title='Heatmap plot with labels')
# Filled contours with labels
ax = axs[1]
m = ax.contourf(data.cumsum(axis=0), labels=True,
cmap='rocket', labels_kw={'weight': 'bold'})
ax.format(title='Filled contour plot with labels')
# Simple contour plot
ax = axs[2]
ax.contour(data.cumsum(axis=1) - 2, color='gray8',
labels=True, lw=2, labels_kw={'weight': 'bold'})
ax.format(title='Contour plot with labels')
Heatmaps and covariance matrices¶
The new heatmap
command calls pcolormesh
and applies default formatting that is suitable for heatmaps – that is, no gridlines, no minor ticks, and major ticks at the center of each box. Among other things, this is useful for displaying covariance matrices. See the below example.
[8]:
import proplot as plot
import numpy as np
import pandas as pd
f, ax = plot.subplots(axwidth=4)
state = np.random.RandomState(51423)
data = state.normal(size=(10, 10)).cumsum(axis=0)
data = (data - data.mean(axis=0)) / data.std(axis=0)
data = (data.T @ data) / data.shape[0]
data[np.tril_indices(data.shape[0], -1)] = np.nan # fill half with empty boxes
data = pd.DataFrame(data, columns=list('abcdefghij'), index=list('abcdefghij'))
m = ax.heatmap(data, cmap='ColdHot', vmin=-1, vmax=1, N=100,
lw=0.5, edgecolor='k', labels=True, labels_kw={'weight': 'bold'},
clip_on=False) # turn off clipping so box edges are not cut in half
ax.format(suptitle='Heatmap demo', title='Pseudo covariance matrix', alpha=0, linewidth=0,
ytickmajorpad=4, # the ytick.major.pad rc setting; add extra space
xloc='top', yloc='right', yreverse=True, ticklabelweight='bold')