Cartopy - multiple arrows using annotate - cartopy

Using this solution as base, is it possible to create multiple arrows emanating from the same source to different targets? e.g. Delhi -> Beijing (116.4, 39.9), Delhi -> Cairo (30.0, 31.2), Delhi -> Tokyo (35.6, 139.6)?
When I repeat the code below, I only get the first arrow.
#Dehli - Beijing
ax.annotate('Beijing', xy=(116.4, 39.9), xycoords=transform,
size=40,
)
ax.annotate('Delhi', xy=(113, 40.5), xytext=(77.23, 28.61),
size=40,
arrowprops=dict(facecolor='red', ec = 'none',
arrowstyle="fancy",
connectionstyle="arc3,rad=-0.2",
),
xycoords=transform,
)
#Dehli - Cairo
ax.annotate('Cairo', xy=(-6.26, 53.34), xycoords=transform,
size=40,
)
ax.annotate('Delhi', xy=(113, 40.5), xytext=(77.23, 28.61),
size=40,
arrowprops=dict(facecolor='red', ec = 'none',
arrowstyle="fancy",
connectionstyle="arc3,rad=-0.2",
),
xycoords=transform,
)
Alternatively, is there a way to put .annotate into this expression which I'm using at present to draw connecting lines. I've tried to no avail:
#Coordinates
lon_dehl, lat_dehl = 116.4, 39.9
lon_beij, lat_beij = 77.59, 12.97
lon_toky, lat_toky = 35.6, 139.6
lon_cair, lat_cair = 30.0, 31.2
plt.plot([lon_dehl, lon_beij], [lat_dehl, lat_beij],
linewidth=2,
linestyle='solid',
solid_capstyle='round',
color='#cb2c31',
marker='o',
markersize=6,
markeredgewidth=None,
markeredgecolor='#cb2c31',
transform=ccrs.PlateCarree(),
)

This isn't perfect (in fact, I'd welcome any improvements), but I achieved multiple arrows with annotate.
The theory is: use the same source for all arrows, but alter the target lat-lons (or more correctly, lon-lat). Seems obvious now.
Also, don't use annotate for city names. Annotate seems to put the name at the start of the arrow rather than the endpoint.
As I say, I'd welcome any suggestions for improvements (incl. for labelling).
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from pyproj import Proj, transform
def main():
ax = plt.axes(projection=ccrs.PlateCarree())
ax.set_extent([-150, 60, -25, 60])
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.BORDERS)
#Labels - city locations & names
ax.plot(77.20, 28.61, 'bo', markersize=7, transform=ccrs.Geodetic())
ax.text(65, 33, 'Dehli', transform=ccrs.Geodetic())
ax.plot(139.69, 35.68, 'bo', markersize=7, transform=ccrs.Geodetic())
ax.text(139.69, 35.68, 'Tokyo', transform=ccrs.Geodetic())
ax.plot(0.12, 51.50, 'bo', markersize=7, transform=ccrs.Geodetic())
ax.text(0.12, 51.50, 'London', transform=ccrs.Geodetic())
ax.plot(-71.05, 42.36, 'bo', markersize=7, transform=ccrs.Geodetic())
ax.text(-71.05, 42.36, 'New York', transform=ccrs.Geodetic())
ax.plot(151.81, -33.86, 'bo', markersize=7, transform=ccrs.Geodetic())
ax.text(151.81, -33.86, 'Sydney', transform=ccrs.Geodetic())
ax.plot(-43.2, -22.9, 'bo', markersize=7, transform=ccrs.Geodetic())
ax.text(-43.2, -22.9, 'Rio', transform=ccrs.Geodetic())
#Arrows lines
transform = ccrs.PlateCarree()._as_mpl_transform(ax)
#Dehli to Tokyo
ax.annotate('', xy=(139.69, 35.68), xytext=(77.20, 28.61),
xycoords='data',
size=20,
arrowprops=dict(facecolor='red', ec = 'none',
arrowstyle="fancy",
connectionstyle="arc3,rad=-0.3"))
#Dehli to London
ax.annotate('', xy=(0.12, 51.50), xytext=(77.20, 28.61),
size=10,
xycoords='data',
arrowprops=dict(facecolor='red', ec = 'none',
arrowstyle="fancy",
connectionstyle="arc3,rad=-0.3"))
#Dehli to New York
ax.annotate('', xy=(-71.05, 42.36), xytext=(77.20, 28.61),
xycoords='data',
size=30,
arrowprops=dict(facecolor='red', ec = 'none',
arrowstyle="fancy",
connectionstyle="arc3,rad=-0.3"))
#Dehli to Sydney
ax.annotate('', xy=(151.81, -33.86), xytext=(77.20, 28.61),
xycoords='data',
size=10,
arrowprops=dict(facecolor='red', ec = 'none',
arrowstyle="fancy",
connectionstyle="arc3,rad=-0.3"))
#Dehli to Rio
ax.annotate('', xy=(-43.2, -22.9), xytext=(77.20, 28.61),
xycoords='data',
size=20,
arrowprops=dict(facecolor='red', ec = 'none',
arrowstyle="fancy",
connectionstyle="arc3,rad=-0.3")
)
#plt.tight_layout()
plt.show()
if __name__ == '__main__':
main()

