Radar charts in matplotlib
In this tutorial I will show you how to create Radar charts using Python and Matplotlib. For more matplotlib charts, check out the gallery:

Important notes:
1. This are my personal notes, so apologies if some explanations and notations are missing.
Radar charts in matplotlib
Click on the links to go to the specific tutorials:
- Radar chart without filled areas (81_1)
- Radar chart with all areas filled (81_2)
- Radar chart with only one area filled (81_3)
- Curved radar chart (81 of 100)




Create a radar chart using matplotlib
We start by importing the libraries, creating the sample data and adding the necessary columns:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.lines import Line2D
color_dict = { 2022: "#A54836", 2004: "#5375D4", }
spines_color, legend_color, grid_color, datalabels_color ='#BABABA',"#101628", "#BABABA", "#FFFFFF"
data = {
"year": [2004, 2022, 2004, 2022, 2004, 2022],
"countries" : ["Sweden", "Sweden", "Denmark", "Denmark", "Norway", "Norway"],
"sites": [13,15,4,10,5,8]
}
df= pd.DataFrame(data)
#custom sort
sort_order_dict = {"Denmark":2, "Sweden":3, "Norway":1, 2004:4, 2022:5}
df = df.sort_values(by=['year','countries',], key=lambda x: x.map(sort_order_dict))
#map the colors of a dict to a dataframe
df['color']= df.year.map(color_dict)
df
year | countries | sites | color | |
---|---|---|---|---|
4 | 2004 | Norway | 5 | #5375D4 |
2 | 2004 | Denmark | 4 | #5375D4 |
0 | 2004 | Sweden | 13 | #5375D4 |
5 | 2022 | Norway | 8 | #A54836 |
3 | 2022 | Denmark | 10 | #A54836 |
1 | 2022 | Sweden | 15 | #A54836 |
Define the variables
labels = df.countries.unique()
sites = df.sites
max_sites = df.sites.max()
years = df.year.unique()
unique_colors = df.color.unique()
#As we have 3 categories the radar chart shoud have 3 radial axis
# To find out the angle of each quadrant we divide 360/3
#angles need to be converted to radian so we multiply by 2*pi and create the list of angles:
angle = np.linspace(0, 2*np.pi, len(labels), endpoint=False)
#add the first angle to the end of the list to close the radar
angles = np.concatenate((angle,[angle[0]]))
Plot the chart
fig, ax= plt.subplots(figsize=(5,5), facecolor = "#FFFFFF", subplot_kw=dict(polar=True) )
for year in years:
sites = df[df.year == year].sites.to_list()
color = df[df.year == year].color.to_list()
sites.append(sites[0])
color.append(color[0])
for c,a, s in zip(color, angles, sites):
ax.plot(angles, sites, 'o-', ms = 14, mec = "w", linewidth=1, color = c, clip_on= False, zorder =2)
#bubble data labels
ax.annotate(s, xy=(a,s), color ="w", size= 8, ha="center", va="center")
# Fix axis to go in the right order and start at 12 o'clock.
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
ax.spines["polar"].set_color("none")
ax.set_rlim(0,max_sites)
# Change the color and linestyle of the circular gridlines.
ax.yaxis.grid(True,color=grid_color, ls= "dotted")
# Set the country labels for the angular axis (x)
ax.set_xticks(angle)
ax.set_xticklabels(labels, size=10)
# Go through labels and adjust alignment based on where
# it is in the circle.
for label, angle in zip(ax.get_xticklabels(), angles):
if 0 < angle < np.pi:
label.set_horizontalalignment('left')
elif angle ==0:
label.set_horizontalalignment('center')
else:
label.set_horizontalalignment('right')
#spine to the back
for k, spine in ax.spines.items():
spine.set_zorder(0)
#hide ticklabels
ax.set_yticklabels([])
#add custom tick labels
half_angle = (2*np.pi)/len(labels)/2
for i in range(0, max_sites,2):
ax.text(half_angle, i, i , size=8)
#add legends
lines = [Line2D([0], [0], color=c, linestyle='-', markersize=6,) for c in unique_colors]
plt.figlegend( lines,years, labelcolor=legend_color,
bbox_to_anchor=(0.5, -0.05), loc="lower center",
ncols = 2,frameon=False, fontsize= 10)

