Complex Text Formatting in Matplotlib Using LaTeX
Complex Text Formatting in Matplotlib Using LaTeX
Install LaTeX
Test LaTeX
\documentclass[a4paper,12pt]{article}
\begin{document}
One may readily verify that if $f$ and $g$ are continuous
functions on $D$ then the functions $f+g$, $f-g$ and
$f.g$ are continuous. If in addition $g$ is everywhere
non-zero then $f/g$ is continuous.
\end{document}
Run the following:
With LaTeX installed and functioning on the system, the next step is
to confirm it's working correctly in Matplotlib. The Matplotlib
documentation covers using LaTeX extensively, see Text rendering
with LaTeX. The easiest way to check is:
Figure 2: Standard Matplotlib Tex demo
For processing we'll hand-off fonts and marking our plot to LaTeX.
This means we have to set-up LaTeX fully for processing all text
handling elements, and tell Matplotlib's native text handling to get
out of the way. It's surprisingly complicated!
import matplotlib
from matplotlib.backends.backend_pgf import FigureCanvasPgf
matplotlib.backend_bases.register_backend('pdf', FigureCanvasPgf)
The default is that LaTeX will use a different font to the general
font that Matplotlib uses, it's best to set the fonts explicitly and to
tell it how we want the Figure set-up. It's possible to define these
settings with the more Pythonic
plt.rcParams['blah']='blah' but there are a lot of them, so
it's easier to do this [7]:
pgf_with_latex = {
"pgf.texsystem": "xelatex", # use Xelatex which is TTF
font aware
"text.usetex": True, # use LaTeX to write all
text
"font.family": "serif", # use serif rather than
sans-serif
"font.serif": "Ubuntu", # use 'Ubuntu' as the
standard font
"font.sans-serif": [],
"font.monospace": "Ubuntu Mono", # use Ubuntu mono if we have
mono
"axes.labelsize": 10, # LaTeX default is 10pt
font.
"font.size": 10,
"legend.fontsize": 8, # Make the legend/label
fonts a little smaller
"xtick.labelsize": 8,
"ytick.labelsize": 8,
"pgf.rcfonts": False, # Use pgf.preamble, ignore
standard Matplotlib RC
"text.latex.unicode": True,
"pgf.preamble": [
r'\usepackage{fontspec}',
r'\setmainfont{Ubuntu}',
r'\setmonofont{Ubuntu Mono}',
r'\usepackage{unicode-math}'
r'\setmathfont{Ubuntu}'
]
}
matplotlib.rcParams.update(pgf_with_latex)
The first option, pgf.texsystem tells Matplotlib to use the xelatex
program to process the PGF backend. The second option,
text.usetex tells Matplotlib that all text should be processed
using LaTeX.
The various font and axes lines set how Matplotlib processes parts
of the plot, we covered many of these in a previous post. Defining
pgf.rcfonts to False means that the backend will obey the fonts
defined in the pgf.preamble rather than over-riding and using
whatever is in your Matplotlib configuration parameters (e.g.
~/.matplotlibrc). The benefit is we're explicitly defining how XeTeX
will function, and there's no risk of confusion with Matplotlib using
settings from elsewhere. We also tell XeTeX to use unicode
(text.latex.unicode) which allows us to send extended
characters [8].
At this point we've told Matplotlib how to handle the plot and that
it should hand over to the LaTeX system. Next, we have to tell the
LaTeX processor how it should handle the incoming stream.
This set-up is sufficient to show the Ubuntu font for text output like
annotate() and xlabel().
Axis Font
At the moment the Axis (ie the numbers along the X and Y axis) will
be in the default sans-serif font: many people consider this to be
correct for complex maths which is why it's the default. However, I
want the same font on all elements of my plot. Matplotlib defines
the font LaTeX should use for the Axis in the Mathsfont setting
which also controls how maths equations display. There are a few
options for changing it, depending on your requirements.
Many free fonts cannot display maths script completely [9]. If you
use maths script then the easiest option is to use the cmbright
sans serif font package. To install it get the package texlive-fonts-
extra and put the following in pgf.preamble:
r'\usepackage{cmbright}'
An alternative way to solve this is to update the Axis after it's been
plotted [10], but while a clever hack it's messy as it intermingles
standard matplotlib labelling and LaTeX labelling.
The last option is to change the font Matplotlib uses for maths. The
Ubuntu font is not fully maths capable, but as I'm not using maths
script (my graphs are simple numbers) it's fine for my purposes.
Consequently, I define the maths font using the unicode-math
package. Install the texlive-math-extra package, then in Matplotlib
we can do:
r'\usepackage{unicode-math}',
r'\setmathfont{Ubuntu}'
It's possible to use any TTF font, the easiest way to see which ones
are available on the command line is:
Formatting strings
The PGF backend doesn't support using LaTeX codes for newlines:
according to this GitHub issue [11] the underlying problem is that
LaTeX doesn't support newlines in a variety of situations. This is
problematic if you're doing a multi-line annotation() or text() but
there are a couple of options. The first is to mix raw text strings and
normal strings together, putting newlines in the normal string:
T h e s e c o n d o p t i o n i s t o u s e textwrap.dedent() a n d
textwrap.fill() with multi-line strings. The advantage of the
multi-line string is we can tab the text in nicely in the source code,
and in the output we can automatically wrap it at whatever length
we want. We have to use double backslashes to escape the LaTeX
codes properly, and add normal strings if we want to specifically
force a newline at a set point in the string:
LaTeX example
In this example we use the PGF backend with LaTeX to do complex
formatting on strings. It was output as a PDF and then converted to
SVG for display with pdftocairo. The results are shown in Figure 1
at the top of this post.
#!/usr/bin/env python3
# Set-up PGF as the backend for saving a PDF
import matplotlib
from matplotlib.backends.backend_pgf import FigureCanvasPgf
matplotlib.backend_bases.register_backend('pdf', FigureCanvasPgf)
pgf_with_latex = {
"pgf.texsystem": "xelatex", # Use xetex for processing
"text.usetex": True, # use LaTeX to write all text
"font.family": "serif", # use serif rather than sans-
serif
"font.serif": "Ubuntu", # use Ubuntu as the font
"font.sans-serif": [], # unset sans-serif
"font.monospace": "Ubuntu Mono",# use Ubuntu for monospace
"axes.labelsize": 10,
"font.size": 10,
"legend.fontsize": 8,
"axes.titlesize": 14, # Title size when one figure
"xtick.labelsize": 8,
"ytick.labelsize": 8,
"figure.titlesize": 12, # Overall figure title
"pgf.rcfonts": False, # Ignore Matplotlibrc
"text.latex.unicode": True, # Unicode in LaTeX
"pgf.preamble": [ # Set-up LaTeX
r'\usepackage{fontspec}',
r'\setmainfont{Ubuntu}',
r'\setmonofont{Ubuntu Mono}',
r'\usepackage{unicode-math}',
r'\setmathfont{Ubuntu}'
]
}
matplotlib.rcParams.update(pgf_with_latex)
# Adjust the plot upwards at the bottom so we can fit the figure
# comment as well as the ylabel()
plt.subplots_adjust(bottom=0.15)
plt.savefig('matplot-latex.pdf', bbox_inches='tight',
transparent=True)
LaTeX resources
These are the most useful resources I found for Matplotlib and
LaTeX:
Final words
With LaTeX handling the formatting of all text we can mark-up our
plots with any form of complex formatting we want. The constraints
are that there are two steps to creating Web ready images, grey
backgrounds on styles don't display properly and the processing of
a plot is slow. Despite those issues I think the results are worth the
extra effort.
Posted in Tech
Sunday 13 March 2016
1 Comment
Futurile
Login
Recommend
Share
Sort by Best
Join the discussion…
Attach
LOG IN WITH
•
Reply
•
Share ›
Twitter
Facebook
ALSO ON FUTURILE
Yay this is the first post
1 comment 5 years ago
Steve George — This is the first post test comment
Powered by Disqus
Subscribe
Privacy