Related

Facing weird problem when trying to plot simple lat/ lon points

I have following dataframe (corresponding csv is hosted here: http://www.sharecsv.com/s/3795d862c1973efa311d8a770e978215/t.csv)
lat lon
count 6159.000000 6159.000000
mean 37.764859 -122.355491
std 0.028214 0.038874
min 37.742200 -122.482783
25% 37.746317 -122.360133
50% 37.746417 -122.333717
75% 37.785825 -122.331300
max 37.818133 -122.331167
Following code plots correctly:
test_ax = plt.axes(projection=ccrs.Mercator())
test_ax.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0,
transform=ccrs.Geodetic())
plt.show()
But if I take one subset, it doesn't:
test_ax = plt.axes(projection=ccrs.Mercator())
test_ax.plot(test_df['lon'][:1001], test_df['lat'][:1001], color="blue", linewidth=4, alpha=1.0,
transform=ccrs.Geodetic())
plt.show()
But does so with another subset.
test_ax = plt.axes(projection=ccrs.Mercator())
test_ax.plot(test_df['lon'][:3501], test_df['lat'][:3501], color="blue", linewidth=4, alpha=1.0,
transform=ccrs.Geodetic())
plt.show()
I am pretty sure I am doing something stupid, but I am just unable to figure the reason for this behaviour.
Edit:
On further experimentation I found that if I set the extent of map manually to include 0 meridian, the plot for the subset :1001, which wasn't showing earlier starts showing (the blue dot near San Francisco).
test_ax = plt.axes(projection=ccrs.Mercator())
test_ax.plot(test_df['lon'][:1001], test_df['lat'][:1001], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.set_extent([-130, 0, 30, 40], crs=ccrs.Geodetic())
test_ax.gridlines(draw_labels=True)
plt.show()
Edit: with reproducible example
(For jupyter notebook)
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import pandas as pd
df_csv_url = 'http://www.sharecsv.com/dl/76dd767525a37180ca54cd1d9314b9dc/t1.csv'
test_df = pd.read_csv(df_csv_url)
figure_params = { 'width': 9.6, 'height': 5.4 }
fig = plt.figure(
figsize=(figure_params["width"], figure_params["height"])
)
test_ax = fig.add_axes((0, 0.5, 0.5, 0.5), projection=ccrs.Mercator(), label="map1")
test_ax.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.gridlines(draw_labels=True)
test_ax.set_title("Path doesn\'t show", y=1.5)
# Including 0 meridian in extent shows the path
test_ax1 = fig.add_axes((0, 0, 0.5, 0.5), projection=ccrs.Mercator(), label="map2")
test_ax1.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax1.set_extent([-130, 0, 30, 40], crs=ccrs.Geodetic())
test_ax1.coastlines()
test_ax1.gridlines(draw_labels=True)
test_ax1.set_title("Path shows (blue dot near San Francisco)", y=1.1)
plt.show()
Edit
(with simplified reproducible example)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
test_df = pd.DataFrame()
test_df['lon'] = np.linspace(-120, -60, num=1000)
test_df['lat'] = 38
test_df1 = pd.DataFrame()
test_df1['lon'] = np.linspace(-120, -60, num=1001)
test_df1['lat'] = 38
fig = plt.figure()
meridian=0
test_ax = fig.add_axes((0, 0, 1, 0.6), projection=ccrs.Mercator())
test_ax.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.set_extent((-125, meridian, 36, 38))
gl = test_ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
test_ax.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
test_ax1 = fig.add_axes((0, 0.7, 1, 0.6), projection=ccrs.Mercator())
test_ax1.plot(test_df1['lon'], test_df1['lat'], color="red", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax1.coastlines()
test_ax1.set_extent((-125, meridian, 36, 38))
gl1 = test_ax1.gridlines(draw_labels=True)
gl1.xlabels_top = False
gl1.ylabels_left = False
test_ax1.set_title('Path with {} points, eastern edge={}'.format(len(test_df1),meridian))
meridian=-10
test_ax2 = fig.add_axes((0, 1.4, 1, 0.6), projection=ccrs.Mercator())
test_ax2.plot(test_df['lon'], test_df['lat'], color="black", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax2.coastlines()
test_ax2.set_extent((-125, -10, 36, 38))
gl2 = test_ax2.gridlines(draw_labels=True)
gl2.xlabels_top = False
gl2.ylabels_left = False
test_ax2.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
test_ax3 = fig.add_axes((0, 2.1, 1, 0.6), projection=ccrs.Mercator())
test_ax3.plot(test_df1['lon'], test_df1['lat'], color="green", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax3.coastlines()
test_ax3.set_extent((-125, -10, 36, 38))
gl3 = test_ax3.gridlines(draw_labels=True)
gl3.xlabels_top = False
gl3.ylabels_left = False
test_ax3.set_title('Path with {} points, eastern edge={}'.format(len(test_df1),meridian))
plt.show()
Given there seems to be a some issue with cartopy at play, the best work around I see is to split your data into chunks of less than 1000, and then plot all parts of it.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
test_df = pd.DataFrame()
test_df['lon'] = np.linspace(-120, -60, num=1001)
test_df['lat'] = 38
fig = plt.figure()
meridian=0
test_ax = fig.add_axes((0, 0.05, 1, 0.3), projection=ccrs.Mercator())
test_ax.plot(test_df['lon'], test_df['lat'], color="red",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.set_extent((-125, meridian, 36, 38))
gl = test_ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
test_ax.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
meridian=-10
test_ax3 = fig.add_axes((0, 0.55, 1, 0.3), projection=ccrs.Mercator())
# plot first 500
test_ax3.plot(test_df['lon'][:500], test_df['lat'][:500], color="green",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
# plot to cover the gap
test_ax3.plot(test_df['lon'][499:501], test_df['lat'][499:501], color="blue",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
# plot last 501
test_ax3.plot(test_df['lon'][500:], test_df['lat'][500:], color="yellow",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax3.coastlines()
test_ax3.set_extent((-125, -10, 36, 38))
gl3 = test_ax3.gridlines(draw_labels=True)
gl3.xlabels_top = False
gl3.ylabels_left = False
test_ax3.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
plt.show()
For the case of 1001 points, I just split it into a section of 500 points and a section of 501 points.
Since you are plotting the line, I also added the plot to cover the gap, shown in blue when you zoom in.
The reason to set up the gap filler instead of overlapping the two sections comes in if you are also plotting the points, like this:
test_ax3.plot(test_df['lon'][:500], test_df['lat'][:500], color="green",
linewidth=1, alpha=1.0, transform=ccrs.Geodetic(), marker='.')
# plot to cover the gap
test_ax3.plot(test_df['lon'][499:501], test_df['lat'][499:501], color="blue",
linewidth=1, alpha=1.0, transform=ccrs.Geodetic(), marker=None)
# plot last 501
test_ax3.plot(test_df['lon'][500:], test_df['lat'][500:], color="yellow",
linewidth=1, alpha=1.0, transform=ccrs.Geodetic(), marker='.')
By separating out the filler, you can make sure you aren't duplicating points, which could be an issue if you have an alpha value of less than 1.0.
Applying this to your original data, you can create a function to loop over the dataframe in chunks equal to whatever size you want.:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import pandas as pd
PLOT_LIMIT = 1000
df_csv_url = 'http://www.sharecsv.com/dl/76dd767525a37180ca54cd1d9314b9dc/t1.csv'
test_df = pd.read_csv(df_csv_url)
figure_params = { 'width': 9.6, 'height': 5.4 }
fig = plt.figure(
figsize=(figure_params["width"], figure_params["height"])
)
print(len(test_df['lon']))
def ax_plot(test_ax, test_df):
# this function will loop over the dataframe in chunks equal to PLOT_LIMIT
len_df = len(test_df)
n=0
for i in range(len_df//PLOT_LIMIT):
test_ax.plot(test_df['lon'][1000*i:1000*(i+1)], test_df['lat'][1000*i:1000*(i+1)], color="blue",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
if (len_df-((n+1)*PLOT_LIMIT)) != 0:
test_ax.plot(test_df['lon'][(1000*i)-1:(1000*(i+1))+1], test_df['lat'][(1000*i)-1:(1000*(i+1))+1], color="blue",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic(), marker='None')
n+=1
test_ax.plot(test_df['lon'][1000*n:], test_df['lat'][1000*n:], color="blue",
linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax1 = fig.add_axes((0, 0.55, 1, 0.45), projection=ccrs.Mercator(), label="map1")
ax_plot(test_ax1, test_df)
test_ax1.coastlines()
test_ax1.gridlines(draw_labels=True)
test_ax1.set_title("Path shows", y=1.5)
# Including 0 meridian in extent shows the path
test_ax2 = fig.add_axes((0, 0.1, 1, 0.45), projection=ccrs.Mercator(), label="map2")
ax_plot(test_ax2, test_df)
test_ax2.set_extent([-130, -30, 30, 40], crs=ccrs.Geodetic())
test_ax2.coastlines()
test_ax2.gridlines(draw_labels=True)
test_ax2.set_title("Path shows (blue dot near San Francisco)", y=1.1)
plt.show()
As you can see, you should now have flexibility in setting the viewing window on the map. I haven't checked edge cases like crossing the antimeridian, but in the cases presented it is working.
I have been able to find another workaround. If the points are transformed prior to using the plot function (instead of passing the transform parameter), it works.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
test_df = pd.DataFrame()
test_df['lon'] = np.linspace(-120, -60, num=1000)
test_df['lat'] = 38
test_df1 = pd.DataFrame()
test_df1['lon'] = np.linspace(-120, -60, num=1001)
test_df1['lat'] = 38
fig = plt.figure()
meridian=0
test_ax = fig.add_axes((0, 0, 1, 0.6), projection=ccrs.Mercator())
test_ax.plot(test_df['lon'], test_df['lat'], color="blue", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax.coastlines()
test_ax.set_extent((-125, meridian, 36, 38))
gl = test_ax.gridlines(draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
test_ax.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
test_ax1 = fig.add_axes((0, 0.7, 1, 0.6), projection=ccrs.Mercator())
test_ax1.plot(test_df1['lon'], test_df1['lat'], color="red", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax1.coastlines()
test_ax1.set_extent((-125, meridian, 36, 38))
gl1 = test_ax1.gridlines(draw_labels=True)
gl1.xlabels_top = False
gl1.ylabels_left = False
test_ax1.set_title('Path with {} points, eastern edge={}'.format(len(test_df1),meridian))
meridian=-10
test_ax2 = fig.add_axes((0, 1.4, 1, 0.6), projection=ccrs.Mercator())
test_ax2.plot(test_df['lon'], test_df['lat'], color="black", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax2.coastlines()
test_ax2.set_extent((-125, -10, 36, 38))
gl2 = test_ax2.gridlines(draw_labels=True)
gl2.xlabels_top = False
gl2.ylabels_left = False
test_ax2.set_title('Path with {} points, eastern edge={}'.format(len(test_df),meridian))
test_ax3 = fig.add_axes((0, 2.1, 1, 0.6), projection=ccrs.Mercator())
test_ax3.plot(test_df1['lon'], test_df1['lat'], color="green", linewidth=4, alpha=1.0, transform=ccrs.Geodetic())
test_ax3.coastlines()
test_ax3.set_extent((-125, -10, 36, 38))
gl3 = test_ax3.gridlines(draw_labels=True)
gl3.xlabels_top = False
gl3.ylabels_left = False
test_ax3.set_title('Path with {} points, eastern edge={}'.format(len(test_df1),meridian))
test_ax4 = fig.add_axes((0, 2.8, 1, 0.6), projection=ccrs.Mercator())
# Instead of transforming within the plot function, transform and then plot
transformed_points = ccrs.Mercator().transform_points(ccrs.Geodetic(), test_df1['lon'].values, test_df1['lat'].values)
test_ax4.plot([p[0] for p in transformed_points], [p[1] for p in transformed_points], color="green", linewidth=4, alpha=1.0)
test_ax4.coastlines()
test_ax4.set_extent((-125, -10, 36, 38))
gl3 = test_ax4.gridlines(draw_labels=True)
gl3.xlabels_top = False
gl3.ylabels_left = False
test_ax4.set_title('Path with {} prior transformed points, eastern edge={}'.format(len(test_df1),meridian))
plt.show()

How can a color fill be done between two histogram lines with matplotlib?

I have two histograms (signal and background) and each of them have upper and lower uncertainty bounds. I would like to plot each histogram as using only a colored edge with no fill and then to plot the upper and lower uncertainty histograms with their own colored edges with no fill, and then to have the region between the upper and lower histograms filled, as a sort of undertainty band. How might this be achieved?
Here's the very rough code I have just now (which can be used in Jupyter):
import matplotlib.pyplot as plt
import numpy as np
from pylab import normal
import seaborn as sns
sns.set(context="paper", font="monospace")
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
plt.rcParams["figure.figsize"] = [8, 8]
plt.rc("text", usetex=True)
plt.rc("font", family="serif")
s = normal(1, .2, 5000)
s_up = normal(1, .2, 5500)
s_dn = normal(1, .2, 4600)
b = normal(2, .2, 2500)
ns, bins, patches = plt.hist(
[s, s_up, s_dn, b],
color=['r', 'g', 'g', 'b'],
label=['signal nominal', 'signal upper bound', 'signal lower bound', 'background'],
alpha=0.5,
linewidth=1,
histtype='stepfilled');
plt.setp(patches[0], edgecolor='r')
plt.setp(patches[1], edgecolor='b')
plt.legend();
plt.show();
Something like that?
s = normal(1, .2, 5000)
s_up = normal(1, .2, 5500)
s_dn = normal(1, .2, 4600)
bins = np.linspace(0., 2., 21)
n_up,b_up,p_up = plt.hist(s_up, bins=bins, bottom=0, linewidth=1, histtype='stepfilled', facecolor='none', edgecolor='red', linestyle='--', label='signal upper bound')
n_dn,b_dn,p_dn = plt.hist(s_dn, bins=bins, bottom=0, linewidth=1, histtype='stepfilled', facecolor='none', edgecolor='green', linestyle='--', label='signal lower bound')
n,b,p = plt.hist(s, bins=bins, bottom=0, linewidth=2, histtype='stepfilled', facecolor='none', edgecolor='k', label='signal nominal')
plt.bar(x=b_up[:-1], height=n_up-n_dn, bottom=n_dn, width=np.diff(b_up), align='edge', linewidth=0, color='red', alpha=0.25, zorder=-1, label='uncertainty band')
plt.legend()

Showing Alaska and Hawaii in Cartopy map

The following code creates a map of continental US states that is shaded by population density. I want to create a similar map (my data is not actually pop density, but this is an easy example), except that it also includes the states of Alaska and Hawaii.
Specifically I would like to have Alaska/Hawaii show up in the figure, but be moved so that they are below the part of the figure showing the continental US. Or something along those lines.
Any idea how I would create such a map using Cartopy?
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection=ccrs.LambertConformal())
ax.set_extent([-125, -66.5, 20, 50], ccrs.Geodetic())
shapename = 'admin_1_states_provinces_lakes_shp'
states_shp = shpreader.natural_earth(resolution='110m',
category='cultural', name=shapename)
popdensity = {
'New Jersey': 438.00,
'Rhode Island': 387.35,
'Massachusetts': 312.68,
'Connecticut': 271.40,
'Maryland': 209.23,
'New York': 155.18,
'Delaware': 154.87,
'Florida': 114.43,
'Ohio': 107.05,
'Pennsylvania': 105.80,
'Illinois': 86.27,
'California': 83.85,
'Virginia': 69.03,
'Michigan': 67.55,
'Indiana': 65.46,
'North Carolina': 63.80,
'Georgia': 54.59,
'Tennessee': 53.29,
'New Hampshire': 53.20,
'South Carolina': 51.45,
'Louisiana': 39.61,
'Kentucky': 39.28,
'Wisconsin': 38.13,
'Washington': 34.20,
'Alabama': 33.84,
'Missouri': 31.36,
'Texas': 30.75,
'West Virginia': 29.00,
'Vermont': 25.41,
'Minnesota': 23.86,
'Mississippi': 23.42,
'Iowa': 20.22,
'Arkansas': 19.82,
'Oklahoma': 19.40,
'Arizona': 17.43,
'Colorado': 16.01,
'Maine': 15.95,
'Oregon': 13.76,
'Kansas': 12.69,
'Utah': 10.50,
'Nebraska': 8.60,
'Nevada': 7.03,
'Idaho': 6.04,
'New Mexico': 5.79,
'South Dakota': 3.84,
'North Dakota': 3.59,
'Montana': 2.39,
'Wyoming': 1.96}
ax.background_patch.set_visible(False)
ax.outline_patch.set_visible(False)
ax.set_title('State Population Density')
for state in shpreader.Reader(states_shp).records():
edgecolor = 'black'
try:
# use the name of this state to get pop_density
state_dens = popdensity[ state.attributes['name'] ]
except:
state_dens = 0
# simple scheme to assign color to each state
if state_dens < 40:
facecolor = "lightyellow"
elif state_dens > 200:
facecolor = "red"
else:
facecolor = "pink"
# `state.geometry` is the polygon to plot
ax.add_geometries([state.geometry], ccrs.PlateCarree(),
facecolor=facecolor, edgecolor=edgecolor)
plt.show()
The figure this (currently) creates is as follows:
To plot inset maps as parts of a main map is challenging. You will need to create an axes for plotting each inset map and place it on the figure at proper location and relative scale. Here is a working code that you can experiment with.
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
import shapely.geometry as sgeom
# A function that draws inset map, ++
# ===================================
def add_insetmap(axes_extent, map_extent, state_name, facecolor, edgecolor, geometry):
# create new axes, set its projection
use_projection = ccrs.Mercator() # preserve shape well
#use_projection = ccrs.PlateCarree() # large distortion in E-W for Alaska
geodetic = ccrs.Geodetic(globe=ccrs.Globe(datum='WGS84'))
sub_ax = plt.axes(axes_extent, projection=use_projection) # normal units
sub_ax.set_extent(map_extent, geodetic) # map extents
# add basic land, coastlines of the map
# you may comment out if you don't need them
sub_ax.add_feature(cartopy.feature.LAND)
sub_ax.coastlines()
sub_ax.set_title(state_name)
# add map `geometry` here
sub_ax.add_geometries([geometry], ccrs.PlateCarree(), \
facecolor=facecolor, edgecolor=edgecolor)
# +++ more features can be added here +++
# plot box around the map
extent_box = sgeom.box(map_extent[0], map_extent[2], map_extent[1], map_extent[3])
sub_ax.add_geometries([extent_box], ccrs.PlateCarree(), color='none', linewidth=0.05)
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection=ccrs.LambertConformal())
ax.set_extent([-125, -66.5, 20, 50], ccrs.Geodetic())
shapename = 'admin_1_states_provinces_lakes_shp'
states_shp = shpreader.natural_earth(resolution='110m',
category='cultural', name=shapename)
popdensity = {
'New Jersey': 438.00,
'Rhode Island': 387.35,
'Massachusetts': 312.68,
'Connecticut': 271.40,
'Maryland': 209.23,
'New York': 155.18,
'Delaware': 154.87,
'Florida': 114.43,
'Ohio': 107.05,
'Pennsylvania': 105.80,
'Illinois': 86.27,
'California': 83.85,
'Virginia': 69.03,
'Michigan': 67.55,
'Indiana': 65.46,
'North Carolina': 63.80,
'Georgia': 54.59,
'Tennessee': 53.29,
'New Hampshire': 53.20,
'South Carolina': 51.45,
'Louisiana': 39.61,
'Kentucky': 39.28,
'Wisconsin': 38.13,
'Washington': 34.20,
'Alabama': 33.84,
'Missouri': 31.36,
'Texas': 30.75,
'West Virginia': 29.00,
'Vermont': 25.41,
'Minnesota': 23.86,
'Mississippi': 23.42,
'Iowa': 20.22,
'Arkansas': 19.82,
'Oklahoma': 19.40,
'Arizona': 17.43,
'Colorado': 16.01,
'Maine': 15.95,
'Oregon': 13.76,
'Kansas': 12.69,
'Utah': 10.50,
'Nebraska': 8.60,
'Nevada': 7.03,
'Idaho': 6.04,
'New Mexico': 5.79,
'South Dakota': 3.84,
'North Dakota': 3.59,
'Montana': 2.39,
'Wyoming': 1.96}
ax.background_patch.set_visible(False)
ax.outline_patch.set_visible(False)
ax.set_title('State Population Density')
for state in shpreader.Reader(states_shp).records():
edgecolor = 'black'
try:
# use the name of this state to get pop_density
state_dens = popdensity[ state.attributes['name'] ]
except:
state_dens = 0
# simple scheme to assign color to each state
if state_dens < 40:
facecolor = "lightyellow"
elif state_dens > 200:
facecolor = "red"
else:
facecolor = "pink"
# special handling for the 2 states
# ---------------------------------
if state.attributes['name'] in ("Alaska", "Hawaii"):
# print("state.attributes['name']:", state.attributes['name'])
state_name = state.attributes['name']
# prep map settings
# experiment with the numbers in both `_extents` for your best results
if state_name == "Alaska":
# (1) Alaska
map_extent = (-178, -135, 46, 73) # degrees: (lonmin,lonmax,latmin,latmax)
axes_extent = (0.04, 0.06, 0.29, 0.275) # axes units: 0 to 1, (LLx,LLy,width,height)
if state_name == "Hawaii":
# (2) Hawii
map_extent = (-162, -152, 15, 25)
axes_extent = (0.27, 0.06, 0.15, 0.15)
# add inset maps
add_insetmap(axes_extent, map_extent, state_name, \
facecolor, \
edgecolor, \
state.geometry)
# the other (conterminous) states go here
else:
# `state.geometry` is the polygon to plot
ax.add_geometries([state.geometry], ccrs.PlateCarree(),
facecolor=facecolor, edgecolor=edgecolor)
plt.show()
The output plot will be:

Adding gridlines using Cartopy

I'm trying to add gridlines to a map I made using Cartopy, however, when I use the example code from the cartopy documentation, it doesn't display what I want and I can't figure out how to manipulate it to do so.
def plotMap():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
#(http://www.naturalearthdata.com/features/)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
#ax.gridlines(xlocs=grids_ma, ylocs=np.arange(-80,90,20), zorder=21,
draw_labels=True )
ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='black',
draw_labels=True, alpha=0.5, linestyle='--')
ax.xlabels_top = False
ax.ylabels_left = False
ax.ylabels_right=True
ax.xlines = True
ax.xlocator = mticker.FixedLocator([-160, -140, -120, 120, 140, 160, 180,])
ax.xformatter = LONGITUDE_FORMATTER
ax.yformatter = LATITUDE_FORMATTER
ax.xlabel_style = {'size': 15, 'color': 'gray'}
ax.xlabel_style = {'color': 'red', 'weight': 'bold'}
return fig, ax
I've attached a picture of the output. For reference, I only want the longitude gridlines to start at the left of my domain and end at the right side, preferably being spaced every 20 degrees. Ideally the same for latitude lines as well.
Bad gridline plot
Is the example you are following the one at the bottom of this page? If so, you are attempting to set attributes on the GeoAxes (ax) instance which should be set on the GridLiner (gl) instance:
import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
def plotMap():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
gl = ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='black', alpha=0.5, linestyle='--', draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
gl.ylabels_right=True
gl.xlines = True
gl.xlocator = mticker.FixedLocator([120, 140, 160, 180, -160, -140, -120])
gl.ylocator = mticker.FixedLocator([0, 20, 40, 60])
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'color': 'red', 'weight': 'bold'}
This produces the following map. The gridliner doesn't seem to be coping with the dateline. I do not know if there is a way around this, but there is a note at the top of the above linked documentation to say that there are currently known limitations with this class, so maybe not.
An alternative is to set the various labels and their styles directly with matplotlib. Note that you have to set the ticklabels separately from the ticks, otherwise you get labels corresponding to the Mercator coordinate reference system:
import cartopy.mpl.ticker as cticker
def plotMap2():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
ax.set_xticks([120., 140., 160., 180., -160., -140., -120.], crs=ccrs.PlateCarree())
ax.set_xticklabels([120., 140., 160., 180., -160., -140., -120.], color='red', weight='bold')
ax.set_yticks([20, 40], crs=ccrs.PlateCarree())
ax.set_yticklabels([20, 40])
ax.yaxis.tick_right()
lon_formatter = cticker.LongitudeFormatter()
lat_formatter = cticker.LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
ax.grid(linewidth=2, color='black', alpha=0.5, linestyle='--')

How to set more margins

I have a pyplot code.
Since I want to group multiple bars, I am trying to write text in the graph using plt.annotate.
However, as you can see in the picture, the word 'Something' in left bottom gets cropped. Does anyone know How I can fix this?
Here is my code
#!/usr/bin/python
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import operator as o
import numpy as np
n_groups = 5
means_men = (20, 35, 30, 35, 27)
std_men = (2, 3, 4, 1, 2)
means_women = (25, 32, 34, 20, 25)
std_women = (3, 5, 2, 3, 3)
fig, ax = plt.subplots()
index = np.arange(n_groups)
bar_width = 0.35
opacity = 0.4
error_config = {'ecolor': '0.3'}
rects1 = plt.bar(index, means_men, bar_width, alpha=opacity, color='b', yerr=std_men, error_kw=error_config, label='Men')
rects2 = plt.bar(index + bar_width, means_women, bar_width,
alpha=opacity,
color='r',
yerr=std_women,
error_kw=error_config,
label='Women')
#plt.xlabel('Group')
plt.ylabel('Scores')
plt.title('Scores by group and gender')
plt.annotate('Something', (0,0), (50,-40), xycoords = 'axes fraction', textcoords='offset points', va='top');
plt.annotate('Something', (0,0), (200,-20), xycoords = 'axes fraction', textcoords='offset points', va='top');
plt.xticks(index + bar_width, ('A', 'B', 'C', 'D', 'E'))
plt.legend()
plt.savefig('barchart_3.png')
For some reason, matplotlib sometimes clips too aggressively. If you add bbox_inches='tight' to save fig this should include the figure correctly,
plt.savefig('barchart_3.png', bbox_inches='tight')
More generally, you can adjust your main figure with something like,
plt.subplots_adjust(bottom=0.1)