12  TikZ 入门

有一些示意图是用来表达数据探索的思路的,而不是直接探索数据的工具。比如象限图、甘特图、思维导图、网络图等,可以用 TikZ 绘制这类图形来阐述分析维度、思路、结构等。当然,绘制这类示意图不仅限于 TikZ,还有很多其它工具,如 LaTeX 社区的 PSTricks,JavaScripts 社区的 Mermaid,软件 Graphviz 等。

TikZ 绘图的优势有很多,语法简单、易于上手、功能强大、资源丰富、成熟稳定等,可以说几乎是集所有优点于一身。正因如此,knitr 包和 tikzDevice 包将其引入 R 语言社区中。knitr 包的 tikz 引擎是用来编译 TikZ 代码的,默认使用的是 standalone 文类。

R 语言绘图遇到公式时,略显不足,而排版公式是 LaTeX 的优势。正因为有所不足,所以我也不会纠结于工具层面的东西,什么好用用什么!三维图 图 12.6 是用 LaTeX 里的优秀绘图工具 TikZ 制作的,细心的读者会发现本书多次用到这个工具。

12.1 standalone 宏包

最常见的 LaTeX 文档类有 article、report、beamer、book,分别对应文章、报告、演示和书籍。有的宏包在此基础上扩展功能,比如 ctex 宏包提供中文支持,有四个文档类 ctexart、 ctexrep 、ctexbeamer 和 ctexbook 与之对应起来。standalone 宏包提供 standalone 文类主要用于绘制独立的图片,默认情况下,文档四周多余的空白部分会被裁剪掉。在 LaTeX 环境中,推荐使用 TikZ 来绘图。standalone 文类可与 tikz 宏包一起使用,生成一张张由 TikZ 代码绘制的独立图片。下面举个简单的例子,用 TikZ 绘制两个坐标轴。

\documentclass[tikz,border=1mm]{standalone}
\begin{document}
\begin{tikzpicture}
\draw[<->] (6,0) -- (0,0) -- (0,6);
\end{tikzpicture}
\end{document}

standalone 文类启用 tikz 选项来绘图,选项 border=1mm 表示图片四周的边空保留 1 毫米,文档内容放在 document 环境里,TikZ 绘图代码放在 tikzpicture 环境中,命令 \draw 负责绘制具体的图形,用 XeLaTeX 编译,效果如 图 12.1 所示。

图 12.1: TikZ 绘图

standalone 文类有很多选项,下面介绍 4 个选项的常用内容。

  • class 选项指定文类环境,默认值为 article,表示在 article 文类中绘图。其它选项还有 beamer ,表示在演示环境中绘图。在不同的文类中,图片渲染出来的效果不同。

  • tikz=true|false 选项是否启用 TikZ 绘图,默认值是 false 。当显式地在 standalone 文类中启用 tikz 选项,就表示用 TikZ 绘图,将自动加载 tikz 宏包。与之类似的选项 pstricks=true|false ,表示是否启用 PSTricks 绘图,PSTricks 是LaTeX 社区中一套语法不同于 TikZ 的绘图工具。

  • crop=true|false 选项是否裁剪变空,默认值是 true ,表示绘图区域以外的部分都裁剪掉。与之相关的另一个选项是 border ,可以更加精细地控制图片四周的各个边空。

  • border 选项指定边空大小,默认值是 0,表示无边空。当 crop=true 时,再指定 border 选项,比如 border=1mm 表示图片四周的边空保留 1 毫米。图片四周的边空大小可以按照左、下、右、上的顺序指定,比如 border={5mm 6mm 0mm -2mm} 表示图片左边空 5 毫米、下边空 6 毫米、右边空 0 毫米、上边空负 2 毫米。

standalone 文类是支持 PSTricks 绘图的,下面在直角坐标系中绘制一个带阴影效果的圆,示例代码如下:

\documentclass[pstricks,border={5mm 6mm 0mm -2mm}]{standalone}
\usepackage{pst-plot}
\begin{document}
\psset{xunit=0.15in, yunit=0.15in}
\begin{pspicture}(0,0)(11,11)
\psaxes[Dx=4,Dy=4, subticks=4]{->}(0,0)(0,0)(10,10)[$x$,0][$y$,0]
\pscircle[runit=0.15in, fillcolor=orange!50, fillstyle=solid,shadow=true](5,5){3}
\end{pspicture}
\end{document}

standalone 文类的选项 pstricks 表示启用 PSTricks 绘图环境,加载 pst-plot 宏包提供额外的命令,PSTricks 是基于 PostScript 语言的,每一个绘图命令都是 \ps 开头的,比如 \psset\psaxes\pscircle 等。\begin{pspicture}\end{pspicture} 之间是 PSTricks 绘图代码,\begin{pspicture} 之后的 (0,0)(11,11) 是左下和右上角两个坐标,定义了一个绘图区域。和 TikZ 绘图代码一样,也用 XeLaTeX 编译,效果如 图 12.2 所示。

图 12.2: PSTricks 绘图

可以在 Quarto 和 R Markdown 文档中插入 PSTricks 绘图代码,使用 knitr 包的 tikz 引擎绘图。只要修改模版文件 tikz2pdf.tex ,移除一行 \usetikzlibrary{matrix} ,不再加载 tikz 宏包及其 matrix 库。TIKZ_CLASSOPTION 不再仅限于 TikZ ,而是 standalone 文类的选项,相应地,EXTRA_TIKZ_PREAMBLE_CODE 变成一般的 LaTeX 文档的导言区,TIKZ_CODE 可以是 PSTricks 代码。新的模版文件 tikz2pdf.tex 如下:

\documentclass[
%% TIKZ_CLASSOPTION %%
]{standalone}
%% EXTRA_TIKZ_PREAMBLE_CODE %%
\begin{document}
%% TIKZ_CODE %%
\end{document}

图 12.2 即是由 knitr 包的 tikz 引擎渲染出来的。在代码块选项 engine-opts 中,传递一个列表,分别包含 classoption(standalone 文类选项)、 extra.preamble(导言区)、 template (TikZ 模版文件)三块内容。生成 图 12.2engine-opts 设置如下:

list(
  classoption = "pstricks,border={5mm 6mm 0mm -2mm}",
  extra.preamble = "\\usepackage{pst-plot}",
  template = "code/tikz2pdf.tex"
)

其它选项和更多详细介绍见 standalone 宏包帮助文档。

12.2 PGF 宏包

TikZ 是 TikZ ist kein Zeichenprogramm 的简写,命名有 Linux 哲学意味。提供一套易于学习和使用的绘图语法,下面比较详细的介绍 LaTeX 宏包 PGF 绘制曲线图的过程。

\begin{tikzpicture}
\draw[<->] (6,0) -- (0,0) -- (0,6);
\end{tikzpicture}

图 12.3: PGF 绘制曲线图

首先,\draw 命令绘制带箭头的坐标轴,坐标轴的范围 \([0,6]\times[0,6]\) 。坐标轴是由线构成的,线有虚线、实线,也有宽度和颜色,虚线还有不同类型,这些都是可以设置的参数,比如将 \draw[<->] 改为 \draw[color=red,<->] ,坐标轴颜色设置为红色。

\begin{tikzpicture}
\draw[<->] (6,0) node[below]{$q$} -- (0,0) -- (0,6) node[left]{$V(q)$};
\end{tikzpicture}

图 12.4: PGF 绘制曲线图

然后,在位置 (6,0) 和 (0,6) 分别添加节点 node[below]{$q$}node[left]{$V(q)$} 。node 表示节点,节点的标签内容在大括号内,标签的位置在中括号内,这里,below 表示在位置 (6,0) 的下方。

\begin{tikzpicture}
\draw[<->] (6,0) node[below]{$q$} -- (0,0) -- (0,6) node[left]{$V(q)$};
\draw[very thick] (0,0) to [out=90,in=145] (5,4.5);
\end{tikzpicture}

图 12.5: PGF 绘制曲线图

最后,从点 (0,0) 至点 (5,4.5) 绘制一条非常粗的曲线。曲线从点 (0,0) 出去的时候,是以 90 度垂直水平轴的方向出去的,到点 (5,4.5) 是以 145 度方向进入的。角度是按照逆时针方向计算的。线的粗细、方向都是由参数决定的。

在这里,TikZ 是用来绘制示意图的,不需要知道每个命令的每个参数的取值有哪些。关键是知道自己想要画什么,其实,可以用铅笔在纸上以最快的方式绘制草图,了解每个绘图元素,然后查找 PGF 帮助手册,找到对应的命令和参数,将草图工整地誊抄一遍。

12.3 三维图

顾名思义,pgfplots 宏包基于 PGF 的,用它来绘制三维图形是非常方便的。

\documentclass[tikz]{standalone}
\usepackage{pgfplots}
\pgfplotsset{width=7cm,compat=1.18}
\begin{document}
%% TikZ 代码%%
\end{document}

首先加载 pgfplots 宏包,设置全局的绘图参数,width=7cm 表示绘图页面宽度,compat=1.18 表示使用 pgfplots 的版本。

\begin{tikzpicture}
\begin{axis}[
    hide axis,
    colormap/viridis,
]
\addplot3[
    mesh,
    samples=50,
    domain=-8:8,
]
{ sin(deg(sqrt(x^2+y^2)))/sqrt(x^2+y^2) };
\addlegendentry{$\frac{\sin(r)}{r}$}
\end{axis}
\end{tikzpicture}

图 12.6: TikZ 绘制三维图
  • \begin{axis}\end{axis} 环境,参数 [hide axis, colormap/viridis,] 表示隐藏坐标轴,三维图形的调色板采用 viridis 。
  • \addplot3 绘制函数 sin(deg(sqrt(x^2+y^2)))/sqrt(x^2+y^2) ,即函数 \(f(x,y)=\frac{\sin(\sqrt{x^2 + y^2})}{\sqrt{x^2 + y^2}}\) 的三维图像。参数 [mesh,samples=50,domain=-8:8,] 表示三维图形是网格状,网格密度是 50,范围是 \([-8,8]\)
  • \addlegendentry 添加图例,图例标签是 \(\frac{\sin(r)}{r}\)

12.4 网络图

绘制网络图用 tikz-network 宏包,也是 PGF 的一个扩展包。图结构是根据顶点和边来定义的,图的复杂程度也可以用顶点和边的规模来衡量。图描述一种非线性的关系,有自己的一套语言,定义顶点 \Vertex 和边 \Edge 的两个命令是最基础的。下面绘制柯尼斯堡七桥问题对应的图。

\documentclass[tikz]{standalone}
\usepackage{tikz-network}
\begin{document}
%% TikZ 代码%%
\end{document}
\begin{tikzpicture}
\Vertex[IdAsLabel, x=5, color=gray, size=1, fontsize=\large]{A}
\Vertex[IdAsLabel, x=10, color=gray, size=1, fontsize=\large]{B}
\Vertex[IdAsLabel, x=15, color=gray, size=1, fontsize=\large]{C}
\Vertex[IdAsLabel, x=10, y=6, color=gray, size=1, fontsize=\large]{D}

\Edge[label=2, bend=45, fontscale=2](A)(B)
\Edge[label=6, bend=30, fontscale=2](A)(D)
\Edge[label=3, bend=45, fontscale=2](B)(A)
\Edge[label=5, bend=45, fontscale=2](B)(C)
\Edge[label=4, bend=45, fontscale=2](C)(B)
\Edge[label=7, bend=30, fontscale=2](D)(C)
\Edge[label=1, fontscale=2](D)(B)
\end{tikzpicture}

图 12.7: 柯尼斯堡七桥
  • \Vertex 命令定义顶点(含标签),参数 IdAsLabel 表示顶点 ID 作为标签,参数 xy 表示坐标位置,参数 color 表示顶点的填充色,参数 size 表示顶点的大小,参数 fontsize 表示标签文本的大小。

  • \Edge 命令在已有顶点的基础上定义边,(A)(B) 表示从顶点 A 到顶点 B 有一条边,参数label 表示边上的标签文本,参数 bend 表示边的弧度,参数 fontscale 表示标签文本的大小。

不难看出,无论是顶点还是边,都有颜色、大小、标签等参数,尽管参数名称有所不同。

12.5 思维导图

思维导图是非常常见的一种树状图,用于梳理层次关系。TikZ 绘制思维导图是通过 mindmap 库实现的,它是 PGF 的一个库。如 图 12.8 所示,看着和脑神经网络有某种相似性,所以,有时候,思维导图也叫脑图。

\documentclass[tikz,svgnames]{standalone}
\usepackage[fontset=fandol]{ctex}
\usetikzlibrary{mindmap}
\begin{document}
%% TikZ 代码%%
\end{document}
\begin{tikzpicture}[
    mindmap, every node/.style=concept, concept color=orange, text=white,
    level 1/.append style={level distance=5cm, sibling angle=60, font=\LARGE},
    level 2/.append style={level distance=3.5cm, sibling angle=45, font=\large}
  ]

  \node{\huge{\textsf{数据分析}}} [clockwise from=60]
  child [concept color=DarkMagenta] {
      node {\textit{数据准备}} [clockwise from=120]
      child { node {数据对象}}
      child { node {数据获取}}
      child { node {数据清洗}}
      child { node {数据操作}}
    }
  child [concept color=DarkBlue] {
      node {\textit{数据探索}} [clockwise from=30]
      child { node {ggplot2 入门}}
      child { node {基础图形}}
      child { node {统计图形}}
    }
  child [concept color=Brown] {
      node {\textit{数据交流}} [clockwise from=-30]
      child { node {交互图形}}
      child { node {交互表格}}
      child { node {交互应用}}
    }
  child [concept color=teal] {
      node {\textit{统计分析}} [clockwise from=-75]
      child { node {统计检验}}
      child { node {回归分析}}
      child { node {功效分析}}
    }
  child [concept color=purple] {
      node {\textit{数据建模}} [clockwise from=-120]
      child { node {网络分析}}
      child { node {文本分析}}
      child { node {时序分析}}
    }
  child [concept color=DarkGreen] {
      node {\textit{优化建模}} [clockwise from=180]
      child { node {统计计算}}
      child { node {数值优化}}
      child { node {优化问题}}
    };
\end{tikzpicture}

图 12.8: TikZ 绘制思维导图

根节点视为一层,则该思维导图有三层。不同的颜色和字体来区分不同的层次或分类,数据分析划分为不同的部分,每个部分有若干章。根节点字体为黑体、第二、三级节点分别为楷体、宋体。

\node{\huge{\textsf{数据科学}}} [clockwise from=60]

定义根节点,节点的文本设置为黑体,大小设置为 \huge 。由根节点向外辐射生成 6 个子节点,每隔 60 度设置一个子节点。

  child [concept color=DarkMagenta] {
      node {\textit{数据准备}} [clockwise from=120]
      child { node {数据对象}}
      child { node {数据获取}}
      child { node {数据清洗}}
      child { node {数据操作}}
    }

第一个子节点,颜色为饱和的紫色 DarkMagenta,二级子节点为「数据准备」,三级子节点有 4 个,逆时针 120 度的位置设置第一个三级子节点「数据对象」,然后顺时针往下,依次是「数据获取」、「数据清洗」和「数据操作」。