Choropleth map with a customized legend

logo of a chart:Choropleth

We will create a Choropleth map using the Matplotlib library to visualize data about CO2 emissions per capita across Europe.

This chart features a unique legend, distinctive annotations, and a vibrant colormap, resulting in an attractive output.

This post provides a step-by-step guide to reproduce the map, starting with a basic background map.


This plot is a choropleth map. It show the CO2 emissions per capita in 2021 for each country.

The chart was made by Joseph Barbier. Thanks to him for accepting sharing his work here!

Let's see what the final picture will look like:

choropleth map with custom legend


First, you need to install the following librairies:

  • matplotlib is used for creating the chart and add customization features
  • pandas and geopandas are used to put the data into a dataframe and manipulate geographical data
  • geopandas: for the geographical data
  • highlight_text: to add beautiful annotations to the chart

And that's it!

import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
from pypalettes import load_cmap
from matplotlib.font_manager import FontProperties
from matplotlib.patches import FancyArrowPatch
from highlight_text import fig_text, ax_text


Creating a choropleth map necessitates a dataset with geographical information. For this example, we need to load the world map from the Gallery's repo.

Next, we should load the dataset containing the values we want to represent on the map, such as CO2 per capita by country. This dataset is also accessible in the Gallery's repo.

url = ""
world = gpd.read_file(url)
pop_est continent name iso_a3 gdp_md_est geometry
0 144373535.0 Europe Russia RUS 1699876 MULTIPOLYGON (((180.00000 71.51571, 180.00000 ...
1 5347896.0 Europe Norway NOR 403336 MULTIPOLYGON (((15.14282 79.67431, 15.52255 80...
2 67059887.0 Europe France FRA 2715518 MULTIPOLYGON (((-51.65780 4.15623, -52.24934 3...
3 10285453.0 Europe Sweden SWE 530883 POLYGON ((11.02737 58.85615, 11.46827 59.43239...
4 9466856.0 Europe Belarus BLR 63080 POLYGON ((28.17671 56.16913, 29.22951 55.91834...

Now we merge this dataset with the world map with a merge function. This function will match the country names in the two datasets and add the CO2 values to the world map dataframe.

url = ""
df = pd.read_csv(url)

# merge data
data = world.merge(df, how='left', left_on='name', right_on='Country')

# filter to keep only specific countries
data = data[data['continent'] == 'Europe']
data = data[~data['name'].isin(['Russia', 'Iceland'])]
data = data[data['Year'] == 2021]
data = data[['name', 'Total', 'geometry']]
data = data.dropna()
name Total geometry
543 Norway 7.573273 MULTIPOLYGON (((15.14282 79.67431, 15.52255 80...
815 France 4.741312 MULTIPOLYGON (((-51.65780 4.15623, -52.24934 3...
1087 Sweden 3.424918 POLYGON ((11.02737 58.85615, 11.46827 59.43239...
1359 Belarus 6.222741 POLYGON ((28.17671 56.16913, 29.22951 55.91834...
1631 Ukraine 4.637058 POLYGON ((32.15944 52.06125, 32.41206 52.28869...

Background map

Thanks to the geopandas library, we can easily add a background map to our plot by simply calling the plot() function on our geo dataframe.

With just a few lines of code, we can create a synthetic map that displays a European map.

# initialize the figure
fig, ax = plt.subplots(figsize=(10, 10), dpi=300)

# create the plot

Custom axis

The next step is to adjust the plot's ranges (latitude and longitude) using the set_xlim() and set_ylim() functions on the axis object.

We also remove the spines around the map since they are not particularly useful in this context.

# initialize the figure
fig, ax = plt.subplots(figsize=(10, 10), dpi=300)

# create the plot

# custom axis
ax.set_xlim(-11, 41)
ax.set_ylim(32, 73)

Choropleth map

A choropleth map is distinct from a background map because it employs color gradients to depict data, with each country shaded according to its data value.

We initiate by loading a colormap via the load_cmap() function from the pypalettes library, subsequently integrating this colormap into the plot by including cmap=cmap in the plot() function.

To enhance readability, we also define the edgecolor and linewidth of the countries.

# load colormap
cmap = load_cmap('BrwnYl', type='continuous')

# initialize the figure
fig, ax = plt.subplots(figsize=(10, 10), dpi=300)

# create the plot
data.plot(ax=ax, column='Total', cmap=cmap, edgecolor='black', linewidth=0.5)

# custom axis
ax.set_xlim(-11, 41)
ax.set_ylim(32, 73)