Python: Font example#

Description

This notebook uses some examples to show how text or labels can be plotted with different fonts or paths.

Software requirements

  • Python 3

  • numpy

  • matplotlib

Example script#

fonts_and_text.py

#!/usr/bin/env python
# coding: utf-8
#
#-- About fonts in a nutshell
#
# 2025 copyright DKRZ licensed under CC BY-NC-ND 4.0 
#                     (https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en)
# 
# This notebook uses some examples to show how text/labels can be plotted with 
# different fonts or paths.
#
# **rcParam**
# 
# - font.family
# - font.style
# - font.variant
# - font.stretch
# - font.weight
# - font.size
# 
# **CCS-based generic-family aliases**
# 
# - 'cursive'
# - 'fantasy'
# - 'monospace'
# - 'sans'
# - {'sans serif', 'sans-serif', 'serif'}
# 
import numpy as np

import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib.patheffects as path_effects

#-- init output file name increment
save_id = 0

#-- function to save a plot
def save_plot():
    global save_id
    plt.savefig(f'plot_font_example_{save_id}.png', bbox_inches='tight', dpi=150)
    save_id += 1

# List all available fonts

fonts = mpl.font_manager.get_font_names()

#search_font = 'Courier'
#search_font = 'Helvetica'
search_font = 'Comic'
search_font = 'Armen'

print([s for s in fonts if search_font in s])

#-- Default font settings
# 
# https://matplotlib.org/stable/api/font_manager_api.html#matplotlib.font_manager.FontProperties

print('Font name:               ', mpl.font_manager.FontProperties().get_name())
print('Font family:             ', mpl.font_manager.FontProperties().get_family()) #-- same as mpl.rcParams['font.family']
print('Font size:               ', mpl.font_manager.FontProperties().get_size())
print('Font size pts:           ', mpl.font_manager.FontProperties().get_size_in_points())
print('Font style:              ', mpl.font_manager.FontProperties().get_style())
print('Font weight:             ', mpl.font_manager.FontProperties().get_weight())
print('Font variant:            ', mpl.font_manager.FontProperties().get_variant())
print('Font stretch:            ', mpl.font_manager.FontProperties().get_stretch())
print('Font slant:              ', mpl.font_manager.FontProperties().get_slant())
print('Font math font family:   ', mpl.font_manager.FontProperties().get_math_fontfamily())
print('Font file:               ',
       mpl.font_manager.findfont(mpl.font_manager.FontProperties(family=mpl.rcParams['font.family'])))
print('Font fontconfig pattern: ', 
       mpl.font_manager.FontProperties().get_fontconfig_pattern())

#-- Default font DejaVu
# 
# Use the default font settings to draw a string.

fig, ax = plt.subplots()
ax.set_axis_off()

text = ax.text(0.1, 0.5, 'Hello world!', ha='right')

plt.show()

#-- Font fallback
# 
# If you want to mix some different characters that are not covered by only one 
# font you can use the 'font fallback'. This means that Matplotlib trys to get 
# the correct characters or glyphs from a list of given fonts.
# The string Հայոց լեզու in the following example is Armenian and means 
# 'Armenian language'.

fig, ax = plt.subplots()
ax.set_axis_off()

text = ax.text(0.01, 0.5, 
               'Use font fallback to switch to Հայոց լեզու (Armenian Language) in same text',
               family=['DejaVu Sans', 
                       '.SF Armenian', 
                       '.SF Armenian Rounded'])
plt.show()

#-- Use fontdict

font_settings = dict(size=20, color='red', family='sans-serif', weight='bold')

fig, ax = plt.subplots()

ax.text(0.2, 0.25, 'How to use fontdict', fontdict=font_settings)
ax.text(0.2, 0.75, 'How to use fontdict', **font_settings)

ax.set_xlabel('x-axis label', **font_settings)
ax.set_ylabel('y-axis label', **font_settings);

plt.show()

#-- Change the font
# 
# **Note:**
# Changing the Matplotlib resource parameters (`mpl.rcParams`) will affect the 
# notebook. It is not always possible to 'reset' the mpl.rcParams['font.sans-serif'] 
# to the default values. Then the  Notebook kernel must be restarted to load 
# the changed resources. So, if the font changes or anything else might not give 
# the correct result, restart the kernel and run the code cells up to the 
# current code cell.

#-- choose whether to use or change the default font setting 
#-- for the text, title, and labels
use_defaults=True

