Cartesian axis settings

This section documents features used for modifying Cartesian x and y axis settings, including axis scales, tick locations, and tick label formatting. It also documents a handy “dual axes” feature.

Tick locations

Tick locators are used to automatically select sensible tick locations based on the axis data limits. In ProPlot, you can change the tick locator using the format keyword arguments xlocator, ylocator, xminorlocator, and yminorlocator (or their aliases, xticks, yticks, xminorticks, and yminorticks). This is powered by the Locator constructor function.

These keyword arguments can be used to apply built-in matplotlib Locators by their “registered” names (e.g. xlocator='log'), to draw ticks every N data values with MultipleLocator (e.g. xlocator=2), or to tick the specific locations in a list using FixedLocator (just like set_xticks and set_yticks). See format and Locator for details.

To generate lists of tick locations, we recommend using ProPlot’s arange function – it’s basically an endpoint-inclusive version of numpy.arange, which is usually what you’ll want in this context.

  1. [1]:
  1. import proplot as plot
  2. import numpy as np
  3. state = np.random.RandomState(51423)
  4. plot.rc.update(
  5. facecolor=plot.scale_luminance('powderblue', 1.15),
  6. linewidth=1, fontsize=10,
  7. color='dark blue', suptitlecolor='dark blue',
  8. titleloc='upper center', titlecolor='dark blue', titleborder=False,
  9. )
  10. fig, axs = plot.subplots(nrows=8, axwidth=5, aspect=(8, 1), share=0)
  11. axs.format(suptitle='Tick locators demo')
  12. # Step size for tick locations
  13. axs[0].format(
  14. xlim=(0, 200), xminorlocator=10, xlocator=30,
  15. title='MultipleLocator'
  16. )
  17. # Specific list of locations
  18. axs[1].format(
  19. xlim=(0, 10), xminorlocator=0.1,
  20. xlocator=[0, 0.3, 0.8, 1.6, 4.4, 8, 8.8, 10],
  21. title='FixedLocator',
  22. )
  23. # Ticks at numpy.linspace(xmin, xmax, N)
  24. axs[2].format(
  25. xlim=(0, 10), xlocator=('linear', 21),
  26. title='LinearLocator',
  27. )
  28. # Logarithmic locator, used automatically for log scale plots
  29. axs[3].format(
  30. xlim=(1, 100), xlocator='log', xminorlocator='logminor',
  31. title='LogLocator',
  32. )
  33. # Maximum number of ticks, but at "nice" locations
  34. axs[4].format(
  35. xlim=(1, 7), xlocator=('maxn', 11),
  36. title='MaxNLocator',
  37. )
  38. # Index locator, only draws ticks where data is plotted
  39. axs[5].plot(np.arange(10) - 5, state.rand(10), alpha=0)
  40. axs[5].format(
  41. xlim=(0, 6), ylim=(0, 1), xlocator='index',
  42. xformatter=[r'$\alpha$', r'$\beta$', r'$\gamma$', r'$\delta$', r'$\epsilon$'],
  43. title='IndexLocator',
  44. )
  45. plot.rc.reset()
  46. # Hide all ticks
  47. axs[6].format(
  48. xlim=(-10, 10), xlocator='null',
  49. title='NullLocator',
  50. )
  51. # Tick locations that cleanly divide 60 minute/60 second intervals
  52. axs[7].format(
  53. xlim=(0, 2), xlocator='dms', xformatter='dms',
  54. title='Degree-Minute-Second Locator (requires cartopy)',
  55. )

_images/axis_2_0.svg

Tick labels

Tick formatters are used to convert floating point numbers to nicely-formatted tick labels. In ProPlot, you can change the tick formatter using the format keyword arguments xformatter and yformatter (or their aliases, xticklabels and yticklabels). This is powered by the Formatter constructor function.

