Matplotlib nestest pie chart ¶
Alexander Dunkel, Leibniz Institute of Ecological Urban and Regional Development,
Transformative Capacities & Research Data Centre (IÖR-FDZ)
The task in this notebook is to take this power point circle diagram and reproduce it with matplotlib.
I first tried pyCirclize, but it did not work well. Matplotlib donut charts, on the other hand, are quite simple.
1 Nested Donut Charts¶
1.1 Import dependencies¶
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patheffects
from typing import List
1.2 Define Parameters¶
Colors
yellow = "#F8E04C"
green = "#006571"
brown = "#BE651A"
lightgreen = "#86BC27"
blue = "#0970B9"
General matplotlib pie chart parameters
General properties of circles:
width = 0.18
wedge_properties = {
"width": width,
"edgecolor": "w",
'linewidth': 2}
Properties of bbox text background
bbox_props = {
"linewidth": 0,
"alpha": 0.7}
Text/ label properties
textprops = {
"size": 14,
"color": "k",
"alpha": 0.9,
"horizontalalignment": 'center',
"bbox": bbox_props,
}
Combine general properties
pie_props = {
"wedgeprops": wedge_properties,
"textprops": textprops,
"startangle": 40,
}
1.3 Define content¶
Starting with the outermost circle. For labeling, see: https://matplotlib.org/stable/gallery/pie_and_polar_charts/pie_and_donut_labels.html
v1 = [1, 1, 1, 1, 1, 1, 1]
labels1 = [
"Biologische Vielfalt \nentwickeln",
"Umweltrisiken \nbegegnen",
"Materialkreisläufe in \nStädten schließen",
"Wandel der Flächennutzung \nverstehen",
"Mensch-Natur-Beziehungen \nstärken",
"Transformation \ngemeinsam gestalten",
"Städte und Regionen der \nZukunft planen"]
colors1 = [yellow, yellow, yellow, yellow, yellow, yellow, yellow]
v2 = [1, 1, 1, 1, 1]
colors2 = [green, green, green, green, green, green, green]
labels2 = [
"Zivilgesellschaft",
"Journalismus",
"Politik",
"Verwaltung",
"Wissenschaft"]
v3 = [1, 1, 1]
labels3 = [
"Workshops",
"Arbeitshilfen",
"Showroom"]
colors3 = [brown, brown, brown]
v4 = [1, 1, 1, 1, 1, 1]
labels4 = [
"Anwendungen",
"Indikatoren",
"Modelle",
"Analysen",
"Simulationen",
"Daten"]
colors4 = [lightgreen, lightgreen, lightgreen,
lightgreen, lightgreen, lightgreen]
v5 = [1, 1, 1, 1]
labels5 = [
"Website",
"Schnittstellen",
"Visualisierungen",
"Repository"]
colors5 = [blue, blue, blue, blue]
1.4 Create pie plot¶
with 5 nested circles
fig, ax = plt.subplots(
nrows=1, ncols=1, figsize=(12, 18))
# outer circle
bbox_props["color"] = yellow
textprops["color"] = "k"
ax.pie(
v1, labels=labels1, labeldistance=1.15, colors=colors1,
**pie_props),
# 4th circle
bbox_props["color"] = green
textprops["color"] = "w"
ax.pie(
v2, labels=labels2, labeldistance=0.90,
radius=1-width, colors=colors2,
**pie_props)
# 3rd circle
bbox_props["color"] = brown
textprops["color"] = "w"
ax.pie(
v3, labels=labels3, labeldistance=0.85,
radius=1-width-width, colors=colors3,
**pie_props)
# 2nd circle
bbox_props["color"] = lightgreen
textprops["color"] = "k"
ax.pie(
v4, labels=labels4, labeldistance=0.80,
radius=1-width-width-width, colors=colors4,
**pie_props)
# inner circle
bbox_props["color"] = blue
textprops["color"] = "w"
ax.pie(
v5, labels=labels5, labeldistance=0.60,
radius=1-width-width-width-width, colors=colors5,
**pie_props)
plt.show()
fig.savefig(
"../resources/circle_v1.svg", format='svg',
bbox_inches='tight', pad_inches=1, facecolor="white")
1.5 Customize text labels¶
Modifying labels can be done by getting wedges
and texts
from the pie.plot()
. See the given example from the MPL docs.
bbox_props_alt = {
"boxstyle": "square,pad=0.3",
"fc": yellow,
"ec": "k",
"lw": 0
}
textprops_alt = {
"arrowprops": dict(arrowstyle="-"),
"bbox": bbox_props_alt,
"zorder": 0,
"va": "center",
"size": 14,
"color": "k",
"alpha": 0.9,
"zorder": 100,
}
def mod_labels(ax, bbox_props_alt, textprops_alt, labels, wedges, yoffset):
"""Add and style labels to pie chart"""
for i, p in enumerate(wedges):
ang = (p.theta2 - p.theta1)/2. + p.theta1
y = np.sin(np.deg2rad(ang))
x = np.cos(np.deg2rad(ang))
horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]
connectionstyle = "angle,angleA=0,angleB={}".format(ang)
textprops_alt["arrowprops"].update(
{"connectionstyle": connectionstyle})
ax.annotate(
labels[i],
xy=(x, (y+yoffset)), xytext=(1.15*np.sign(x), 1.05*(y+yoffset)),
horizontalalignment=horizontalalignment, **textprops_alt)
fig, ax = plt.subplots(
nrows=1, ncols=1, figsize=(12, 18))
# outer circle
bbox_props_alt["fc"] = yellow
textprops_alt["color"] = "k"
wedges, texts = ax.pie(
v1, colors=colors1, **pie_props)
mod_labels(ax, bbox_props_alt, textprops_alt, labels1, wedges, 0.02)
# 4th circle
bbox_props["color"] = green
textprops["color"] = "w"
ax.pie(
v2, labels=labels2, labeldistance=0.90,
radius=1-width, colors=colors2,
**pie_props)
# 3rd circle
bbox_props["color"] = brown
textprops["color"] = "w"
ax.pie(
v3, labels=labels3, labeldistance=0.85,
radius=1-width-width, colors=colors3,
**pie_props)
# 2nd circle
bbox_props["color"] = lightgreen
textprops["color"] = "k"
ax.pie(
v4, labels=labels4, labeldistance=0.80,
radius=1-width-width-width, colors=colors4,
**pie_props)
# inner circle
bbox_props["color"] = blue
textprops["color"] = "w"
ax.pie(
v5, labels=labels5, labeldistance=0.60,
radius=1-width-width-width-width, colors=colors5,
**pie_props)
plt.show()
fig.savefig(
"../resources/circle_v2.svg", format='svg',
bbox_inches='tight', pad_inches=1, facecolor="white")
1.6 Colorize path¶
Similarly, we can update the method to label/colorize different paths of the nested pie plot. As a start, we need to select the path by updating color lists per circle.
purple = "#6E2F9D"
colors1 = [yellow, yellow, yellow, purple, yellow, yellow, yellow]
colors2 = [purple, purple, purple, purple, green, purple, purple]
colors3 = [brown, brown, purple]
colors4 = [lightgreen, lightgreen, lightgreen,
purple, lightgreen, purple]
colors5 = [purple, blue, purple, purple]
def mod_labels_color(ax, wedges, bbox_props, textprops, labels, colors, labeldistance, starting_textcolor):
"""Add and style labels to pie chart"""
highlight = False
if "#6E2F9D" in colors:
highlight = True
for i, p in enumerate(wedges):
bbox_props_mod = bbox_props.copy()
cur_col = colors[i]
# p.set_path_effects(
# [patheffects.SimplePatchShadow(offset=(10, -10)),
# patheffects.Normal()])
if cur_col == "#6E2F9D":
# we want text labels in selected
# path to be shown with purple
# color background and white text
textprops["alpha"] = 0.9
textprops["color"] = "w"
bbox_props_mod["color"] = "#6E2F9D"
# also highlight selected path by
# adding a shadow to patches
p.set_path_effects(
[patheffects.SimplePatchShadow(offset=(10, -10)),
patheffects.Normal()])
else:
# we want text labels not in selected path
# to be shown without background color
# and reduced alpha
if highlight:
textprops["alpha"] = 0.3
bbox_props_mod["color"] = "none"
else:
# if no path is selected
# use default values
textprops["alpha"] = 0.9
# highlight all paths (excluding the first) by
# adding a shadow to patches
if i>1:
p.set_path_effects(
[patheffects.SimplePatchShadow(offset=(10, -10)),
patheffects.Normal()])
textprops["color"] = starting_textcolor
textprops["bbox"] = bbox_props_mod
ang = (p.theta2 - p.theta1)/2. + p.theta1
y = np.sin(np.deg2rad(ang))*labeldistance
x = np.cos(np.deg2rad(ang))*labeldistance
ax.annotate(
labels[i],
xy=(x, y), **textprops)
def plot_pie(colors: List[List[str]], highlight_color: str):
"""Plot pie chart, highlight selected path"""
colors1 = colors[0]
colors2 = colors[1]
colors3 = colors[2]
colors4 = colors[3]
colors5 = colors[4]
fig, ax = plt.subplots(
nrows=1, ncols=1, figsize=(12, 18))
# outer circle
bbox_props["color"] = yellow
wedges, texts = ax.pie(
v1, colors=colors1, **pie_props)
mod_labels_color(
ax, wedges, bbox_props, textprops, labels1, colors1, 1.00, "k")
# 4th circle
bbox_props["color"] = green
wedges, texts = ax.pie(
v2, radius=1-width, colors=colors2, **pie_props)
mod_labels_color(
ax, wedges, bbox_props, textprops, labels2, colors2, 0.75, "w")
# 3rd circle
bbox_props["color"] = brown
wedges, texts = ax.pie(
v3, radius=1-width-width, colors=colors3, **pie_props)
mod_labels_color(
ax, wedges, bbox_props, textprops, labels3, colors3, 0.55, "w")
# 2nd circle
bbox_props["color"] = lightgreen
wedges, texts = ax.pie(
v4, radius=1-width-width-width, colors=colors4, **pie_props)
mod_labels_color(
ax, wedges, bbox_props, textprops, labels4, colors4, 0.35, "k")
# inner circle
bbox_props["color"] = blue
textprops["color"] = "w"
wedges, texts = ax.pie(
v5, radius=1-width-width-width-width, colors=colors5, **pie_props)
mod_labels_color(
ax, wedges, bbox_props, textprops, labels5, colors5, 0.15, "w")
plt.show()
return fig
fig = plot_pie(
colors=[colors1, colors2, colors3, colors4, colors5],
highlight_color=purple)
1.7 Colorize different path¶
colors1 = [yellow, yellow, yellow, yellow, yellow, purple, yellow]
colors2 = [purple, green, green, purple, purple, purple, purple]
colors3 = [brown, brown, purple]
colors4 = [lightgreen, lightgreen, lightgreen,
purple, lightgreen, purple]
colors5 = [purple, blue, purple, purple]
fig = plot_pie(
colors=[colors1, colors2, colors3, colors4, colors5],
highlight_color=purple)
1.8 Colorize no path¶
colors1 = [yellow, yellow, yellow, yellow, yellow, yellow, yellow]
colors2 = [green, green, green, green, green, green, green]
colors3 = [brown, brown, brown]
colors4 = [lightgreen, lightgreen, lightgreen,
lightgreen, lightgreen, lightgreen]
colors5 = [blue, blue, blue, blue]
fig = plot_pie(
colors=[colors1, colors2, colors3, colors4, colors5],
highlight_color=purple)
1.9 Save to SVG and PNG¶
fig.savefig(
"../resources/circle_v4.png", dpi=300, format='PNG',
bbox_inches='tight', pad_inches=1, facecolor="white")
fig.savefig(
"../resources/circle_v4.svg", format='svg',
bbox_inches='tight', pad_inches=1, facecolor="white")
2 Create notebook HTML¶
!jupyter nbconvert --to html_toc \
--output-dir=../resources/html/ ./2024-11-21_donut_mpl.ipynb \
--template=../nbconvert.tpl \
--ExtractOutputPreprocessor.enabled=False >&- 2>&-