X and Y axis settings¶
This section documents features used for modifying x and y axis settings, including axis scales, tick locations, and tick label formatting. It also documents a handy “dual axes” feature.
Axis tick locations¶
Axis locators are used to select tick locations based on the axis data limits.
ProPlot lets you easily specify axis locators with format
(keyword args xlocator
, ylocator
, xminorlocator
, and yminorlocator
, or their aliases xticks
, yticks
, xminorticks
, and yminorticks
).
Pass a number to tick every N
data values, lookup a builtin matplotlib ticker
with a string key name, or pass a list of numbers to tick specific locations. I recommend using ProPlot’s arange
function to generate lists of ticks – it’s like numpy’s arange
, but is endpoint-inclusive, which is usually what you’ll want in this context. See format
and Locator
for details.
[1]:
import proplot as plot
import numpy as np
# shade makes it a bit brighter, multiplies luminance channel by this much!
plot.rc.facecolor = plot.shade('powder blue', 1.15)
plot.rc.update(linewidth=1, small=10, large=12,
color='dark blue', suptitlecolor='dark blue')
f, axs = plot.subplots(nrows=5, axwidth=5, aspect=(8, 1), share=0, hspace=0.3)
# Manual locations
axs[0].format(xlim=(0, 200), xminorlocator=10, xlocator=30,
suptitle='Declaring tick locations with ProPlot')
axs[1].format(xlim=(0, 10), xlocator=[
0, 0.3, 0.8, 1.6, 4.4, 8, 8.8, 10], xminorlocator=0.1)
# Locator classes
state = np.random.RandomState(51423)
# Approx number of ticks you want, but not exact locations
axs[3].format(xlim=(1, 10), xlocator=('maxn', 20))
# Log minor locator, automatically applied for log scale plots
axs[2].format(xlim=(1, 100), xlocator='log', xminorlocator='logminor')
# Index locator, something must be plotted for it to work
axs[4].plot(np.arange(10)-5, state.rand(10), alpha=0)
axs[4].format(xlim=(0, 6), xlocator='index', xformatter=[
r'$\alpha$', r'$\beta$', r'$\gamma$', r'$\delta$', r'$\epsilon$', r'$\zeta$', r'$\eta$'])
plot.rc.reset()
Axis tick labels¶
Axis formatters are used to convert float numbers to tick label strings.
ProPlot lets you easily change the axis formatter with format
(keyword args xformatter
and yformatter
, or their aliases xticklabels
and yticklabels
). The builtin matplotlib formatters can be referenced by string name, and several new formatters have been introduced – for example, you can now easily label your axes as fractions or as geographic coordinates. You can also just pass a list of strings or a %
style format directive. See format
and Formatter
for details.
[2]:
import proplot as plot
import numpy as np
plot.rc.update(linewidth=1.2, small=10, large=12, facecolor='gray8', figurefacecolor='gray8',
suptitlecolor='w', gridcolor='w', color='w')
f, axs = plot.subplots(nrows=6, axwidth=5, aspect=(8,1), share=0, hspace=0.3)
# Fraction formatters
axs[0].format(xlim=(0,4*np.pi), xlocator=plot.arange(0, 4, 0.25)*np.pi, xformatter='pi')
axs[1].format(xlim=(0,2*np.e), xlocator=plot.arange(0, 2, 0.5)*np.e, xticklabels='e')
# Geographic formatter
axs[2].format(xlim=(-90,90), xlocator=plot.arange(-90, 90, 30), xformatter='deglat')
# User input labels
axs[3].format(xlim=(-1.01,1), xlocator=0.5, xticklabels=['a', 'b', 'c', 'd', 'e'])
# Custom style labels
axs[4].format(xlim=(0, 0.001), xlocator=0.0001, xformatter='%.E')
axs[5].format(xlim=(0,100), xtickminor=False, xlocator=20, xformatter='{x:.1f}')
axs.format(ylocator='null', suptitle='Setting tick styles with ProPlot')
plot.rc.reset()
ProPlot also changes the default axis formatter. The new formatter trims trailing zeros by default, and can be used to filter tick labels within some data range, as demonstrated below. See AutoFormatter
for details.
[3]:
import proplot as plot
plot.rc.linewidth = 2
plot.rc.small = plot.rc.large = 11
locator = [0, 0.25, 0.5, 0.75, 1]
f, axs = plot.subplots([[1, 1, 2, 2], [0, 3, 3, 0]], axwidth=1.5, share=0)
# Formatter comparison
axs[0].format(xformatter='scalar', yformatter='scalar',
title='Matplotlib formatter')
axs[1].format(yticklabelloc='both', title='ProPlot formatter')
axs[:2].format(xlocator=locator, ylocator=locator)
# Limited tick range
axs[2].format(title='Limiting the tick range', ticklen=5, xlim=(0, 5), ylim=(0, 5),
xtickrange=(0, 2), ytickrange=(0, 2), xlocator=1, ylocator=1)
axs.format(ytickloc='both', yticklabelloc='both',
titleweight='bold', titlepad='0.5em')
plot.rc.reset()
Axis scales¶
The axis scale can now be changed with format
(keyword args xscale
and yscale
). You can also configure the 'log'
and 'symlog'
axis scales with the more sensible base
, linthresh
, linscale
, and subs
keyword args (i.e. you can omit the x
and y
). See format
, Scale
, LogScale
and SymmetricalLogScale
for details.
[4]:
import proplot as plot
import numpy as np
N = 200
lw = 3
plot.rc.update({'linewidth': 1, 'ticklabelweight': 'bold',
'axeslabelweight': 'bold'})
f, axs = plot.subplots(ncols=2, nrows=2, axwidth=1.8, share=0)
axs.format(suptitle='Changing the axis scale')
# Linear and log scales
axs[0].format(yscale='linear', ylabel='linear scale')
axs[1].format(ylim=(1e-3, 1e3), yscale='log',
yscale_kw={'subs': np.arange(1, 10)}, ylabel='log scale')
axs[:2].plot(np.linspace(0, 1, N), np.linspace(0, 1000, N), lw=lw)
# Symlog and logit scales
ax = axs[2]
ax.format(yscale='symlog', yscale_kw={'linthresh': 1}, ylabel='symlog scale')
ax.plot(np.linspace(0, 1, N), np.linspace(-1000, 1000, N), lw=lw)
ax = axs[3]
ax.format(yscale='logit', ylabel='logit scale')
ax.plot(np.linspace(0, 1, N), np.linspace(0.01, 0.99, N), lw=lw)
plot.rc.reset()
ProPlot also adds several new axis scales. The 'cutoff'
scale is great when you have weirdly distributed data (see CutoffScale
). The 'sine'
scale scales the axis as the sine of the coordinate, resulting in an “area-weighted” spherical latitude coordinate. The 'inverse'
scale is perfect for labeling spectral coordinates (this is more useful with the dualx
and dualy
commands; see Dual unit axes).
[5]:
import proplot as plot
import numpy as np
f, axs = plot.subplots(width=6, nrows=4, aspect=(5, 1), sharex=False)
ax = axs[0]
x = np.linspace(0, 4*np.pi, 100)
dy = np.linspace(-1, 1, 5)
y1 = np.sin(x)
y2 = np.cos(x)
state = np.random.RandomState(51423)
data = state.rand(len(dy)-1, len(x)-1)
scales = [(3, np.pi), (0.3, 3*np.pi),
(np.inf, np.pi, 2*np.pi), (5, np.pi, 2*np.pi)]
titles = ('Zoom out of left', 'Zoom into left', 'Discrete cutoff', 'Fast jump')
locators = [np.pi/3, np.pi/3, *
([x*np.pi for x in plot.arange(0, 4, 0.25) if not (1 < x <= 2)] for i in range(2))]
for ax, scale, title, locator in zip(axs, scales, titles, locators):
ax.pcolormesh(x, dy, data, cmap='grays', cmap_kw={'right': 0.8})
for y, color in zip((y1, y2), ('coral', 'sky blue')):
ax.plot(x, y, lw=4, color=color)
ax.format(xscale=('cutoff', *scale), title=title,
xlim=(0, 4*np.pi), ylabel='wave amplitude',
xformatter='pi', xlocator=locator,
xtickminor=False, xgrid=True, ygrid=False, suptitle='Demo of cutoff scales')
plot.rc.reset()
[6]:
import proplot as plot
import numpy as np
plot.rc.reset()
f, axs = plot.subplots(nrows=3, ncols=2, axwidth=1.5, share=0)
axs.format(rowlabels=[
'Power\nscales', 'Exponential\nscales', 'Geographic\nscales'], suptitle='Demo of esoteric axis scales')
x = np.linspace(0, 1, 50)
y = 10*x
state = np.random.RandomState(51423)
data = state.rand(len(y)-1, len(x)-1)
# Power scales
colors = ('coral', 'sky blue')
for ax, power, color in zip(axs[:2], (2, 1/4), colors):
ax.pcolormesh(x, y, data, cmap='grays', cmap_kw={'right': 0.8})
ax.plot(x, y, lw=4, color=color)
ax.format(ylim=(0.1, 10), yscale=('power', power),
title=f'$x^{{{power}}}$')
# Exp scales
for ax, a, c, color in zip(axs[2:4], (np.e, 2), (0.5, -1), colors):
ax.pcolormesh(x, y, data, cmap='grays', cmap_kw={'right': 0.8})
ax.plot(x, y, lw=4, color=color)
ax.format(ylim=(0.1, 10), yscale=('exp', a, c),
title=f'${(a,"e")[a==np.e]}^{{{(c,"-")[c==-1]}x}}$')
# Geographic scales
n = 20
x = np.linspace(-180, 180, n)
y = np.linspace(-85, 85, n)
y2 = np.linspace(-85, 85, n)
data = state.rand(len(x), len(y2))
for ax, scale, color in zip(axs[4:], ('sine', 'mercator'), ('coral', 'sky blue')):
ax.plot(x, y, '-', color=color, lw=4)
# Use 'right' to trim the colormap from 0-1 color range to 0-0.8 color range
ax.pcolormesh(x, y2, data, cmap='grays', cmap_kw={'right': 0.8})
ax.format(title=scale.title() + ' y-axis', yscale=scale,
ytickloc='left',
yformatter='deglat', grid=False, ylocator=20,
xscale='linear', xlim=None, ylim=(-85, 85))
Datetime axes¶
Labeling datetime axes is incredibly easy with ProPlot. Pass a unit string as the locator argument, and the axis will be ticked at those units. Pass a (unit, interval)
tuple to tick every interval
unit
s. Use the formatter argument for % style formatting of datetime. Again, see format
, Locator
, and Formatter
for details.
[7]:
import proplot as plot
import numpy as np
plot.rc.update(linewidth=1.2, small=10, large=12, ticklenratio=0.7)
plot.rc.update(figurefacecolor='w', facecolor=plot.shade('C0', 2.7))
f, axs = plot.subplots(nrows=6, axwidth=6, aspect=(8, 1), share=0)
# Default date locator enabled if you plot datetime data or set datetime limits
axs[0].format(xlim=(np.datetime64('2000-01-01'), np.datetime64('2001-01-02')),
xrotation=0)
# Concise date formatter introduced in matplotlib 3.1
axs[1].format(xlim=(np.datetime64('2000-01-01'), np.datetime64('2001-01-01')),
xformatter='concise', xrotation=0)
# Minor ticks every year, major every 10 years
axs[2].format(xlim=(np.datetime64('2000-01-01'), np.datetime64('2050-01-01')), xrotation=0,
xlocator=('year', 10), xformatter='\'%y')
# Minor ticks every 10 minutes, major every 2 minutes
axs[3].format(xlim=(np.datetime64('2000-01-01T00:00:00'), np.datetime64('2000-01-01T12:00:00')), xrotation=0,
xlocator=('hour', range(0, 24, 2)), xminorlocator=('minute', range(0, 60, 10)), xformatter='T%H:%M:%S')
# Month and year labels, with default tick label rotation
axs[4].format(xlim=(np.datetime64('2000-01-01'), np.datetime64('2008-01-01')),
xlocator='year', xminorlocator='month', xformatter='%b %Y') # minor ticks every month
axs[5].format(xlim=(np.datetime64('2000-01-01'), np.datetime64('2001-01-01')),
xgridminor=True, xgrid=False,
xlocator='month', xminorlocator='weekday', xformatter='%B') # minor ticks every Monday, major every month
axs.format(ylocator='null',
suptitle='Tick locators and formatters with time axes in ProPlot')
plot.rc.reset()
Dual unit axes¶
The new dualx
and dualy
methods build duplicate x and y axes meant to represent alternate units in the same coordinate range as the “parent” axis. For simple transformations, just use the offset
and scale
keyword args. For more complex transformations, pass the name of any registered “axis scale” to the xscale
or yscale
keyword args (see below).
[8]:
import proplot as plot
plot.rc.update({'grid.alpha': 0.4, 'linewidth': 1, 'grid.linewidth': 1})
f, axs = plot.subplots(ncols=2, share=0, aspect=2.2, axwidth=3)
# Meters and kilometers
ax = axs[0]
c1, c2 = plot.shade('cerulean', 0.5), plot.shade('red', 0.5)
ax.format(yformatter='null', xlabel='meters', xlocator=1000, xlim=(0, 5000),
xcolor=c2, ylocator=[], gridcolor=c2,
suptitle='Duplicate x-axes with simple, custom transformations')
ax.dualx(lambda x: x*1e-3, label='kilometers', grid=True, color=c1)
# Kelvin and Celsius
ax = axs[1]
ax.format(yformatter='null', xlabel='temperature (K)', title='', xlim=(200, 300), ylocator='null',
xcolor=c2, gridcolor=c2)
ax.dualx(lambda x: x - 273.15,
label='temperature (\N{DEGREE SIGN}C)', grid=True, color=c1)
plot.rc.reset()
[9]:
import proplot as plot
# Special scalings for atmospheric scientists, assumed scale height is 7km
plot.rc.update({'grid.alpha': 0.4, 'linewidth': 1, 'grid.linewidth': 1})
f, axs = plot.subplots(ncols=2, share=0, aspect=0.4, axwidth=1.8)
ax = axs[0]
c1, c2 = plot.shade('cerulean', 0.5), plot.shade('red', 0.5)
ax.format(xformatter='null', ylabel='pressure (hPa)',
ylim=(1000, 10), xlocator=[], ycolor=c1)
ax.dualy('height', label='height (km)', ticks=2.5, color=c2, grid=True)
ax = axs[1] # span
ax.format(xformatter='null', ylabel='height (km)', ylim=(0, 20), xlocator='null', gridcolor=c2, ycolor=c2,
suptitle='Duplicate y-axes with special transformations', grid=True)
ax.dualy('pressure', label='pressure (hPa)', locator=100, color=c1, grid=True)
plot.rc.reset()
[10]:
import proplot as plot
import numpy as np
# Inverse scaling, useful for frequency analysis
plot.rc['axes.ymargin'] = 0
cutoff = 1/5
c1, c2 = plot.shade('cerulean', 0.5), plot.shade('red', 0.5)
x = np.linspace(0.01, 0.5, 1000) # in wavenumber days
response = (np.tanh(-((x - cutoff)/0.03))
+ 1) / 2 # imgarinary response function
f, ax = plot.subplots(aspect=(3, 1), width=6)
ax.plot(x, response, color='gray7', lw=2)
ax.axvline(cutoff, lw=2, ls='-', color=c2)
ax.fill_between([cutoff - 0.03, cutoff + 0.03], 0, 1, color=c2, alpha=0.3)
ax.format(xlabel='wavenumber (days$^{-1}$)', ylabel='response', gridminor=True)
ax = ax.dualx('inverse', locator=[
20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05], label='period (days)')
ax.format(title='Imaginary response function',
suptitle='Duplicate x-axes with wavenumber and period')
plot.rc.reset()