Search code examples
pythonmatplotlibcartopy

How to plot zebra style axis in matplotlib


I want plot the axis in a zebra style similar to this:

Zebra border axis

Below is my code:

import matplotlib.pyplot as plt
import cartopy.io.shapereader as shpreader
import cartopy.crs as ccrs
from cartopy.feature  import ShapelyFeature

fig, ax = plt.figure(figsize=(12,9),  dpi=150 )
sFilename_shapefile = './some_shape.shp'
pShapeReader = shpreader.Reader(sFilename_shapefile)
pProjection_map = ccrs.PlateCarree()
aShapeFeature = ShapelyFeature(pShapeReader.geometries(), 
  pProjection_map, facecolor='grey', edgecolor='grey', 
  linewidth=0.5)
ax.add_feature(aShapeFeature, zorder = 4)
plt.show()

What I got is like this:

Normal border axis


Solution

  • I've got a hacky solution that's working for my purposes:

    enter image description here

    The example usage:

    import cartopy.crs as ccrs
    import matplotlib.pyplot as plt
    
    crs = ccrs.PlateCarree()
    
    fig = plt.figure(figsize=(5, 2))
    ax = fig.add_subplot(projection=crs)
    
    ax.coastlines()
    ax.set_extent((-125, -85, 22, 42))
    ax.set_xticks((-120, -110, -100, -90))
    ax.set_yticks((25, 30, 35, 40))
    
    add_zebra_frame(ax, crs=crs)
    

    I've put the frame in a function for now. It likely will not work for many polar-type projections that mix lat/lon ticks, and right now it doesn't work that well if you don't specify which tick marks you want (I'm still unclear how Cartopy picks the default ticks).

    https://gist.github.com/scottstanie/dff0d597e636440fb60b3c5443f70cae

    Basically all I'm doing is turning off the spines and plotting an alternating black/white line between each of the xticks/yticks.

    import itertools
    import matplotlib.patheffects as pe
    import numpy as np
    
    def add_zebra_frame(ax, lw=2, crs="pcarree", zorder=None):
    
        ax.spines["geo"].set_visible(False)
        left, right, bot, top = ax.get_extent()
        
        # Alternate black and white line segments
        bws = itertools.cycle(["k", "white"])
    
        xticks = sorted([left, *ax.get_xticks(), right])
        xticks = np.unique(np.array(xticks))
        yticks = sorted([bot, *ax.get_yticks(), top])
        yticks = np.unique(np.array(yticks))
        for ticks, which in zip([xticks, yticks], ["lon", "lat"]):
            for idx, (start, end) in enumerate(zip(ticks, ticks[1:])):
                bw = next(bws)
                if which == "lon":
                    xs = [[start, end], [start, end]]
                    ys = [[bot, bot], [top, top]]
                else:
                    xs = [[left, left], [right, right]]
                    ys = [[start, end], [start, end]]
    
                # For first and lastlines, used the "projecting" effect
                capstyle = "butt" if idx not in (0, len(ticks) - 2) else "projecting"
                for (xx, yy) in zip(xs, ys):
                    ax.plot(
                        xx,
                        yy,
                        color=bw,
                        linewidth=lw,
                        clip_on=False,
                        transform=crs,
                        zorder=zorder,
                        solid_capstyle=capstyle,
                        # Add a black border to accentuate white segments
                        path_effects=[
                            pe.Stroke(linewidth=lw + 1, foreground="black"),
                            pe.Normal(),
                        ],
                    )