By Z.H. Fu
https://fuzihaofzh.github.io/blog/
引言
在绘制流程框图时,有许多工具可以采用,最著名的有微软的Viso,而开源的有Graphviz、PGF/TikZ,最近要再Mac上画一个流程图,Graphviz是采用dot语言的一个矢量图生成系统,但是不支持L a T e X LaTeX L a T e X 公式,安装也比较麻烦。PGF是用于生成矢量图的一种语言,而TikZ则是在Tex环境下的一个宏包,实现了对PGF的封装,所以我们要使用TikZ,就需要先配置L a T e X LaTeX L a T e X 环境,用TikZ绘制出来的图形效果非常好,原生支持所有L a T e X LaTeX L a T e X 语法,同时能和正文的文字字体保持高度一致,但是学习曲线略微陡峭,适合有排版强迫症的同学。那么下面我们一步一步地熟悉用TikZ来绘制流程图。
![TikZ_sample](/blog/images/tikz/TikZ_sample.svg)
环境安装与配置
我们首先要安装Tex环境,Windows系统推荐Texlive 完整安装,Mac系统推荐MacTex 完整安装后,在Windows下,已安装默认编辑器WinEdt,而在Mac下的编辑器则是TeXShop,这两个编辑器都非常好用,直接用就可以了。
第一个例子
我们先从一个最简单的例子开始:画一条直线。
代码如下:
1 2 3 4 5 6 7 \documentclass[tikz]{standalone} \usepackage{tikz} \begin{document} \begin{tikzpicture} \draw (0,0) -- (1,1); \end{tikzpicture} \end{document}
这样,我们得到了一个pdf文件,我们用pdf2svg工具将其转换为svg格式的矢量图。
输出:
![TikZ_s0](/blog/images/tikz/TikZ_s0.svg)
带\的都是LaTeX的宏命令,可以看到,这段代码的核心就一句话 \draw (0,0) -- (1,1);
这句话的意思就是从(0,0)到(1,1)画一条线段。我们还可以画的稍微复杂一点:
1 2 3 4 5 6 7 8 \documentclass[tikz,convert=pdf2svg]{standalone} \usepackage{tikz} \begin{document} \begin{tikzpicture} \draw [color=blue!50,->](0,0) node[left]{$A$}-- node [color=red!70,pos=0.25,above,sloped]{Hello}(3,3) node[right]{$B$}; \end{tikzpicture} \end{document}
输出:
![TikZ_s1](/blog/images/tikz/TikZ_s1.svg)
可以看到,`\draw`后面的方括号中跟的是对线的一些设置,`color=blue!50`表示的是用50%的蓝色,因为LaTeX中,%用作了注释,所以这里用!替代,`->`表示的是线形是一个箭头,我们注意到,在起点坐标,--,终点坐标后面,我们分别加入了一个node元素,起点后面的node表示的是加入一个标示,它在坐标点(0,0)的左边,`--`后面的node采用70%的红色,位置在线段的上方0.25的位置,随线段倾斜,花括号中是node的文字,为Hello,终点坐标同理。node经常用于加入一些标注,这一点我们在后面将会看到。
一些复杂的形状
在TikZ中,除了画线段之外,还支持各种复杂的形状,下面一个例子给出了一些常见的形状:
1 2 3 4 5 6 7 8 9 10 11 \documentclass[tikz,convert=pdf2svg]{standalone} \usepackage{tikz} \begin{document} \begin{tikzpicture} \draw (0,0) circle (10pt); %画一个圆心在原点,半径为10pt的圆; \draw (0,0) .. controls (1,1) and (2,1) .. (2,0); %画一个起点为(0,0),终点为(2,0),控制点为(1,1),(2,1)的贝塞尔曲线; \draw (0,0) ellipse (20pt and 10pt); %画一个中心在原点,长轴、短轴分别为20pt和10pt的椭圆; \draw (0,0) rectangle (0.5,0.5); %画一个从(0,0)到(0.5,0.5)的矩形 \filldraw[fill=green!20!white, draw=green!50!black](0,0) -- (3mm,0mm) arc (0:30:3mm) -- cycle;画一个扇形,并填充,扇形的边色和填充色的透明度不同。 \end{tikzpicture} \end{document}
输出:
![TikZ_s2](/blog/images/tikz/TikZ_s2.svg)
## 属性预定义
在刚才的例子中我们看到,随着我们对样式需求的多样化,属性越来越长,而且多个实体之间往往具有相同的属性,这样一来,我们希望能预定义一个属性集合,到时候直接赋给相应的实体,TikZ本身就是个宏,因此它为我们提供了强大的属性定义功能,来看这段代码:
1 2 3 4 5 6 7 8 9 10 \documentclass[tikz,convert=pdf2svg]{standalone} \usepackage{tikz} \begin{document} \begin{tikzpicture} [L1Node/.style={circle, draw=blue!50, fill=blue!20, very thick, minimum size=10mm}, L2Node/.style={rectangle,draw=green!50,fill=green!20,very thick, minimum size=10mm}] \node[L1Node] (n1) at (0, 0){$\int x dx$}; \node[L2Node] (n2) at (2, 0){$n!$}; \end{tikzpicture} \end{document}
输出:
![TikZ_s3](/blog/images/tikz/TikZ_s3.svg)
在这段代码中,我们在最开始定义了两个名为L1Node和L2Node的属性,在生成node结点的时候直接填到属性的位置即可。
循环
TikZ相比于Viso的一个优势就在于其循环功能,Viso里面要循环画十个圆就得复制十次,还要调节各自的位置,如果遇到需要调整位置又得重来,TikZ这种命令行的方式能直接画出来,如果需要调整位置,更改参数即可,我们在上一个例子上生成十个结点:
1 2 3 4 5 6 7 8 9 10 \documentclass[tikz,convert=pdf2svg]{standalone} \usepackage{tikz} \begin{document} \begin{tikzpicture} [L1Node/.style={circle, draw=blue!50, fill=blue!20, very thick, minimum size=10mm}, L2Node/.style={rectangle,draw=green!50,fill=green!20,very thick, minimum size=10mm}] \foreach \x in {1,...,5} \node[L1Node] (w1_\x) at (2*\x, 0){$\int_\Omega x_\x$}; \end{tikzpicture} \end{document}
输出:
![TikZ_s4](/blog/images/tikz/TikZ_s4.svg)
## node树
node结点不但可以用于添加标识,还可以来绘制树形图,下面看一个例子
1 2 3 4 5 6 7 8 9 10 11 12 \documentclass[tikz,convert=pdf2svg]{standalone} \usepackage{tikz} \begin{document} \begin{tikzpicture} \node {root} child {node {a1}} child {node {a2} child {node {b1}} child {node {b2}}} child {node {a3}}; \end{tikzpicture} \end{document}
输出:
![TikZ_s5](/blog/images/tikz/TikZ_s5.svg)
稍微加点样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 \documentclass[tikz,convert=pdf2svg]{standalone} \usepackage{tikz} \begin{document} \begin{tikzpicture} [every node/.style={fill=blue!30,draw=blue!70,rounded corners}, edge from parent/.style={blue,thick,draw}] \node {root} child {node {a1}} child {node {a2} child {node {b1}} child {node {b2}}} child {node {a3}}; \end{tikzpicture} \end{document}
输出:
![TikZ_s6](/blog/images/tikz/TikZ_s6.svg)
## 绘制函数图像
TikZ提供了强大的函数绘制功能,下面的代码展示了如何绘制函数,当然,绘制数据图表并非TikZ擅长的事情,也并非其设计初衷,TikZ着眼于定性的图表,定量数据的演示还是用其他工具绘制较好。
1 2 3 4 5 6 7 8 9 10 11 12 13 \documentclass[tikz]{standalone} \usepackage{tikz} \begin{document} \begin{tikzpicture}[domain=0:4] \draw[very thin,color=gray] (-0.1,-1.1) grid (3.9,3.9); \draw[->] (-0.2,0) -- (4.2,0) node[right] {$x$}; \draw[->] (0,-1.2) -- (0,4.2) node[above] {$f(x)$}; \draw[color=red] plot (\x,\x) node[right] {$f(x) =x$}; % \x r 表示弧度 \draw[color=blue] plot (\x,{sin(\x r)}) node[right] {$f(x) = \sin x$}; \draw[color=orange] plot (\x,{0.05*exp(\x)}) node[right] {$f(x) = \frac{1}{20} \mathrm e^x$}; \end{tikzpicture} \end{document}
输出:
![TikZ_s7](/blog/images/tikz/TikZ_s7.svg)
## 图
TikZ提供了图的支持,通过类似于dot语言的方式来生成图关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 \documentclass[tikz]{standalone} \usepackage{tikz} \usetikzlibrary{graphs} \begin{document} \begin{tikzpicture} \graph { "$x_1$" -> "$x_2$"[red] -> "$x_3,x_4$"; "$x_1$" ->[bend left] "$x_3,x_4$"; }; \end{tikzpicture} \begin{tikzpicture} \graph { a -> { b -> c, d -> e } -> f }; \end{tikzpicture} \end{document}
输出:
![TikZ_s8](/blog/images/tikz/TikZ_s8.svg)
![TikZ_s82](/blog/images/tikz/TikZ_s82.svg)
## 总结
关于TikZ的入门就写到这里,通过上面几个例子我们可以看出,TikZ能精确绘制各种复杂的示意图,能非常好地完成Viso,Graphviz等工具完成的功能。参考文献中给出了一个例子的网站,通常想绘制的图形都能在这上面搜到,自己稍微改一改就能直接使用。
## 参考文献
[1] http://www.texample.net/tikz/examples/ (提供了大量示例)
[2] The TikZ and PGF Packages Manual(TikZ的手册)
[3] A very minimal introduction to TikZ (一个入门教程)