Radar chart with both shaded areas
We start by importing the libraries, creating the sample data and adding the necessary columns:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.lines import Line2D
color_dict = { 2022: "#A54836", 2004: "#5375D4", }
spines_color, legend_color, grid_color, datalabels_color ='#BABABA',"#101628", "#BABABA", "#FFFFFF"
data = {
"year": [2004, 2022, 2004, 2022, 2004, 2022],
"countries" : ["Sweden", "Sweden", "Denmark", "Denmark", "Norway", "Norway"],
"sites": [13,15,4,10,5,8]
}
df= pd.DataFrame(data)
#custom sort
sort_order_dict = {"Denmark":2, "Sweden":3, "Norway":1, 2004:4, 2022:5}
df = df.sort_values(by=['year','countries',], key=lambda x: x.map(sort_order_dict))
#map the colors of a dict to a dataframe
df['color']= df.year.map(color_dict)
df
year | countries | sites | color | |
---|---|---|---|---|
4 | 2004 | Norway | 5 | #5375D4 |
2 | 2004 | Denmark | 4 | #5375D4 |
0 | 2004 | Sweden | 13 | #5375D4 |
5 | 2022 | Norway | 8 | #A54836 |
3 | 2022 | Denmark | 10 | #A54836 |
1 | 2022 | Sweden | 15 | #A54836 |
Define the variables
labels = df.countries.unique()
sites = df.sites
max_sites = df.sites.max()
years = df.year.unique()
unique_colors = df.color.unique()
#As we have 3 categories the radar chart shoud have 3 radial axis
# To find out the angle of each quadrant we divide 360/3
#angles need to be converted to radian so we multiply by 2*pi and create the list of angles:
angle = np.linspace(0, 2*np.pi, len(labels), endpoint=False)
#add the first angle to the end of the list to close the radar
angles = np.concatenate((angle,[angle[0]]))
Plot the chart
fig, ax= plt.subplots(figsize=(5,5), facecolor = "#FFFFFF", subplot_kw=dict(polar=True) )
for year in years:
sites = df[df.year == year].sites.to_list()
color = df[df.year == year].color.to_list()
sites.append(sites[0])
color.append(color[0])
for c, a, s in zip(color, angles, sites):
ax.plot(angles, sites, '-', ms = 14, mec = "w", linewidth=2, color = c, clip_on= False, zorder =2)
#bubble data labels
ax.fill(angles, sites, alpha= .2, color = c)
# Fix axis to go in the right order and start at 12 o'clock.
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
ax.spines["polar"].set_color("none")
ax.set_rlim(0,max_sites)
# Change the color and linestyle of the circular gridlines.
ax.yaxis.grid(True,color=grid_color, ls= "dotted", lw=1)
# Set the country labels for the angular axis (x)
ax.set_xticks(angle)
ax.set_xticklabels(labels, size=10)
# Go through labels and adjust alignment based on where
# it is in the circle.
for label, angle in zip(ax.get_xticklabels(), angles):
if 0 < angle < np.pi:
label.set_horizontalalignment('left')
elif angle ==0:
label.set_horizontalalignment('center')
else:
label.set_horizontalalignment('right')
#hide ticklabels
ax.set_yticklabels([])
#add custom tick labels
half_angle = (2*np.pi)/len(labels)/2
for i in range(0, max_sites,2):
ax.text(half_angle, i, i , size=8)
#add legends
lines = [Line2D([0], [0], color=c, linestyle='-', markersize=6,) for c in unique_colors]
plt.figlegend( lines,years, labelcolor=legend_color,
bbox_to_anchor=(0.5, -0.05), loc="lower center",
ncols = 2,frameon=False, fontsize= 10)