if not use_defaults:
    print('... change defaults ...')
    #-- Make general changes to the rcParams defaults, this is 
    #-- temporarily used but will affect all following code cells. 
    #mpl.rcParams['text.usetex'] = False
    #mpl.rcParams['font.family'] = 'sans-serif' 
    mpl.rcParams['font.sans-serif'] = 'Comic Sans MS'
else:
    mpl.rcParams['font.sans-serif'] = 'DejaVu Sans'
    
fig, ax = plt.subplots(figsize=(8,4))

fontdict = dict(size=16, color='red', family='sans-serif', weight='bold')

ax.text(0.05, 0.05, 'How to change the font to Comic Sans MS', 
        fontname='Comic Sans MS', fontdict=fontdict)
ax.text(0.05, 0.25, 'How to change the font to Arial', 
        fontname='Arial', fontdict=fontdict)
ax.text(0.05, 0.45, 'How to change the font to Times New Roman', 
        fontname='Times New Roman', fontdict=fontdict)
ax.text(0.05, 0.65, 'How to change the font to Courier New', 
        fontname='Courier New', fontdict=fontdict)
ax.text(0.05, 0.85, 'Default font DejaVu Sans', fontdict=fontdict)

#-- use default font but use another font for the axis ticks and labels
if use_defaults:
    print('... defaults ...')
    #-- change the axis tick value font
    for tick in ax.get_xticklabels():
        tick.set_fontname('Comic Sans MS')
    for tick in ax.get_yticklabels():
        tick.set_fontname('Comic Sans MS')
    #-- set font name for axis labels
    labelfontdict = dict(name='Comic Sans MS')
else:
    labelfontdict = {}
    
ax.set_xlabel('x-axis label', color='blue', weight='bold', **labelfontdict)
ax.set_ylabel('y-axis label', color='green', weight='bold', **labelfontdict);

plt.show()

# Another way:

#-- reset to defaults
mpl.rc_file_defaults()

#-- choose whether to use or change the default font setting 
#-- for the text, title, and labels
use_defaults = False

if not use_defaults:
    print('... change defaults ...')
    mpl.rcParams['font.family'] = 'Comic Sans MS'
    
    fig, ax = plt.subplots(figsize=(8,4))
 
    #-- don't use family='sans-serif' in fontdict for COMIC Sans MS
    fontdict = dict(size=16, color='red', weight='bold')
    ax.text(0.05, 0.05, 'How to change the font to Comic Sans MS', 
            fontname='Comic Sans MS', fontdict=fontdict)
    ax.text(0.05, 0.25, 'How to change the font to Arial', 
            fontname='Arial', fontdict=fontdict)
    ax.text(0.05, 0.45, 'How to change the font to Times New Roman', 
            fontname='Times New Roman', fontdict=fontdict)
    ax.text(0.05, 0.65, 'How to change the font to Courier New', 
            fontname='Courier New', fontdict=fontdict)
    ax.text(0.05, 0.85, 'Default font Comic Sans MS', fontdict=fontdict)
    
    labelfontdict = {}
else:
    print('... use defaults ...')
    mpl.rc_file_defaults()
    
    fig, ax = plt.subplots()
    
    fontdict = dict(size=16, color='red', family='sans-serif', weight='bold')
    ax.text(0.05, 0.05, 'How to change the font to Comic Sans MS', 
            fontname='Comic Sans MS', fontdict=fontdict)
    ax.text(0.05, 0.25, 'How to change the font to Arial', 
            fontname='Arial', fontdict=fontdict)
    ax.text(0.05, 0.45, 'How to change the font to Times New Roman', 
            fontname='Times New Roman', fontdict=fontdict)
    ax.text(0.05, 0.65, 'How to change the font to Courier New', 
            fontname='Courier New', fontdict=fontdict)
    ax.text(0.05, 0.85, 'Default font DejaVu Sans', fontdict=fontdict)
    
    labelfontdict = {}

ax.set_xlabel('x-axis label', color='blue', weight='bold', **labelfontdict)
ax.set_ylabel('y-axis label', color='green', weight='bold', **labelfontdict);

#-- save plot
save_plot()
print(f'--> save_id = {save_id}')

plt.show()


#-- Reset to default font
mpl.rc_file_defaults()

#-- Change the automatic 'offset text'
# 
# Matplotlib automatically change the scale of the y-axis values (auto-scaled 
# graph) and displays the exponential notation at the upper-left corner of the 
# plot. 
# 
# Change the default offset value notation at the upper-left corner.

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8,3), layout='tight')
fig.subplots_adjust(wspace=1)

