geopandas plotting with legend filled up with slash lines - python

I use different colors and patterns to show three counties on the PA map. The Centre County is represented by the slash lines using hatch='\\'. But I got difficulties to show such pattern on the legend.
I kind of know that this is not going to work, but I tried Line2D([0],[0],color='white',hatch='\\',lw=4,label='Centre County'), and got errors saying something like "hatch is not an attribute".
%matplotlib inline
import geopandas as gpd
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
fig, ax = plt.subplots(1,figsize=(8,8))
pa.plot(ax=ax,color='white',edgecolor='grey')
centre.plot(ax=ax,color='white',hatch='\\\\\\\\',edgecolor='black')
pike.plot(ax=ax,color='grey')
perry.plot(ax=ax,color='red')
LegendElement = [
Line2D([0],[0],color='red',lw=4,label='Perry County'),
Line2D([0],[0],color='grey',lw=4,label='Pike County'),
Line2D([0],[0],color='white',lw=4,label='Centre County')
]
ax.legend(handles=LegendElement,loc='upper right')

When you create polygons, the property facecolor defines the fill color. And to create correct legend for polygon features, mpatches.Patch is needed.
Here is the code that demonstrates how to use facecolor, and mpatches.Patch:
import geopandas as gpd
import matplotlib.pyplot as plt
#from matplotlib.lines import Line2D
import matplotlib.patches as mpatches
from cartopy import crs as ccrs
#fig, ax = plt.subplots(1,figsize=(8,8))
fig, ax = plt.subplots(figsize=(9,9), subplot_kw={'projection': ccrs.PlateCarree()})
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))
asia = world[(world.continent == "Asia")] #take Asia countries
asia.plot(ax=ax, color="lightgreen")
china = asia[(asia.name == "China")]
india = asia[(asia.name == "India")]
saudi = asia[(asia.name == "Saudi Arabia")]
ax.add_geometries(china['geometry'], crs=ccrs.PlateCarree(), \
facecolor='w', hatch='\\\\\\\\', edgecolor='k', label='China')
ax.add_geometries(india['geometry'], crs=ccrs.PlateCarree(), \
color='grey', label='India')
ax.add_geometries(saudi['geometry'], crs=ccrs.PlateCarree(), \
color='red', label='Saudi Arabia')
LegendElement = [
mpatches.Patch(facecolor='w', hatch='\\\\\\\\', edgecolor='k', label='China'),
mpatches.Patch(color='grey', label='India'),
mpatches.Patch(color='red', label='Saudi Arabia')
]
ax.legend(handles = LegendElement, loc='upper right')
plt.show()
The output plot looks like this:

Related

Goeopandas plot shape and apply opacity outside shape

I am plotting a city boundary (geopandas dataframe) to which I added a basemap using contextily.
I would like to apply opacity to the region of the map outside of the city limits.
The below example shows the opposite of the desired effect, as the opacity should be applied everywhere except whithin the city limits.
import osmnx as ox
import geopandas as gpd
import contextily as cx
berlin = ox.geocode_to_gdf('Berlin,Germany')
fig, ax = plt.subplots(1, 1, figsize=(10,10))
_ = ax.axis('off')
berlin.plot(ax=ax,
color='white',
edgecolor='black',
alpha=.7,
)
# basemap
cx.add_basemap(ax,crs=berlin.crs,)
plt.savefig('stackoverflow_question.png',
dpi=100,
bbox_inches='tight',
)
Plot showing opposite of desired result:
You can create a new polygon that is a buffer on the total bounds of your geometry minus your geometry
import osmnx as ox
import geopandas as gpd
import contextily as cx
import matplotlib.pyplot as plt
from shapely.geometry import box
berlin = ox.geocode_to_gdf("Berlin,Germany")
notberlin = gpd.GeoSeries(
[
box(*box(*berlin.total_bounds).buffer(0.1).bounds).difference(
berlin["geometry"].values[0]
)
],
crs=berlin.crs,
)
fig, ax = plt.subplots(1, 1, figsize=(10, 10))
_ = ax.axis("off")
notberlin.plot(
ax=ax,
color="white",
edgecolor="black",
alpha=0.7,
)
# basemap
cx.add_basemap(
ax,
crs=berlin.crs,
)
# plt.savefig('stackoverflow_question.png',
# dpi=100,
# bbox_inches='tight',
# )

Image as axis values