Radar chart with one shaded area only
We start by importing the libraries, creating the sample data and adding the necessary columns:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.lines import Line2D
color_dict = { 2022: "#A54836", 2004: "#5375D4", }
spines_color, legend_color, grid_color, datalabels_color ='#BABABA',"#101628", "#BABABA", "#FFFFFF"
data = {
"year": [2004, 2022, 2004, 2022, 2004, 2022],
"countries" : ["Sweden", "Sweden", "Denmark", "Denmark", "Norway", "Norway"],
"sites": [13,15,4,10,5,8]
}
df= pd.DataFrame(data)
#custom sort
sort_order_dict = {"Denmark":2, "Sweden":3, "Norway":1, 2004:4, 2022:5}
df = df.sort_values(by=['year','countries',], key=lambda x: x.map(sort_order_dict))
#map the colors of a dict to a dataframe
df['color']= df.year.map(color_dict)
df
year | countries | sites | color | |
---|---|---|---|---|
4 | 2004 | Norway | 5 | #5375D4 |
2 | 2004 | Denmark | 4 | #5375D4 |
0 | 2004 | Sweden | 13 | #5375D4 |
5 | 2022 | Norway | 8 | #A54836 |
3 | 2022 | Denmark | 10 | #A54836 |
1 | 2022 | Sweden | 15 | #A54836 |
Define the variables
labels = df.countries.unique()
sites = df.sites
max_sites = df.sites.max()
years = df.year.unique()
unique_colors = df.color.unique()
#As we have 3 categories the radar chart shoud have 3 radial axis
# To find out the angle of each quadrant we divide 360/3
#angles need to be converted to radian so we multiply by 2*pi and create the list of angles:
angle = np.linspace(0, 2*np.pi, len(labels), endpoint=False)
#add the first angle to the end of the list to close the radar
angles = np.concatenate((angle,[angle[0]]))
Plot the chart
fig, ax= plt.subplots(figsize=(5,5), facecolor = "#FFFFFF", subplot_kw=dict(polar=True) )
for year, ap in zip(years,alpha):
sites = df[df.year == year].sites.to_list()
color = df[df.year == year].color.to_list()
sites.append(sites[0])
color.append(color[0])
for c, a, ap, s in zip(color, angles, ap, sites):
ax.plot(angles, sites, '-', ms = 14, mec = "w", linewidth=2, color = c, clip_on= False, zorder =2)
#bubble data labels
ax.fill(angles, sites, alpha= ap, color = c)
# Fix axis to go in the right order and start at 12 o'clock.
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
ax.spines["polar"].set_color("none")
ax.set_rlim(0,max_sites)
# Change the color and linestyle of the circular gridlines.
ax.yaxis.grid(True,color=grid_color, ls= "dotted", lw=1)
# Set the country labels for the angular axis (x)
ax.set_xticks(angle)
ax.set_xticklabels(labels, size=10)
# Go through labels and adjust alignment based on where
# it is in the circle.
for label, angle in zip(ax.get_xticklabels(), angles):
if 0 < angle < np.pi:
label.set_horizontalalignment('left')
elif angle ==0:
label.set_horizontalalignment('center')
else:
label.set_horizontalalignment('right')
#hide ticklabels
ax.set_yticklabels([])
#add custom tick labels
half_angle = (2*np.pi)/len(labels)/2
for i in range(0, max_sites,2):
ax.text(half_angle, i, i , size=8)
#add legends
lines = [Line2D([0], [0], color=c, linestyle='-', markersize=6,) for c in unique_colors]
plt.figlegend( lines,years, labelcolor=legend_color,
bbox_to_anchor=(0.5, -0.05), loc="lower center",
ncols = 2,frameon=False, fontsize= 10)

Looking for more matplotlib tutorials?
Check out the 1 dataset, 100 matplotlib visualizations project:

Reader Interactions