Generate the data and import packages¶
First, we need to create the data. I'll start by defining it as a dictionary and then convert it into a pandas DataFrame, since pandas is commonly used in many projects for data manipulation.
import math
import geopandas as gpd
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.lines import Line2D
from svgpathtools import svg2paths
from svgpath2mpl import parse_path
color_dict = {2022: "#CC5A43", 2004: "#5375D4", }
xy_ticklabel_color,color_map, h_line ='#101628','#D3D3D3', '#939AA1'
data = {
"year": [2004, 2022, 2004, 2022, 2004, 2022],
"countries" : ["Sweden", "Sweden", "Denmark", "Denmark", "Norway", "Norway"],
"sites": [13,15,4,10,5,8]
}
df= pd.DataFrame(data)
df['diff']=df.groupby('countries')['sites'].diff().fillna(df.sites).astype(int)
df['sub_total'] = df.groupby('countries')['diff'].transform('sum')
#custom sort
sort_order_dict = {"Denmark":2, "Sweden":3, "Norway":1, 2004:5, 2022:4}
df = df.sort_values(by=['countries','year',], key=lambda x: x.map(sort_order_dict))
df['color']= df.year.map(color_dict)
df
| year | countries | sites | diff | sub_total | color | |
|---|---|---|---|---|---|---|
| 5 | 2022 | Norway | 8 | 3 | 8 | #CC5A43 |
| 4 | 2004 | Norway | 5 | 5 | 8 | #5375D4 |
| 3 | 2022 | Denmark | 10 | 6 | 10 | #CC5A43 |
| 2 | 2004 | Denmark | 4 | 4 | 10 | #5375D4 |
| 1 | 2022 | Sweden | 15 | 2 | 15 | #CC5A43 |
| 0 | 2004 | Sweden | 13 | 13 | 15 | #5375D4 |
icon_path, attributes = svg2paths('../flags/Unesco_World_Heritage_logo_notext_transparent.svg')
#matplotlib path object of the icon
icon_marker = parse_path(attributes[0]['d'])
icon_marker.vertices -= icon_marker.vertices.mean(axis=0)
icon_marker = icon_marker.transformed(mpl.transforms.Affine2D().rotate_deg(180))
icon_marker = icon_marker.transformed(mpl.transforms.Affine2D().scale(-1,1))
Get the map¶
# Get the NUTS2 GeoJSON here: https://github.com/eurostat/Nuts2json
url = "https://raw.githubusercontent.com/eurostat/Nuts2json/master/pub/v2/2021/3035/20M/0.json"
map_df = gpd.read_file(url, layer='nutsrg')
map_df = map_df[map_df.id.isin(['NO','SE', 'DK']) ]
map_df
c:\Users\Ruth Pozuelo\Documents\SynologyDrive\100-matplotlib\.venv\Lib\site-packages\pyogrio\geopandas.py:275: UserWarning: More than one layer found in '0.json': 'nutsrg' (default), 'nutsbn', 'cntrg', 'cntbn', 'gra'. Specify layer parameter to avoid this warning. result = read_func(
| id | na | geometry | country | |
|---|---|---|---|---|
| 33 | DK | Danmark | MULTIPOLYGON (((4649929.995 3564341.094, 46459... | DK |
| 34 | SE | Sverige | MULTIPOLYGON (((4968534.919 4802763.317, 49724... | SE |
| 36 | NO | Norge | MULTIPOLYGON (((5121937.289 5303632.941, 51295... | NO |
Plot the map¶
Plot the map on its own axes: ax_map
fig = plt.figure(figsize=(15,10))
ax_map = fig.add_axes([0, 0, 1, 1])
map_df.plot(color=color_map,ax=ax_map)
ax_map.set_axis_off()
Plot the matrices on the map¶
First define the coordinates of the matrices:
lat = [0.32,0.22,0.68]
lon = [0.5,0.14,0.28]
xmin_line =[1,1,0]
xmax_line =[2,2.5,-1]
y_line = -0.4
Define the matrices:
max_sites = df.sub_total.max()
matrix_rows = 3
matrix_columns = math.ceil(max_sites/ matrix_rows)
#create the matrix
x = np.repeat(np.arange(1, matrix_columns + 1), matrix_rows)
y = np.tile(np.arange(1, matrix_rows + 1), matrix_columns)
Plot the matrices on its own axes: ax_matrix:
for i, (country, group) in enumerate(df.groupby("countries", sort = False)):
axes_height = 0.2
axes_width = 0.1
#add the axes at lat and lon
ax_matrix = fig.add_axes([lat[i] ,lon[i] , axes_width, axes_height])
ax_matrix.set(xlim = (0,4), ylim = (0,6))
symbol_color = np.insert( np.repeat(group.color, group['diff']), 0, ["#FFFFFF"] * (max_sites - group.sub_total.iloc[0]))
ax_matrix.scatter(
y,
x,
marker=icon_marker,
s=800,
color= symbol_color
)
#add the x labels
ax_matrix.set_xlabel(
f'{group.countries.iloc[0]} {group.sub_total.iloc[0]}',
color =xy_ticklabel_color,
size =14,
)
#add horizontal line
ax_matrix.axhline(
y= y_line,
xmin = xmin_line[i],
xmax = xmax_line[i],
lw = 1,
clip_on = False,
color = h_line
)
# hlines uses axes coordinates, convert to data coordinates
x0, x1 = ax_matrix.get_xlim()
x_end = x0 + xmax_line[i] * (x1 - x0)
# add a circle at the end using scatter
ax_matrix.scatter(
x_end,
y_line,
s=20,
color=h_line,
fc = color_map,
zorder=3,
clip_on=False
)
#add styling
ax_matrix.grid(False)
ax_matrix.set_frame_on(False)
ax_matrix.tick_params(
axis='both',
which='both',
length = 0,
labelleft = False,
labelbottom = False
)
fig
Add legends¶
#add legend
text_legends = ["Before", "After"]
colors = df.color.unique()
lines = [Line2D([0], [0], color=c, marker=icon_marker,linestyle='', markersize=20,) for c in colors]
labels = [f'{text_legend} 2004' for text_legend in text_legends]
for year in df.year.unique():
fig.legend(
lines,
labels,
labelcolor=xy_ticklabel_color,
bbox_to_anchor=(0.5, -0.05),
loc="lower center",
ncols = 2,
frameon=False,
fontsize= 12
)
fig