y = 10000
data = [y + i for i in range(10)]

#-- left axis
ax1.plot(data)
ax1.set_title('default yaxis exponential notation', y=1.1)

#-- right axis
ax2.plot(data)
ax2.set_title('turn off yaxis "offsetText"', y=1.1)

ax2.yaxis.offsetText.set_visible(False)
ax2.text(-0.4, max(data)+0.7, f'*{y:{len(str(y))}.0f}', 
         color='r', size='small');

plt.show()

# Change the format of the offset notation and add it to the y-axis label. To 
# do this you have to change the formatter and redraw the figure canvas to 
# update the auto-scale graph.

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8,3), layout='tight')
fig.subplots_adjust(wspace=1)

y = 10000
data = [y + i for i in range(10)]

#-- left plot
ax1.plot(data)
ax1.set_title('default yaxis exponential notation', y=1.1)
ax1.set_ylabel('y-axis label')

#-- right plot
ax2.plot(data)
ax2.set_title('change yaxis "offsetText" format', y=1.1)

#-- change formatter and update the axis by re-draw the canvas
formatter = mticker.ScalarFormatter(useMathText=True)
formatter.set_powerlimits((-3,2))
ax2.yaxis.set_major_formatter(formatter)
fig.canvas.draw()
#-- change font size and color of the offset text
ax2.yaxis.get_offset_text().set_fontsize(8)
ax2.yaxis.get_offset_text().set_color('red')

#-- get the y-axis offset text and append it to the ylabel
offset = ax2.yaxis.get_major_formatter().get_offset()
ax2.set_ylabel('y-axis label '+offset);

plt.show()

# Mixed font sizes
# 
# LaTex font sizes:
#     \tiny             \normalsize     \huge
#     \scriptsize       \large          \Huge
#     \footnotesize     \Large
#     \small            \LARGE
#
# If the LaTeX notation is not rendered correctly re-run the code cell a second time to make sure the `mpl.rcParams['text.usetex'] = True` works as expected. This is a typical LaTeX interpreter behavior.

mpl.rcParams['text.usetex'] = True

fig, ax = plt.subplots()

ax.set_title(r'{\small{This is small}} \ and \  {\Huge{this is big}}')

sizes = ['\\tiny', '\\scriptsize', '\\footnotesize', '\\small', 
         '\\normalsize', '\\large', '\\Large', '\\LARGE',  
         '\\huge', '\\Huge']

for i,fs in enumerate(sizes):
    ax.text(0.05+(0.05*i), 0.02+(0.08*i), r'{}'.format(fs +' '+ 'Hello world!'));

plt.show()

#-- Try to change the font type of the axis tick labels
# 
# **Note:** There is just a small set of fonts available for LaTeX in Matplotlib!
# 
# Serif:
# - Computer Modern Roman
# - Palatino (mathpazo)
# - Times (mathptmx)
# - Bookman (bookman)
# - New Century Schoolbook (newcent)
# - Charter (charter)
# 
# Sans-serif:
# - Computer Modern Serif
# - Helvetica (helvet)
# - Avant Garde (avant)
# 
# Cursive:
# - Zapf Chancery (chancery)
# 
# Monospace:
# - Computer Modern Typewriter, Courier (courier)
# 
# https://matplotlib.org/stable/users/explain/text/usetex.html

new_font = 'Avant Garde'

#-- set resource params
#-- LateX settings
mpl.rcParams['text.usetex'] = True

#-- add sans-serif fonts for math-text; single or multiple fonts
mpl.rc('text.latex', preamble=r'\usepackage{sfmath} \usepackage{amsmath}')

#-- font settings
mpl.rcParams['font.family'] = 'sans-serif'
mpl.rcParams['font.sans-serif'] = new_font

#-- create the figure
fig, ax = plt.subplots()

ax.set_title(r'{\small{This is small}} \ and \  {\Huge{this is Huge}}');

#-- font sizes
sizes = ['\\tiny', '\\scriptsize', '\\footnotesize', '\\small', 
         '\\normalsize', '\\large', '\\Large', '\\LARGE',  
         '\\huge', '\\Huge']

#-- write increasing string
for i,fs in enumerate(sizes):
    ax.text(0.05+(0.05*i), 0.02+(0.09*i), r'{}'.format(fs +' '+ '1,2,3,4 Hello world!'))