These keyword arguments can be used to apply built-in matplotlib Formatters by their “registered” names (e.g. xformatter='log'), to apply a %-style format directive with FormatStrFormatter (e.g. xformatter='%.0f'), or to apply custom tick labels with FixedFormatter (just like set_xticklabels and set_yticklabels). They can also be used to apply one of ProPlot’s new tick formatters – for example, xformatter='deglat' to label ticks as the geographic latitude, xformatter='pi' to label ticks as fractions of \(\pi\), or xformatter='sci' to label ticks with scientific notation. See format and Formatter for details.

ProPlot also changes the default tick 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. To disable the trailing zero-trimming feature, set [rc[‘formatter.zerotrim’]](https://proplot.readthedocs.io/en/latest/configuration.html?highlight=formatter.zerotrim#rc-proplot) to False.

  1. [2]:
  1. import proplot as plot
  2. import numpy as np
  3. plot.rc.update(
  4. linewidth=1.2, fontsize=10, facecolor='gray0', figurefacecolor='gray2',
  5. color='gray8', gridcolor='gray8', titlecolor='gray8', suptitlecolor='gray8',
  6. titleloc='upper center', titleborder=False,
  7. )
  8. fig, axs = plot.subplots(nrows=9, axwidth=5, aspect=(8, 1), share=0)
  9. # Scientific notation
  10. axs[0].format(xlim=(0, 1e20), xformatter='sci', title='SciFormatter')
  11. # N significant figures for ticks at specific values
  12. axs[1].format(
  13. xlim=(0, 20), xlocator=(0.0034, 3.233, 9.2, 15.2344, 7.2343, 19.58),
  14. xformatter=('sigfig', 2), title='SigFigFormatter', # 2 significant digits
  15. )
  16. # Fraction formatters
  17. axs[2].format(
  18. xlim=(0, 3 * np.pi), xlocator=np.pi / 4, xformatter='pi', title='FracFormatter',
  19. )
  20. axs[3].format(
  21. xlim=(0, 2 * np.e), xlocator=np.e / 2, xticklabels='e', title='FracFormatter',
  22. )
  23. # Geographic formatters
  24. axs[4].format(
  25. xlim=(-90, 90), xlocator=30, xformatter='deglat', title='Latitude Formatter'
  26. )
  27. axs[5].format(
  28. xlim=(0, 360), xlocator=60, xformatter='deglon', title='Longitude Formatter'
  29. )
  30. # User input labels
  31. axs[6].format(
  32. xlim=(-1.01, 1), xlocator=0.5,
  33. xticklabels=['a', 'b', 'c', 'd', 'e'], title='FixedFormatter',
  34. )
  35. # Custom style labels
  36. axs[7].format(
  37. xlim=(0, 0.001), xlocator=0.0001, xformatter='%.E', title='FormatStrFormatter',
  38. )
  39. axs[8].format(
  40. xlim=(0, 100), xtickminor=False, xlocator=20,
  41. xformatter='{x:.1f}', title='StrMethodFormatter',
  42. )
  43. axs.format(ylocator='null', suptitle='Tick formatters demo')
  44. plot.rc.reset()

_images/axis_4_0.svg

  1. [3]:
  1. import proplot as plot
  2. plot.rc.linewidth = 2
  3. plot.rc.fontsize = 11
  4. locator = [0, 0.25, 0.5, 0.75, 1]
  5. fig, axs = plot.subplots(ncols=2, nrows=2, axwidth=1.5, share=0)
  6. # Formatter comparison
  7. axs[0].format(
  8. xformatter='scalar', yformatter='scalar', title='Matplotlib formatter'
  9. )
  10. axs[1].format(yticklabelloc='both', title='ProPlot formatter')
  11. axs[:2].format(xlocator=locator, ylocator=locator)
  12. # Limiting the tick range
  13. axs[2].format(
  14. title='Omitting tick labels', ticklen=5, xlim=(0, 5), ylim=(0, 5),
  15. xtickrange=(0, 2), ytickrange=(0, 2), xlocator=1, ylocator=1
  16. )
  17. # Setting the wrap range
  18. axs[3].format(
  19. title='Wrapping the tick range', ticklen=5, xlim=(0, 7), ylim=(0, 6),
  20. xwraprange=(0, 5), ywraprange=(0, 3), xlocator=1, ylocator=1
  21. )
  22. axs.format(
  23. ytickloc='both', yticklabelloc='both',
  24. titlepad='0.5em', suptitle='Default formatters demo'
  25. )
  26. plot.rc.reset()

_images/axis_5_0.svg

Datetime ticks

ProPlot can also be used to customize the tick locations and tick label format of “datetime” axes. To draw ticks on some particular time unit, just use a unit string (e.g. xlocator='month'). To draw ticks every N time units, just use a (unit, N) tuple (e.g. xlocator=('day', 5)). For % style formatting of datetime tick labels, just use a string containing '%' (e.g. xformatter='%Y-%m-%d'). See format, Locator, and Formatter for details.

  1. [4]:
  1. import proplot as plot
  2. import numpy as np
  3. plot.rc.update(
  4. linewidth=1.2, fontsize=10, ticklenratio=0.7,
  5. figurefacecolor='w', facecolor='pastel blue',
  6. titleloc='upper center', titleborder=False,
  7. )
  8. fig, axs = plot.subplots(nrows=5, axwidth=6, aspect=(8, 1), share=0)
  9. axs[:4].format(xrotation=0) # no rotation for these examples
  10. # Default date locator
  11. # This is enabled if you plot datetime data or set datetime limits
  12. axs[0].format(
  13. xlim=(np.datetime64('2000-01-01'), np.datetime64('2001-01-02')),
  14. title='Auto date locator and formatter'
  15. )
  16. # Concise date formatter introduced in matplotlib 3.1
  17. axs[1].format(
  18. xlim=(np.datetime64('2000-01-01'), np.datetime64('2001-01-01')),
  19. xformatter='concise', title='Concise date formatter',
  20. )
  21. # Minor ticks every year, major every 10 years
  22. axs[2].format(
  23. xlim=(np.datetime64('2000-01-01'), np.datetime64('2050-01-01')),
  24. xlocator=('year', 10), xformatter='\'%y', title='Ticks every N units',
  25. )
  26. # Minor ticks every 10 minutes, major every 2 minutes
  27. axs[3].format(
  28. xlim=(np.datetime64('2000-01-01T00:00:00'), np.datetime64('2000-01-01T12:00:00')),
  29. xlocator=('hour', range(0, 24, 2)), xminorlocator=('minute', range(0, 60, 10)),
  30. xformatter='T%H:%M:%S', title='Ticks at specific intervals',
  31. )
  32. # Month and year labels, with default tick label rotation
  33. axs[4].format(
  34. xlim=(np.datetime64('2000-01-01'), np.datetime64('2008-01-01')),
  35. xlocator='year', xminorlocator='month', # minor ticks every month
  36. xformatter='%b %Y', title='Ticks with default rotation',
  37. )
  38. axs.format(
  39. ylocator='null', suptitle='Datetime locators and formatters demo'
  40. )
  41. plot.rc.reset()

_images/axis_7_0.svg

Changing the axis scale

“Axis scales” like 'linear' and 'log' control the x and y axis coordinate system. To change the axis scale, simply pass e.g. xscale='log' or yscale='log' to format. This is powered by the Scale constructor function.

ProPlot also makes several changes to the axis scale API:

  • By default, the AutoFormatter formatter is used for all axis scales instead of e.g. LogFormatter for LogScale scales. This can be changed e.g. by passing xformatter='log' or yformatter='log' to format.

  • To make its behavior consistent with Locator and Formatter, the Scale constructor function returns instances of ScaleBase, and set_xscale and set_yscale now accept these class instances in addition to string names like 'log'.

  • While matplotlib axis scales must be instantiated with an Axis instance (for backward compatibility reasons), ProPlot axis scales can be instantiated without the axis instance (e.g. plot.LogScale() instead of plot.LogScale(ax.xaxis)).

  • The default subs for the 'symlog' axis scale is now np.arange(1, 10), and the default linthresh is now 1. Also the 'log' and 'symlog' axis scales now accept the keywords base, linthresh, linscale, and subs rather than keywords with trailing x or y.

  1. [5]:
  1. import proplot as plot
  2. import numpy as np
  3. N = 200
  4. lw = 3
  5. plot.rc.update({
  6. 'linewidth': 1, 'ticklabelweight': 'bold', 'axeslabelweight': 'bold'
  7. })
  8. fig, axs = plot.subplots(ncols=2, nrows=2, axwidth=1.8, share=0)
  9. axs.format(suptitle='Axis scales demo', ytickminor=True)
  10. # Linear and log scales
  11. axs[0].format(yscale='linear', ylabel='linear scale')
  12. axs[1].format(ylim=(1e-3, 1e3), yscale='log', ylabel='log scale')
  13. axs[:2].plot(np.linspace(0, 1, N), np.linspace(0, 1000, N), lw=lw)
  14. # Symlog scale
  15. ax = axs[2]
  16. ax.format(yscale='symlog', ylabel='symlog scale')
  17. ax.plot(np.linspace(0, 1, N), np.linspace(-1000, 1000, N), lw=lw)
  18. # Logit scale
  19. ax = axs[3]
  20. ax.format(yscale='logit', ylabel='logit scale')
  21. ax.plot(np.linspace(0, 1, N), np.linspace(0.01, 0.99, N), lw=lw)
  22. plot.rc.reset()

_images/axis_9_0.svg

Special axis scales

ProPlot introduces several new axis scales. The 'cutoff' scale (see CutoffScale) is useful when the statistical distribution of your data is very unusual. The 'sine' scale (see SineLatitudeScale) scales the axis with a sine function, resulting in an area weighted spherical latitude coordinate, and the 'mercator' scale (see MercatorLatitudeScale) scales the axis with the Mercator projection latitude coordinate. The 'inverse' scale (see InverseScale) can be useful when working with spectral data, especially with “dual” unit axes.

  1. [6]:
  1. import proplot as plot
  2. import numpy as np
  3. fig, axs = plot.subplots(width=6, nrows=4, aspect=(5, 1), sharex=False)
  4. ax = axs[0]
  5. # Sample data
  6. x = np.linspace(0, 4 * np.pi, 100)
  7. dy = np.linspace(-1, 1, 5)
  8. y1 = np.sin(x)
  9. y2 = np.cos(x)
  10. state = np.random.RandomState(51423)
  11. data = state.rand(len(dy) - 1, len(x) - 1)
  12. # Loop through various cutoff scale options
  13. titles = ('Zoom out of left', 'Zoom into left', 'Discrete jump', 'Fast jump')
  14. args = (
  15. (np.pi, 3), # speed up
  16. (3 * np.pi, 1 / 3), # slow down
  17. (np.pi, np.inf, 3 * np.pi), # discrete jump
  18. (np.pi, 5, 3 * np.pi) # fast jump
  19. )
  20. locators = (
  21. np.pi / 3,
  22. np.pi / 3,
  23. np.pi * np.append(np.linspace(0, 1, 4), np.linspace(3, 4, 4)),
  24. np.pi * np.append(np.linspace(0, 1, 4), np.linspace(3, 4, 4)),
  25. )
  26. for ax, iargs, title, locator in zip(axs, args, titles, locators):
  27. ax.pcolormesh(x, dy, data, cmap='grays', cmap_kw={'right': 0.8})
  28. for y, color in zip((y1, y2), ('coral', 'sky blue')):
  29. ax.plot(x, y, lw=4, color=color)
  30. ax.format(
  31. xscale=('cutoff', *iargs), title=title,
  32. xlim=(0, 4 * np.pi), ylabel='wave amplitude',
  33. xformatter='pi', xlocator=locator,
  34. xtickminor=False, xgrid=True, ygrid=False, suptitle='Cutoff axis scales demo'
  35. )

_images/axis_11_0.svg

  1. [7]:
  1. import proplot as plot
  2. import numpy as np
  3. plot.rc.reset()
  4. fig, axs = plot.subplots(nrows=2, ncols=3, axwidth=1.7, share=0, order='F')
  5. axs.format(
  6. collabels=('Power scales', 'Exponential scales', 'Cartographic scales'),
  7. suptitle='Additional axis scales demo'
  8. )
  9. x = np.linspace(0, 1, 50)
  10. y = 10 * x
  11. state = np.random.RandomState(51423)
  12. data = state.rand(len(y) - 1, len(x) - 1)
  13. # Power scales
  14. colors = ('coral', 'sky blue')
  15. for ax, power, color in zip(axs[:2], (2, 1 / 4), colors):
  16. ax.pcolormesh(x, y, data, cmap='grays', cmap_kw={'right': 0.8})
  17. ax.plot(x, y, lw=4, color=color)
  18. ax.format(
  19. ylim=(0.1, 10), yscale=('power', power),
  20. title=f'$x^{{{power}}}$'
  21. )
  22. # Exp scales
  23. for ax, a, c, color in zip(axs[2:4], (np.e, 2), (0.5, 2), colors):
  24. ax.pcolormesh(x, y, data, cmap='grays', cmap_kw={'right': 0.8})
  25. ax.plot(x, y, lw=4, color=color)
  26. ax.format(
  27. ylim=(0.1, 10), yscale=('exp', a, c),
  28. title=f"${(a, 'e')[a == np.e]}^{{{(c, '')[c == 1]}x}}$"
  29. )
  30. # Geographic scales
  31. n = 20
  32. x = np.linspace(-180, 180, n)
  33. y1 = np.linspace(-85, 85, n)
  34. y2 = np.linspace(-85, 85, n)
  35. data = state.rand(len(x) - 1, len(y2) - 1)
  36. for ax, scale, color in zip(axs[4:], ('sine', 'mercator'), ('coral', 'sky blue')):
  37. ax.plot(x, y1, '-', color=color, lw=4)
  38. ax.pcolormesh(x, y2, data, cmap='grays', cmap_kw={'right': 0.8})
  39. ax.format(
  40. title=scale.title() + ' y-axis', yscale=scale, ytickloc='left',
  41. yformatter='deg', grid=False, ylocator=20,
  42. xscale='linear', xlim=None, ylim=(-85, 85)
  43. )

_images/axis_12_0.svg

Dual unit axes

The dualx and dualy methods can be used to draw duplicate x and y axes meant to represent alternate units in the same coordinate range as the “parent” axis. This feature is powered by the FuncScale class.

dualx and dualy accept either (1) a single linear forward function, (2) a pair of arbitrary forward and inverse functions, or (3) a scale name or scale class instance. In the latter case, the scale’s transforms are used for the forward and inverse functions, and the scale’s default locators and formatters are used for the default FuncScale locators and formatters.

In the below examples, we generate dual axes with each of these three methods. Note that the “parent” axis scale is now arbitrary – in the first example shown below, we create a dualx axis for an axis scaled by the symlog scale.

  1. [8]:
  1. import proplot as plot
  2. plot.rc.update({'grid.alpha': 0.4, 'linewidth': 1, 'grid.linewidth': 1})
  3. c1 = plot.scale_luminance('cerulean', 0.5)
  4. c2 = plot.scale_luminance('red', 0.5)
  5. fig, axs = plot.subplots(
  6. [[1, 1, 2, 2], [0, 3, 3, 0]],
  7. share=0, aspect=2.2, axwidth=3
  8. )
  9. axs.format(
  10. suptitle='Duplicate axes with custom transformations',
  11. xcolor=c1, gridcolor=c1,
  12. ylocator=[], yformatter=[]
  13. )
  14. # Meters and kilometers
  15. ax = axs[0]
  16. ax.format(xlim=(0, 5000), xlabel='meters')
  17. ax.dualx(
  18. lambda x: x * 1e-3,
  19. label='kilometers', grid=True, color=c2, gridcolor=c2
  20. )
  21. # Kelvin and Celsius
  22. ax = axs[1]
  23. ax.format(xlim=(200, 300), xlabel='temperature (K)')
  24. ax.dualx(
  25. lambda x: x - 273.15,
  26. label='temperature (\N{DEGREE SIGN}C)', grid=True, color=c2, gridcolor=c2
  27. )
  28. # With symlog parent
  29. ax = axs[2]
  30. ax.format(xlim=(-100, 100), xscale='symlog', xlabel='MegaJoules')
  31. ax.dualx(
  32. lambda x: x * 1e6,
  33. label='Joules', formatter='log', grid=True, color=c2, gridcolor=c2
  34. )
  35. plot.rc.reset()

_images/axis_14_0.svg

  1. [9]:
  1. import proplot as plot
  2. plot.rc.update({'grid.alpha': 0.4, 'linewidth': 1, 'grid.linewidth': 1})
  3. c1 = plot.scale_luminance('cerulean', 0.5)
  4. c2 = plot.scale_luminance('red', 0.5)
  5. fig, axs = plot.subplots(ncols=2, share=0, aspect=0.4, axwidth=1.8)
  6. axs.format(suptitle='Duplicate axes with special transformations')
  7. # Pressure as the linear scale, height on opposite axis (scale height 7km)
  8. ax = axs[0]
  9. ax.format(
  10. xformatter='null', ylabel='pressure (hPa)',
  11. ylim=(1000, 10), xlocator=[], ycolor=c1, gridcolor=c1
  12. )
  13. ax.dualy(
  14. 'height', label='height (km)', ticks=2.5, color=c2, gridcolor=c2, grid=True
  15. )
  16. # Height as the linear scale, pressure on opposite axis (scale height 7km)
  17. ax = axs[1] # span
  18. ax.format(
  19. xformatter='null', ylabel='height (km)', ylim=(0, 20), xlocator='null',
  20. grid=True, gridcolor=c2, ycolor=c2
  21. )
  22. ax.dualy(
  23. 'pressure', label='pressure (hPa)', locator=100, color=c1, gridcolor=c1, grid=True,
  24. )
  25. plot.rc.reset()

_images/axis_15_0.svg

  1. [10]:
  1. import proplot as plot
  2. import numpy as np
  3. plot.rc.margin = 0
  4. c1 = plot.scale_luminance('cerulean', 0.5)
  5. c2 = plot.scale_luminance('red', 0.5)
  6. fig, ax = plot.subplots(aspect=(3, 1), width=6)
  7. # Sample data
  8. cutoff = 1 / 5
  9. x = np.linspace(0.01, 0.5, 1000) # in wavenumber days
  10. response = (np.tanh(-((x - cutoff) / 0.03)) + 1) / 2 # response func
  11. ax.axvline(cutoff, lw=2, ls='-', color=c2)
  12. ax.fill_between([cutoff - 0.03, cutoff + 0.03], 0, 1, color=c2, alpha=0.3)
  13. ax.plot(x, response, color=c1, lw=2)
  14. # Add inverse scale to top
  15. ax.format(
  16. xlabel='wavenumber (days$^{-1}$)', ylabel='response', grid=False,
  17. title='Imaginary response function',
  18. suptitle='Duplicate axes with wavenumber and period',
  19. )
  20. ax = ax.dualx(
  21. 'inverse', locator='log', locator_kw={'subs': (1, 2, 5)}, label='period (days)'
  22. )
  23. plot.rc.reset()

_images/axis_16_0.svg