Bubble map with annotations and arrows

logo of a chart:BubbleMap

Bubble map are a kind of maps where dots are plotted on a map according to their latitude and longitude.

This post explains how to create this kind of chart with annotations, arrows and nice theme to make meaningful bubble maps. The process is described step-by-step, starting from a basic example to the final chart with reproducible code.


A bubble map is a map combined with a scatter plot where the size of each bubble corresponds to specific numerical values. In the example below, the map illustrates earthquake locations globally, with the bubble size representing their depth.

This chart has been created by Joseph Barbier. Thanks to him for accepting sharing its work here!

As a teaser, here is the plot we’re gonna try building:

bubble map


First, we need to install the following libraries:

  • matplotlib and geoplot: for creating the plot
  • pandas and geopandas: for data manipulation
  • highlight_text: for annotations
  • geoplot and cartopy for geospatial manipulation
# data manipulation
import numpy as np
import pandas as pd
import geopandas as gpd

# visualization
import matplotlib.pyplot as plt
from matplotlib import font_manager
from matplotlib.font_manager import FontProperties
from highlight_text import fig_text, ax_text
from matplotlib.patches import FancyArrowPatch

# geospatial manipulation
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import geoplot
import geoplot.crs as gcrs


In order to create a bubble map, we need 2 kind of datasets:

  • a dataset with country shapes, that we load with the following code:
proj = ccrs.Mercator()

url = "https://raw.githubusercontent.com/holtzy/The-Python-Graph-Gallery/master/static/data/all_world.geojson"
world = gpd.read_file(url)
world = world[~world['name'].isin(["Antarctica", "Greenland"])]
world = world.to_crs(proj.proj4_init)
name geometry
0 Fiji MULTIPOLYGON (((20037508.343 -1800679.237, 200...
1 Tanzania POLYGON ((3774143.866 -105050.440, 3792946.708...
2 W. Sahara POLYGON ((-964649.018 3185897.152, -964597.245...
3 Canada MULTIPOLYGON (((-13674486.249 6242596.000, -13...
4 United States of America MULTIPOLYGON (((-13674486.249 6242596.000, -13...


  • a dataset with latitude and longitude values, and another numerical column:
#Load data
url = "https://raw.githubusercontent.com/holtzy/The-Python-Graph-Gallery/master/static/data/earthquakes.csv"
df = pd.read_csv(url)

# Filter dataset: big earth quakes only
df = df[df['Depth (km)']>=0.01] # depth of at least 10 meters

# Sort: big bubbles must be below small bubbles for visibility
df.sort_values(by='Depth (km)', ascending=False, inplace=True)

Date Time (utc) Region Magnitude Depth (km) Latitude Longitude Mode Map year
7961 20/02/2019 06:50:47 Banda Sea 5.0 2026 -6.89 129.15 A - 2019.0
6813 07/07/2019 07:50:53 Eastern New Guinea Reg, P.N.G. 5.4 1010 -5.96 147.90 A - 2019.0
8293 17/01/2019 14:01:50 Fiji Islands 4.7 689 -18.65 179.44 A - 2019.0
11258 03/01/2018 06:42:58 Fiji Islands Region 5.5 677 -19.93 -178.89 A - 2018.0
9530 06/09/2018 18:22:24 Fiji Islands Region 5.8 672 -18.88 179.30 A - 2018.0

Simple background map

We start this reproduction by creating the most simple background map possible. It only uses the world dataset:

proj = ccrs.Mercator()
fig, ax = plt.subplots(figsize=(12, 8), dpi=300, subplot_kw={'projection':proj})

# background map