6

I work on both Windows and Linux. I already include SVG graphics that I create in Inkscape with \usepackage{svg}. For maintainablitly reasons I would welcome to re-use that for matplotlib plots, but I am open for additional suggestions.

With plots and graphs created with matplotlib I would like to have axis labels and ticks as well as legends etc texts in latex font and style, very much like what I get when including SVG from Inkscape. I am aware of usetex=True on the matplotlib side, but I fear that the rendered text in the plot will scale with the created SVG and produce to small or to big fonts compared to the text in my document rendered by LaTeX.

What is the state-of-the-art way to include matplotlib vector graphics?

  • I have been trying to do the same for decades. pgfplots would be very nice but, I play with millions of data points in each plot and there will be hundreds of plots. I am literally frustrated with what is available to me. Practically nothing works the way I want. I was waiting for latex3 for better memory handling, so that it could do the job. But again, that seems not a near future option. – Sumit Adhikari Dec 29 '22 at 07:09
  • @SumitAdhikari It would be better if you can clarify exactly how the options listed in the answers below doesn't work the way you want so I can try to work on a fix. – user202729 Feb 21 '24 at 22:16

3 Answers3

7

I have been asking myself the very same question since I started writing my thesis a few months ago. It seems to me there is no state-of-the-art solution. Judging from a handful of journal publications I went through, most people export to PDF or PNG (ugh!) and call it a day. Font sizes and styles are all over the place, even in professional settings.

Of course, I wouldn't write this if I didn't care myself :) Having tried several solutions, I encourage you to give the usetex=True way a try. Before explaining why other options didn't work for me, I will offer some tips on how to make the best of LaTeX rendering in matplotlib.

The matplotlib way

I think this is the least complicated solution. A little bit of initial setup will let you focus on things that matter, instead of endlessly post-processing your figures. Here are some hints to get you started:

  • Pick a font that matches your document font style and size.
  • To keep font sizes consistent when scaling figures... don't scale them! That's right. Simply pick a figsize that matches your \textwidth and never scale a figure.
  • If you ever need to let a figure spill into the margins, you can wrap it in \makebox. I know it's not pretty, but sometimes that's just what you need to get the job done.

You can put these settings in a style sheet specific for the document you're writing and load it every time you want to make plots for this work. The relevant bit of my style sheet looks like this:

figure.figsize : 4.9, 3.5
font.size:    11.0
font.family: serif
font.serif: Palatino
axes.titlesize: medium
figure.titlesize: medium
text.usetex: True
text.latex.preamble: \usepackage{amsmath}\usepackage{amssymb}\usepackage{siunitx}[=v2]

It sets the right width of the figure, adjusts all the fonts to be the same size and of the correct style, and adds a minimal set of LaTeX packages to cover all the math symbols and SI units I might need.

You may want to experiment with parameters of savefig, such as bbox_inches and pad_inches. I didn't do that. The 4.9" figsize width happens to be just below when LaTeX starts to complain about overfilled lines in my case.

The svg package

Nothing prevents you from using the same way you are importing your Inkscape figures. Actually, that's how I started, but several months later I deeply regretted the decision.

