matplotlib 方式

matplotlib 方式

我在 Windows 和 Linux 上工作。我已经包含了我在 Inkscape 中创建的 SVG 图形\usepackage{svg}。出于可维护性原因,我欢迎将其重新用于 matplotlib 图,但我也欢迎更多建议。

对于使用 matplotlib 创建的图表和图形,我希望轴标签和刻度以及图例等文本采用乳胶字体和样式,非常类似于我从 Inkscape 中包含 SVG 时获得的效果。我知道usetex=Truematplotlib 方面的情况,但我担心图表中呈现的文本会随着创建的 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_inchespad_inches。我没有这样做。 4.9 英寸的figsize宽度恰好位于 LaTeX 开始抱怨我的情况中线条过满时下方。

svg 包

没有什么可以阻止您使用与导入 Inkscape 图形相同的方式。实际上,这就是我开始的方式,但几个月后我深深后悔了这个决定。

我对 matplotlib+svg 组合的主要问题是文本对齐。由于所有文本标签都必须是正确的 TeX,因此任何数学内容都必须转义并逐字输入。这可能会导致标签非常长,当您在 Inkscape 中手动调整所有内容时,这可能是可以接受的,但会完全破坏 matplotlib 中的对齐。忘记tight_layoutconstrained_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

所以我很惊讶没有人提到pgfmatplotlib 的后端,有了它,只要配置正确,我就能得到相当不错的结果。此外,的创造者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:

相关内容