Hello,
Exploring the countries with Bokeh is easy and fun. Here is my demo called Nereus. Feel free to share or extend.
from bokeh.plotting import figure, curdoc
from bokeh.models import (GeoJSONDataSource, HoverTool, Div,
TapTool, CustomJS)
from bokeh.layouts import column
import json
import requests
import pandas as pd
def create_map():
# Download world GeoJSON data
url = "https://raw.githubusercontent.com/python-visualization/folium/master/examples/data/world-countries.json"
response = requests.get(url)
world_geo = response.json()
# Get country data from REST Countries API
api_url = "https://restcountries.com/v3.1/all?fields=name,flags,population,capital,currencies,languages,area,region,subregion"
response = requests.get(api_url)
countries_data = response.json()
# Create a mapping of country data
country_info = {}
for country in countries_data:
common_name = country['name']['common']
official_name = country['name']['official']
# Extract currency info
currencies = []
if 'currencies' in country:
for currency_code, currency_info in country['currencies'].items():
currencies.append(f"{currency_info['name']} ({currency_code})")
# Extract language info
languages = list(country.get('languages', {}).values())
info = {
'flag_url': country['flags']['png'],
'population': country.get('population', 'N/A'),
'capital': ', '.join(country.get('capital', ['N/A'])),
'area': country.get('area', 'N/A'),
'region': country.get('region', 'N/A'),
'subregion': country.get('subregion', 'N/A'),
'currencies': ', '.join(currencies) or 'N/A',
'languages': ', '.join(languages) or 'N/A'
}
country_info[common_name] = info
country_info[official_name] = info
# Add data to GeoJSON properties
for feature in world_geo['features']:
country_name = feature['properties']['name']
if country_name in country_info:
feature['properties'].update(country_info[country_name])
# Create GeoJSONDataSource
geosource = GeoJSONDataSource(geojson=json.dumps(world_geo))
# Create figure with dark theme
p = figure(title='Nereus: Exploring the countries of the World',
width=1200,
height=600,
toolbar_location='right',
tools='pan,box_zoom,reset,save,wheel_zoom,tap',
x_axis_location=None,
y_axis_location=None,
background_fill_color='deepskyblue',
border_fill_color='#E3E4E0',styles = {"border-radius": "10px"})
# Remove grid and axes
p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.minor_tick_line_color = None
p.axis.major_label_text_color = None
# Add the map patches
countries = p.patches('xs', 'ys',
source=geosource,
fill_color='darkorange',
line_color='black',
line_width=1,
fill_alpha=1,
hover_fill_color='yellow',
hover_line_color='red',
hover_fill_alpha=1,
selection_fill_color='lime',
selection_line_color='black',
selection_fill_alpha=1,
nonselection_fill_color='darkorange',
nonselection_line_color='black',
nonselection_fill_alpha=1,
nonselection_line_width=1,
nonselection_line_alpha=1,
)
# Add hover tool
p.add_tools(
HoverTool(
tooltips="""<div style="background-color: #f0f0f0; padding: 5px; border-radius: 5px; box-shadow: 0px 0px 5px rgba(0,0,0,0.3);"> <font size="5" style="background-color: #f0f0f0; padding: 5px; border-radius: 5px;">
<i>Country:</i> <b>@name</b> <br>
<i>Region:</i> <b>@region</b> <br>
</font> </div> <style> :host { --tooltip-border: transparent; /* Same border color used everywhere */ --tooltip-color: transparent; --tooltip-text: #2f2f2f;} </style> """,
)
)
# Style the plot
p.title.text_font_size = '20pt'
p.title.text_font = 'helvetica'
p.title.text_color = 'black'
p.title.align = 'center'
# Configure plot size and aspect ratio
p.x_range.range_padding = 0
p.y_range.range_padding = 0
# Create a Div for the country info
info_div = Div(
text="""
<div style="
width: 1200px;
height: 300px;
background-color: #E3E4E0;
color: black;
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
font-family: helvetica;
font-size: 16px;
border-radius: 10px;
">
<p>Click on a country to see detailed information</p>
</div>
""",
width=1200,
height=300
)
def format_number(num):
if num == 'N/A':
return "N/A"
try:
num = float(num)
if num >= 1_000_000:
return f"{num/1_000_000:.1f}M"
if num >= 1_000:
return f"{num/1_000:.1f}K"
return str(int(num))
except:
return str(num)
def update_info(attr, old, new):
if new: # If there's a selection
index = new[0]
# Parse the GeoJSON to get the properties
geojson_data = json.loads(geosource.geojson)
properties = geojson_data['features'][index]['properties']
country_name = properties['name']
flag_url = properties['flag_url']
population = format_number(properties['population'])
capital = properties['capital']
area = format_number(properties['area'])
region = properties['region']
subregion = properties['subregion']
currencies = properties['currencies']
languages = properties['languages']
info_div.text = f"""
<div style="
width: 1200px;
height: 300px;
background-color: #E3E4E0;
color: black;
padding: 20px;
display: flex;
font-family: helvetica;
border-radius: 10px;
">
<div style="flex: 1; display: flex; justify-content: center; align-items: center;">
<img src="{flag_url}" style="max-height: 200px; border: 1px solid #333;">
</div>
<div style="flex: 2; padding: 20px;">
<h2 style="margin-bottom: 20px; color: #ffd700;">{country_name}</h2>
<div style="display: grid; grid-template-columns: auto auto; gap: 10px;">
<div style="color: #888;">Capital:</div>
<div>{capital}</div>
<div style="color: #888;">Population:</div>
<div>{population}</div>
<div style="color: #888;">Area:</div>
<div>{area} km²</div>
<div style="color: #888;">Region:</div>
<div>{region} ({subregion})</div>
<div style="color: #888;">Currency:</div>
<div>{currencies}</div>
<div style="color: #888;">Languages:</div>
<div>{languages}</div>
</div>
</div>
</div>
"""
# Connect the selection to the callback
geosource.selected.on_change('indices', update_info)
# Create the layout
layout = column([p, info_div])
return layout
# Add the layout to the document
doc = curdoc()
doc.add_root(create_map())
doc.title = "Nereus: Interactive World Map with Country Information"
In Greek mythology, Nereus (/ˈnɪəriəs/ NEER-ee-əs; Ancient Greek: Νηρεύς, romanized: Nēreús) was the eldest son of Pontus (the Sea) and Gaia (the Earth), with Pontus himself being a son of Gaia. Nereus and Doris became the parents of 50 daughters (the Nereids) and a son (Nerites), with whom Nereus lived in the Aegean Sea.