\chapter{插图} \label{sec:graphics} \begin{quotation} A picture says more than a thousand words. \begin{flushright} --- Shakespeare \end{flushright} \end{quotation} 当年~Knuth~开发~\TeX~时,GIF、JPEG、PNG、EPS~等图形格式还没有问世,所以~DVI~不能直接支持这些格式。但是高手就是高手,Knuth~在~\TeX~上留了一个后门:\verb|\special|~命令,让后面的~Driver~决定怎样处理图形。 这和当年老毛把港澳台,老邓把钓鱼岛都“留给后人解决”有异曲同工之妙。曾经有位出版社的编辑看上了包老师写的一个程序,要包老师改改当作教学辅助软件出版,但是包老师手头没有~DOS~中断的资料没办法加鼠标操作。该编辑说:你把鼠标驱动打包在软件里,让用户自己琢磨是怎么回事。 下面我们会在~\ref{sec:graphics_format}~节介绍一下~\LaTeX~所用图形格式,\ref{sec:includegraphics}~节介绍怎样插入已有的图形,\ref{sec:mp}--\ref{sec:pgf}~节讨论怎样制作矢量图形。 \section{图形格式} \label{sec:graphics_format} \LaTeX~支持点阵图形格式~JPEG~和~PNG,也支持矢量格式~EPS~和~PDF。对于示意图,我们应该首选矢量格式;包含大量自然色彩的图像(比如照片)应该选~JPEG,人工点阵图像应该选~PNG。 \subsection{EPS} 80~年代中后期,PS~风头之劲一时无两,人们自然会考虑把它作为文档中嵌入图形的标准格式。然而~PS~实在太强大,人们担心嵌入文档的~PS~会搞破坏,于是就产生了戴着手铐的~Encapsulated PostScript(EPS)。出于同样的原因,人们也担心嵌入~HTML~的~ActiveX、Java Applet、JavaScript~中混入恶意代码,所以才会对它们也有所限制。 早年间~DVI~经常被转换为~PS,所以~EPS~就成了~\LaTeX~的标准图形格式。 \subsection{Driver~们的口味} \subsubsection{dvips} \verb|dvips|~喜欢~PS,所以就爱屋及乌只支持嵌入~EPS。MiKTeX~看不惯这种垄断行为,就把~\verb|dvips|~破解,添加了对~JPEG~和~PNG~的支持。如果你反对这种黑客破解行径,只好找软件把其它图形格式转换为~EPS。 \subsubsection{pdf\LaTeX} pdf\LaTeX~支持~JPEG、PNG和PDF,不支持~EPS。传说pdf\LaTeX~不支持~EPS~的原因是~PS~解释器的版权问题。包老师认为这种说法不可信,因为~1997~年~Hàn The Thành~发布~pdf\TeX~时~PS~已经被~PDF~赶超,Adobe~与其保护~PS~还不如保护~PDF。 \LaTeX~有两个宏包~\verb|epstopdf|~和~\verb|pst-pdf|~可以实时地(on the fly)把~EPS~转换为~PDF\footnote{在这里on the fly是指在后台处理,用户不用操心。包老师不确定把它翻译为“实时”是否合适,因为~real time~通常被翻译为实时。对于用户无须干涉、知情的情况,有人说~user transparent,也有人说~black box,语言还真奇妙。}。然而前者有安全漏洞,后者用法繁琐,用户最好还是用其它软件事先把~EPS~转为~PDF。 \subsubsection{dvipdfm} \verb|dvipdfm|~支持~JPEG、PNG、PDF,不支持~EPS,但是它可以实时地调用~Ghostscript~把~EPS~转为~PDF。所以从图形格式支持的角度来讲,\verb|dvipdfm|~比~\verb|dvips|~和~pdf\LaTeX~都好。 那位同学说了,你这么多废话作甚,直接告诉我们用~\verb|dvipdfm|~不就完了。然而你别忘了~\verb|dvipdfm|~需要~DVI~作为输入,不幸的是用来生成~DVI~的~\verb|latex|~对~JPEG~和~PNG~有意见。 综上所述,这些~driver~都不能把图形格式痛快地通吃,所以同学们别着急,且听包老师慢慢忽悠怎样转换和处理图形格式。 \subsection{图形格式转换} 注意把点阵图形转换为矢量图形并不能提高图形本身的质量,正所谓“garbage in, garbage out”。 \subsubsection{JPEG~和~PNG~的范围框} 作为中间格式的~DVI~不包含图形本身,它只记录图形的尺寸和文件名,因为具体的图形处理由后面的~driver~负责。DVI~中图形的尺寸来自它的范围框(bounding box),而~\verb|latex|~无法从点阵图形文件中提取这一信息,所以我们需要以某种方式把范围框信息告诉它。 一种方法是打开图形文件,记下尺寸,插入图形时加上相应的参数。 另一种方法用~\verb|ebb|~程序生成一个含范围框信息的文件。比如下例会生成~\verb|graph.bb|~文件,有了它插入图形时就不需要范围框参数。注意有时此程序算出的范围框不准,不知道是它的~bug~还是包老师的人品问题。 \begin{code} ebb graph.jpg \end{code} \subsubsection{其它格式转为EPS} 有很多程序都可以把点阵图形转换为~EPS,比如~\href{http://www.imagemagick.org/}{ImageMagick},以及~\href{http://www.tex.ac.uk/cgi-bin/texfaq2html?label=dvipsgraphics}{~a2ping/sam2p、bmeps、jpeg2ps、sam2p}~等。 PS~从~Level 2~开始才支持点阵图形压缩,所以在把其它格式转为~EPS~时应尽量使用~Level 2~或~3,否则输出的~EPS~会很大。 下面是一个~ImageMagick~中~\verb|convert|~程序的例子。 \begin{code} convert photo.jpg eps2:photo.eps \end{code} 另外还有一种~PS~虚拟打印机的方法,优点是可以把几乎所有文件“打印”成~EPS,缺点是输出的是~PS Level 1,即使驱动程序提供了其它~Level~的选项。 \begin{enumerate} \item 找一个~PS~打印机驱动程序。Windows~安装盘附带很多打印机驱动,其中带~PS~字样的就是~PS~驱动。包老师选的是“HP Color LaserJet 8550-PS”,Adobe~提供的~PS~驱动效果不太好。其它驱动安装过程可能稍有不同。 \item 安装时端口选“FILE”,或者后面打印时选择“Print to File”。高级选项里的~PS~选项选“Encapsulated PostScript (EPS)”。 \item 打开点阵图形文件,打印到上面的虚拟打印机,输出的文件就是~EPS,但是它没有范围框。 \item 用~GSview~打开上面生成的~EPS,不用理会没范围框的警告,Options~菜单里选上“EPS Clip”,用~File~菜单的“PS to EPS”生成含范围框的~EPS。 \end{enumerate} \subsubsection{其它格式转为~PDF} \LaTeX~附带的~\verb|epstopdf|~程序\footnote{这个命令行程序和上面提到的~epstopdf~宏包是两样东西。}可以把~EPS~转为~PDF。类似地我们也可以安装一个~PDF~虚拟打印机,用它来把其它图形文件转为~PDF。 \section{插入图形} \label{sec:includegraphics} \subsection{插入命令} 如今万事俱备,只欠插入。上回讲到哪儿来着?Yeah,~Knuth~的后门~\verb|\special|。 用低级命令~\verb|\special|~来插入图形很不爽,于是~\LaTeX~v2.09~增加了~\verb|epsf|~和~\verb|psfig|~宏包。之后~\LaTeXe~推出了更好的~\verb|graphics|~和~\verb|graphicx|~宏包,这两个宏包有个共同的命令:\verb|\includegraphics|。\verb|graphicx|~版本的语法更简单,功能更强大,所以一般推荐用它。 插入图形的具体命令如下,如果是点阵图形需要加范围框参数(左上角和右下角坐标)。 \begin{code} \includegraphics[bb=0 0 410 307]{photo.jpg} \end{code} 若想省略文件后缀,可在插入图形前使用两个命令。前者指定一个后缀列表,让~\LaTeX~自行查找;后者告诉~\LaTeX~未知后缀的都是~EPS。 \begin{code} \DeclareGraphicsExtensions{.eps,.mps,.pdf,.jpg,.png} \DeclareGraphicsRule{*}{eps}{*}{} \end{code} \subsection{缩放、旋转} \Fref{tab:scale_angle}~和\Fref{tab:clip}~的选项可以用来缩放、旋转和裁剪插图。 \begin{table}[htbp] \caption{includegraphics~命令的缩放和旋转选项} \label{tab:scale_angle} \centering \begin{tabularx}{350pt}{lX} \toprule scale & 缩放比例 \\ width & 宽度 \\ height & 高度 \\ totalheight & 范围框高度,旋转时它不等于高度 \\ keepaspectratio & 如果不使用它而同时指定插图的宽度、高度,长宽比可能会失调;使用它时长宽比不变,宽度、高度都不超过指定参数 \\ angle & 旋转角度 \\ origin & 旋转原点 \\ \bottomrule \end{tabularx} \end{table} \begin{table}[htbp] \caption{includegraphics~命令的裁剪选项} \label{tab:clip} \centering \begin{tabularx}{350pt}{lX} \toprule viewport & 可视区域的左上角和右下角坐标。\\ trim & 左、下、右、上四边裁剪的数值。\\ clip & 是否真正裁剪。缺省为false,不执行裁剪,多出的部分就那样放着;设置为true时执行裁剪。似乎是多此一举。\\ \bottomrule \end{tabularx} \end{table} 若想深入了解~\LaTeX~插入图形的功能,请参考~Keith Reckdahl~的《Using Imported Graphics in \LaTeX~ and pdf\LaTeX》\citep{Reckdahl_2006}(简称epslatex)。 \subsection{figure环境} 插图通常需要占据大块空白,所以在文字处理软件中用户经常需要调整插图的位置。\LaTeX~有一个~\verb|figure|~环境可以自动完成这样的任务,这种自动调整位置的环境称作浮动环境。 \begin{code} \begin{figure}[htbp]%位置选项 \centering \includegraphics[bb=0 0 410 307,scale=.8]{photo} \caption{10个月大的Anna} \label{fig:anna} \end{figure} \end{code} \begin{figure}[htbp] \centering \includegraphics[bb=0 0 410 307,scale=.8]{dscf4684} \caption{10个月大的Anna} \label{fig:anna} \end{figure} 上述代码中,\verb|[htbp]|~选项用来指定插图排版的理想位置,这几个字母分别代表~here、top、bottom、float page,也就是固定位置、页顶、页尾、单独的浮动页。我们可以使用这几个字母的任意组合,一般不推荐单独使用~\verb|[h]|,因为那个位置也许很不合适,\LaTeX~会很生气。 \verb|\centering|~用来使插图居中,\verb|\caption|~命令设置插图标题,\LaTeX~会自动给浮动环境的标题加上编号。注意~\verb|label|~应放在\verb|caption|~之后,否则引用时指向的是前一个插图。 \subsection{插入多幅图形} \subsubsection{并排摆放,共享标题} 当我们需要两幅图片并排摆放,并共享标题时,可以在~\verb|figure|~环境中使用两个~\verb|\includegraphics|~命令。 \begin{fdemo}{ \centering \includegraphics[scale=2]{examples/subfig_left.mps} \includegraphics[scale=2]{examples/subfig_right.mps} \captionof{figure}{反清复明} } \begin{figure}[htbp] \centering \includegraphics{left} \includegraphics{right} \caption{反清复明} \end{figure} \end{fdemo} \subsubsection{并排摆放,各有标题} 如果想要两幅并排的图片各有自己的标题,可以在~\verb|figure|~环境中使用两个~\verb|minipage|~环境,每个环境里插入一个图。 \begin{code} \begin{figure}[htbp] \centering \begin{minipage}[t]{0.3\textwidth} \centering \includegraphics{left} \caption{清明} \end{minipage} \end{code} \begin{code} \begin{minipage}[t]{0.3\textwidth} \centering \includegraphics{right} \caption{反复} \end{minipage} \end{figure} \end{code} \begin{figure}[htbp] \centering \begin{minipage}[t]{0.3\textwidth} \centering \includegraphics[scale=2]{examples/subfig_left.mps} \caption{清明} \end{minipage} \begin{minipage}[t]{0.3\textwidth} \centering \includegraphics[scale=2]{examples/subfig_right.mps} \caption{反复} \end{minipage} \end{figure} \subsubsection{并排摆放,共享标题,各有子标题} 如果想要两幅并排的图片共享一个标题,并各有自己的子标题,可以使用~\verb|subfig|~宏包提供的~\verb|\subfloat|~命令。 \verb|subfloat|~命令缺少宽度参数。虽然我们可以用~\verb|\hspace|~命令调整子图的距离,子标题却只能和子图本身一样宽,就会出现折行。 \begin{code} \usepackage{subfig} \begin{figure}[htbp] \centering \subfloat[清明]{ \label{fig:subfig_a} \includegraphics{left} } \hspace{80pt} \subfloat[反复]{ \label{fig:subfig_b} \includegraphics{right} } \caption{反清复明} \end{figure} \end{code} 每个子图可以有各自的引用,就象这个样子:\Fref{fig:subfig_a}、\Fref{fig:subfig_b}。 \begin{figure}[htbp] \centering \subfloat[清明]{ \label{fig:subfig_a} \includegraphics[scale=2]{examples/subfig_left.mps} } \hspace{80pt} \subfloat[反复]{ \label{fig:subfig_b} \includegraphics[scale=2]{examples/subfig_right.mps} } \caption{反清复明} \end{figure} \subsubsection{改进的子图方法} 为了避免子标题折行,我们可以在~\verb|\subfloat|~里再嵌套个~\verb|minipage|,因为后者是有宽度的。 \begin{code} \begin{figure}[htbp] \centering \subfloat[清明]{ \label{fig:improved_subfig_a} \begin{minipage}[t]{0.3\textwidth} \centering \includegraphics{left} \end{minipage} } \subfloat[反复]{ \label{fig:improved_subfig_b} \begin{minipage}[t]{0.3\textwidth} \centering \includegraphics{right} \end{minipage} } \caption{反清复明} \end{figure} \end{code} \begin{figure}[htbp] \centering \subfloat[清明]{ \begin{minipage}[t]{0.3\textwidth} \centering \includegraphics[scale=2]{examples/subfig_left.mps} \end{minipage} } \subfloat[反复]{ \begin{minipage}[t]{0.3\textwidth} \centering \includegraphics[scale=2]{examples/subfig_right.mps} \end{minipage} } \caption{反清复明} \end{figure} \section{图形绘制工具比较} 与~\LaTeX~配套使用的绘图工具主要有三种:\MP、PSTricks~和~PGF,它们的特点如下。 \begin{itemize} \item 工作方式。\MP~离线绘图,生成的~EPS~可以插入~\LaTeX~文档;PSTricks~和~PGF~都采用在线绘图的方式,也就是~\LaTeX~文档内直接使用绘图命令。 \item 兼容性。\MP~生成的~MPS~需要先转为~PDF~才能被~pdf\LaTeX~使用;PSTricks~生成的~EPS和~pdf\LaTeX~不兼容;PGF~提供针对各种~driver~的接口,兼容性最好。 \item 功能。PSTricks~有~PS~作后盾,功能最强;\MP~擅长处理数学内容;PGF~的流程图有独到之处。 \end{itemize} 限于篇幅,本文只对这三种工具进行简介。除了它们,用户也可以考虑一些面向~\LaTeX~的绘图前端,比如~Unix/Linux~下的~xfig~和~Windows~下的~TpX;或可以输出~EPS~的专用软件,比如~gnuplot~和~Matlab。 \section{\MP} \label{sec:mp} 1989~年~John D. Hobby\footnote{Hobby 1985年从斯坦福获博士学位,导师就是Knuth,现供职于贝尔实验室。}开始设计一种绘图语言及其编译器,也就是~\MP。\MP~从~\MF~那里获得了大量灵感和源代码,学生从导师那里顺点东西自然是手到擒来。\MP~和~\MF~语法类似,\MP~的主要优点在于是它输出的是~EPS,而且支持彩色;\MF~输出的是点阵格式,不支持彩色。Knuth~声称自己画图时只用~\MP。 从~Hobby~主页上~\MP~的更新记录看,它的最后版本是~0.63,年份是~1994。目前~Taco Hoekwater\footnote{他也是LuaTeX开发者之一。}继续~\MP~的开发工作,最新版本是~1.005。 本文只对~\MP~作简单介绍,若想深入了解请参阅~Hobby~的《A User's Manual for MetaPost》\citep{Hobby_2007}。 \subsection{准备工作} 用户一般需要把~\MP~源文件(.mp)用一个命令行程序~\verb|mpost|~编译为一种特殊的~EPS,也称作~MPS,然后再把~MPS~插入~\LaTeX~源文件中使用。 \begin{figure}[htbp] \centering \begin{tikzpicture} \node[box] (mp) {.mp}; \node[box, right=5 of mp] (mps) {.mps}; \path (mp) edge [arrow] node[auto] {mpost} (mps); \end{tikzpicture} \caption{MetaPost~的编译} \label{fig:mp} \end{figure} 一个~\MP~源文件可以包含多个图形,一般形式如下。代码中每行语句以~\verb|;|~结尾,注释行以~\verb|%|~起始。每个图形的绘图命令包含在一对起始和结尾声明之间。文件结尾也要有一个结尾声明。 \begin{code} beginfig(1); %图形起始 ... %绘图命令 endfig; %图形结尾 beginfig(2); ... endfig; ... end; %文件结尾 \end{code} 假如上面的源文件名字是~\verb|fig.mp|,我们可以执行以下编译命令。 \begin{code} mpost fig(.mp) \end{code} 编译后就会生成“fig.1、fig.2、$\cdots$”~等文件,每个文件的后缀就是相应的图形起始声明的编号。所以此编号在一个源文件中应保持唯一,否则后生成的文件就会覆盖前面的。 这样的文件名管理起来很麻烦,插入它们时也不能省略后缀,因为~\LaTeX~不能识别它们。用~\verb|\DeclareGraphicsExtensions|~来逐一声明后缀看起来很傻,自己改文件名更傻,\verb|\DeclareGraphicsRule|~也显得不够严谨。 然而~\MP~已经考虑到这个问题,为此提供了一个文件名模板命令。把下面的代码加到源文件头部,编译输出的文件名就会是“fig-01.mps、fig-02.mps、$\cdots$”。 \begin{code} filenametemplate "%j-%2c.mps"; %加在源文件头部 \end{code} 我们也可以把这个命令加在每个图形的起始声明之前,指定个性化的输出文件名,这样可能更便于记忆。 \begin{code} filenametemplate "flowchart.mps" %加在每个图形前面 \end{code} MPS~可以用~GSview~查看,我们也可以用以下命令把它转为~PDF~再用~Adobe Reader~查看。 \begin{code} epstopdf flowchart.mps \end{code} \subsection{基本图形对象} 为了节省空间,本节后面的示例会略去图形起始声明和结尾声明等不重要的细节。 \subsubsection{直线} 绘图命令~\verb|draw|~把几个点以直线段连接起来。\MP~中的缺省长度单位是~bp,用户也可以使用\Fref{tab:unit}~中的其它单位。我们还可以定义一个缩放系数,把坐标都转换成此系数的倍数,这样以后想缩放图形时只要改这个系数即可。 注意~\MP~中的变量赋值符号是~\verb|:=|,而~\verb|=|~用于方程式。变量在同一源文件中只须定义一次,其后的图形中都可以使用。 \begin{fdemo}{\includegraphics{examples/line.mps}} draw (0,0)--(40,0)--(20,20)--(0,0); u:=10pt; %缩放系数 draw (5u,0)--(9u,0)--(7u,2u)--cycle; \end{fdemo} 几段直线或曲线可以构成一条路径(path),在路径末尾加个~\verb|cycle|~就能构成封闭路径(closed path)。上例中的两个三角形看起来都是封闭的,但是前面这个其实不是真正的封闭路径。 \subsection{点和线宽} \verb|drawdot|~命令可以在指定坐标画一个点,为了使它醒目些我们可以换支粗一点的画笔。\MP~中的画笔缺省是直径~0.5pt~的圆形,拿它画出来的线宽就是~0.5pt。 \begin{fdemo}{\includegraphics{examples/dot.mps}} draw (0,0)--(10u,4u); pickup pencircle scaled 2pt; drawdot (0,0); drawdot (10u,4u); \end{fdemo} 上面的~\verb|pickup|~是一种全局操作,也就是说它会影响到之后所有的绘图命令,我们也可以用~\verb|withpen|~为单个绘图命令设置画笔。 \begin{code} draw (0,0)--(10u,4u) withpen pencircle scaled 2pt; \end{code} \subsubsection{曲线} 曲线和直线的命令相近,只是把连接两个点的~\verb|--|~换成了~\verb|..|。如果共用一些坐标,直线和曲线也可以混在一条语句里画。 \begin{fdemo}{\includegraphics{examples/curve.mps}} draw (0,.5u)..(5u,3u)..(10u,1.5u).. (7u,0)..(5u,1.5u)..(7u,1.5u); \end{fdemo} \MP~的曲线用三次贝塞尔(Cubic B\'ezier)算法实现。用户可以在命令中增加~direction(方向)、Tension(张力)和~Curl(曲率)等控制,限于篇幅本文不赘述。 \subsubsection{预定义图形} ~\verb|fullcircle|~命令以原点为圆心画一个单位圆,类似的预定义图形还有~\verb|halfcircle、quartercircle、unitsquare|~等。注意单位正方形的参考点在左下而不在其中心。 通过不同的横向和纵向缩放系数,我们可以把圆形和正方形变成椭圆和长方形。 \begin{code} draw fullcircle scaled 2u; draw halfcircle scaled 2u shifted (3u,0); draw quartercircle scaled 2u shifted (5u,0); draw fullcircle xscaled 4u yscaled 2u shifted (9u,0); draw unitsquare scaled 2u shifted (12u,-u); draw unitsquare xscaled 4u yscaled 2u shifted (15u,-u); \end{code} \begin{out} \includegraphics{examples/predefined.mps} \end{out} \subsection{图形控制} \subsubsection{线型和箭头} 在绘制图形时,我们不仅可以变换线宽,也可以使用多种线型。 \begin{code} draw (0,0)--(10u,0) dashed withdots; draw (0,1u)--(10u,1u) dashed withdots scaled 2; draw (0,2u)--(10u,2u) dashed evenly; draw (0,3u)--(10u,3u) dashed evenly scaled 2; \end{code} \begin{out} \includegraphics{examples/dashed.mps} \end{out} 箭头和直线、曲线的语法相近,注意画反向箭头时需要把两个坐标用一对~\verb|()|~括起来。 \begin{fdemo}{\includegraphics{examples/arrow.mps}} drawarrow (0,4u)--(9u,4u); drawarrow reverse ((0,2u)--(9u,2u)); drawdblarrow (0,0)--(9u,0); \end{fdemo} \subsubsection{颜色和填充} \MP~预定义的颜色有黑、白、红、绿、蓝,它们的~RGB~值分别为(0,0,0)、(1,1,1)、(1,0,0)、(0,1,0)、(0,0,1),缺省色就是黑色。 绘图命令一般都可以通过~\verb|withcolor|~参数来使用各种颜色。封闭路径可以用~\verb|fill|~命令填充。 \begin{fdemo}{\includegraphics{examples/color.mps}} draw (0,4u)--(9u,4u) withcolor red; draw (0,2u)--(9u,2u) withcolor green; draw (0,0)--(9u,0) withcolor blue; \end{fdemo} \begin{code} fill p scaled u; fill p scaled u shifted (3u,0) withcolor red; fill p scaled u shifted (6u,0) withcolor green; fill p scaled u shifted (9u,0) withcolor blue; \end{code} \begin{out} \includegraphics{examples/fill.mps} \end{out} 另一个命令~\verb|filldraw|~可以看作是~\verb|fill+draw|,它除了填充外还会把路径用指定的画笔画一遍。然而不幸的是画边缘和填充内部只能用同一种颜色,所以它的用处不大。 除了为每个绘图命令单独指定颜色,我们也可以使用一个全局命令,使得其后的绘图命令都使用某种颜色。 \begin{code} drawoption(withcolor blue); \end{code} 下面的方法可以用来定义基本色以外的颜色,用基本色混色或直接用RGB值效果是一样的。 \begin{code} color c[]; c1 := .9red + .6green + .3blue; c2 := (.9,.6,.3); \end{code} \subsubsection{图形变换} 我们可以对路径进行缩放、平移、旋转等变换操作,横向和纵向缩放可以分开进行。由于旋转是围绕原点进行的,所以要注意平移和旋转的顺序。下例中定义了一个~\verb|path|~变量,以便后面重用。 \begin{code} path p; p := (0,0)--(2,0)--(1,1.732)--cycle; draw p scaled u; draw p xscaled 2u yscaled u shifted (3u,0); draw p scaled u rotated 60 shifted (8u,0); \end{code} \begin{out} \includegraphics{examples/transform.mps} \end{out} \subsubsection{标注} \verb|\label|~命令可以在指定的点附近加文字标注。\MP~也可以用一对~\verb|btex|~和~\verb|etex|~来嵌入一些~\TeX~内容,比如数学标注。 \begin{code} draw unitsquare xscaled 10u yscaled 4u; label.top("top", (5u,4u)); label.bot("bottom", (5u,0)); label.lft("left", (0,2u)); label.rt("right", (10u,2u)); \end{code} \begin{code} label.ulft("upper left", (0,4u)); label.urt("upper right", (10u,4u)); label.llft("lower left", (0,0)); label.lrt("lower right", (10u,0)); label.rt(btex $E=mc^2$ etex, (3u,2u)); \end{code} \begin{out} \includegraphics{examples/label.mps} \end{out} 因为用缺省方法编译生成的~MPS~不嵌入字体,当~\MP~包含文字时,GSview~就不能正常查看。这时我们可以给编译命令加个参数,生成的~MPS~就会包含字体信息。注意这种方法生成的~MPS~虽然~GSview~能查看,\verb|dvipdfmx|~却不能正常处理。 \begin{code} mpost \prologues:=2; input fig.mp \end{code} \subsection{编程功能} \subsubsection{数据类型和变量} \MP~中有10~种基本数据类型:numeric、pair、path、pen、~color~、cmykcolor、transform、string、boolean、picture。我们已经接触过其中几种,比如缩放系数~u~就是一个~numeric,一个点的坐标是一个~pair,几个点用直线或曲线连起来是一个~path,pencircle~是一种~pen,black~是一种~color,scaled、rotated、shifted~都是~transform。 numeric~类型变量的精度是~1/65536,它的绝对值不能超过~4096,在计算过程中数值可以达到~32768。这样的规定也应归功于当年的电脑硬件,不过对于科技文档插图而言,4096~一般还是够用的。 除了缺省的~numeric,其它变量在使用之前都需要用数据类型来显式声明。相同类型的变量可以在一行语句中声明,但是带下标的变量不能放在同一行(这个规定很蹊跷)。 \begin{code} numeric x,y,z; %正确 numeric x1,x2,x3; %错误 numeric x[]; %正确 \end{code} \subsubsection{数学运算} \MP~中可以使用普通的运算符,比如~\verb|+ - * /|;也提供一些特殊的运算符,比如~\verb|a++b|~表示$\sqrt{a^2+b^2}$,\verb|a+-+b|~表示$\sqrt{a^2-b^2}$;另外\Fref{tab:math_function}~列出一些常用数学函数。 \begin{table}[htbp] \caption{数学函数} \label{tab:math_function} \centering \begin{tabular}{llll} \toprule abs & 绝对值 & mexp & 指数 \\ round & 四舍五入 & mlog & 对数 \\ ceiling & 向上圆整 & sind & 正弦 \\ floor & 向下圆整 & cosd & 余弦 \\ mod & 模余 & normaldeviate & 正态分布随机数 \\ sqrt & 开方 & uniformdeviate & 均匀分布随机数 \\ \bottomrule \end{tabular} \end{table} \subsubsection{循环} 当执行重复任务时,循环语句可以让程序变得简洁。注意下例中的循环语句是一条命令,之所以分成三行写是为了看起来清晰点。 \begin{fdemo}{\includegraphics{examples/loop.mps}} draw (0,0) %注意这里没有分号 for x=1 upto 3: ..(x*x,x)*u endfor; \end{fdemo} 循环语句缺省步长是~1,我们也可以改用其它步长。\verb|upto|~其实就是~\verb|step 1 until|~的缩写方式。 \begin{code} for x=1 step .5 until 3: \end{code} \section{PSTricks} \label{sec:pstricks} PSTricks~是一个基于~PS~的宏包,有了它用户就可以直接在~\LaTeX~文档中插入绘图命令。PSTricks~早期的作者是~Timothy Van Zandt\footnote{法国Insead大学经济系教授。},初始开发年月不详,他于~1997~年退居二线,之后由~Denis Girou、Sebastian Rahtz\footnote{牛津大学计算机系网管。}、Herbert Voß~等维护。 本文只介绍~PSTricks~的基本功能,若想深入了解请参阅~Van Zandt~的《PSTricks User's Guide》\citep{Zandt_2007}。另外\Fref{tab:pst_add}~列出了一些可以和~PsTricks~配合使用的辅助宏包。 \begin{table}[htbp] \caption{PSTricks~辅助宏包} \label{tab:pst_add} \centering \begin{tabular}{llll} \toprule multido & 循环语句 & pst-eucl & 几何函数 \\ pst-plot & 函数绘图 & pst-math & 弧度三角函数 \\ pst-plot3d & 三维绘图 & pstricks-add & 极坐标 \\ \bottomrule \end{tabular} \end{table} \subsection{准备工作} 首先要引入~PSTricks~宏包。PSTricks~中缺省长度单位是~1cm,我们也可以设置自己的单位。 \psset{unit=10pt} \begin{code} \usepackage{pstricks} \psset{unit=10pt} \end{code} 绘图命令一般要放在~\verb|pspicture|~环境里,这样~\LaTeX~就会给图形预留一个矩形区域,注意这个矩形要能容纳所有图形对象。为了节省空间,在本节后面的示例代码中,~\verb|pspicture|~环境将被略去。 \begin{code} \begin{pspicture}(0,0)(4,2) ... \end{pspicture} \end{code} 另外需要注意的是,嵌入~\LaTeX~的~PSTricks~生成的是~PS,\verb|dvips|~可以处理,\verb|dvipdfm|~和pdf\LaTeX~则不能。所以如果使用后两种~driver,需要先行生成~EPS。 第一种方法是把~PSTricks~代码放进一个空白的\LaTeX~页面,用它生成一个简单的~DVI。 \begin{code} \documentclass{article} \usepackage{pstricks} \pagestyle{empty} %页面样式为空 \begin{document} \psset{unit=10pt} \colorbox{white}{% \begin{pspicture}(0,0)(4,2)% \psdot(0,0)% \psdots(0,2)(2,2)(4,2)% \end{pspicture}% } \end{document} \end{code} 然后用以下命令把~DVI~转为~EPS,\verb|-E|~参数即代表~EPS。上面代码中的~colorbox~使得生成的~EPS~有正确的范围框。 \begin{code} dvips pst_dots(.dvi) -E -o dots.eps \end{code} 第二种方法是用~\verb|pst-eps|~宏包,它能够在线处理~PSTricks~代码并生成~EPS,这样用户就可以在同一文件中使用该~EPS。 然而~\verb|dvipdfmx|~不能正确处理~\verb|pst-eps|~生成的~EPS。它和~\verb|\rput|、\verb|\uput|~命令,\verb|pst-plot|~宏包中的~\verb|\psaxes|~命令都不兼容。类似的~\verb|ps4pdf|~宏包和~\verb|tabularx|~宏包不兼容。 \begin{comment} \usepackage{pst-eps} \PSTtoEPS[bbllx=0,bblly=0,bburx=4,bbury=2,makeeps=all] {dots.eps}{ \psdot(0,0) \psdots(0,2)(2,2)(4,2) } \includegraphics{dots.eps} \end{comment} \subsection{基本图形对象} \subsubsection{点} 我们可以用以下命令画一个或多个点。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=4,bbury=2,makeeps=all] {examples/dot.eps}{ \psdot(0,0) \psdots(0,2)(2,2)(4,2) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_dot.eps}} \psdot(0,0) \psdots(0,2)(2,2)(4,2) \end{fdemo} \subsubsection{直线、多边形、矩形} \verb|\psline|~命令把多个点用直线段连接起来,线段之间的连接缺省为尖角,也可以设置圆角。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=9,bbury=2,makeeps=all] {examples/line.eps}{ \psline(0,0)(2,2)(4,0) \psline[linearc=.3](5,0)(7,2)(9,0) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_line.eps}} \psline(0,0)(2,2)(4,0) \psline[linearc=.3](5,0)(7,2)(9,0) \end{fdemo} \verb|\pspolygon|~命令和~\verb|\psline|~类似,但是它会形成封闭路径。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=9,bbury=2,makeeps=all] {examples/polygon.eps}{ \pspolygon(0,0)(2,2)(4,0) \pspolygon[linearc=.3](5,0)(7,2)(9,0) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_polygon.eps}} \pspolygon(0,0)(2,2)(4,0) \pspolygon[linearc=.3](5,0)(7,2)(9,0) \end{fdemo} 矩形用~\verb|\psframe|~命令,其参数就是矩形左下角和右上角的坐标。矩形也可以设置圆角。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=9,bbury=2,makeeps=all] {examples/frame.eps}{ \psframe(0,0)(4,2) \psframe[framearc=.3](5,0)(9,2) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_frame.eps}} \psframe(0,0)(4,2) \psframe[framearc=.3](5,0)(9,2) \end{fdemo} \subsubsection{圆、椭圆、圆弧、扇形} 圆形用~\verb|\pscircle|~命令,参数是圆心和半径。椭圆用~\verb|\psellipse|~命令,参数是中心、长径、短径。注意这两个命令的半径参数用不同的括号,可能是作者的笔误。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=7,bbury=2,makeeps=all] {examples/circle.eps}{ \pscircle(1,1){1} \psellipse(5,1)(2,1) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_circle.eps}} \pscircle(1,1){1} \psellipse(5,1)(2,1) \end{fdemo} 圆弧用~\verb|\psarc|~命令,其参数是圆心、半径、起止角度,逆时针作图。~\verb|\psarcn|~类似,只是顺时针作图。扇形用~\verb|\pswedge|~命令。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=11,bbury=2,makeeps=all] {examples/arc.eps}{ \psarc(1,0){2}{0}{120} \psarcn(5,0){2}{120}{0} \pswedge(9,0){2}{0}{120} } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_arc.eps}} \psarc(1,0){2}{0}{120} \psarcn(5,0){2}{120}{0} \pswedge(9,0){2}{0}{120} \end{fdemo} \subsubsection{曲线} \verb|\pscurve|~命令把一系列点用平滑曲线连接起来;它的变形版本~\verb|\psecurve|~命令不显示曲线的两个端点;另一变形命令~\verb|\psccurve|~则把曲线封闭起来。 参数~\verb|showpoints=true|~用来显示曲线的构成点,此参数也可用于其它绘图命令。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=15,bbury=2,makeeps=all] {examples/curve.eps}{ \pscurve[showpoints=true](0,1)(1,2)(3,0)(4,2)(1,0) \psecurve[showpoints=true](5,1)(6,2)(8,0)(9,2)(5,0) \psccurve[showpoints=true](11,1)(12,2)(14,0)(15,2)(12,0) } \end{comment} \begin{code} \pscurve[showpoints=true](0,1)(1,2)(3,0)(4,2)(1,0) \psecurve[showpoints=true](5,1)(6,2)(8,0)(9,2)(5,0) \psccurve[showpoints=true](11,1)(12,2)(14,0)(15,2)(12,0) \end{code} \begin{out} \includegraphics{examples/pst_curve.eps} \end{out} \verb|\psbezier|~命令输出一条贝塞尔曲线,其参数就是曲线的控制点。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=6,bbury=2,makeeps=all] {examples/bezier.eps}{ \psbezier[showpoints=true] (0,0)(2,2)(4,0)(6,2) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_bezier.eps}} \psbezier[showpoints=true] (0,0)(2,2)(4,0)(6,2) \end{fdemo} 抛物线用~\verb|\psparabola|~命令,它有两个参数,第一个是抛物线通过的一点,第二个是抛物线的顶点。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=2,bbury=2,makeeps=all] {examples/parabola.eps}{ \psparabola[showpoints=true] (2,2)(1,0) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_parabola.eps}} \psparabola[showpoints=true] (2,2)(1,0) \end{fdemo} \subsubsection{网格和坐标} 科技制图通常会用到坐标网格。\verb|\psgrid|~命令输出一个矩形网格,它有三个参数点。网格坐标标注在通过第一个点的两条直线上,第二和第三个点是矩形的两个对角顶点。当第一个参数省略时,坐标标注在通过第一个顶点的两条矩形边上。 \begin{comment} \psset{unit=10pt} \PSTtoEPS[bbllx=-1,bblly=-1,bburx=8.4,bbury=2.4,makeeps=all] {examples/grid.eps}{ \psgrid(0,0)(-1,-1)(3,2) \psgrid(5,0)(8,2) } \end{comment} \begin{code} \psgrid(0,0)(-1,-1)(3,2) \psgrid(5,0)(8,2) \end{code} \begin{out} \includegraphics{examples/pst_grid.eps} \end{out} \verb|pst-plot|~宏包提供的~\verb|\psaxes|~命令输出坐标轴。它的参数和~\verb|\psgrid|~的类似,刻度和标注都可以灵活地设置,也可以把坐标轴改成一个矩形框的形式。 \begin{code} \psset{unit=10pt} \psaxes{<->}(0,0)(-1,-1)(3,2) \psaxes[tickstyle=top,labels=none]{->}(5,0)(8,2) \psaxes[axesstyle=frame,tickstyle=top]{->}(10,0)(13,2) \end{code} \begin{out} \includegraphics{examples/pst_axis.eps} \end{out} \subsection{图形控制} \subsubsection{线型和箭头} PSTricks~中的缺省线宽是~0.8pt,缺省线型是实线。以下参数可以控制单个绘图命令的线宽和线型。 \begin{comment} \psset{unit=10pt} \PSTtoEPS[bbllx=0,bblly=0,bburx=9,bbury=4,makeeps=all] {examples/linestyle.eps}{ \psline[linewidth=1.5pt](0,4)(9,4) \psline[linestyle=dotted](0,2)(9,2) \psline[linestyle=dashed](0,0)(9,0) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_linestyle.eps}} \psline[linewidth=1.5pt](0,4)(9,4) \psline[linestyle=dotted](0,2)(9,2) \psline[linestyle=dashed](0,0)(9,0) \end{fdemo} 我们也可以用命令~\verb|\psset|~命令来设置全局参数。 \begin{code} \psset{linewidth=1pt,linestyle=dashed} \end{code} 以下参数可以控制绘图命令的箭头。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=9,bbury=4,makeeps=all] {examples/arrow.eps}{ \psline{->}(0,4)(9,4) \psline{<-}(0,2)(9,2) \psline{<->}(0,0)(9,0) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_arrow.eps}} \psline{->}(0,4)(9,4) \psline{<-}(0,2)(9,2) \psline{<->}(0,0)(9,0) \end{fdemo} \subsubsection{颜色和填充} PSTricks~预定义的颜色有~black、darkgray、gray、lightgray、white~等灰度颜色,也有~red、green、blue、cyan、magenta、yellow~等彩色。我们也可以自定义灰度颜色和彩色。 \begin{code} \newgray{mygray}{.3} \newrgbcolor{mycolor}{.3 .4 .5} \end{code} 以下参数可以控制单个绘图命令的颜色,我们也可以用~\verb|\psset|~命令设置全局参数。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=9,bbury=4,makeeps=all] {examples/color.eps}{ \psline[linecolor=red](0,4)(9,4) \psline[linecolor=green](0,2)(9,2) \psline[linecolor=blue](0,0)(9,0) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_color.eps}} \psline[linecolor=red](0,4)(9,4) \psline[linecolor=green](0,2)(9,2) \psline[linecolor=blue](0,0)(9,0) \end{fdemo} 以下参数可以控制单个绘图命令的填充模式和填充颜色,注意只有封闭路径才可以填充。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=11,bbury=2,makeeps=all] {examples/fill.eps}{ \pscircle[fillstyle=solid,fillcolor=red](1,1){1} \pscircle[fillstyle=vlines](4,1){1} \pscircle[fillstyle=hlines](7,1){1} \pscircle[fillstyle=crosshatch](10,1){1} } \end{comment} \begin{code} \pscircle[fillstyle=solid,fillcolor=red](1,1){1} \pscircle[fillstyle=vlines](4,1){1} \pscircle[fillstyle=hlines](7,1){1} \pscircle[fillstyle=crosshatch](10,1){1} \end{code} \begin{out} \includegraphics{examples/pst_fill.eps} \end{out} \subsection{对象布局} \subsubsection{平移} 参数~\verb|origin|~可以让一个图形对象平移到指定的坐标点,我们也可以用~\verb|\psset|~命令设置全局平移参数。 \begin{comment} \PSTtoEPS[bbllx=0,bblly=0,bburx=7,bbury=2,makeeps=all] {examples/origin.eps}{ \psframe(0,0)(3,2) \psframe[origin={4,0}](0,0)(3,2) } \end{comment} \begin{fdemo}{\includegraphics{examples/pst_origin.eps}} \psframe(0,0)(3,2) \psframe[origin={4,0}](0,0)(3,2) \end{fdemo} \subsubsection{旋转} \verb|\rput|~命令可以对一个图形对象同时进行旋转和平移操作。它有两个参数,第一个是旋转角度,第二个是平移到的坐标点。 \begin{fdemo}{\includegraphics{examples/pst_rput.eps}} \psframe(0,0)(3,2) \rput{30}(5,0){\psframe(0,0)(3,2)} \end{fdemo} \subsubsection{文字标注} \verb|\rput|~命令还可以在指定的坐标点标注文字,这时它的第一个参数是坐标点相对于标注的位置。其取值可以是纵向的~t、b(上下),或横向的~l、r(左右),也可以是纵向和横向位置的组合。 \begin{fdemo}{\includegraphics{examples/pst_label.eps}} \pspolygon(0,0)(4,0)(2,3) \rput[r](0,0){A} \rput[l](4,0){B} \rput[b](2,3){C} \end{fdemo} \verb|\rput|~生成的标注就在坐标点上,有时会感觉离图形太近。另一个命令~\verb|\uput|~则生成缺省距离指定坐标点~5pt的标注,它的第一个参数是标注相对于坐标点的角度。 \begin{fdemo}{\includegraphics{examples/pst_uput.eps}} \pspolygon(0,0)(4,0)(2,3) \uput[l](0,0){A} \uput[r](4,0){B} \uput[u](2,3){C} \end{fdemo} \verb|\uput|~的角度参数可以是任意角度,也可以是字母,参见。注意~\verb|\uput|~的角度参数和~\verb|\rput|~命令的参考点位置参数的定义几乎正好相反,这也许反映了作者洒脱的风格。 \begin{table}[htbp] \caption{uput~命令的角度参数} \label{tab:uput} \centering \begin{tabular}{lrlr} \toprule r & 0$^\circ$ & ur & 45$^\circ$ \\ u & 90$^\circ$ & ul & 135$^\circ$ \\ l & 180$^\circ$ & dl & 225$^\circ$ \\ d & 270$^\circ$ & dr & 315$^\circ$ \\ \bottomrule \end{tabular} \end{table} \section{PGF} \label{sec:pgf} PGF~和~Beamer~的作者都是~Till Tantau\footnote{德国Lübeck大学计算机研究所教授。}。Tantau~当初开发~Beamer~是为了应付~2003~年他的博士学位论文答辩,之后它在~CTAN~上流行开来。2005~年~PGF~从~Beamer~项目中分离出来,成为一个独立的宏包。 本文只对~PGF~作简单介绍,若想深入了解请参阅~Tantau~的《TikZ and PGF Manual》\citep{Tantau_2008}。 \subsection{准备工作} 一般人们并不直接使用~PGF~命令,而是通过它前端~TikZ~来调用~PGF。在引用~\verb|tikz|~宏包之前,用户需要设置~PGF~系统~driver。 \begin{code} \def\pgfsysdriver{pgfsys-dvipdfmx.def} \usepackage{tikz} \end{code} PGF~的缺省长度单位是~1cm,我们也可以改用其它单位。注意这样预定义的长度单位有时会失效,这可能是~PGF~的~bug。 \begin{code} \pgfsetxvec{\pgfpoint{10pt}{0}} \pgfsetyvec{\pgfpoint{0}{10pt}} \end{code} TikZ~提供一个~\verb|tikz|~命令和一个~\verb|tikzpicture|~环境,具体绘图指令可以放在~\verb|tikz|~后面,也可以放在~\verb|tikzpicture|~中间。两种方法效果相同,用户可以任意选择。为了节省空间,本节的示例将省略部分环境代码。 \begin{code} \tikz ... %绘图命令 \begin{tikzpicture} ... %绘图命令 \end{tikzpicture} \end{code} \subsection{基本图形对象} \subsubsection{直线、多边形、矩形} TikZ~中直线的语法和~\MP~类似,加~\verb|cycle|~参数才能构成真正的封闭路径。 \begin{fdemo}{ \begin{tikzpicture} \draw (0,0)--(4,0)--(2,2)--(0,0); \draw (5,0)--(9,0)--(7,2)--cycle; \end{tikzpicture} } \draw (0,0)--(4,0)--(2,3)--(0,0); \draw (5,0)--(9,0)--(7,3)--cycle; \end{fdemo} 矩形命令如下,它的两个参数是矩形的两个对角顶点。 \begin{fdemo}{ \begin{tikzpicture} \draw (0,0) rectangle (4,2); \end{tikzpicture} } \draw (0,0) rectangle (4,2); \end{fdemo} \subsubsection{圆、椭圆、弧} 圆和椭圆命令如下,圆的参数是圆心和半径,椭圆的参数是中心、长径、短径。 \begin{fdemo}{ \begin{tikzpicture} \draw (1,1) circle (1); \draw (5,1) ellipse (2 and 1); \end{tikzpicture} } \draw (1,1) circle (1); \draw (5,1) ellipse (2 and 1); \end{fdemo} 圆弧和椭圆弧命令如下,圆弧的参数是起始点,起始角度、终止角度、半径;椭圆弧则把半径换成了长径和短径。 \begin{fdemo}{ \begin{tikzpicture} \draw (2,1) arc (0:270:1); \draw (7,1) arc (0:270:2 and 1); \end{tikzpicture} } \draw (2,1) arc (0:270:1); \draw (7,1) arc (0:270:2 and 1); \end{fdemo} \subsubsection{曲线和抛物线} 曲线命令如下,中间的参数是控制点。 \begin{fdemo}{ \begin{tikzpicture} \draw (0,0) .. controls (2,2) and (4,2) .. (4,0); \filldraw (0,0) circle (.1) (2,2) circle (.1) (4,2) circle (.1) (4,0) circle (.1); \end{tikzpicture} } \draw (0,0) .. controls (2,2) and (4,2) .. (4,0); \end{fdemo} 抛物线命令如下,除了起止点还可以指定顶点。 \begin{fdemo}{ \begin{tikzpicture} \draw (-1,1) parabola bend (0,0) (1.414,2); \filldraw (-1,1) circle (.1) (0,0) circle (.1) (1.414,2) circle (.1); \end{tikzpicture} } \draw (-1,1) parabola bend (0,0) (1.414,2); \end{fdemo} \subsection{图形控制} \subsubsection{线型和箭头} 绘图命令可以设置线型和箭头参数。 \begin{fdemo}{ \begin{tikzpicture} \draw[line width=2pt] (0,3)--(9,3); \draw[->] (0,2)--(9,2); \draw[<-] (0,1)--(9,1); \draw[<->] (0,0)--(9,0); \end{tikzpicture} } \draw[line width=2pt] (0,0)--(9,0); \draw[->] (0,1)--(9,1); \draw[<-] (0,2)--(9,2); \draw[<->] (0,3)--(9,3); \end{fdemo} \subsubsection{颜色、填充、阴影} 颜色参数的用法如下。PGF~可以使用~\verb|xcolor|~宏包\citep{Kern_2007}中定义的所有颜色。 \begin{fdemo}{ \begin{tikzpicture} \draw[red] (0,4)--(9,4); \draw[green] (0,2)--(9,2); \draw[blue] (0,0)--(9,0); \end{tikzpicture} } \draw[red] (0,4)--(9,4); \draw[green] (0,2)--(9,2); \draw[blue] (0,0)--(9,0); \end{fdemo} 封闭路径可以用颜色填充,\verb|\filldraw|~命令可以分别指定边框色和填充色。 \begin{fdemo}{ \begin{tikzpicture} \fill[red] (1,1) circle (1); \filldraw[fill=lightgray,draw=black] (4,1) circle (1); \end{tikzpicture} } \fill[red] (1,1) circle (1); \filldraw[fill=lightgray,draw=black] (4,1) circle (1); \end{fdemo} \verb|\shade|~命令可以产生渐变和光影效果,缺省是从上到下,灰色渐变为白色。我们也可以使用其它方向和颜色的渐变。 \begin{code} \shade (0,0) rectangle (2,2); \shade[left color=red,right color=orange] (3,0) rectangle (5,2); \shade[inner color=red,outer color=orange] (6,0) rectangle (8,2); \shade[ball color=blue] (10,1) circle (1); \end{code} \begin{out} \begin{tikzpicture} \shade (0,0) rectangle (2,2); \shade[left color=red,right color=orange] (3,0) rectangle (5,2); \shade[inner color=red,outer color=orange] (6,0) rectangle (8,2); \shade[ball color=blue] (10,1) circle (1); \end{tikzpicture} \end{out} \subsubsection{图形变换} 对图形对象可以进行平移和旋转操作,注意如果两种操作同时进行,它们是有顺序的。注意预定义的长度单位在这里对平移参数失效。 \begin{code} \draw (0,0) rectangle (2,2); \draw[xshift=30pt] (0,0) rectangle (2,2); \draw[xshift=75pt,rotate=45] (0,0) rectangle (2,2); \end{code} \begin{out} \begin{tikzpicture} \draw (0,0) rectangle (2,2); \draw[xshift=30pt] (0,0) rectangle (2,2); \draw[xshift=75pt,rotate=45] (0,0) rectangle (2,2); \end{tikzpicture} \end{out} \subsection{样式} PGF~比~\MP~和~PSTricks~多了一个有趣的概念:样式(style),它的思路和~HTML~的~CSS~相近。我们可以先定义两种样式, \tikzset{ myline/.style={line width=2pt}, myblueline/.style={myline,blue} } \begin{code} \tikzset{ myline/.style={line width=2pt}, myblueline/.style={myline,blue} } \end{code} 然后就可以在绘图命令中这样使用样式。 \begin{fdemo}{ \begin{tikzpicture} \draw[myline] (0,2)--(9,2); \draw[myblueline] (0,0)--(9,0); \end{tikzpicture} } \draw[myline] (0,2)--(9,2); \draw[myblueline] (0,0)--(9,0); \end{fdemo} 除了用~\verb|\tikzset|~命令定义样式,我们也可以在~\verb|tikzpicture|~环境头部声明样式。前者是全局性的,后者则是局部性的。 \begin{code} \begin{tikzpicture}[ thickline/.style=2pt, bluethickline/.style={thickline,color=blue} ] ... \end{tikzpicture} \end{code} 注意在样式中预定义长度单位有时会失效,所以最好使用绝对单位。 \subsection{流程图} \subsubsection{节点} PGF~中的节点(node)可以是简单的标签,也可以有各种形状的边框,还可以有各种复杂的属性。比如下例中的节点样式:\verb|box|,它的边框是矩形,有圆角;它有最小宽度、高度、文字和边框的距离,边框和填充颜色等属性。 \begin{code} \tikzset{ box/.style={rectangle, rounded corners=6pt, minimum width=50pt, minimum height=20pt, inner sep=6pt, draw=gray,thick, fill=lightgray} } \end{code} 除了上述类别属性,节点还可以有名字、位置等属性。在下例中,我们先画了三个有名字的文本框;然后用箭头把文本框连接起来,注意连接时要引用文本框的名字;接着在箭头上加了标签。 \begin{code} \node[box] (tex) at(0,0) {.tex}; %文本框 \node[box] (dvi) at(10,0) {.dvi}; %文本框 \node[box] (pdf) at(20,0) {.pdf}; %文本框 \draw[->] (tex)--(dvi); %箭头 \draw[->] (dvi)--(pdf); %箭头 \node at (5,1) {latex}; %标签 \node at (15,1) {dvipdfmx}; %标签 \end{code} \begin{out} \begin{tikzpicture} \node[box] (tex) at(0,0) {.tex}; \node[box] (dvi) at(10,0) {.dvi}; \node[box] (pdf) at(20,0) {.pdf}; \draw[->] (tex)--(dvi); \draw[->] (dvi)--(pdf); \node at (5,1) {latex}; \node at (15,1) {dvipdfmx}; \end{tikzpicture} \end{out} 在上例中的节点都使用了绝对位置,PGF~中还可以使用更灵活一点的相对位置。比如在下例中,dvi~节点在~tex~节点右边~50pt~处(我们前面定义的基本长度单位是~10pt),而~pdf~节点又在~dvi~节点右边~50pt~处。 箭头可以换为专门用来连接节点的~\verb|edge|~;标签也改成相对位置,箭头上方~5pt~处。 \begin{code} \node[box] (tex) {.tex}; \node[box,right=5 of tex] (dvi) {.dvi}; \node[box,right=6 of dvi] (pdf) {.pdf}; \path (tex) edge[->] node[above=.5] {latex} (dvi) (dvi) edge[->] node[above=.5] {dvipdfmx} (pdf); \end{code} \begin{out} \begin{tikzpicture} \node[box] (tex) {.tex}; \node[box,right=5 of tex] (dvi) {.dvi}; \node[box,right=6 of dvi] (pdf) {.pdf}; \path (tex) edge[->] node[above=.5] {latex} (dvi) (dvi) edge[->] node[above=.5] {dvipdfmx} (pdf); \end{tikzpicture} \end{out} \subsubsection{树} 下面是一棵简单的树。我们可以用一个参数控制相邻节点的距离,预定义长度单位对此参数也会失效。 \begin{code} \begin{tikzpicture}[sibling distance=80pt] \node[box] {TeX} child {node[box] {Plain\TeX}} child {node[box] {\LaTeX} child {node[box] {MiKTeX}} child {node[box] {TeX Live}} child {node[box] {MacTeX}} }; \end{tikzpicture} \end{code} \begin{out} \begin{tikzpicture}[sibling distance=80pt] \node[box] {TeX} child {node[box] {Plain\TeX}} child {node[box] {\LaTeX} child {node[box] {MiKTeX}} child {node[box] {TeX Live}} child {node[box] {MacTeX}} }; \end{tikzpicture} \end{out} \bibliographystyle{unsrtnat} \bibliography{reading} \newpage