Is there a way to keep images as axis values?
Two similar questions here and here does not answer my question.
import seaborn as sns
import matplotlib.pyplot as plt
titanic = sns.load_dataset("titanic")
sns.catplot(x="sex", y="survived", hue="class", kind="bar", data=titanic)
I would like to replace the male and female axis values with the corresponding image present in the image link. Can we map the axis values to the image links?
Male:
https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSuGDLqvyU56RbTEFQP3ohzx9d0vJv-nQOk1g&usqp=CAU
Female:
https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcRnSlVrt4o9yHIHnJ7H-cPi_fhOC4bePnyOoA&usqp=CAU
The answer using an OffsetBox in the questions you linked is probably the best option
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnchoredOffsetbox
titanic = sns.load_dataset("titanic")
images = ["https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcSuGDLqvyU56RbTEFQP3ohzx9d0vJv-nQOk1g&usqp=CAU",
"https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcRnSlVrt4o9yHIHnJ7H-cPi_fhOC4bePnyOoA&usqp=CAU"]
pos = [0,1]
fig, ax = plt.subplots()
ax = sns.barplot(x="sex", y="survived", hue="class", data=titanic)
ax.set_xticklabels([])
for m,p in zip(images,pos):
image = plt.imread(m)
im = OffsetImage(image, zoom=0.1)
ab = AnchoredOffsetbox(loc='upper center', child=im,
bbox_to_anchor=(p,0), bbox_transform=ax.get_xaxis_transform(),
frameon=False)
ax.add_artist(ab)
plt.tight_layout()

How to live update Matplotlib plot on top of a background image?

I'm trying to have my matplotlib plot update in real-time as data is added to a CSV file. The plot is of a small geographic location, axes given by longitude and latitude. This is what I have so far:
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
df = pd.read_csv("cayugacoords.txt")
BoundaryBox = [-76.5119, -76.5013, 42.4596, 42.4642]
ruh_m = plt.imread('map.png')
fig, ax = plt.subplots(figsize=(8, 7))
ax.scatter(df.longitude, df.latitude, zorder=1, alpha=1, c='r', s=10)
ax.set_title('Cayuga Lake Shore')
ax.set_xlim(BoundaryBox[0], BoundaryBox[1])
ax.set_ylim(BoundaryBox[2], BoundaryBox[3])
ax.imshow(ruh_m, zorder=0, extent=BoundaryBox, aspect='equal')
plt.show()
And this is what shows when I run the code (the three points on the bottom left are already in the CSV file):
Current plot
And here's the background image on its own: Cayuga Lake
I want the map to be regularly updated as new coordinates are added to the CSV file. How can this be done? I've looked into animation tools but I'm having trouble retaining the background image of the map while updating the plot. For reference, the CSV file "cayugacoords.txt" looks like this:
longitude,latitude
-76.51,42.46
-76.511,42.46
-76.5105,42.46
Thank you!
An alternative solution which updates only the points on the background image is provided by using ax.collections = [] which clears ALL lines plotted on the image. For the sake of demonstration I plot each coordinate per frame.
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
df = pd.read_csv("cayugacoords.txt")
BoundaryBox = [-76.5119, -76.5013, 42.4596, 42.4642]
ruh_m = plt.imread('map.png')
fig, ax = plt.subplots(figsize=(8, 7))
ax.set_title('Cayuga Lake Shore')
ax.set_xlim(BoundaryBox[0], BoundaryBox[1])
ax.set_ylim(BoundaryBox[2], BoundaryBox[3])
ax.imshow(ruh_m, zorder=0, extent=BoundaryBox, aspect='equal')
def animate(nframe):
ax.collections = []
points = ax.scatter(df.longitude[nframe], df.latitude[nframe], zorder=1,
alpha=1, c='r', s=10)
return
anim = animation.FuncAnimation(fig, animate, frames=3)
This code worked for me. It seems quite hacky but it works. You can adjust the time.sleep to your liking.
from matplotlib import pyplot as plt
from IPython.display import clear_output
import pandas as pd
import numpy as np
import time
%matplotlib inline
ruh_m = plt.imread('map.png')
BoundaryBox = [-76.5119, -76.5013, 42.4596, 42.4642]
while True:
clear_output(wait=True)
df = pd.read_csv("cayugacoords.txt")
fig, ax = plt.subplots(figsize=(10, 10))
ax.scatter(df.longitude, df.latitude, zorder=1, alpha=1, c='r', s=10)
ax.set_title('Cayuga Lake Shore')
ax.set_xlim(BoundaryBox[0], BoundaryBox[1])
ax.set_ylim(BoundaryBox[2], BoundaryBox[3])
ax.imshow(ruh_m, zorder=0, extent=BoundaryBox, aspect='equal')
plt.show()
time.sleep(1E-3)

