pythonmatplotlibastropy

Add "+" sign in positive values using astropy and matplotlib


I'm using astropy, matplotlib, skyfield and astroquery to create sky charts with Python. With skyfield I do the stereographic projection of the sky. With astropy I use WCS, something like this:

fig = plt.figure(figsize=[600/96, 500/96])
plt.subplots_adjust(left=0.1, right=0.75, top=0.9, bottom=0.1)

wcs = WCS(naxis=2)
wcs.wcs.crpix = [1, 1]
wcs.wcs.cdelt = np.array([-360 / np.pi, 360 / np.pi])
wcs.wcs.crval = [COORD.ra.deg, COORD.dec.deg]
wcs.wcs.ctype = ["RA---STG", "DEC--STG"]

ax = fig.add_subplot(111, projection=wcs)

angle = np.pi - FOV / 360.0 * np.pi
limit = np.sin(angle) / (1.0 - np.cos(angle))

ax.set_xlim(-limit, limit)
ax.set_ylim(-limit, limit)
ax.set_aspect('equal')

ax.coords.grid(True, color='white', linestyle='dotted')
ax.coords[0].set_axislabel(' ')
ax.coords[1].set_axislabel(' ')

I wish to add the plus sign (“+”) for the positive values in the Declination axis (Y). I tried a lot of solutions. For example, the method set_major_formatter, but it seems very limited, because it only allows a small usecase. With this line of code I can configure the decimal numbers, for example, but nothing else:

ax.coords[1].set_major_formatter('d')

It’s crazy because it’s a very simple action in terms of programming, however I’m not able to achieve this through the built-in functions that astropy and matplotlib have.

Of course unsigned values are positive per se, however I'm preparing sky charts for all public usage, so adding the "+" sign would be helpful for the people.

I also tried to iterate the values. Then I would create a simple bucle with a conditional for the positive numbers. However, this iteration seems impossible.

I also tried matplotlib.ticker, and this is not working at all because [I think] it's about WCS from astropy.


Solution

  • You can't do it in an official way, but here are two hacky workarounds. Both, however, depend on implementation details and may not work on future versions of astropy. Also you'll have to switch off the removal of repeating tick label parts ("tick label simplification") which can't be done in a documented way (so we just replace this function with a noop).

    Variant 1: subclass AngleFormatterLocator
    As you've noticed there's no working way to set your own formatter, so we'll have to directly assign it to the internal _formatter_locator variable:

    from astropy.wcs import WCS
    from astropy.visualization.wcsaxes.formatter_locator import AngleFormatterLocator
    from matplotlib.ticker import Formatter
    
    class AngleFormatterLocatorPlus(AngleFormatterLocator):
        def formatter(self, values, spacing, format="auto"):
            ticklabels = super().formatter(values, spacing, format)
            minus = Formatter.fix_minus('-')
            return [tl if tl.startswith(minus) else '+' + tl for tl in ticklabels]
    
    wcs = WCS(naxis=2)
    wcs.wcs.crpix = [1, 1]
    wcs.wcs.cdelt = np.array([-360 / np.pi, 360 / np.pi])
    wcs.wcs.ctype = ["RA---STG", "DEC--STG"]
    
    fig, ax = plt.subplots(subplot_kw=dict(projection=wcs))
    
    ax.coords[1]._formatter_locator = AngleFormatterLocatorPlus()
    
    ax.coords[1].ticklabels.simplify_labels = lambda: None
    
    ax.set_xlim(-.3, .3)
    ax.set_ylim(-.1, .3)
    

    variant 1

    Variant 2: globally supply the alwayssign argument to Angle.to_string
    which affects both axes (which might or might not be desired):

    import functools
    import matplotlib.pyplot as plt
    import numpy as np
    
    from astropy.wcs import WCS
    from astropy.visualization.wcsaxes.formatter_locator import Angle
    
    Angle.to_string = functools.partialmethod(Angle.to_string, alwayssign=True)
    
    wcs = WCS(naxis=2)
    wcs.wcs.crpix = [1, 1]
    wcs.wcs.cdelt = np.array([-360 / np.pi, 360 / np.pi])
    wcs.wcs.ctype = ["RA---STG", "DEC--STG"]
    
    fig, ax = plt.subplots(subplot_kw=dict(projection=wcs))
    
    ax.coords[0].ticklabels.simplify_labels = lambda: None
    ax.coords[1].ticklabels.simplify_labels = lambda: None
    
    ax.set_xlim(-.3, .3)
    ax.set_ylim(-.1, .3)
    

    variant 2