Matplotlib figures with Helvetica labels – helvet vs. tgheros

I recently spent a frustrating amount of time figuring out a good way to have labels and text in the Helvetica font in Matplotlib. Here’s what I found:

The figure


is generated by the following code:

import pylab as pl
import numpy as np

from matplotlib import rc

rc('text', usetex=True)
pl.rcParams['text.latex.preamble'] = [
    r'\usepackage{tgheros}',    # helvetica font
    r'\usepackage{sansmath}',   # math-font matching  helvetica
    r'\sansmath'                # actually tell tex to use it!
    r'\usepackage{siunitx}',    # micro symbols
    r'\sisetup{detect-all}',    # force siunitx to use the fonts

pl.figure(1, figsize=(6,4))
ax = pl.axes([0.1, 0.1, 0.8, 0.7])
t = np.arange(0.0, 1.0+0.01, 0.01)
s = np.cos(2*2*np.pi*t)+2
pl.plot(t, s)

pl.xlabel(r'time $\lambda$')
pl.ylabel(r'\textit{voltage (mV)}',fontsize=16)
pl.title(r'\TeX\ Number 1234567890 anisotropic ' + 
         r'$\displaystyle\sum_{n=1}^\infty' +
         r'\frac{-e^{i\pi}}{2^n}$' + 
         r' and \SI{3}{\micro\metre}',fontsize=16, color='r')


To achieve the consistent Helvetica font in the figure the LaTeX rendering of Matplotlib labels and text is used. This setup is adapted from an StackExchange answer by Paul H.

Most importantly, I use the tgheros instead of the helvet package. You can see the difference in the images below:


The bottom rendering of helvet has some baseline alignment problems, the “i” is positively floating in the air and the jump between “t” and “r” is particularly noticeable.

The sansmath package is the correct math-font matching Helvetica, as pointed out here.

Finally, siunitx is used for the correct display of measurements in SI units. Here, for example, 3 micrometres are rendered in the title – don’t make the mistake of putting the siuntix expression in a math environment, then the fonts won’t work!

Arrowheads for axis in Matplotlib

This is a short demo showing how to make abstract plots in matplotlib that have arrows pointing in the x and y direction as axis.


The idea is to remove the default axis completely and insert arrows with the correct dimensions as substitute axis:

import pylab as pl

fig = pl.figure()
ax = fig.add_subplot(111)

x = pl.arange(-5,5,0.1)
ax.plot(x, x**2-8.8)

xmin, xmax = ax.get_xlim() 
ymin, ymax = ax.get_ylim()

# removing the default axis on all sides:
for side in ['bottom','right','top','left']:

# removing the axis ticks
pl.xticks([]) # labels 
ax.xaxis.set_ticks_position('none') # tick markers

# wider figure for demonstration

# get width and height of axes object to compute 
# matching arrowhead length and width
dps = fig.dpi_scale_trans.inverted()
bbox = ax.get_window_extent().transformed(dps)
width, height = bbox.width, bbox.height

# manual arrowhead width and length
hw = 1./20.*(ymax-ymin) 
hl = 1./20.*(xmax-xmin)
lw = 1. # axis line width
ohg = 0.3 # arrow overhang

# compute matching arrowhead length and width
yhw = hw/(ymax-ymin)*(xmax-xmin)* height/width 
yhl = hl/(xmax-xmin)*(ymax-ymin)* width/height

# draw x and y axis
ax.arrow(xmin, 0, xmax-xmin, 0., fc='k', ec='k', lw = lw, 
         head_width=hw, head_length=hl, overhang = ohg, 
         length_includes_head= True, clip_on = False) 

ax.arrow(0, ymin, 0., ymax-ymin, fc='k', ec='k', lw = lw, 
         head_width=yhw, head_length=yhl, overhang = ohg, 
         length_includes_head= True, clip_on = False) 

# clip_on = False if only positive x or y values.

pl.savefig('arrow_axis.png', dpi = 300)