The major issue I have with the matplotlib+svg combination is text alignment. Since all text labels must be proper TeX, any math content has to be escaped and typed verbatim. This can lead to extremely long labels, which might be OK when you adjust everything by hand in Inkscape, but completely derails alignment in matplotlib. Forget about tight_layout or constrained_layout. Legend placement with a long title is out of whack. I also learned the hard way that text anchors in matplotlib can be found in surprising places (legend title, I'm looking at you).

These things can be done, but the more complicated are your graphs, the more pain you will have to endure. There is stuff I have never figured out, like scientific notation in log-plots. Inevitably, I ended up doing increasing amounts of manual post-processing, which grew old pretty quickly. It's not exactly what I want to do every time I receive feedback from my supervisor.

Nevertheless, it might be worth a try if you already have your svg pipeline set up for Inkscape and aren't planning to do anything complicated. You may want to have a look at the issue with the Unicode minus sign before you start. Also, put svg.fonttype : none in your style sheet, otherwise text will be rendered as path.

tikzplotlib

This is the solution proposed by r0the and is potentially the most LaTeX-y way. I won't do this package justice, since I tried it only very briefly. Maybe the fact that at this point I was really frustrated with matplotlib+svg had something to do here :)

Anyway, to my disappointment, the figure I generated with tikzplotlib didn't look in my document like in the jupyter notebook at all. Linewidths were different, subplot sizes were different, and I could already smell all the manual adjustments I would need to make in the TEX file. Moreover, there seems to be a good deal of things that simply aren't implemented. I wanted to have degree symbols in my tick labels. Easy to do with tick formatters, but didn't make it to the TEX file. I discarded this solution pretty quickly.

Don't get me wrong — I'm not saying it's a bad package. My guess is that the philosophy of tikzplotlib is to dump your data and the general layout from your Python script to a TEX file and finish it off in a text editor. This definitely saves you a lot of hassle in comparison with creating the file entirely by hand, but it's not a drop-in replacement for a PDF export in matplotlib. It's also not what I was looking for.

2

I'd say the state-of-the-art way to have plots with LaTeX fonts and style is using PGF/Tikz, so have a look at tikzplotlib.

r0the
  • 121
0

So I'm surprised nobody mentions the pgf backend of matplotlib, with it I can get quite decent results when properly configured. Also, as the creator of robust-externalize (a very generic library to cache arbitrary content), let me do some shameful advertisement on how to include the diagram directly in your tex (which also has the benefit of configuring directly the width of the diagram to the current line width). Make sure to get version 2.6 or above (not to worry about indentation, if your distribution is too old, you can just save the .sty file to your main folder).

Then, just type (cf comments to tune the font size of title etc.):

\documentclass{article} 
\usepackage{lipsum}
\usepackage{tikz}
\usepackage{robust-externalize}
\robExtConfigure{enable fallback to manual mode} % prints command to run in PDF if shell-escape is not used/forgotten
\def\mathdefault#1{#1} % Needed in matplotlib 3.8: https://github.com/matplotlib/matplotlib/issues/27907

\begin{document}

\lipsum[1]

\begin{figure}[ht] \centering \begin{CacheMeCode}{python, custom include command={\input{\robExtAddCachePathAndName{\robExtFinalHash.pgf}}}, set placeholder eval={LINEWIDTH}{\lenToCmNoUnit[in]{\linewidth}}} import matplotlib.pyplot as plt import matplotlib from matplotlib.pyplot import figure matplotlib.use("pgf") # See this link for details on how to preview the image in jupyter # https://matplotlib.org/stable/users/explain/text/pgf.html matplotlib.rcParams.update({ "font.family": "serif", "font.serif": [], # Use LaTeX default serif font. "text.usetex": True, # use inline math for ticks ## You can change the font size of individual items with: # "font.size": 11, # "axes.titlesize": 11, # "legend.fontsize": 11, # "axes.labelsize": 11, })

figure(figsize=(__LINEWIDTH__, 0.7*__LINEWIDTH__))
year = [2014, 2015, 2016, 2017, 2018, 2019]
tutorial_count = [39, 117, 111, 110, 67, 29]
plt.plot(year, tutorial_count, color="#6c3376", linewidth=2)
plt.title("Simple plot for $\delta = 2$")
plt.xlabel('Year')
plt.ylabel('Number of futurestud.io Tutorials')
print(get_filename_from_extension(".pgf"))
# https://stackoverflow.com/a/52587591/4987648
plt.savefig(get_filename_from_extension(".pgf"), bbox_inches="tight")

\end{CacheMeCode} \caption{Test}% \end{figure}

\end{document}

% Local Variables: % TeX-command-extra-options: "--shell-escape -halt-on-error" % End:

and compile with --shell-escape (see documentation if you do not want shell-escape) to get:

enter image description here

If you try to change the margins with \usepackage[margins=2.5cm]{geometry}, you can see that it will automatically adapt:

enter image description here

Create a style to avoid long copy/paste and increase reusability

You can also create based on the above template your own robust-externalize preset:

\documentclass[11pt]{article} 
\usepackage{lipsum}
\usepackage{tikz}
\usepackage{robust-externalize}
\robExtConfigure{enable fallback to manual mode} % prints command to run in PDF if shell-escape is not used/forgotten
\def\mathdefault#1{#1} % Needed in matplotlib 3.8: https://github.com/matplotlib/matplotlib/issues/27907

\begin{PlaceholderFromCode}{MY_MATPLOTLIB_START} import matplotlib.pyplot as plt import matplotlib from matplotlib.pyplot import figure matplotlib.use("pgf")

https://matplotlib.org/stable/users/explain/text/pgf.html

matplotlib.rcParams.update({ "font.family": "serif", "font.serif": [], # Use LaTeX default serif font. "text.usetex": True, # use inline math for ticks

You can change the font size of individual items with:

"font.size": 11,

"axes.titlesize": 11,

"legend.fontsize": 11,

"axes.labelsize": 11,

}) figure(figsize=(MY_MATPLOTLIB_WIDTH, MY_MATPLOTLIB_HEIGHT)) \end{PlaceholderFromCode}

\begin{PlaceholderFromCode}{MY_MATPLOTLIB_END}

https://stackoverflow.com/a/52587591/4987648

plt.savefig(get_filename_from_extension(".pgf"), bbox_inches="tight") \end{PlaceholderFromCode}

\robExtConfigure{ new preset={my matplotlib pgf}{ python, % If you want to include it via a simple input custom include command={\input{\robExtAddCachePathAndName{\robExtFinalHash.pgf}}}, % If you also want to cache the tikz code for faster rendering % (otherwise only python -> tikz will be cached, but not tikz -> pdf) % custom include command={\evalPlaceholder{\cacheMe[tikz]{\input{ROBEXT_OUTPUT_PREFIX.pgf}}}}, set placeholder eval={LINEWIDTH}{\lenToCmNoUnit[in]{\linewidth}}, add import={MY_MATPLOTLIB_START}, add to placeholder={ROBEXT_MAIN_CONTENT}{MY_MATPLOTLIB_END}, %% Create a few functions to change the width easily: set size inches/.style 2 args={ set placeholder={MY_MATPLOTLIB_WIDTH}{#1}, set placeholder={MY_MATPLOTLIB_HEIGHT}{#2}, }, set size cm/.style 2 args={ set size inches={#1/2.54}{#2/2.54}, }, set size pc/.style 2 args={ set size inches={#1LINEWIDTH}{#2LINEWIDTH}, }, set size pc={1}{.7}, % default size } }

\begin{document}

\lipsum[1]

\begin{figure}[ht] \centering \begin{CacheMeCode}{my matplotlib pgf, set size pc={1}{.5}} year = [2014, 2015, 2016, 2017, 2018, 2019] tutorial_count = [39, 117, 111, 110, 67, 29] plt.plot(year, tutorial_count, color="#6c3376", linewidth=2) plt.title("Simple plot for $\delta = 2$") plt.xlabel('Year') plt.ylabel('Number of futurestud.io Tutorials') \end{CacheMeCode} \caption{Test}% \end{figure}

\end{document}

% Local Variables: % TeX-command-extra-options: "--shell-escape -halt-on-error" % End:

tobiasBora
  • 8,684