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 matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import matplotlib.patches as patches
import numpy as np
import pandas as pd
color_dict = {
(2022, "Norway"): "#464B64",
(2004, "Norway"): "#2B314D",
(2022, "Denmark"): "#D57968",
(2004, "Denmark"): "#A54836",
(2022, "Sweden"): "#7296E9",
(2004, "Sweden"): "#5375D4",
}
xy_ticklabel_color, grid_color, datalabels_color = "#757C85", "#C8C9C9", "#2B314D"
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["year_lbl"] = "'" + df["year"].astype(str).str[-2:].astype(str)
sort_order_dict = {"Denmark": 2, "Sweden": 3, "Norway": 1, 2004: 5, 2022: 4}
df = df.sort_values(
by=[
"year",
"countries",
],
key=lambda x: x.map(sort_order_dict),
)
# Add the color based on the color dictionary
df["color"] = df.set_index(["year", "countries"]).index.map(color_dict.get)
df
| year | countries | sites | year_lbl | color | |
|---|---|---|---|---|---|
| 3 | 2022 | Norway | 8 | '22 | #464B64 |
| 1 | 2022 | Denmark | 10 | '22 | #D57968 |
| 5 | 2022 | Sweden | 15 | '22 | #7296E9 |
| 2 | 2004 | Norway | 5 | '04 | #2B314D |
| 0 | 2004 | Denmark | 4 | '04 | #A54836 |
| 4 | 2004 | Sweden | 13 | '04 | #5375D4 |
Define some variables that will be used:
countries = df.countries.unique()
colors = df.color
img = [
plt.imread("../flags/no-rd.png"),
plt.imread("../flags/de-rd.png"),
plt.imread("../flags/sw-rd.png"),
]
Define the x axis, with a list of 0 to 3 and then add an offset of 0.2
x_axis = np.array(list(range(1, len(countries) + 1)) * 2, dtype=float)
offset = 0.2
x_axis[0:len(countries)] += offset
print(x_axis)
[1.2 2.2 3.2 1. 2. 3. ]
Plot the chart¶
To keep the correct zorder, we will draw the grids first:
fig, ax = plt.subplots(figsize=(7, 5), facecolor="#FFFFFF")
ax.yaxis.set_ticks(np.arange(0, 20, 5))
# Customize the major grid
ax.grid(
which = "major",
axis = "y",
linestyle = "-",
linewidth = "0.5",
color = xy_ticklabel_color,
zorder = 0
)
# Customize the minor grid
ax.grid(
which = "minor",
axis = "y",
linestyle = "dotted",
linewidth = "0.5",
color = grid_color,
zorder = 1
)
ax.minorticks_on()
ax.bar(
x_axis,
height = df.sites,
width = 0.5,
color = colors,
zorder = 2
)
<BarContainer object of 6 artists>
Add the data labels¶
for bar, label in zip(ax.patches, df.year_lbl):
ax.text(
bar.get_x() + bar.get_width() / 2,
bar.get_height() + bar.get_y() - 1,
label,
ha="center",
va="bottom",
size=12,
color="w",
)
fig
Add the flags¶
To add the borders to the flag images we are drawing an ellipse behind. We can not use the circle because of the different scaling in the x and y axis.
# set flags
ax.xaxis.set_ticks([1, 2, 3], labels=countries)
tick_labels = ax.xaxis.get_ticklabels()
for i, im in enumerate(img):
pos = tick_labels[i].get_position()
# Add a white circle "dot" behind the image
circle = patches.Ellipse(
pos,
width=0.2,
height= 6.5 * (ax.get_xlim()[1] / ax.get_ylim()[1]), # adjust for aspect
facecolor='w',
edgecolor='none',
clip_on=False,
zorder=2
)
ax.add_patch(circle)
ib = OffsetImage(im, zoom=0.04)
ab = AnnotationBbox(
ib,
pos,
frameon=False,
box_alignment=(0.5, 0.5),
zorder=3
)
ax.add_artist(ab)
ax.tick_params(
axis = "both",
which = "both",
length = 0,
labelsize = 12,
colors = xy_ticklabel_color,
pad = 20
)
fig
and add some final styling:
ax.set_ylim(0, df.sites.max() + 2)
ax.set_frame_on(False)
fig