Plotting text on basemap

Suppose I want to plot 'text' on a basemap over Spain, this would work.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
m = Basemap(resolution='l')
fig = plt.figure(figsize=(12,10))
m.drawcoastlines(linewidth=0.5)
plt.annotate('Text',xy=(0,40),ha="center")
plt.show()
But it doesn't work on Merc view, no matter what x/y value I specify. E.g:
m = Basemap(projection='merc',resolution='c',llcrnrlat=36,llcrnrlon=-20,urcrnrlat=61,urcrnrlon=33)
fig = plt.figure(figsize=(12,10))
m.drawcoastlines(linewidth=0.5)
plt.annotate('Text',xy=(0,40),ha="center")
plt.show()
Will only show the text in the very bottom left. How to plot text in this view?

seaborn or matplotlib grid is covering the lines in the plot

Here is my (incomplete, I have note added the data itself) code, which produces a somewhat confusing plot, where one line is covered by the grid but the other not.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pylab
sns.set_context("poster",font_scale=fs)
sns.set_style("darkgrid") # No grid lines
# sns.set_style({'legend.frameon': 'True'})
sns.set_style({'xtick.major.size':'0.0'})
c1,c2 = sns.color_palette("hls",2)#sns.color_palette("colorblind", 2)
a = sns.color_palette("BuGn_r")
# runs_plot = pd.DataFrame(runs.values+8.5)
# Plot just first state trajectory
fig, ax1 = plt.subplots(1,sharey=True, sharex=True, figsize=(30,8))
ax1.plot((ground.values+6),label='Ground Truth',color=c1)
ax1.set_xlabel('Time [$s$]')
ax1.set_ylim(0,10)
ax1.set_ylabel('State [$\#$]')
for tl in ax1.get_yticklabels():
tl.set_color(c1)
ax2 = ax1.twinx()
ax2.plot(0.4*signal_syn.values+1,color=c2,label='Emission Signal')
ax2.set_ylabel('Observations')
ax2.set_ylim(0,10)
# ax2.set_axisbelow(True)
for tl in ax2.get_yticklabels():
tl.set_color(c2)
# ask matplotlib for the plotted objects and their labels
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines + lines2, labels + labels2,ncol=5,loc='upper center', bbox_to_anchor=(0.5, -0.2))
plt.show()
which produces
now and you can probably see, that for the "Ground Truth" the line is covered by the 'darkgrid' option of the seaborn (which produces a white grid as seen above). Now for some reason the grid is not above the emission signal but only the ground truth.
Any ideas for why this might be?
So this is what I ended up doing, it is probably more of a hack than an actual solution, but it works. I just moved the plotting elements so that they're all plotted above the grid.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pylab
sns.set_context("poster",font_scale=fs)
sns.set_style("darkgrid") # No grid lines
# sns.set_style({'legend.frameon': 'True'})
sns.set_style({'xtick.major.size':'0.0'})
c1,c2 = sns.color_palette("hls",2)#sns.color_palette("colorblind", 2)
a = sns.color_palette("BuGn_r")
# runs_plot = pd.DataFrame(runs.values+8.5)
# Plot just first state trajectory
fig, ax1 = plt.subplots(1,sharey=True, sharex=True, figsize=(30,8))
ax1.set_xlabel('Time [$s$]')
ax1.set_ylim(0,10)
ax1.set_ylabel('State [$\#$]')
for tl in ax1.get_yticklabels():
tl.set_color(c1)
ax2 = ax1.twinx()
ax2.plot((ground.values+6),label='Ground Truth',color=c1)
ax2.plot(0.4*signal_syn.values+1,color=c2,label='Emission Signal')
ax2.set_ylabel('Observations')
ax2.set_ylim(0,10)
# ax2.set_axisbelow(True)
for tl in ax2.get_yticklabels():
tl.set_color(c2)
# ask matplotlib for the plotted objects and their labels
lines, labels = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines + lines2, labels + labels2,ncol=5,loc='upper center', bbox_to_anchor=(0.5, -0.2))
plt.show()
Seems like the answer is in this question:
Matplotlib: draw grid lines behind other graph elements
And it is basically: Axis.set_axisbelow(True)

Categories