Generate the data and import packages¶
First we need to create the data. I will do it using a dictionary and then converting it to a pandas dataframe as a lot projects use pandas to work with data.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
color_dict = {
"Norway": "#2B314D",
"Denmark": "#A54836",
"Sweden": "#5375D4",
}
xy_ticklabel_color, grand_totals_color, datalabels_color = (
"#757C85",
"#101628",
"#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 = df.sort_values(["year"], ascending=True).reset_index(drop=True)
df["sub_total"] = df.groupby("year")["sites"].transform("sum")
df["pct_group"] = 100 * df["sites"] / df.sub_total
df["ctry_code"] = df.countries.astype(str).str[:2].astype(str).str.upper()
df["color"] = df.countries.map(color_dict)
df
| year | countries | sites | sub_total | pct_group | ctry_code | color | |
|---|---|---|---|---|---|---|---|
| 0 | 2004 | Denmark | 4 | 22 | 18.181818 | DE | #A54836 |
| 1 | 2004 | Norway | 5 | 22 | 22.727273 | NO | #2B314D |
| 2 | 2004 | Sweden | 13 | 22 | 59.090909 | SW | #5375D4 |
| 3 | 2022 | Denmark | 10 | 33 | 30.303030 | DE | #A54836 |
| 4 | 2022 | Norway | 8 | 33 | 24.242424 | NO | #2B314D |
| 5 | 2022 | Sweden | 15 | 33 | 45.454545 | SW | #5375D4 |
Define the variables we will need later:
groups = df.groupby("year")
Plot the chart¶
We will use ax.bar() method and need the following parameters:
| Parameter | Description | Value |
|---|---|---|
| x | The x position of each bar | |
| y | The y position of each bar | |
| bottom | The bottom of each bar | |
| width | The width of each bar |
The width in ax.bar() is centered on the x-position, meaning the bar extends equally to the left and right from each x, so we need to accumulate the x values and find its middle for the x values.
fig, axes = plt.subplots( ncols=df.year.nunique(), nrows=1, figsize=(10, 5), sharex=True, sharey=True, facecolor="#FFFFFF", zorder=1,)
fig.tight_layout(pad=3.0)
for (year, group), ax in zip(groups, axes.ravel()):
y = group.sub_total
width = group.pct_group.tolist()
color = group.color
print(group)
# get the new x position
x = []
for i, w in enumerate(width):
x.append(sum(width[:i]) + width[i] / 2)
ax.bar(
x,
height = y,
width = width,
color = color)
ax.bar(x, height=y, color = "orange") #I added this so you can see where the x values are
year countries sites sub_total pct_group ctry_code color 0 2004 Denmark 4 22 18.181818 DE #A54836 1 2004 Norway 5 22 22.727273 NO #2B314D 2 2004 Sweden 13 22 59.090909 SW #5375D4 year countries sites sub_total pct_group ctry_code color 3 2022 Denmark 10 33 30.303030 DE #A54836 4 2022 Norway 8 33 24.242424 NO #2B314D 5 2022 Sweden 15 33 45.454545 SW #5375D4
and now we add the labels:
fig, axes = plt.subplots( ncols=df.year.nunique(), nrows=1, figsize=(10, 5), sharex=True, sharey=True, facecolor="#FFFFFF", zorder=1,)
fig.tight_layout(pad=3.0)
for (year, group), ax in zip(groups, axes.ravel()):
y = group.sub_total
width = group.pct_group.tolist()
color = group.color
total = group.sub_total.iloc[0]
code = group.ctry_code
# get the new x position
x = []
for i, w in enumerate(width):
x.append(sum(width[:i]) + width[i] / 2)
ax.bar(
x,
height = y,
width = width,
color = color)
# add the grand totals
ax.text(
50,
total + 4,
total,
ha = "center",
color = grand_totals_color,
size = 30,
weight = "bold"
)
for x_coord, c, wdt in zip(x, code, width):
ax.text(
x_coord,
1,
c,
color = datalabels_color,
weight = "bold",
ha = "center",
va = "center"
)
ax.text(
x_coord,
3,
f" {int(wdt)}%",
size = 12,
weight = "light",
color = datalabels_color,
ha = "center",
va = "center",
)
#add styling
ax.tick_params(
labelleft = False,
labelbottom = False,
length = 0
)
ax.set_frame_on(False)
# add the year labels
ax.set_xlabel(
year,
color=xy_ticklabel_color,
size=16,
labelpad =23
)