#-- tick values font
for tick in ax.get_xticklabels():
    tick.set_fontfamily('sans-serif')
    tick.set_fontname(new_font)
for tick in ax.get_yticklabels():
    tick.set_fontfamily('sans-serif')
    tick.set_fontname(new_font)

#-- axis labels font
labelfontdict = {}
ax.set_xlabel('x-axis label', color='blue', weight='bold', **labelfontdict)
ax.set_ylabel('y-axis label', color='green', weight='bold', **labelfontdict);

plt.show()

# List all Matplotlib resource settings from .matplotlibrc and its changes.

#mpl.rcParams

# **Note:** The following example demonstrates how to get the position and 
# labels of the axis ticks.

x,y = ax.get_xticklabels()[0].get_position()
print(x,y)
text = ax.get_xticklabels()[0].get_text()
print(text)

# ## Accents and "Umlaute"

fig, ax = plt.subplots()
ax.set_axis_off()

mpl.rc_file_defaults()
mpl.rcParams['text.usetex'] = False

uml      = r'$\ddot{A}  \: \ddot{a}  \: \ddot{O}  \: \ddot{o}  \: \ddot{U}  \: \ddot{u} $'
acute    = r'$\acute{e}  \: \grave{e} $'
arrow_up = r'$\hat{O} $'
vector   = r'$\vec{q} $'

size = 12

ax.text(0.1, 0.85, uml, fontsize=size)
ax.text(0.1, 0.65, acute, fontsize=size)
ax.text(0.1, 0.45, arrow_up, fontsize=size)
ax.text(0.1, 0.25, vector, fontsize=size)

ax.set_title(r'$\ddot{o} \: \acute{e}  \:  \grave{e}  \:  \hat{O} '
             r'\breve{i}  \:  \bar{A}  \:  \tilde{n}  \:  \vec{q} $', 
             loc='left', fontsize=20)

#-- shortkeys (not really better readable)
ax.text(0.4, 0.8, r"$F=m\ddot{x}$", fontsize=size)
ax.text(0.4, 0.5, r'''$\"o \: \ddot o \: \'e \: \`e \: \~n \: \.x \: \^y$''', 
        fontsize=size);

plt.show()

#--  Font demo (Matplotlib)
# 
# https://matplotlib.org/stable/gallery/text_labels_and_annotations/fonts_demo.html#sphx-glr-gallery-text-labels-and-annotations-fonts-demo-py

#  Background text box - bbox

fig, ax = plt.subplots()
ax.set_axis_off()
ax.grid()

#-- square (default) box
txbox_kw = dict(facecolor='yellow', edgecolor='black')
text = ax.text(0.1, 0.1, 'default', fontsize=16, bbox=txbox_kw)

#-- round box
txbox_kw.update(dict(boxstyle='round', linewidth=1, linestyle='--'))
text = ax.text(0.1, 0.25, 'round', fontsize=16, bbox=txbox_kw)

#-- sawtooth box
txbox_kw.update(dict(boxstyle='sawtooth', linewidth=0.5, linestyle='-'))
text = ax.text(0.1, 0.4, 'sawtooth', fontsize=16, bbox=txbox_kw)

#-- roundtooth box
txbox_kw.update(dict(boxstyle='roundtooth', linewidth=0.5, linestyle='-'))
text = ax.text(0.1, 0.55, 'roundtooth', fontsize=16, bbox=txbox_kw)

#-- larrow, rarrow, darrow
txbox_kw.update(dict(boxstyle='larrow', linewidth=0.5, linestyle='-'))
text = ax.text(0.12, 0.72, 'larrow', fontsize=16, bbox=txbox_kw)
txbox_kw.update(dict(boxstyle='rarrow'))
text = ax.text(0.45, 0.72, 'rarrow', fontsize=16, bbox=txbox_kw)
txbox_kw.update(dict(boxstyle='darrow'))
text = ax.text(0.8, 0.72, 'darrow', fontsize=16, bbox=txbox_kw)

#-- round box
txbox_kw.update(dict(boxstyle='round4', linewidth=1, linestyle='--'))
text = ax.text(0.45, 0.25, 'round4', fontsize=16, bbox=txbox_kw)

#-- circle box
txbox_kw.update(dict(boxstyle='circle', linewidth=1, linestyle='-', 
                     facecolor='red', alpha=0.5))
text = ax.text(0.8, 0.15, 'circle', fontsize=16, bbox=txbox_kw)

