I was trying to get a map projection using cartopy in python and it wasn't made so I'm trying to piece it together using to subplots with the following code:
fig = plt.figure(figsize =(25,13),facecolor='white')
gs = fig.add_gridspec(1,2,width_ratios=[4,2],height_ratios = [1], hspace=0.2,wspace=.0)
ax1=fig.add_subplot(gs[0,0],projection=ccrs.PlateCarree())
ax2=fig.add_subplot(gs[0,1],projection=ccrs.PlateCarree())
ax2.set_extent([-180,0,-90,90])
ax1.set_extent([-180,180,-90,90])
ax1.add_feature(cfeature.LAND, color = 'lightgray')
ax2.add_feature(cfeature.LAND, color = 'lightgray')
ax1.add_feature(cfeature.COASTLINE)
ax2.add_feature(cfeature.COASTLINE)
and I get the right projection I was looking for, however I am trying to remove the line between the two subplots and I keep getting issues, any suggestions?
Your question is a challenge as it is uncommon to plot a map with longitude extent greater than 360 degrees. What you have done is already a good achievement. What I will do just to finish your work.
Here is the code that produces the plot you need.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
#from shapely.geometry import Point, Polygon
import cartopy.feature as cfeature
import matplotlib.transforms as transforms
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
fig = plt.figure(figsize =(25,9.5), facecolor='white')
gs = fig.add_gridspec(1, 2, width_ratios=[4,2], height_ratios = [1], hspace=0.2, wspace=.0)
proj = ccrs.PlateCarree(central_longitude=0)
ax1=fig.add_subplot( gs[0,0], projection=proj )
ax2=fig.add_subplot( gs[0,1], projection=proj )
ax1.set_extent([-179.9, 180, -90, 90]) #Tricky, -180 not works!
ax2.set_extent([-179.9, 0, -90, 90])
ax1.add_feature(cfeature.LAND, color = 'lightgray')
ax2.add_feature(cfeature.LAND, color = 'lightgray')
ax1.add_feature(cfeature.COASTLINE)
ax2.add_feature(cfeature.COASTLINE)
# Set color of ax2's boundaries
# If set 'white' the gridline at that position will be gone!
ax2.outline_patch.set_edgecolor('lightgray') # set color to match other gridlines
# Draw 3 sides boundaries of ax2
# ------------------------------
# Define a `transformation`
# Signature: blended_transform_factory(x_transform, y_transform)
# the y coords of this transformation are data (as is = ax.transData)
# but the x coord are axes coordinate (0 to 1, ax.transAxes)
transAD = transforms.blended_transform_factory(ax2.transAxes, ax2.transData)
# Plot 3 lines around extents of ax2
# Color is intentionally set as 'red'
# You need to change it to 'black' for your work
ax2.plot([0.996, 0.996], [-90, 90], color='red', lw=2, transform=transAD)
ax2.plot([0.0, 0.996], [-90, -90], color='red', lw=2, transform=transAD)
ax2.plot([0.0, 0.996], [89.6, 89.6], color='red', lw=2, transform=transAD)
gl1 = ax1.gridlines(ccrs.PlateCarree(),
xlocs=range(-180,181,20),
ylocs=range(-90,90,10),
linestyle='-',
y_inline=False, x_inline=False,
color='b', alpha=0.6, linewidth=0.25, draw_labels=True)
gl1.xformatter = LONGITUDE_FORMATTER
gl1.yformatter = LATITUDE_FORMATTER
gl1.right_labels = False
gl2 = ax2.gridlines(ccrs.PlateCarree(),
xlocs=range(-180,180,20),
ylocs=range(-90,90,10),
linestyle='-',
y_inline=False, x_inline=False,
color='b', alpha=0.6, linewidth=0.25, draw_labels=True)
gl2.xformatter = LONGITUDE_FORMATTER
gl2.yformatter = LATITUDE_FORMATTER
gl2.left_labels = False
Related
I'm trying to create an inset figure that has a different projection from the parent. The only issue I have at this point is the inset figures's tick labels are not legible because they are black and blend in with the plot behind it. I could change the color of the ticks and labels to white, but that does not help when the data in ax0 yields lighter colors. Here is the MWE:
import calipsoFunctions as cf
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import numpy as np
import pylab as pl
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
from mpl_toolkits.axes_grid1.inset_locator import inset_axes, mark_inset, InsetPosition
x, y = np.arange(100), np.arange(200)
X, Y = np.meshgrid(x, y)
C = np.random.randint(0, 100, (200, 100))
fig = pl.figure(figsize=(6.5, 5.25))
gs0 = pl.GridSpec(3, 1)
gs0.update(left=0.08, right=0.925,
top=0.95, bottom=0.33,
hspace=0.10, wspace=0.0)
gs1 = pl.GridSpec(1, 2)
gs1.update(left=0.08, right=0.925,
top=0.225, bottom=0.05,
hspace=0.0, wspace=0.025)
# create primary axes
ax0 = pl.subplot(gs0[0])
ax1 = pl.subplot(gs0[1])
ax0.pcolormesh(X, Y, C, vmin=0, vmax=75)
ax1.pcolormesh(X, Y, C, vmin=0, vmax=75)
# add map plot (inset axis)
loc_box = [0.8, 0.55, 0.20, 0.45]
ax0_inset = fig.add_axes(loc_box,
projection=ccrs.PlateCarree(),
aspect="auto",
facecolor="w",
frameon=True)
lat_array = np.arange(-20, 20)
lon_array = np.arange(-10, 10, 0.5)
ax0_inset.plot(lat_array, lon_array, "k-", lw=1)
ip = InsetPosition(ax0, loc_box)
ax0_inset.set_axes_locator(ip)
ax0_inset.coastlines(resolution="10m", linewidth=0.25, color="k")
ax0_inset.add_feature(cfeature.LAND)
llat, ulat = lat_array.min(), lat_array.max()
llon, ulon = lon_array.min(), lon_array.max()
llat = np.round(llat / 10) * 10
ulat = np.round(ulat / 10) * 10
llon = np.round(llon / 5) * 5
ulon = np.round(ulon / 5) * 5
ax0_inset.set_yticks(np.arange(llat, ulat, 20), minor=False)
ax0_inset.set_yticks(np.arange(llat, ulat, 10), minor=True)
ax0_inset.set_yticklabels(np.arange(llat, ulat, 20),
fontsize=8)
ax0_inset.yaxis.set_major_formatter(LatitudeFormatter())
ax0_inset.set_xticks(np.arange(llon, ulon, 5), minor=False)
ax0_inset.set_xticks(np.arange(llon, ulon, 1), minor=True)
ax0_inset.set_xticklabels(np.arange(llon, ulon, 5),
fontsize=8,
rotation=45)
ax0_inset.xaxis.set_major_formatter(LongitudeFormatter())
ax0_inset.grid()
ax0_inset.tick_params(which="both",
axis="both",
direction="in",
labelsize=8)
fig.show()
Is there a way to change the background color of ax0_inset so that these tick labels are legible? I tried changing the face_color to "w", but that did not work. Ideally, I want the same behavior as ax0.figure.set_facecolor("w"), but for the ax0_inset axis. Is this doable?
Following #Mr. T's comment suggestion, a work-around solution could be:
# insert transparent (or opaque) rectangle around inset_axes plot
# to make axes labels more visible
# make buffer variable to control amount of buffer around inset_axes
buffer = 0.1 # fractional axes coordinates
# use ax inset tuple coords in loc_box to add rectangle patch
# [left, bottom, width, height] (fractional axes coordinates)
fig.add_patch(plt.Rectangle((
loc_box[0]-buffer, loc_box[1]-buffer),
loc_box[2]+buffer, loc_box[3]+buffer,
linestyle="-", edgecolor="k", facecolor="w",
linewidth=1, alpha=0.75, zorder=5,
transform=ax0.transAxes))
I was wondering if it's possible to plot all the steps from this single netcdf file into a separate plots.
Step 113 means that the current accessed data is for the date of October 22,2019. The Step 0 is July 1,2019. There are 135 time steps overall. Which means I need to produce 135 maps for each and single day.
#x,y,u,v for the maps
X=Data.longitude; Y=Data.latitude;
U=Data.u10[113]; V=Data.v10[113];
pm2p5=Data.pm2p5[113];
This is my code so far.
import xarray as xr
import cartopy.crs as ccrs
from cartopy import feature as cf
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
Data=xr.open_dataset('PMs ECMWF2.nc')
#x,y,u,v for the maps
X=Data.longitude; Y=Data.latitude;
U=Data.u10[113]; V=Data.v10[113];
pm2p5=Data.pm2p5[113];
nlon, nlat = np.meshgrid(X,Y)
fig, ax = plt.subplots(figsize=(12, 12), dpi=300)
# Add Plotting the plot
ax=plt.subplot(111,projection=ccrs.PlateCarree())
# Add Plot features
ax.add_feature(cf.BORDERS, linewidth=.5, edgecolor="yellow")
ax.coastlines('50m', linewidth=0.8)
ax.add_feature(cf.LAKES)
ax.add_feature(cf.OCEAN)
ax.add_feature(cf.BORDERS, edgecolor="yellow")
ax.add_feature(cf.COASTLINE, edgecolor="yellow")
ax.add_feature(cf.RIVERS)
ax.gridlines()
#changing the location of the map
ax.set_extent([90, 141, 24, -10])
# Add gridlines, and set their font size
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
linewidth=1, color='black', alpha=0.05, linestyle='-')
gl.top_labels = False
gl.left_labels = True
gl.right_labels = False
gl.xlines = True
gl.ylines = True
#colorbar
cmap = cm.get_cmap('jet') # Colour map coolwarm,hsv,bwr, seismic
# plotting the variables
pm2p5.plot(transform=ccrs.PlateCarree(), cbar_kwargs={'shrink': 0.5}, cmap=cmap)
plt.contour(nlon, nlat, pm2p5, fontsize=10,cmap=cmap) #plotting the contours
#plotting the quiver
ax.quiver(X[::3],Y[::3],U[::3,::3],V[::3,::3], color='white')
#plot title
#plt.title('Carbon Monoxide on October 22, 2019')
plt.show()
As of right now this code only produce one image. I have to do this over and over again.
One example of making a loop over time variable and maps of variable inside the loop, is here:
#!/usr/bin/env ipython
# ---------------------------
import numpy as np
from netCDF4 import Dataset,num2date
# ---------------------------
# let us generate data, preferably to the netcdf:
nx=50;ny=50;ntime=10;
A=np.random.random((ntime,ny,nx));
lon = np.linspace(0,360,nx);
lat = np.linspace(-90,90,ny);
time = np.linspace(0,366,ntime);timeunit = 'days since 2020-01-01 00:00:00'
fout = 'test.nc'
with Dataset(fout,'w') as f:
f.createDimension('longitude',nx);
f.createDimension('latitude',ny);
f.createDimension('time',);
xv = f.createVariable('longitude','float32',('longitude'));xv[:]=lon
yv = f.createVariable('latitude','float32',('latitude'));yv[:]=lat;
tv = f.createVariable('time','float32',('time'));tv[:]=time;tv.setncattr('units',timeunit);
u10 = f.createVariable('u10','float32',('time','latitude','longitude'));u10[:]=A;
v10 = f.createVariable('v10','float32',('time','latitude','longitude'));v10[:]=-A;
pm2 = f.createVariable('pm2p5','float32',('time','latitude','longitude'));pm2[:]=A;
# -----------------------------------------------
# let us now make some maps:
import xarray as xr
import cartopy.crs as ccrs
from cartopy import feature as cf
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
Data=xr.open_dataset(fout)
#x,y,u,v for the maps
X=Data.longitude; Y=Data.latitude;
time = Data.time;
for itime in range(len(time)):
U=Data.u10[itime]; V=Data.v10[itime];
pm2p5=Data.pm2p5[itime];
nlon, nlat = np.meshgrid(X,Y)
fig, ax = plt.subplots(figsize=(12, 12), dpi=300)
# Add Plotting the plot
ax=plt.subplot(111,projection=ccrs.PlateCarree())
# Add Plot features
ax.add_feature(cf.BORDERS, linewidth=.5, edgecolor="yellow")
ax.coastlines('50m', linewidth=0.8)
ax.add_feature(cf.LAKES)
ax.add_feature(cf.OCEAN)
ax.add_feature(cf.BORDERS, edgecolor="yellow")
ax.add_feature(cf.COASTLINE, edgecolor="yellow")
ax.add_feature(cf.RIVERS)
ax.gridlines()
#changing the location of the map
ax.set_extent([90, 141, 24, -10])
# Add gridlines, and set their font size
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,
linewidth=1, color='black', alpha=0.05, linestyle='-')
gl.top_labels = False
gl.left_labels = True
gl.right_labels = False
gl.xlines = True
gl.ylines = True
#colorbar
cmap = cm.get_cmap('jet') # Colour map coolwarm,hsv,bwr, seismic
# plotting the variables
pm2p5.plot(transform=ccrs.PlateCarree(), cbar_kwargs={'shrink': 0.5}, cmap=cmap)
plt.contour(nlon, nlat, pm2p5, fontsize=10,cmap=cmap) #plotting the contours
#plotting the quiver
ax.quiver(X[::3],Y[::3],U[::3,::3],V[::3,::3], color='white')
#plot title
#plt.title('Carbon Monoxide on October 22, 2019')
plt.savefig('fig_'+str(itime).rjust(3,'0')+'.png',bbox_inches='tight');
plt.show();
# ----------------------------
I first generated a netCDF with random values and then plotted the variables for each time moment.
I'd like to draw a lognormal distribution of a given bar plot.
Here's the code
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
import numpy as np; np.random.seed(1)
import scipy.stats as stats
import math
inter = 33
x = np.logspace(-2, 1, num=3*inter+1)
yaxis = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.01,0.03,0.3,0.75,1.24,1.72,2.2,3.1,3.9,
4.3,4.9,5.3,5.6,5.87,5.96,6.01,5.83,5.42,4.97,4.60,4.15,3.66,3.07,2.58,2.19,1.90,1.54,1.24,1.08,0.85,0.73,
0.84,0.59,0.55,0.53,0.48,0.35,0.29,0.15,0.15,0.14,0.12,0.14,0.15,0.05,0.05,0.05,0.04,0.03,0.03,0.03, 0.02,
0.02,0.03,0.01,0.01,0.01,0.01,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0,0]
fig, ax = plt.subplots()
ax.bar(x[:-1], yaxis, width=np.diff(x), align="center", ec='k', color='w')
ax.set_xscale('log')
plt.xlabel('Diameter (mm)', fontsize='12')
plt.ylabel('Percentage of Total Particles (%)', fontsize='12')
plt.ylim(0,8)
plt.xlim(0.01, 10)
fig.set_size_inches(12, 12)
plt.savefig("Test.png", dpi=300, bbox_inches='tight')
Resulting plot:
What I'm trying to do is to draw the Probability Density Function exactly like the one shown in red in the graph below:
An idea is to convert everything to logspace, with u = log10(x). Then draw the density histogram in there. And also calculate a kde in the same space. Everything gets drawn as y versus u. When we have u at a top twin axes, x can stay at the bottom. Both axes get aligned by setting the same xlims, but converted to logspace on the top axis. The top axis can be hidden to get the desired result.
import matplotlib.pyplot as plt
import numpy as np
from scipy import stats
inter = 33
u = np.linspace(-2, 1, num=3*inter+1)
x = 10**u
us = np.linspace(u[0], u[-1], 500)
yaxis = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.01,0.03,0.3,0.75,1.24,1.72,2.2,3.1,3.9,
4.3,4.9,5.3,5.6,5.87,5.96,6.01,5.83,5.42,4.97,4.60,4.15,3.66,3.07,2.58,2.19,1.90,1.54,1.24,1.08,0.85,0.73,
0.84,0.59,0.55,0.53,0.48,0.35,0.29,0.15,0.15,0.14,0.12,0.14,0.15,0.05,0.05,0.05,0.04,0.03,0.03,0.03, 0.02,
0.02,0.03,0.01,0.01,0.01,0.01,0.01,0.0,0.0,0.0,0.0,0.0,0.01,0,0]
yaxis = np.array(yaxis)
# reconstruct data from the given frequencies
u_data = np.repeat((u[:-1] + u[1:]) / 2, (yaxis * 100).astype(np.int))
kde = stats.gaussian_kde((u[:-1]+u[1:])/2, weights=yaxis, bw_method=0.2)
total_area = (np.diff(u)*yaxis).sum() # total area of all bars; divide by this area to normalize
fig, ax = plt.subplots()
ax2 = ax.twiny()
ax2.bar(u[:-1], yaxis, width=np.diff(u), align="edge", ec='k', color='w', label='frequencies')
ax2.plot(us, total_area*kde(us), color='crimson', label='kde')
ax2.plot(us, total_area * stats.norm.pdf(us, u_data.mean(), u_data.std()), color='dodgerblue', label='lognormal')
ax2.legend()
ax.set_xscale('log')
ax.set_xlabel('Diameter (mm)', fontsize='12')
ax.set_ylabel('Percentage of Total Particles (%)', fontsize='12')
ax.set_ylim(0,8)
xlim = np.array([0.01,10])
ax.set_xlim(xlim)
ax2.set_xlim(np.log10(xlim))
ax2.set_xticks([]) # hide the ticks at the top
plt.tight_layout()
plt.show()
PS: Apparently this also can be achieved directly without explicitly using u (at the cost of being slightly more cryptic):
x = np.logspace(-2, 1, num=3*inter+1)
xs = np.logspace(-2, 1, 500)
total_area = (np.diff(np.log10(x))*yaxis).sum() # total area of all bars; divide by this area to normalize
kde = gaussian_kde((np.log10(x[:-1])+np.log10(x[1:]))/2, weights=yaxis, bw_method=0.2)
ax.bar(x[:-1], yaxis, width=np.diff(x), align="edge", ec='k', color='w')
ax.plot(xs, total_area*kde(np.log10(xs)), color='crimson')
ax.set_xscale('log')
Note that the bandwidth set for gaussian_kde is a somewhat arbitrarily value. Larger values give a more equalized curve, smaller values keep closer to the data. Some experimentation can help.
I want to plot a time series of a damped random walk in one subplot and then zoom into it in a second subplot. I know mark_inset from matplotlib, which works fine. The code I have so far is:
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
from astroML.time_series import generate_damped_RW
fig = plt.figure()
ax = fig.add_subplot(111)
ax0 = fig.add_subplot(211)
ax1 = fig.add_subplot(212)
ax.set_ylabel('Brightness[mag]')
ax.yaxis.labelpad=30
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.spines['left'].set_color('none')
ax.spines['right'].set_color('none')
ax.tick_params(labelcolor='w', top='off', bottom='off', left='off',
right='off')
t = np.linspace(0, 5000, 100000)
data = generate_damped_RW(t, tau=100, xmean=20, z=0, SFinf=0.3,
random_state=1)
ax0.scatter(t, data, s=0.5)
ax0.text(1, 1, r'$E(m) = %.2f, \sigma(m) = %.2f$'%(np.mean(data),
np.std(data)),
verticalalignment='top', horizontalalignment='right',
transform=ax0.transAxes, fontsize=23)
mask = (t > 370) & (t < 470)
ax1.set_xlabel('Time[years]')
ax1.scatter(t[mask], data[mask], s=0.5)
mark_inset(ax0, ax1, loc1=2, loc=1, fc='none')
which creates a plot like this:
Which is almost what I want, except that the lines connecting the 2 subplots start at the upper edges of the box in the first subplot. Is it possible to have those start at the lower two edges while they still end up at the upper two in the second subplot? What would I have to do to achieve this?
The mark_inset has two arguments loc1 and loc2 to set the locations of the two connectors. Those locations are then the same for the box and and the inset axes.
We may however add two new arguments to the mark_inset function to set different locations for the start and end of the connector.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import TransformedBbox, BboxPatch, BboxConnector
import numpy as np
fig, (ax, axins) = plt.subplots(nrows=2)
x = np.linspace(0,6*np.pi)
y = np.sin(x)
ax.plot(x,y)
axins.plot(x,y)
axins.set_xlim((2*np.pi, 2.5*np.pi))
axins.set_ylim((0, 1))
# draw a bbox of the region of the inset axes in the parent axes and
# connecting lines between the bbox and the inset axes area
# loc1, loc2 : {1, 2, 3, 4}
def mark_inset(parent_axes, inset_axes, loc1a=1, loc1b=1, loc2a=2, loc2b=2, **kwargs):
rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData)
pp = BboxPatch(rect, fill=False, **kwargs)
parent_axes.add_patch(pp)
p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1a, loc2=loc1b, **kwargs)
inset_axes.add_patch(p1)
p1.set_clip_on(False)
p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2a, loc2=loc2b, **kwargs)
inset_axes.add_patch(p2)
p2.set_clip_on(False)
return pp, p1, p2
mark_inset(ax, axins, loc1a=1, loc1b=4, loc2a=2, loc2b=3, fc="none", ec="crimson")
plt.draw()
plt.show()
Unfortunately, mark_inset always has to connect the same corners (i.e. bottom right always has to connect to bottom right, etc.).
We can make our own function that mimics the mark_inset function though, to connect the two bottom corners with the two top corners in the inset (custom_mark_inset in the code below).
This makes use of a Rectangle patch to draw the box on the primary axes, and the ConnectionPatch instances to draw the connecting lines between axes.
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
#from astroML.time_series import generate_damped_RW
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
ax0 = fig.add_subplot(211)
ax1 = fig.add_subplot(212)
ax.set_ylabel('Brightness[mag]')
ax.yaxis.labelpad=30
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.spines['left'].set_color('none')
ax.spines['right'].set_color('none')
ax.tick_params(labelcolor='w', top='off', bottom='off', left='off',
right='off')
t = np.linspace(0, 5000, 10000)
#data = generate_damped_RW(t, tau=100, xmean=20, z=0, SFinf=0.3,
# random_state=1)
## Fake some data
data = np.sin(t/800.) + 20.
ax0.scatter(t, data, s=0.5)
ax0.text(1, 1, r'$E(m) = %.2f, \sigma(m) = %.2f$'%(np.mean(data),
np.std(data)),
verticalalignment='top', horizontalalignment='right',
transform=ax0.transAxes, fontsize=23)
mask = (t > 370) & (t < 470)
ax1.set_xlabel('Time[years]')
ax1.scatter(t[mask], data[mask], s=0.5)
def custom_mark_inset(axA, axB, fc='None', ec='k'):
xx = axB.get_xlim()
yy = axB.get_ylim()
xy = (xx[0], yy[0])
width = xx[1] - xx[0]
height = yy[1] - yy[0]
pp = axA.add_patch(patches.Rectangle(xy, width, height, fc=fc, ec=ec))
p1 = axA.add_patch(patches.ConnectionPatch(
xyA=(xx[0], yy[0]), xyB=(xx[0], yy[1]),
coordsA='data', coordsB='data',
axesA=axA, axesB=axB))
p2 = axA.add_patch(patches.ConnectionPatch(
xyA=(xx[1], yy[0]), xyB=(xx[1], yy[1]),
coordsA='data', coordsB='data',
axesA=axA, axesB=axB))
return pp, p1, p2
pp, p1, p2 = custom_mark_inset(ax0, ax1)
plt.show()
I want plot the axis in a zebra style similar to this:
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:
I've got a hacky solution that's working for my purposes:
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(),
],
)