Visualization Guide
A library of styled, presentation-ready chart templates for health reporting, available in
Python (Matplotlib) and R (ggplot2). Each entry is complete and self-contained: expand
Show the code, pick your language, and everything needed to reproduce the output below it
is there — imports, data, styling, and layout. Swap the sample dataframe for your own and the
styling carries over. Chart types and styling follow the HIC house style for charts.
The Python source notebook is analysis/spatial-analysis/Bar-Plots.ipynb.
The datasets in these examples are illustrative placeholders used only to demonstrate layout and styling. They contain no real health, patient, or facility data.
Horizontal bar
Horizontal bar with a highlighted category
Ranks named categories and draws the eye to one row with a single accent color, keeping the rest as neutral context.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import pandas as pd
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
print(f"Using font: {FONT}")
else:
print("Gill Sans not found – using matplotlib default. "
"Install it and re-run for the exact look.")
# Data
HIGHLIGHT = "Spain"
COLOR_DEFAULT = "#1a4a7a"
COLOR_HIGHLIGHT = "#5fc4c0"
BG_COLOR = "#dce8f0"
df = pd.DataFrame({
"country": ["Germany", "Spain", "Austria", "France", "Luxembourg",
"Portugal", "Britain", "Denmark", "Sweden"],
"consultations_per_person": [8.2, 7.5, 6.8, 6.7, 6.3, 5.8, 5.1, 4.7, 2.9],
})
df["highlight"] = df["country"] == HIGHLIGHT
df["color"] = df["highlight"].map({True: COLOR_HIGHLIGHT, False: COLOR_DEFAULT})
df["y_pos"] = range(len(df) - 1, -1, -1) # top-to-bottom order
print(df.to_string(index=False))
fig, ax = plt.subplots(figsize=(7, 5))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
# Bars
ax.barh(df["y_pos"], df["consultations_per_person"],
color=df["color"], height=0.6)
# Axes
ax.set_xlim(0, 8.5)
ax.set_xticks([0, 2, 4, 6, 8])
ax.xaxis.set_tick_params(labelsize=9, colors="#555555")
ax.xaxis.tick_top()
ax.set_yticks(df["y_pos"])
ax.set_yticklabels(
[f"$\\bf{{{c}}}$" if c == HIGHLIGHT else c for c in df["country"]],
fontsize=10
)
ax.tick_params(axis="y", length=0)
# Grid & spines
ax.xaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
ax.tick_params(axis="x", length=0)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.055, 0.016,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.965, "Salud", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.930, "Doctors' consultations per person", fontsize=9,
va="top", color="#333333", transform=fig.transFigure)
fig.text(0.02, 0.905, "Selected countries, 2009", fontsize=8.5,
va="top", color="#555555", transform=fig.transFigure)
# Footer
fig.text(0.02, 0.02, "Source: Health Management Information System",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.79, 0.02, "Health Intelligence Center",
fontsize=8, color="#666666", transform=fig.transFigure)
# rect=[left, bottom, right, top] — lower 'top' pushes the plot area down
plt.tight_layout(rect=[0, 0.03, 1, 0.78])
plt.show()
Horizontal bar, two series
Compares two measures per category (for example two periods or two indicators) with paired bars.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import pandas as pd
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
print(f"Using font: {FONT}")
else:
print("Gill Sans not found – using matplotlib default.")
# Data
df = pd.DataFrame({
"country": ["Rubavu", "Rusizi", "Bugesera", "Nyabihu", "Musanze",
"Burera", "Rulindo", "Gasabo", "Nyamasheke"],
"middle_class": [80, 72, 60, 58, 55, 50, 62, 50, 48],
"low_income": [68, 63, 50, 62, 38, 42, 55, 48, 42],
})
# Reverse so Chile appears on top
df = df.iloc[::-1].reset_index(drop=True)
print(df.to_string(index=False))
# Colors
COLOR_MIDDLE = "#1a4a7a"
COLOR_LOW = "#5fc4c0"
BG_COLOR = "#dce8f0"
# Layout
n = len(df)
y = np.arange(n)
height = 0.35 # bar height for each trace
fig, ax = plt.subplots(figsize=(7, 5.5))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
# Bars
ax.barh(y + height / 2, df["middle_class"], height=height,
color=COLOR_MIDDLE, label="Middle class")
ax.barh(y - height / 2, df["low_income"], height=height,
color=COLOR_LOW, label="Low income")
# Axes
ax.set_xlim(0, 85)
ax.set_xticks([0, 20, 40, 60, 80])
ax.xaxis.set_tick_params(labelsize=9, colors="#555555")
ax.xaxis.tick_top()
ax.set_yticks(y)
ax.set_yticklabels(df["country"], fontsize=10, fontweight="bold")
ax.tick_params(axis="y", length=0)
# Grid & spines
ax.xaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
ax.tick_params(axis="x", length=0)
# Legend
legend = ax.legend(
loc="lower right",
fontsize=9,
frameon=False,
handlelength=1.2,
handleheight=0.9,
handletextpad=0.5,
)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.965), 0.045, 0.016,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.955, "Defenders of democracy",
fontsize=12, fontweight="bold", va="top",
color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.915,
"Respondents saying that honest elections held regularly\n"
"with a choice of at least two political parties are very\n"
"important, 2007, %",
fontsize=8.5, va="top", color="#444444",
transform=fig.transFigure, linespacing=1.5)
# Footer
fig.text(0.02, 0.02, "Source: Pew Global Attitudes Survey",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.72, 0.02, "Health Intelligence Center",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.03, 1, 0.78])
plt.show()
Grouped horizontal bar with sub-categories
Breaks each category into grouped sub-category bars with group labels.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import matplotlib.text as mtext
import pandas as pd
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
print(f"Using font: {FONT}")
else:
print("Gill Sans not found – using matplotlib default.")
# Data: two forecast vintages (June / September) per district and year
df = pd.DataFrame({
"country": ["Burera", "Burera", "Nyabihu", "Nyabihu", "Rulindo",
"Rulindo", "Gasabo", "Gasabo", "Musanze", "Musanze"],
"year": [2011, 2012] * 5,
"june": [3.0, 1.8, 2.1, 1.4, 2.0, 1.5, 0.8, 1.1, 1.0, 0.3],
"september": [2.7, 1.3, 1.7, 1.0, 1.6, 1.1, 0.7, 0.3, 0.6, 0.1],
})
print(df.to_string(index=False))
# Colors
COLOR_JUNE = "#1a4a7a"
COLOR_SEP = "#5fc4c0"
BG_COLOR = "#dce8f0"
# Build y positions
# Layout (bottom→top): Italy_2012, Italy_2011, gap, Spain_2012, Spain_2011 …
# Each year-row occupies 2 bar slots stacked: june on top, sep below.
# june centre = y_row + BAR_H/2
# sep centre = y_row - BAR_H/2
BAR_H = 0.30 # height of each individual bar
ROW_STEP = BAR_H * 2 + 0.05 # vertical space per year-row
GROUP_GAP = 0.30 # extra gap between country groups
countries = df["country"].unique() # original order: Germany … Italy
positions = []
y = 0
# Build bottom-up: last country first, within each country 2012 first
for country in reversed(countries):
sub = df[df["country"] == country].sort_values("year", ascending=False) # 2012, 2011
# Place 2012 at bottom of group, 2011 above it
for _, row in sub.iterrows():
positions.append({
"country": country,
"year": int(row["year"]),
"june": row["june"],
"sep": row["september"],
"y": y
})
y += ROW_STEP
y += GROUP_GAP
pos_df = pd.DataFrame(positions)
# After building bottom-up: lower y = 2012, higher y = 2011 ✓
# Plot
fig, ax = plt.subplots(figsize=(6, 6.5))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
for _, r in pos_df.iterrows():
# June on top (+BAR_H/2), September below (-BAR_H/2)
ax.barh(r["y"] + BAR_H / 2, r["june"], height=BAR_H,
color=COLOR_JUNE, align="center")
ax.barh(r["y"] - BAR_H / 2, r["sep"], height=BAR_H,
color=COLOR_SEP, align="center")
# Y-axis: year labels only, country drawn via ax.text
ax.set_yticks(pos_df["y"])
ax.set_yticklabels([str(r["year"]) for _, r in pos_df.iterrows()],
fontsize=9, color="#555555")
ax.tick_params(axis="y", length=0, pad=2)
# Draw country name centred between its two year rows
for country in countries:
rows = pos_df[pos_df["country"] == country]
mid_y = rows["y"].mean()
ax.text(-0.18, mid_y, country,
ha="right", va="center", fontsize=9, fontweight="bold",
transform=ax.get_yaxis_transform())
# X-axis
ax.set_xlim(0, 3.3)
ax.set_xticks([0, 1, 2, 3])
ax.xaxis.set_tick_params(labelsize=9, colors="#555555")
ax.xaxis.tick_top()
ax.tick_params(axis="x", length=0)
# Grid & spines
ax.xaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Legend
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor=COLOR_JUNE, label="June"),
Patch(facecolor=COLOR_SEP, label="September")]
ax.legend(handles=legend_elements, loc="lower right", fontsize=8,
frameon=False, title="Forecasts made in:", title_fontsize=8)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.045, 0.013,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "Autumn fall", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.936, "GDP, % increase on previous year", fontsize=9,
va="top", color="#444444", transform=fig.transFigure)
# Footer
fig.text(0.02, 0.02, "Source: HMIS",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.79, 0.02, "Health Intelligence Center",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.03, 1, 0.80])
plt.show()
Ranked horizontal bar with emphasis
A longer ranked list where one bar carries the story.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import pandas as pd
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
print(f"Using font: {FONT}")
else:
print("Gill Sans not found – using matplotlib default.")
# Data
df = pd.DataFrame({
"sector": ["Internet", "Software", "Tech hardware", "IT services",
"Health care", "Total", "Leisure", "Chemicals",
"Oil, gas and fuels", "Metals and mining", "Property",
"Energy equipment"],
"change": [57, 34, 20, 16, 10, 4, -3, -5, -18, -22, -28, -58],
"bold": [False, False, False, False, False, True,
False, False, False, False, False, False],
})
# Reverse so Internet is on top
df = df.iloc[::-1].reset_index(drop=True)
print(df.to_string(index=False))
# Colors
COLOR_BAR = "#1a4a7a"
COLOR_ZERO = "#c0392b"
BG_COLOR = "#dce8f0"
# Plot
fig, ax = plt.subplots(figsize=(6, 6.5))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
y = np.arange(len(df))
ax.barh(y, df["change"], color=COLOR_BAR, height=0.6, zorder=2)
# Zero line (red)
ax.axvline(x=0, color=COLOR_ZERO, linewidth=1.5, zorder=3)
# Axes
ax.set_xlim(-65, 65)
ax.set_xticks([-60, -40, -20, 0, 20, 40, 60])
ax.xaxis.set_tick_params(labelsize=9, colors="#555555")
ax.xaxis.tick_top()
ax.tick_params(axis="x", length=0)
ax.set_yticks(y)
ax.set_yticklabels(df["sector"], fontsize=10)
ax.tick_params(axis="y", length=0)
# Bold "Total" label
for label, (_, row) in zip(ax.get_yticklabels(), df.iterrows()):
if row["bold"]:
label.set_fontweight("bold")
# Grid & spines
ax.xaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.045, 0.013,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "Another dotcom boom", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.935, "Change in worldwide capital spending*, %",
fontsize=9, va="top", color="#444444", transform=fig.transFigure)
fig.text(0.02, 0.912, "2014-17 forecast",
fontsize=9, va="top", color="#444444", transform=fig.transFigure)
# Footer
fig.text(0.02, 0.02, "Source: Goldman Sachs",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.68, 0.02, "*In dollar terms",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.03, 1, 0.78])
plt.show()
Vertical column
Vertical column chart
Ordered categories or a short time series reading left to right.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import pandas as pd
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
print(f"Using font: {FONT}")
else:
print("Gill Sans not found – using matplotlib default.")
# Data
df = pd.DataFrame({
"year": ["2004","06","08","10","11","12","13","14","15*"],
"gdp": [ 11.7, 12.6, 11.2, 10.0, 11.2, 10.9, 9.7, 10.3, 8.7],
"estimate": [ False, False, False, False, False, False, False, False, True],
})
# Colors
COLOR_DEFAULT = "#1a4a7a"
COLOR_ESTIMATE = "#5fc4c0"
BG_COLOR = "#dce8f0"
df["color"] = df["estimate"].map({False: COLOR_DEFAULT, True: COLOR_ESTIMATE})
print(df.to_string(index=False))
# Plot
fig, ax = plt.subplots(figsize=(5, 5))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
x = range(len(df))
ax.bar(x, df["gdp"], color=df["color"], width=0.75, zorder=2)
# Axes
ax.set_xlim(-0.5, len(df) - 0.5)
ax.set_ylim(0, 14)
ax.set_yticks([0, 2, 4, 6, 8, 10, 12, 14])
ax.yaxis.set_tick_params(labelsize=9, colors="#555555")
ax.yaxis.tick_right() # y-axis on the right
ax.tick_params(axis="y", length=0)
ax.set_xticks(list(x))
ax.set_xticklabels(df["year"], fontsize=9, color="#555555")
ax.tick_params(axis="x", length=0)
# Grid & spines
ax.yaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.055, 0.014,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "African lion", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.935, "Ethiopia's GDP, % change on a year earlier",
fontsize=9, va="top", color="#444444", transform=fig.transFigure)
# Footer
fig.text(0.02, 0.02, "Source: IMF",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.02, 0.055, "*Estimate",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.79, 0.02, "Health Intelligence Center",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.06, 1, 0.80])
plt.show()
Vertical column chart, alternate styling
The same column pattern with a different palette and emphasis.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import matplotlib.patches as mpatches
import pandas as pd
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
print(f"Using font: {FONT}")
else:
print("Gill Sans not found – using matplotlib default.")
# Data
df = pd.DataFrame({
"period": ["20 years", "10 years", "5 years"],
"equities": [8.5, 7.5, 8.5],
"all_property": [9.5, 8.0, 13.5],
"rural": [14.5, 13.5, 13.0],
"forestry": [11.0, 18.5, 20.5],
})
print(df.to_string(index=False))
# Colors
COLORS = {
"equities": "#1a4a7a",
"all_property": "#5fc4c0",
"rural": "#c9a84c",
"forestry": "#2d7a4a",
}
LABELS = {
"equities": "Equities",
"all_property": "All property",
"rural": "Rural property",
"forestry": "Forestry",
}
BG_COLOR = "#dce8f0"
keys = list(COLORS.keys())
n_groups = len(df)
n_bars = len(keys)
width = 0.18 # width of each bar
group_w = width * n_bars + 0.08 # total group width incl. gap
x = np.arange(n_groups)
# Plot
fig, ax = plt.subplots(figsize=(6, 5))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
for i, key in enumerate(keys):
offsets = x - (n_bars - 1) * width / 2 + i * width
ax.bar(offsets, df[key], width=width, color=COLORS[key],
label=LABELS[key], zorder=2)
# Axes
ax.set_xlim(-0.5, n_groups - 0.5)
ax.set_ylim(0, 22)
ax.set_yticks([0, 5, 10, 15, 20])
ax.yaxis.set_tick_params(labelsize=9, colors="#555555")
ax.yaxis.tick_right()
ax.tick_params(axis="y", length=0)
ax.set_xticks(x)
ax.set_xticklabels(df["period"], fontsize=9, color="#555555")
ax.tick_params(axis="x", length=0)
# Grid & spines
ax.yaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Legend
handles = [mpatches.Patch(facecolor=COLORS[k], label=LABELS[k]) for k in keys]
ax.legend(handles=handles, ncol=2, fontsize=8, frameon=False,
loc="upper left", bbox_to_anchor=(0, 1.02),
handlelength=1.0, handleheight=0.9, columnspacing=1.0)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.055, 0.014,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "The mighty jungle", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.935, "Britain, annualised rate of return\non investment*, %",
fontsize=9, va="top", color="#444444",
transform=fig.transFigure, linespacing=1.5)
# Footer
fig.text(0.02, 0.02, "Sources: MSCI; JP Morgan",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.75, 0.02, "*To 2014",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.79, 0.055, "Health Intelligence Center",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.06, 1, 0.75])
plt.show()
Vertical column chart with a highlighted bar
A column chart that highlights a single period or category.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import matplotlib.patches as mpatches
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
print(f"Using font: {FONT}")
else:
print("Gill Sans not found – using matplotlib default.")
# Data
# Approximate net private capital inflows/outflows in $bn, 1994–2011
# Values estimated from the chart image
years = [
1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
]
values = [
-4, -5, -23, -18, -8, -18, -25, -15, -8, -7,
-35, -30, 40, 80, -130, -50, -35, 5
]
# Two-tone: teal for positive inflows, darker teal for outflows (negative)
# The chart uses a medium teal for most bars and a lighter teal for the
# 2006-2008 positive bars — we'll use a single accent for positive values
COLOR_NEG = "#3a9aa0" # muted teal (outflows)
COLOR_POS = "#1a6a7a" # darker teal (inflows)
COLOR_ZERO = "#c0392b" # red zero line
BG_COLOR = "#dce8f0" # light blue-grey house background
bar_colors = [COLOR_POS if v >= 0 else COLOR_NEG for v in values]
# Plot
fig, ax = plt.subplots(figsize=(5, 5.5))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
x = np.arange(len(years))
ax.bar(x, values, color=bar_colors, width=0.75, zorder=2)
# Zero / baseline (red)
ax.axhline(y=0, color=COLOR_ZERO, linewidth=1.5, zorder=3)
# Axes
ax.set_ylim(-160, 120)
ax.set_yticks([100, 50, 0, -50, -100, -150])
ax.yaxis.set_tick_params(labelsize=9, colors="#555555")
ax.yaxis.tick_right()
ax.tick_params(axis="y", length=0)
# X-axis: show subset of year labels to match original
label_map = {
1994: "1994", 1996: "96", 1998: "98", 2000: "2000", 2002: "02",
2004: "04", 2006: "06", 2008: "08", 2011: "11*"
}
xtick_positions = [i for i, yr in enumerate(years) if yr in label_map]
xtick_labels = [label_map[years[i]] for i in xtick_positions]
ax.set_xticks(xtick_positions)
ax.set_xticklabels(xtick_labels, fontsize=8.5, color="#555555")
ax.tick_params(axis="x", length=0)
ax.xaxis.tick_bottom()
# Grid & spines
ax.yaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Red badge (house style)
fig.add_artist(plt.Rectangle(
(0.02, 0.975), 0.045, 0.013,
transform=fig.transFigure,
color="#c0392b", clip_on=False
))
# Title block
fig.text(0.02, 0.968, "The money drain",
fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.935, "Net private capital inflows/outflows, $bn",
fontsize=9, va="top", color="#444444", transform=fig.transFigure)
# Footer
fig.text(0.02, 0.02, "Source: Central Bank of Russia",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.55, 0.02, "*To June 30th",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.04, 1, 0.80])
plt.show()
Stacked column chart
Shows how a total splits into parts over time. The house style caps stacked charts at four categories with the standard palette — swap in other colors beyond that, and use grey for “Other”.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import pandas as pd
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
# Data
df = pd.DataFrame({
"year": np.arange(2008, 2018),
"paid_search": [22, 25, 29, 34, 40, 47, 55, 64, 74, 85],
"display": [10, 11, 13, 15, 18, 21, 25, 30, 36, 43],
"classified": [8, 8, 9, 10, 11, 12, 13, 14, 15, 16],
"online_video": [2, 3, 4, 6, 8, 11, 15, 20, 27, 35],
})
# Colors (house stack order: darkest at the bottom)
COLORS = {
"paid_search": "#1a4a7a",
"display": "#5fc4c0",
"classified": "#c9a84c",
"online_video": "#2d7a4a",
}
LABELS = {
"paid_search": "Paid search",
"display": "Display",
"classified": "Classified",
"online_video": "Online video",
}
BG_COLOR = "#dce8f0"
print(df.to_string(index=False))
fig, ax = plt.subplots(figsize=(6, 4.8))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
# Forecast panel behind the last two columns
ax.axvspan(2015.5, 2017.6, color="#c3d6e4", zorder=1)
ax.text(2016.55, 196, "FORECAST", fontsize=7.5, color="#555555",
ha="center", va="top")
# Stacked bars
bottom = np.zeros(len(df))
for key, color in COLORS.items():
ax.bar(df["year"], df[key], bottom=bottom, color=color,
width=0.75, label=LABELS[key], zorder=2)
bottom += np.asarray(df[key], dtype=float)
# Axes
ax.set_xlim(2007.4, 2017.6)
ax.set_xticks([2008, 2010, 2012, 2014, 2016, 2017])
ax.set_xticklabels(["2008", "10", "12", "14", "16", "17"])
ax.set_ylim(0, 210)
ax.set_yticks([0, 50, 100, 150, 200])
ax.yaxis.tick_right()
ax.xaxis.set_tick_params(labelsize=9, colors="#555555", length=0)
ax.yaxis.set_tick_params(labelsize=9, colors="#555555", length=0)
# Grid & spines
ax.yaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Legend
ax.legend(loc="upper left", frameon=False, fontsize=8,
handlelength=1.0, handleheight=0.9, labelspacing=0.4)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.05, 0.014,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "Getting sociable", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.933, "Global internet-advertising spending, $bn", fontsize=9,
va="top", color="#444444", transform=fig.transFigure)
# Footer
fig.text(0.02, 0.02, "Source: ZenithOptimedia",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.72, 0.02, "Health Intelligence Center",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.05, 1, 0.82])
plt.show()
Line
Line chart, two series
The default for time series. The house palette orders line colors dark blue, then teal, then gold — up to six series, though four is a practical ceiling.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
# Data — yields over the 13 years following each period start
years = np.arange(1, 14)
japan = np.array([8.0, 7.2, 6.1, 5.2, 4.4, 3.5, 3.0, 2.4, 1.9, 1.7, 1.5, 1.4, 1.3])
us = np.array([6.6, 6.0, 5.2, 4.6, 4.2, 4.4, 4.7, 4.2, 3.6, 3.2, 2.6, 2.2, 2.0])
# Colors
COLOR_JAPAN = "#1a4a7a"
COLOR_US = "#5fc4c0"
BG_COLOR = "#dce8f0"
fig, ax = plt.subplots(figsize=(6, 4.8))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
ax.plot(years, japan, color=COLOR_JAPAN, linewidth=2.2,
solid_capstyle="round", label="January 1989 (Japan)", zorder=3)
ax.plot(years, us, color=COLOR_US, linewidth=2.2,
solid_capstyle="round", label="January 1999 (US)", zorder=3)
# Axes
ax.set_xlim(0.6, 13.4)
ax.set_xticks([1, 5, 10, 13])
ax.set_xlabel("Years since start", fontsize=9, color="#555555")
ax.set_ylim(0, 8.4)
ax.set_yticks([0, 2, 4, 6, 8])
ax.yaxis.tick_right()
ax.xaxis.set_tick_params(labelsize=9, colors="#555555", length=0)
ax.yaxis.set_tick_params(labelsize=9, colors="#555555", length=0)
# Grid & spines
ax.yaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Legend under the title block
fig.text(0.02, 0.885, "Period beginning:", fontsize=8.5, va="top",
color="#444444", transform=fig.transFigure)
fig.legend(loc="upper left", bbox_to_anchor=(0.02, 0.87), frameon=False,
fontsize=8.5, ncol=2, handlelength=1.4, columnspacing=1.2)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.05, 0.014,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "Where Tokyo leads", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.933, "Ten-year government-bond yields, %", fontsize=9,
va="top", color="#444444", transform=fig.transFigure)
# Footer
fig.text(0.02, 0.02, "Source: Thomson Reuters",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.72, 0.02, "Health Intelligence Center",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.05, 1, 0.76])
plt.show()
Stacked area chart
Shows a total and its composition at once. Label the bands directly on the chart instead of using a legend.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
# Data
years = np.arange(2002, 2012)
china = np.array([0.3, 0.4, 0.6, 0.8, 1.1, 1.5, 1.9, 2.4, 2.9, 3.2])
other = np.array([0.9, 1.0, 1.2, 1.5, 1.8, 2.2, 2.4, 2.6, 3.0, 3.3])
# Colors
COLOR_CHINA = "#1a4a7a"
COLOR_OTHER = "#5fc4c0"
BG_COLOR = "#dce8f0"
fig, ax = plt.subplots(figsize=(5.5, 4.8))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
ax.stackplot(years, china, other,
colors=[COLOR_CHINA, COLOR_OTHER], zorder=2)
# In-chart labels instead of a legend
ax.text(2009.6, 1.2, "China", color="white", fontsize=9,
fontweight="bold", ha="center", zorder=3)
ax.text(2009.2, 4.3, "Other\ndeveloping\ncountries", color="#0e3f4a", fontsize=9,
fontweight="bold", ha="center", linespacing=1.3, zorder=3)
# Axes
ax.set_xlim(2002, 2011)
ax.set_xticks([2002, 2004, 2006, 2008, 2010, 2011])
ax.set_xticklabels(["2002", "04", "06", "08", "10*", "11*"])
ax.set_ylim(0, 6.6)
ax.set_yticks([0, 2, 4, 6])
ax.yaxis.tick_right()
ax.xaxis.set_tick_params(labelsize=9, colors="#555555", length=0)
ax.yaxis.set_tick_params(labelsize=9, colors="#555555", length=0)
# Grid & spines
ax.yaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.05, 0.014,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "Foreign hoards", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.933, "Developing-country currency reserves\n$trn", fontsize=9,
va="top", color="#444444", transform=fig.transFigure, linespacing=1.4)
# Footer
fig.text(0.02, 0.02, "Source: IMF",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.78, 0.02, "*Forecast",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.05, 1, 0.80])
plt.show()
Scatter
Annotated scatter plot
Relates two continuous variables, labelling points directly instead of using a legend.
Show the code
Python
import matplotlib
matplotlib.rcParams['text.usetex'] = False
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import matplotlib.patches as mpatches
import numpy as np
available = {f.name for f in fm.fontManager.ttflist}
FONT = next((f for f in ["Gill Sans","Gill Sans MT","URW Classico","Trebuchet MS"] if f in available), None)
if FONT:
plt.rcParams["font.family"] = FONT
C_MW = "#8b3030" # Midwest — dark red (highlighted)
C_OTHER = "#3bbfbf" # All other — teal
C_GRAY = "#9e9e9e" # Large cities — grey
BG = "#dce8f0"
RED_LINE= "#c0392b"
RED_BAR = "#c0392b"
np.random.seed(42)
def make_counties(n, health_mu, health_sd, swing_mu, swing_sd, size_mu, size_sd):
"""Simulate county-level data. Replace with real arrays when available."""
health = np.random.normal(health_mu, health_sd, n)
swing = np.random.normal(swing_mu, swing_sd, n)
size = np.abs(np.random.normal(size_mu, size_sd, n)) + 10
return health, swing, size
# Other regions (teal) — spread across full x-range, mixed swing
h_ot, s_ot, sz_ot = make_counties(600, 48, 8, -2, 16, 20, 18)
# Midwest (dark red) — clustered at lower health, stronger R swing
h_mw, s_mw, sz_mw = make_counties(450, 38, 5, 16, 10, 18, 14)
# Large cities (grey) — few, larger bubbles, less partisan swing
h_gr = np.array([35, 45, 52, 58, 40, 30 ])
s_gr = np.array([-5, -8, -12, -2, -15, -3 ])
sz_gr = np.array([180, 220, 300, 250, 160, 140 ])
# Named/highlighted counties — white-outlined dots with leader lines
named = {
"Jefferson, OH": (32, 42),
"Knox, OH": (43, 32),
}
# Figure
fig, ax = plt.subplots(figsize=(7.4, 5.6))
fig.patch.set_facecolor(BG)
ax.set_facecolor(BG)
fig.subplots_adjust(left=0.11, right=0.76, top=0.77, bottom=0.18)
# Layer order: other (bottom) → grey cities → Midwest (top)
ax.scatter(h_ot, s_ot, s=sz_ot, color=C_OTHER, alpha=0.55,
linewidths=0, zorder=2)
ax.scatter(h_gr, s_gr, s=sz_gr, color=C_GRAY, alpha=0.65,
linewidths=0.5, edgecolors='white', zorder=3)
ax.scatter(h_mw, s_mw, s=sz_mw, color=C_MW, alpha=0.60,
linewidths=0, zorder=4)
# Named counties — white-filled dot with dark outline + leader line + label
for name, (x, y) in named.items():
ax.scatter(x, y, s=38, color='white', linewidths=1.2,
edgecolors=C_MW, zorder=6)
ox = 1.2 if "Knox" in name else 1.0
ax.annotate(name, xy=(x, y), xytext=(x + ox, y + 3.5),
fontsize=8, color="#1a1a1a", zorder=7,
arrowprops=dict(arrowstyle="-", color="#555555", lw=0.7))
# Region label inside cluster
ax.text(37, 14, "Midwest", fontsize=13, fontweight="bold",
color="black", alpha=0.85, zorder=5, style='italic')
# Zero reference line
ax.axhline(0, color=RED_LINE, linewidth=1.0, zorder=2)
# Axes
ax.set_xlim(18, 72)
ax.set_ylim(-55, 55)
ax.set_xticks([20, 30, 40, 50, 60, 70])
ax.set_yticks([-50, -25, 0, 25, 50])
ax.xaxis.set_tick_params(labelsize=8.5, colors="#555555", length=0)
ax.yaxis.set_tick_params(labelsize=8.5, colors="#555555", length=0)
ax.xaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.yaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for sp in ax.spines.values():
sp.set_visible(False)
# Axis labels
ax.set_xlabel("Index of county health metrics*", fontsize=9,
color="#444444", labelpad=22)
ax.text(0.02, -0.13, "◄ WORSE HEALTH", fontsize=7.5, color="#555555",
transform=ax.transAxes, va='top')
ax.text(0.98, -0.13, "BETTER HEALTH ►", fontsize=7.5, color="#555555",
ha='right', transform=ax.transAxes, va='top')
ax.text(-0.10, 0.75, "MORE REPUBLICAN", fontsize=7.5, color="#555555",
transform=ax.transAxes, va='center', ha='center', rotation=90)
ax.text(-0.10, 0.25, "LESS REPUBLICAN", fontsize=7.5, color="#555555",
transform=ax.transAxes, va='center', ha='center', rotation=90)
# Right-side y-axis description
fig.text(0.77, 0.77,
"Change in Republican margin over\nDemocrats, 2012-16, % points",
fontsize=7.8, color="#444444", va="top", ha="left",
linespacing=1.4, transform=fig.transFigure)
# Bubble size legend
lx, ly = 0.790, 0.62
circ = mpatches.Circle((lx + 0.025, ly - 0.018), 0.026,
transform=fig.transFigure, fill=False,
edgecolor="#555555", linewidth=0.9, clip_on=False)
fig.add_artist(circ)
fig.text(lx + 0.060, ly, "Voting\neligible\npopulation",
fontsize=7.5, color="#1a1a1a", va="center",
transform=fig.transFigure, linespacing=1.3)
# Red badge + Title
fig.add_artist(plt.Rectangle((0.06, 0.942), 0.038, 0.008,
transform=fig.transFigure, color=RED_BAR, clip_on=False))
fig.text(0.06, 0.938, "Vitality and the vote",
fontsize=11, fontweight="bold", va="top", color="#1a1a1a",
transform=fig.transFigure)
fig.text(0.06, 0.910,
"United States, health metrics against swing to Donald Trump",
fontsize=8.5, va="top", color="#444444", transform=fig.transFigure)
fig.text(0.06, 0.890, "By county",
fontsize=8, va="top", color="#444444", transform=fig.transFigure)
# Footer
fig.text(0.06, 0.022,
"Sources: Atlas of US Presidential Elections; Census Bureau;\n"
"IPUMS, University of Minnesota; Institute for Health Metrics\n"
"and Evaluation; Health Intelligence Center",
fontsize=7, color="#666666", transform=fig.transFigure, linespacing=1.4)
fig.text(0.52, 0.022,
"*Weighted index of obesity, diabetes,\nheavy drinking, physical exercise and\nlife expectancy, 2010-12",
fontsize=7, color="#666666", transform=fig.transFigure, linespacing=1.4)
print("Saved: vitality_vote_midwest.png")
Thermometer
Dot-range chart
Compares two values per category — two periods, two scenarios — with dot terminals joined by a range line. The house style recommends dot terminals when plain thermometer lines become unclear.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import pandas as pd
import numpy as np
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
# Data
df = pd.DataFrame({
"district": ["Gasabo", "Kicukiro", "Nyarugenge", "Musanze", "Rubavu",
"Huye", "Rusizi", "Nyagatare", "Burera", "Rwamagana"],
"june": [310, 285, 300, 240, 255, 220, 205, 190, 175, 160],
"september": [385, 350, 330, 310, 290, 270, 240, 250, 210, 205],
})
# Colors
COLOR_JUNE = "#5fc4c0"
COLOR_SEP = "#1a4a7a"
COLOR_RANGE = "#9bb8c8"
BG_COLOR = "#dce8f0"
print(df.to_string(index=False))
fig, ax = plt.subplots(figsize=(6, 5))
fig.patch.set_facecolor(BG_COLOR)
ax.set_facecolor(BG_COLOR)
# Range lines with dot terminals
y = np.arange(len(df))[::-1]
ax.hlines(y, df["june"], df["september"],
color=COLOR_RANGE, linewidth=1.6, zorder=2)
ax.scatter(df["june"], y, s=42, color=COLOR_JUNE, zorder=3, label="June")
ax.scatter(df["september"], y, s=42, color=COLOR_SEP, zorder=3, label="September")
# Axes
ax.set_xlim(0, 420)
ax.set_xticks([0, 100, 200, 300, 400])
ax.xaxis.tick_top()
ax.xaxis.set_tick_params(labelsize=9, colors="#555555", length=0)
ax.set_yticks(y)
ax.set_yticklabels(df["district"], fontsize=10)
ax.tick_params(axis="y", length=0)
# Grid & spines
ax.xaxis.grid(True, color="white", linewidth=0.8, zorder=0)
ax.set_axisbelow(True)
for spine in ax.spines.values():
spine.set_visible(False)
# Legend
ax.legend(loc="lower right", frameon=False, fontsize=8.5,
handletextpad=0.3, borderaxespad=0.2)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.05, 0.014,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "Catching up", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.933, "Outpatient consultations per 1,000 population*",
fontsize=9, va="top", color="#444444", transform=fig.transFigure)
# Footer
fig.text(0.02, 0.02, "Source: HMIS",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.68, 0.02, "*Illustrative data",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0, 0.04, 1, 0.82])
plt.show()
Pie / doughnut
Doughnut chart
Use pies and doughnuts with caution: beyond four categories, prefer a stacked bar. Keep at most six slices, order them clockwise from 12 o’clock, and use grey for “Other”.
Show the code
Python
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
# Font setup
available = {f.name for f in fm.fontManager.ttflist}
FONT = next(
(f for f in ["Gill Sans", "Gill Sans MT", "URW Classico", "Trebuchet MS"]
if f in available),
None
)
if FONT:
plt.rcParams["font.family"] = FONT
# Data
labels = ["Jaguar Land Rover", "Tata Steel", "Tata Consultancy Services",
"Tata Chemicals", "Taj (hotels)", "Others"]
values = [19.0, 19.4, 4.9, 0.5, 0.4, 1.2]
# Colors — standard order, grey last for "Others"
colors = ["#1a4a7a", "#5fc4c0", "#c9a84c", "#2d7a4a", "#8b3030", "#b8c4cc"]
BG_COLOR = "#dce8f0"
fig, ax = plt.subplots(figsize=(6.5, 4.4))
fig.patch.set_facecolor(BG_COLOR)
# Doughnut: a pie with a wedge width < 1
wedges, _ = ax.pie(values, colors=colors, startangle=90, counterclock=False,
wedgeprops={"width": 0.42, "edgecolor": BG_COLOR,
"linewidth": 1})
ax.text(0, 0, "Total\n45.4", ha="center", va="center",
fontsize=11, fontweight="bold", color="#1a1a1a", linespacing=1.2)
# Color-keyed labels with values, house style
legend_labels = [f"{l} {v}" for l, v in zip(labels, values)]
ax.legend(wedges, legend_labels, loc="center left", bbox_to_anchor=(1.0, 0.5),
frameon=False, fontsize=8.5, labelspacing=0.8, handlelength=0.8,
handleheight=1.1)
# Red badge
fig.add_artist(plt.Rectangle((0.02, 0.975), 0.045, 0.014,
transform=fig.transFigure,
color="#c0392b", clip_on=False))
# Title block
fig.text(0.02, 0.968, "Naan bigger", fontsize=12, fontweight="bold",
va="top", color="#1a1a1a", transform=fig.transFigure)
fig.text(0.02, 0.933, "Tata's workforce in Britain, September 2011\n'000",
fontsize=9, va="top", color="#444444", transform=fig.transFigure,
linespacing=1.4)
# Footer
fig.text(0.02, 0.02, "Source: Tata",
fontsize=8, color="#666666", transform=fig.transFigure)
fig.text(0.72, 0.02, "Health Intelligence Center",
fontsize=8, color="#666666", transform=fig.transFigure)
plt.tight_layout(rect=[0.02, 0.05, 0.72, 0.80])
plt.show()