#-- ellipse box
txbox_kw.update(dict(boxstyle='ellipse', linewidth=0.5, linestyle='-'))
text = ax.text(0.8, 0.4, 'ellipse', fontsize=16, bbox=txbox_kw)

plt.show()

# Math equations

fig, ax = plt.subplots()
#fig, ax = plt.subplots(figsize=(2,0.5), layout='tight')
ax.set_axis_off()

text = ax.text(0.02, 0.5, r'Math formular:  $circumference = 2*\pi*r$')

plt.show()

#-- Artists
# 
# Path effects

fig, ax = plt.subplots()
#fig, ax = plt.subplots(figsize=(2,1))
ax.set_axis_off()

text = ax.text(0.02, 0.5, 'Text outlines', color='silver', fontsize=36, va='center')

text.set_path_effects([path_effects.Stroke(linewidth=3, foreground='black'),
                       path_effects.Normal()])

plt.show()

#---

fig, ax = plt.subplots()
ax.set_axis_off()

text = ax.text(0.02, 0.5, 'Text with a shadow', fontsize=36, va='center')

text.set_path_effects([path_effects.PathPatchEffect(offset=(4, -4), 
                                                    hatch='xxxxx', 
                                                    linewidth=0.4,
                                                    facecolor='silver'), 
                       path_effects.PathPatchEffect(edgecolor='white', 
                                                    linewidth=1.1, 
                                                    facecolor='black')])
#- change the hatches linewidth
mpl.rcParams['hatch.linewidth'] = 0.4

plt.show()

#---

fig, ax = plt.subplots()
#fig, ax = plt.subplots(figsize=(2,0.5), layout='tight')
ax.set_axis_off()

text = ax.text(0.02, 0.5, 'Text with a shadow', fontsize=36, weight='bold')

text.set_path_effects([path_effects.PathPatchEffect(offset=(4, -4), 
                                                    hatch='.....', 
                                                    edgecolor='grey',
                                                    facecolor='silver'), 
                       path_effects.PathPatchEffect(linewidth=1.1,
                                                    edgecolor='cyan',
                                                    facecolor='black')])

#- change the hatches linewidth
mpl.rcParams['hatch.linewidth'] = 1.

plt.show()

# Circular annotations

from matplotlib.textpath import TextPath
from matplotlib.patches import PathPatch

#mpl.rc_file_defaults()

#-- annotate the pie chart sections (pie slice)
def add_pie_slice_text(text, angle, radius=1, scale=0.01, y=0.):
    text_path = TextPath((0, 0), text, size=10)
    text_path.vertices.flags.writeable = True
    vertices = text_path.vertices
    
    xmin, xmax = vertices[:, 0].min(), vertices[:, 0].max()
    ymin, ymax = vertices[:, 1].min(), vertices[:, 1].max()
    
    vertices -= (xmin+xmax)/2, (ymin+ymax)/2
    vertices *= scale
    
    for i in range(len(vertices)):
        theta = angle - vertices[i, 0]
        vertices[i, 0] = (radius-y + vertices[i, 1]) * np.cos(theta)
        vertices[i, 1] = (radius-y + vertices[i, 1]) * np.sin(theta)
        
    patch = PathPatch(text_path, facecolor='k', linewidth=0)
    ax.add_artist(patch)

#-- create the figure and axis
fig, ax = plt.subplots()
ax.set_aspect(1.0)

#-- Choose 12 colors from Spectral colormap
colors = mpl.cm.Spectral(np.linspace(0, 1, 12))

#-- draw pie chart (circular band with 12 sections),
#-- set size of the circular band width of the pie chart
size = 0.3   
ax.pie(np.ones(12), radius=1, colors=colors, 
       wedgeprops=dict(width=size, edgecolor='black'))

#-- get the month names as 3 character abbreviations
import calendar
text_list = [m.upper() for m in list(calendar.month_abbr[1:])]

#-- add annotations
for i in range(12):
    add_pie_slice_text(text_list[i], angle=(2.5-i)*2*np.pi/12, radius=1-0.5*size, y=0.)

seasons = ['MAM','JJA','SON','DJF']
for i in range(len(seasons)):
    add_pie_slice_text(seasons[i], angle=(11.5-i*3)*2*np.pi/12,
                       radius=1+size, scale=0.0125, y=0.2)

fig.text(0.51, 0.49, 'Seasons', ha='center');

#-- save plot
save_plot()
print(f'--> save_id = {save_id}')

plt.show()



Plot result#

image0

image1