cartopy icon indicating copy to clipboard operation
cartopy copied to clipboard

Adding tick marks to plot axes

Open PAGWatson opened this issue 3 years ago • 6 comments

Is there an easy way to have tick marks shown on plot axes, without needing to specify the tick positions every time (e.g. in code where you might plot data for different world regions and so can't hard code the tick positions)? I think this is important to have, particularly when making multi-panel figures and not wanting to put the tick labels on all of them to reduce whitespace - then tick marks allow easy reading of the axis values on each panel.

In cartopy 0.20.1, the following gets me a plot with axes with no tick marks:

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np

proj=ccrs.PlateCarree()
fig, ax = plt.subplots(1, 1, subplot_kw={'projection':proj})
ax.coastlines()
gl=ax.gridlines(draw_labels=True)

image

Solutions to get the tick marks that I found by searching use ax.set_xticks and ax.set_yticks e.g.:

gl.bottom_labels=False
ax.set_xticks([-180,-120,-60,0,60,120,180])

image

(My tick marks point outwards since I have "xtick.direction : out" in my matplotlibrc file.)

Is there a way to get the tick marks to show without needing to specify what the tick values should be? I tried extracting the values from gl.xlabel_artists, but I couldn't see how to get them put in the right place with ax.set_xticks (the labels appear in the wrong places on the x-axis - I've not understood why). It would also be nice to be able to more easily keep cartopy's tick labels.

If there isn't a straightforward way to do this, this would be a very useful feature to have.

PAGWatson avatar Jan 31 '22 18:01 PAGWatson

Just to note further that if using a central longitude of 180 degrees, the plotted x ticks don't come out in the correct places using axis.set_xticks with hard coded values either with the method I'm using (proceeding along similar lines to the one here - using "ccrs.PlateCarree(central_longitude=180)" in the argument to ax.set_xticks doesn't help). (Edit - I've realised that the problem here is not using cartopy.mpl.gridliner.LongitudeFormatter - see my subsequent post below.)

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np

proj=ccrs.PlateCarree(central_longitude=180)
fig, ax = plt.subplots(1, 1, subplot_kw={'projection':proj})
ax.coastlines()
gl=ax.gridlines(draw_labels=True)

gl.bottom_labels=False
ax.set_xticks([0,60,120,180,-120,-60], crs=ccrs.PlateCarree())

image

PAGWatson avatar Jan 31 '22 19:01 PAGWatson

I experimented a bit more and found something that seemed to work for PlateCarree projections - I've not tested it for others. I thought others may find it useful. I included 4 panels to illustrate a use case of having multiple panels with little whitespace in between, where tick marks allow coordinate values to be read off (very useful when there are enough panels to fill a whole page/screen). However, this assumes that all panels use the same axis ranges.

import cartopy.crs as ccrs
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib.pyplot as plt
import numpy as np

#Function for plotting tick marks
def cartopy_plot_tickmarks(ax,labels,axis):
    
    assert axis in ['x','y']

    ticks=[]
    values=[float(txt.get_text().split('°')[0]) for txt in labels]
    directions=[txt.get_text().split('°')[1] for txt in labels]  #what comes after the degree symbol
    for i,txt in enumerate(labels):
        value=values[i]
        if directions[i] in ['W','S']:
            ticks+=[-value]
        else:  #for 'E', 'N' and '' (e.g. 180 degrees)
            ticks+=[value]
    
    if axis=='x':
        ax.set_xticks(ticks, crs=ccrs.PlateCarree())
        ax.set_xticklabels(['' for i in range(len(ticks))])  #make new ticks have blank labels to not overplot cartopy's
    elif axis=='y':
        ax.set_yticks(ticks, crs=ccrs.PlateCarree())
        ax.set_yticklabels(['' for i in range(len(ticks))])

        
proj=ccrs.PlateCarree(central_longitude=45)  #to show this working for a non-zero central_longitude
nrows=2
ncolumns=2
fig, axarr = plt.subplots(nrows, ncolumns, figsize=(10, 6), subplot_kw={'projection':proj})

#Making plots and adding cartopy labels to left and bottom panels
for i in range(nrows*ncolumns):
    row_ind=i//ncolumns
    col_ind=i%ncolumns
    ax=axarr[row_ind, col_ind]
    ax.coastlines()
    ax.set_extent([-100,100,-45,70], proj)  #just to show it working with axes different from the default
    draw_labels=[]
    if row_ind==nrows-1:
        draw_labels+=['bottom']
    if col_ind==0:
        draw_labels+=['left']

    gl=ax.gridlines(draw_labels=draw_labels)
    gl.xlines = False  #removing gridlines
    gl.ylines = False
    if row_ind==nrows-1 and col_ind==0:  #saving gridliner that has both left and bottom labels for getting tick values below
        gl_save=gl

    lon_formatter = LongitudeFormatter()
    ax.xaxis.set_major_formatter(lon_formatter)
    lat_formatter = LatitudeFormatter()
    ax.yaxis.set_major_formatter(lat_formatter)

#getting the cartopy tick information
plt.draw()  
xlabels=gl_save.xlabel_artists
ylabels=gl_save.ylabel_artists

#adding tick marks at label locations
for i in range(nrows*ncolumns):
    row_ind=i//ncolumns
    col_ind=i%ncolumns
    ax=axarr[row_ind, col_ind]
    
    cartopy_plot_tickmarks(ax,xlabels,'x')
    cartopy_plot_tickmarks(ax,ylabels,'y')

fig.subplots_adjust(hspace=0.05, wspace=0.05)  #removing whitespace

image

PAGWatson avatar Feb 01 '22 17:02 PAGWatson

Hi @PAGWatson, I copied exactly your script - adding only plt.show() in the end. However, the resulting plot does not have any ticks. (Tick labels are there, but no tick marks.) Debugging shows that xlabels=gl_save.xlabel_artists and yabels=gl_save.ylabel_artists return empty lists. Can you let me know which cartopy and matplotlib versions you are on so that I can try to figure out what is going on? I haven't found any other, flexible solution to get ticks without having grid lines...

gesape avatar Jul 28 '23 10:07 gesape

@gesape Just checked it still works with cartopy 0.21.1 and matplotlib 3.7.1.

PAGWatson avatar Jul 30 '23 20:07 PAGWatson