Search code examples
python-3.xmatplotlibcartopy

How can one set cartopy's Gridline label styles (major_xticks and minor_xticks) of a set of geoaxes - subplots?


I am having serious difficulties in setting the Text styles of the xlabels and ylabels from the cartopy's gridlines.

When I follow the example given the cartopy's website, everything goes well, and I get the expected figure: Figure 1.

Nevertheless, when I try to create multiple subplots, and each subplot with its own gridline (notice that they are all from the same geographical region, and thus, have the same gridline X and Y coordinates), I am unable to properly set the Gridlines' Text styles of its labels. In fact, some of the xlabels end up missing from the plot, as presented in this second example Figure 2.

For sake of reproducibility, here is the code I used for the creation of Figure 2:

import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import cartopy.crs as ccrs

from cartopy.mpl.ticker import (LongitudeFormatter, LatitudeFormatter,
                                LatitudeLocator)

fig, axes = plt.subplots(2,2, subplot_kw={'projection':ccrs.Mercator()})

axes = axes.ravel()
    
for ax in axes:
        
    ax.coastlines()
    
    ax.set_extent([-120, -25, -45, 25])
    
    gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
                      linewidth=2, color='gray', alpha=0.5, linestyle='--')
    gl.top_labels = False
    gl.left_labels = False
    gl.xlines = False
    
    gl.ylocator = LatitudeLocator()
    gl.xformatter = LongitudeFormatter()
    gl.yformatter = LatitudeFormatter()
    
    gl.xlocator = ticker.MaxNLocator(6)
    gl.ylocator = ticker.MaxNLocator(6)
    
    gl.xlabel_style = {'size': 12, 'color': 'k', 'rotation':45}
    gl.ylabel_style = {'color': 'red', 'weight': 'bold', 'size':6}
plt.tight_layout()
plt.show()

The present issue may also help other parties, as the present topic has been indirectly subject of third other posts: 1) How can I rotate gridline labels on a cartopy map?; 2) How to set curvilinear lat lon gridline labels parallel to gridlines with cartopy?;

Sincerely,


Solution

  • Questions:

    1. How can I rotate gridline labels on a cartopy map?
    2. How to set curvilinear lat/lon gridline labels parallel to gridlines with cartopy?

    Answer to question 1

    Difficulties found:

    • missing some/all longitude labels (bottom edges)
    • not achieve result when define some styling (text rotation)

    From ref. document: https://scitools.org.uk/cartopy/docs/latest/matplotlib/gridliner.html

    Cartopy's gridliner function is a new feature that will be evolving a lot in the future (mentioned in its documentation).

    The current status is very good for most cylindrical types of projection with default style settings.

    If one manipulates some label text's styles without proper settings of associate options, you don't get what you expect.

    For gridlines' labels, the following rules apply: Extracted from help(gl._update_labels_visibility)

    • Labels are plotted and checked by order of priority, with a high priority for longitude labels at the bottom and top of the map, and the reverse for latitude labels.
    • A label must not overlap another label marked as visible.
    • A label must not overlap the map boundary.
    • When a label is about to be hidden, other angles are tried in the absolute given limit of max_delta_angle by increments of delta_angle of difference from the original angle.

    Here are some of the options that should be set with proper values, default values often lead to unexpect results.

    1. gridlines() options 'xpadding' and 'ypadding', can be set to get more space to plot label texts

    2. gridlines.xlabel_style() keys

      • 'size': default values = ??
      • 'rotation': default value = 0 degrees
      • 'ha': default is 'center' (clever use of text justification can avoid setting 'xpadding' and 'ypadding')

    Here is the runnable code that generates the subplots of maps with all 4 sets of tick-labels on all sides of each map.

    import matplotlib.pyplot as plt
    import matplotlib.ticker as mticker
    import cartopy.crs as ccrs
    from cartopy.mpl.ticker import (LongitudeFormatter, LatitudeFormatter,
                                    LatitudeLocator)
    
    fig = plt.figure(figsize=(8, 10))  # set reasonable width, height in inches
    axes = fig.subplots(2,2, subplot_kw={'projection':ccrs.Mercator()})
    axes = axes.ravel()
    
    # This plots all axes, adds most of map contents/components
    #  some maps' marginal info will be plotted in second for-loop
    for ax in axes:
        ax.coastlines()
        ax.set_extent([-120, -25, -45, 25])
    
        # plot grid lable text: left, right, bottom
        gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, 
                          linewidth=2, color='gray', alpha=0.5, linestyle='--')
    
        # manipulate the `gl` object as needed as follows
        # some of these are not accepted as options in the statment above (cause errors)
        gl.top_labels = False
        #gl.left_labels = True  #default already
        #gl.right_labels = True
        #gl.bottom_labels = True
    
        gl.xlines = True
        gl.ylines = True
    
        gl.ylocator = LatitudeLocator()
    
        gl.xformatter = LongitudeFormatter()
        gl.yformatter = LatitudeFormatter()
    
        gl.xlocator = mticker.MaxNLocator(6)
        gl.ylocator = mticker.MaxNLocator(6)
    
        # declare text size
        XTEXT_SIZE = 10
        YTEXT_SIZE = 10
        # to facilitate text rotation at bottom edge, ...
        # text justification: 'ha':'right' is used to avoid clashing with map's boundary
        # default of 'ha' is center, often causes trouble when text rotation is not zero
        gl.xlabel_style = {'size': XTEXT_SIZE, 'color': 'k', 'rotation':45, 'ha':'right'}
        gl.ylabel_style = {'size':YTEXT_SIZE, 'color': 'k', 'weight': 'normal'}
    
    # Second round of plots, do the top marginal texts in particular
    # also plot title text of each axes
    for ix,ax in enumerate(axes):
    
        gl2 = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, color='none')
        gl2.left_labels = False
        gl2.right_labels = False 
        gl2.bottom_labels = False
    
        gl2.xlines = False  # already plotted in first loop, so we hide them
        gl2.ylines = False
    
        XTEXT_SIZE = 12  # ***intended larger value***
        # to facilitate text rotation on top edge, ...
        # text justification: 'ha':'left' is used here to avoid clashing with map's boundary
        # default of 'ha' is center, often causes trouble when text rotation is not zero
        gl2.xlabel_style = {'size': XTEXT_SIZE, 'color': 'blue', 'rotation':45, 'ha':'left'}
    
        # plot title
        ax.set_title("Subplot:"+str(ix))
    
    plt.draw()  # draw/update enables better tight_layout adjustment
    plt.tight_layout()  # without plt.draw(), real tight!!
    plt.show()
    

    2x2plot-of-maps

    Answer to question 2 (concepts only):

    • use gl._labels to get access to all label texts, you get them as a list
    • iterate for each of them as a 'text object'
    • get/inspect styles/attributes of the 'text object'
    • manipulate the text styles/attributes individually

    Some comments

    • good question should focus on one topic only
    • your second question should be posted as a new question