我在 Windows 和 Linux 上工作。我已经包含了我在 Inkscape 中创建的 SVG 图形\usepackage{svg}
。出于可维护性原因,我欢迎将其重新用于 matplotlib 图,但我也欢迎更多建议。
对于使用 matplotlib 创建的图表和图形,我希望轴标签和刻度以及图例等文本采用乳胶字体和样式,非常类似于我从 Inkscape 中包含 SVG 时获得的效果。我知道usetex=True
matplotlib 方面的情况,但我担心图表中呈现的文本会随着创建的 SVG 缩放,并产生与 LaTeX 呈现的文档中的文本相比过小或过大的字体。
包含 matplotlib 矢量图形的最佳方法是什么?
答案1
自从几个月前开始写论文以来,我一直在问自己同样的问题。在我看来,没有最先进的解决方案。从我看过的几份期刊出版物来看,大多数人导出为 PDF 或 PNG(呃!)然后就结束了。字体大小和样式随处可见,即使在专业环境中也是如此。
当然,如果我自己不在乎的话,我是不会写这篇文章的 :) 尝试了几种解决方案后,我鼓励您尝试一下usetex=True
。在解释为什么其他选项对我不起作用之前,我将提供一些关于如何在 matplotlib 中充分利用 LaTeX 渲染的提示。
matplotlib 方式
我认为这是最不复杂的解决方案。只需进行一点初始设置,您就可以专注于重要的事情,而不是无休止地对数据进行后期处理。以下是一些入门提示:
- 选择与您的文档字体样式和大小相匹配的字体。
- 为了在缩放图形时保持字体大小一致...不要缩放它们!没错。只需选择一个
figsize
符合你的\textwidth
并且永远不会缩放数字。 - 如果你需要让数字溢出到页边距,你可以包起来
\makebox
。我知道这并不好看,但有时这正是完成工作所需要的。
您可以将这些设置放在样式表特定于您正在编写的文档,并在每次想要为此工作制作图表时加载它。我的样式表的相关部分如下所示:
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]
它设置图形的正确宽度,调整所有字体为相同的大小和正确的样式,并添加一组最少的 LaTeX 包来涵盖我可能需要的所有数学符号和 SI 单位。
您可能想要尝试 的参数savefig
,例如bbox_inches
和pad_inches
。我没有这样做。 4.9 英寸的figsize
宽度恰好位于 LaTeX 开始抱怨我的情况中线条过满时下方。
svg 包
没有什么可以阻止您使用与导入 Inkscape 图形相同的方式。实际上,这就是我开始的方式,但几个月后我深深后悔了这个决定。
我对 matplotlib+svg 组合的主要问题是文本对齐。由于所有文本标签都必须是正确的 TeX,因此任何数学内容都必须转义并逐字输入。这可能会导致标签非常长,当您在 Inkscape 中手动调整所有内容时,这可能是可以接受的,但会完全破坏 matplotlib 中的对齐。忘记tight_layout
或constrained_layout
。带有长标题的图例位置不合适。我还从惨痛的经历中了解到,matplotlib 中的文本锚点可以在令人惊讶的地方找到(图例标题,我正在看你)。
这些事情是可以做到的,但图表越复杂,你就越需要忍受痛苦。有些东西我从来没有搞清楚过,比如对数图中的科学符号。不可避免的是,我最终做了越来越多的手动后处理,这很快就让我厌烦了。这并不是我每次收到主管反馈时都想做的事情。
尽管如此,如果你已经为 Inkscape 设置了管道,并且不打算做任何复杂的事情,那么它可能值得一试。svg
你可能想看看Unicode 减号问题开始之前。另外,请放入svg.fonttype : none
样式表,否则文本将被呈现为路径。
tikzplotlib
这是解决方案由 r0the 提出并且可能是最像 LaTeX 的方式。我无法公正地评价这个包,因为我只尝试了很短的时间。也许当时我对 matplotlib+svg 感到非常沮丧,这和此有关 :)
无论如何,令我失望的是,我用 tikzplotlib 生成的图形在我的文档中看起来根本不像在 jupyter 笔记本中那样。线宽不同,子图大小不同,我已经闻到了在 TEX 文件中需要进行的所有手动调整的味道。此外,似乎有很多事情根本没有实现。我想在我的刻度标签中使用度数符号。使用刻度格式化程序很容易做到,但没有将其放入 TEX 文件中。我很快就放弃了这个解决方案。
别误会我的意思——我并不是说这是一个糟糕的软件包。我猜 tikzplotlib 的理念是将您的数据和 Python 脚本中的总体布局转储到 TEX 文件中,然后在文本编辑器中完成它。与完全手动创建文件相比,这无疑为您节省了很多麻烦,但它并不是 matplotlib 中 PDF 导出的替代品。这也不是我想要的。
答案2
我认为使用 LaTeX 字体和样式绘制图表的最先进的方法是使用 PGF/Tikz,所以看看tikzplotlib。
答案3
所以我很惊讶没有人提到pgf
matplotlib 的后端,有了它,只要配置正确,我就能得到相当不错的结果。此外,的创造者robust-externalize
(一个非常通用的库,用于缓存任意内容),让我来做一些关于如何将图表直接包含在您的 tex 中的可耻广告(这也具有将图表的宽度直接配置为当前线宽的好处)。确保获取 2.6 或更高版本(不用担心缩进,如果您的发行版太旧,您可以保存.sty 文件到您的主文件夹)。
然后,只需输入(参见注释来调整标题的字体大小等):
\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:
并进行编译--shell-escape
(如果不想进行 shell 转义,请参阅文档)以获得:
如果你尝试使用 更改边距\usepackage[margins=2.5cm]{geometry}
,你会看到它会自动适应:
创建一种样式以避免长时间复制/粘贴并提高可重用性
您还可以基于上述模板创建自己的 robust-externalize 预设:
\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={#1*__LINEWIDTH__}{#2*__LINEWIDTH__},
},
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: