def create_gradient_colormap(colors):
"""
Based on 2 color input, create a colormap of it.
"""
cmap = LinearSegmentedColormap.from_list("custom_gradient", colors, N=256)
return cmap
def draw_arrow(tail_position, head_position, fig, invert=False):
"""
Draw a curve arrow at given tail/head position, on a figure.
"""
kw = dict(
arrowstyle="Simple, tail_width=0.5, head_width=4, head_length=8", color="white")
if invert:
connectionstyle = "arc3,rad=-.5"
else:
connectionstyle = "arc3,rad=.5"
a = FancyArrowPatch(tail_position, head_position,
connectionstyle=connectionstyle,
transform=fig.transFigure,
**kw)
fig.patches.append(a)
def plot_map_on_ax(column, ax, cmap):
"""
Add a map on a given axis.
"""
data.plot(
column=column,
cmap=cmap,
edgecolor='black', linewidth=0.4,
ax=ax
)
ax.set_xlim(-13.8, 40)
ax.set_ylim(32, 72)
ax.axis('off')
def path_effect_stroke(**kwargs):
return [path_effects.Stroke(**kwargs), path_effects.Normal()]
pe = path_effect_stroke(linewidth=1, foreground="black")
# colors for the chart
colors = {
'share_Generosity': ['#c9ada7', '#4a4e69'],
'share_Perceptions of corruption': ['#fcbf49', '#d62828'],
'share_Freedom to make life choices': ['#90e0ef', '#0077b6'],
'share_Social support': ['#80ed99', '#38a3a5'],
}
background_col = '#22333b'
# initialize the figure
fig, axs = plt.subplots(nrows=3, ncols=2, figsize=(8, 10))
axs = axs.flatten()
# set background color
fig.set_facecolor(background_col)
axs[1].set_facecolor(background_col)
# list that we use to display maps,
# with empty values for 2 first axes
columns = [
'', '',
'share_Generosity',
'share_Perceptions of corruption',
'share_Freedom to make life choices',
'share_Social support'
]
# annotation positions on the lollipop
annotations_pos = [
'', '',
[8, 0],
[9, 1],
[15, 2],
[3, 3]
]
# iterate over of the 6 axes AND column names
for i, (ax, column) in enumerate(zip(axs, columns)):
# skip first two axes (on top of the maps)
if i in [0, 1]:
continue
# create a colormap based on colors
cmap = create_gradient_colormap(colors[column])
# add map on the current axe
plot_map_on_ax(column=column, ax=ax, cmap=cmap)
# annotations below each map
ax_text(
-15, 33, # fixed position for each map
'<'+column[6:]+'>',
ha='left', va='center',
fontsize=9, fontweight='bold',
color=cmap(0.5),
highlight_textprops=[
{"path_effects": pe}
], ax=ax
)
# annotations on lollipop
x, y = annotations_pos[i]
ax_text(
x, y,
'<'+column[6:]+'>',
ha='left', va='center',
fontweight='bold',
fontsize=10,
color=cmap(0.5),
highlight_textprops=[
{"path_effects": pe}
], ax=axs[1]
)
# Lollipop plot
min_max_df = data[columns[2:]].agg(['min', 'max']).T
for i, col in enumerate(columns[2:]):
# colors
min_color = colors[col][0]
max_color = colors[col][1]
# filter on current column
subset = min_max_df.iloc[i].T
# add data points of lollipop
axs[1].scatter(subset['min'], i, zorder=2, s=160, edgecolor='black', linewidth=0.5, color=min_color)
axs[1].scatter(subset['max'], i, zorder=2, s=160, edgecolor='black', linewidth=0.5, color=max_color)
# horizontal lines of lollipop
axs[1].hlines(
y=range(4),
xmin=min_max_df['min'], xmax=min_max_df['max'],
color='white', linewidth=0.8, zorder=1
)
# custom lollipop axis features
axs[1].spines[['right', 'top', 'left']].set_visible(False)
axs[1].set_xticks([0, 10, 20, 30, 40])
axs[1].spines['bottom'].set_color('white')
axs[1].tick_params(axis='x', colors='white')
axs[1].set_yticks([])
axs[1].set_ylim(-1, 6)
axs[1].set_xlim(-3, 33)
# remove top left axis
axs[0].set_axis_off()
# title and credit
text = """
<What determines happiness>
<in Europe? Well, it depends>
<Share, in %, of happiness explained by different>
<factors across Europe. For each country, the>
<darker the color is, the more the factor explains>
<happiness in this country.>
"""
ax_text(
-0.02, 0.6,
text,
ha='left', va='center',
fontsize=15,
color='black',
highlight_textprops=[
{'fontweight': 'bold',
'color': 'white'},
{'fontweight': 'bold',
'color': 'white'},
{'color': 'darkgrey',
'fontsize': 11},
{'color': 'darkgrey',
'fontsize': 11},
{'color': 'darkgrey',
'fontsize': 11},
{'color': 'darkgrey',
'fontsize': 11}
],
ax=axs[0]
)
# credit
text = """
<Design:> Joseph Barbier
<Data:> World Happiness Report 2024
"""
ax_text(
-17.6, 25, text,
ha='left', va='center',
fontsize=6, color='white',
highlight_textprops=[
{'fontweight': 'bold'},
{'fontweight': 'bold'},
], ax=axs[4]
)
# reduce size and change position of lollipop axe
axs[1].set_position([0.56, 0.68, 0.2, 0.1])
# legend arrows
draw_arrow((0.7, 0.92), (0.76, 0.87), fig=fig, invert=True)
draw_arrow((0.82, 0.92), (0.862, 0.87), fig=fig, invert=True)
ax_text(
6, 4.7, 'Minimum',
fontsize=8, color='white',
ax=axs[1]
)
ax_text(
16.7, 4.8, 'Maximum',
fontsize=8, color='white',
ax=axs[1]
)
plt.tight_layout()
fig.savefig('../../static/graph/web-multiple-maps.png', dpi=300, bbox_inches='tight')
plt.show()