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 numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
color_dict_bar = {
(2022, "Norway"): "#9194A3",
(2004, "Norway"): "#2B314D",
(2022, "Denmark"): "#E2AFA5",
(2004, "Denmark"): "#CE5A43",
(2022, "Sweden"): "#C4D6F8",
(2004, "Sweden"): "#5375D4",
}
xy_ticklabel_color, label_color, grid_color, datalabels_color ='#757C85',"#444E56", "#C8C9C9", "#FFFFFF"
data = {
"year": [2004, 2022, 2004, 2022, 2004, 2022],
"countries" : [ "Denmark", "Denmark", "Norway", "Norway","Sweden", "Sweden"],
"sites": [4,10,5,8,13,15]
}
df= pd.DataFrame(data)
df['pct_change'] = (df.groupby('countries', sort=False)['sites'].apply(lambda x: x.pct_change()).to_numpy()).round(3)
df["color"] = df.set_index(["year", "countries"]).index.map(color_dict_bar.get)
df['year_lbl'] = "'"+df['year'].astype(str).str[-2:].astype(str)
df = df.sort_values(["year"], ascending=False)
df
| year | countries | sites | pct_change | color | year_lbl | |
|---|---|---|---|---|---|---|
| 1 | 2022 | Denmark | 10 | 1.500 | #E2AFA5 | '22 |
| 3 | 2022 | Norway | 8 | 0.600 | #9194A3 | '22 |
| 5 | 2022 | Sweden | 15 | 0.154 | #C4D6F8 | '22 |
| 0 | 2004 | Denmark | 4 | NaN | #CE5A43 | '04 |
| 2 | 2004 | Norway | 5 | NaN | #2B314D | '04 |
| 4 | 2004 | Sweden | 13 | NaN | #5375D4 | '04 |
Get flags:
img = [
plt.imread("../flags/de-rd.png"),
plt.imread("../flags/no-rd.png"),
plt.imread("../flags/sw-rd.png"),
plt.imread("../flags/de-rd.png"),
plt.imread("../flags/sw-rd.png"),
plt.imread("../flags/no-rd.png")]
Define x and y coordinates¶
We are going to plot this as a smooth curve. To be similar to the original we are going to manually add mid points to bend the curve.
x_coords = [
[0, .10, 1.25, 1.50],
[0, .15, 1.25, 1.50],
[0, .15, 1.30, 1.50]
]
y_coords = [
[0, 0, 1.40, 1.50],
[0, 0, .55, .60],
[0, 0, .14, .15]
]
Plot the chart¶
fig, ax = plt.subplots(figsize=(8,8), facecolor = "#FFFFFF" )
from matplotlib.colors import LinearSegmentedColormap
ax.set_prop_cycle(color =df.color.unique()) #to control which colors matplotlib cycles through
for i, (x, y, (country, group)) in enumerate(zip(x_coords, y_coords, df.groupby("countries"))):
x_new = np.linspace(min(x), max(x), 500)
f = interp1d(x, y, kind='quadratic')
y_smooth=f(x_new)
#create the gradient lines
cmap = LinearSegmentedColormap.from_list("", group.color.tolist())
norm = plt.Normalize(x_new.min(), x_new.max())
line_colors = cmap(norm(x_new))
ax.scatter(
x_new,
y_smooth,
lw=.2,
color = line_colors)
value = (group['pct_change'].iloc[0])*100
if value == int(value):
display_value = f"+{int(value)}%"
else:
display_value = f"+{value:.1f}%"
#add data labels
ax.annotate(
display_value,
xy=(max(x_new) + .3, max(y_smooth)),
size = 16,
color = label_color,
weight= "bold",
annotation_clip=False
)
positionx = [df['pct_change'].max() +.2]*3 + [-.1,-.15, -.20]
positiony = df['pct_change'].dropna().tolist() + [0]*3
line_y = [0.05, 0.95]
for i, (posx, posy) in enumerate(zip(positionx, positiony)):
image_box = OffsetImage(img[i], zoom = 0.05) #container for the image
ab = AnnotationBbox(
image_box,
(1,1),
xybox=(posx, posy),
frameon = False)
ax.add_artist(ab)
fig
line_y = [0.05, 0.95]
line_x = [0, df['pct_change'].max()]
year_labels = df.year_lbl.unique().tolist()
for i in range(2):
x_line = line_x[i]
y_line = line_y[i]
print(x_line,y_line)
#add the year labels lines
ax.axvline(
x=x_line,
ymin=0,
ymax=y_line,
lw=1,
zorder=0,
color= "#DFE3E5"
)
#add the year labels
ax.annotate(
year_labels[i],
xy=(line_x[i], -.15),
size = 16,
color = "#9EA6AC",
ha = "center",
va = "center",
annotation_clip=False
)
ax.set_axis_off()
fig
0 0.05 1.5 0.95