-
Notifications
You must be signed in to change notification settings - Fork 368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Zebra style axis for map #1830
Comments
I have the beginning of an answer (which probably is not very general, but might help someone craft a better answer). I'm plotting alternative black/white lines with black path effects between every tick location. https://gist.github.com/scottstanie/dff0d597e636440fb60b3c5443f70cae 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) And here's the function itself 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(),
],
) |
Thanks for this effort! I think this is close enough although I didn't test other SRS. I also believe this approach is similar to what the @idl-coyote uses in the above example. |
My try based on @scottstanie . The white frame seems not shown on my computer: So I update code to deal with the frame linewidth I use the code onto EquidistantConic projection. Unfortunatelly I don't know how to hide1 the out of extent part in Cartopy. Some gridline labels are also missing. import itertools
from matplotlib.patheffects import Stroke, Normal
import numpy as np
import cartopy.mpl.geoaxes
def zebra_frame(self, lw=2, crs=None, zorder=None):
# Alternate black and white line segments
bws = itertools.cycle(["k", "w"])
self.spines["geo"].set_visible(False)
left, right, bottom, top = self.get_extent()
# xticks = sorted([left, *self.get_xticks(), right])
xticks = sorted([*self.get_xticks()])
xticks = np.unique(np.array(xticks))
# yticks = sorted([bottom, *self.get_yticks(), top])
yticks = sorted([*self.get_yticks()])
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 = [[yticks[0], yticks[0]], [yticks[-1], yticks[-1]]]
else:
xs = [[xticks[0], xticks[0]], [xticks[-1], xticks[-1]]]
ys = [[start, end], [start, end]]
# For first and last lines, used the "projecting" effect
capstyle = "butt" if idx not in (0, len(ticks) - 2) else "projecting"
for (xx, yy) in zip(xs, ys):
self.plot(xx, yy, color=bw, linewidth=max(0, lw - self.spines["geo"].get_linewidth()*2), clip_on=False,
transform=crs, zorder=zorder, solid_capstyle=capstyle,
# Add a black border to accentuate white segments
path_effects=[
Stroke(linewidth=lw, foreground="black"),
Normal(),
],
)
setattr(cartopy.mpl.geoaxes.GeoAxes, 'zebra_frame', zebra_frame) import cartopy.crs as ccrs
import matplotlib.pyplot as plt
crs = ccrs.EquidistantConic(central_longitude=-90)
fig = plt.figure(figsize=(8, 4))
ax = fig.add_subplot(
1, 1, 1, projection=crs)
ax.coastlines()
ax.set_extent((-125, -60, 0, 30), crs=ccrs.PlateCarree())
ax.set_xticks(np.arange(-120, -60+1, 5))
ax.set_yticks(np.arange(0, 30+1, 5))
ax.set_axis_off()
ax.gridlines(draw_labels=True, dms=True, linestyle='--',
# # x_inline=True, y_inline=True,
xlocs=np.arange(-120, -60+1, 15), ylocs=np.arange(0, 30+1, 15))
ax.zebra_frame(lw=5, crs=ccrs.PlateCarree(), zorder=3)
plt.show() Footnotes
|
Many thanks @wohenbushuang. You may also need to import numpy as np in the second script file. |
Based on @wohenbushuang version, I added an option to allow the frame to be plotted using the map extent instead of following the latlon paths:
Thank you for all the contributions. @wohenbushuang and @scottstanie |
This looks great! It sounds like (https://stackoverflow.com/questions/57313303/how-to-plot-zebra-style-axis-in-matplotlib) you will make it into a pull request? |
Problem
Zebra style map frame is a nice feature supported in many map applications including ArcGIS, ENVI/IDL, GMT.
http://www.idlcoyote.com/idldoc/cg/cgdrawshapes.html
https://gmt-tutorials.org/en/making_first_map.html
Proposed solution
Add a flag to turn on this feature if plotting a map with a map structure.
Additional context and prior art
https://stackoverflow.com/questions/57313303/how-to-plot-zebra-style-axis-in-matplotlib
The text was updated successfully, but these errors were encountered: