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)
# 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$'])

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.

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')

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.

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')

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.

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)

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).

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')
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='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),
# 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))

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='Tick locators and formatters with time axes in ProPlot')

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).

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)
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)
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')