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.

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)
axs.format(suptitle='Tick locators demo')
# Manual locations
axs[0].format(xlim=(0, 200), xminorlocator=10, xlocator=30)
axs[1].format(xlim=(0, 10), xminorlocator=0.1,
              xlocator=[0, 0.3, 0.8, 1.6, 4.4, 8, 8.8, 10])
# 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, only draws ticks where data is plotted
axs[4].plot(np.arange(10) - 5, state.rand(10), alpha=0)
axs[4].format(xlim=(0, 6), ylim=(0, 1), xlocator='index',
              xformatter=[r'$\alpha$', r'$\beta$', r'$\gamma$', r'$\delta$', r'$\epsilon$'])

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.

ProPlot also changes the default axis formatter to AutoFormatter. This class trims trailing zeros by default, can be used to omit tick labels outside of some data range, and can add arbitrary prefixes and suffixes to each label. See AutoFormatter for details.

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, 3*np.pi),
              xlocator=plot.arange(0, 4, 0.25) * np.pi,
axs[1].format(xlim=(0, 2*np.e),
              xlocator=plot.arange(0, 2, 0.5) * np.e,
# Geographic formatter
axs[2].format(xlim=(-90, 90), xlocator=plot.arange(-90, 90, 30),
# 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,
axs[5].format(xlim=(0, 100), xtickminor=False,
              xlocator=20, xformatter='{x:.1f}')
axs.format(ylocator='null', suptitle='Tick formatters demo')
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='Omitting tick labels', ticklen=5, xlim=(0, 5), ylim=(0, 5),
              xtickrange=(0, 2), ytickrange=(0, 2), xlocator=1, ylocator=1)
axs.format(ytickloc='both', yticklabelloc='both',
           titlepad='0.5em', suptitle='Default formatters demo')

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 units. Use the formatter argument for % style formatting of datetime. Again, see format, Locator, and Formatter for details.

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')),
# 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
           suptitle='Datetime locators and formatters demo')

Old axis scales

The axis scale can be changed with format (keyword args xscale and yscale). You can now configure the 'log' and 'symlog' axis scales with the more sensible base, linthresh, linscale, and subs keyword args, rather than basex, basey, etc. Also, ProPlot’s AutoFormatter formatter is used for all axis scales by default; this can be changed e.g. by passing yformatter='log' to format. See Scale, LogScale and SymmetricalLogScale for details.

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='Axis scales demo')
# 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)

New axis scales

ProPlot adds several new axis scales. The 'cutoff' scale is great when you have weirdly distributed data (see CutoffScale). The 'sine' scale uses the sine function, resulting in an area weighted spherical latitude coordinate, and the 'mercator' scale uses the Mercator projection latitude coordinate. The 'inverse' scale is useful when working with spectral data (this is more useful with dualx and dualy; see Dual unit axes).

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)
titles = ('Zoom out of left', 'Zoom into left', 'Discrete jump', 'Fast jump')
args = [
    (np.pi, 3),  # speed up
    (3*np.pi, 1/3),  # slow down
    (np.pi, np.inf, 3*np.pi),  # discrete jump
    (np.pi, 5, 3*np.pi)  # fast jump
locators = (
    + 2*[[*np.linspace(0, 1, 4) * np.pi, *(np.linspace(0, 1, 4) * np.pi + 3*np.pi)]]
for ax, iargs, title, locator in zip(axs, args, 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', *iargs), title=title,
              xlim=(0, 4*np.pi), ylabel='wave amplitude',
              xformatter='pi', xlocator=locator,
              xtickminor=False, xgrid=True, ygrid=False, suptitle='Cutoff scales demo')
import proplot as plot
import numpy as np
f, axs = plot.subplots(nrows=3, ncols=2, axwidth=1.5, share=0)
    'Power\nscales', 'Exponential\nscales',  'Geographic\nscales'], suptitle='Esoteric axis scales demo')
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),
# 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),
# 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,
              yformatter='deglat', grid=False, ylocator=20,
              xscale='linear', xlim=None, ylim=(-85, 85))

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. Both dualx and dualy accept either a linear function, a pair of arbitrary forward and inverse functions, or a scale name or class. In the latter case, the scale is passed to Scale and its transforms are used for the forward and inverse functions.

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 axes with 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, gridcolor=c1)
import proplot as plot
# Vertical scales 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)
axs.format(suptitle='Duplicate axes with special transformations')
c1, c2 = plot.shade('cerulean', 0.5), plot.shade('red', 0.5)
ax = axs[0]
ax.format(xformatter='null', ylabel='pressure (hPa)',
          ylim=(1000, 10), xlocator=[], ycolor=c1, gridcolor=c1)
ax.dualy('height', label='height (km)', ticks=2.5,
         color=c2, gridcolor=c2, grid=True)
ax = axs[1]  # span
ax.format(xformatter='null', ylabel='height (km)', ylim=(0, 20), xlocator='null',
          grid=True, gridcolor=c2, ycolor=c2)
ax.dualy('pressure', label='pressure (hPa)', locator=100,
         color=c1, gridcolor=c1, grid=True)
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.axvline(cutoff, lw=2, ls='-', color=c2)
ax.fill_between([cutoff - 0.03, cutoff + 0.03], 0, 1, color=c2, alpha=0.3)
ax.plot(x, response, color=c1, lw=2)
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 axes with wavenumber and period')