MetaPost: 强大的图形语言

什么是 MetaPost?

MetaPost 是由 Bell Labs 的 John Hobby 设计的一种绘图语言, 它的语法近似于 MetaFont,是一种描述性的绘图语言。

用它可以作出非常漂亮的图形. 特别适合于精确的图形. MetaPost 可以生成高质量的 EPS 文件,方便的插入到 TeX 文件里。MetaPost 图片里可以插入TeX对象,跟TeX很完美的结合。

为什么要用 MetaPost?

你能用鼠标画出所有你需要的图形吗?能?不要那么肯定。看看这些:

  1. 点a和点b相距2cm, 在它们连线的 2/3 处,有一条线段,与ab成60度角,交点处要画一个半径 3pt 的圆圈。

  2. 从线段外一点作它的平行线。想不到吧?这么简单的图,你最后发现无法用一个鼠标的工具完成!

  3. 箭头ab,从圆圈 a 的中心出发,开始时向右,然后进入圆圈b的中心,进入时方向是向下的,箭头在圆圈内的部分不画出来。而圆圈 a 和 b 现在的位置还不能确定,需要根据其它圆圈的位置定位。

  4. 这样的每个弧线只有几个参数有规律的变化的图片:

    不好办吧?点击以上4个图形就可以知道如何用 MetaPost 得到他们,注意这个短短的文件包含了所有这4个图形。再看看这些:

  5. 画了一棵很大的二叉树,后来发现下面部分太大了,需要把上面的结点张角全部长大 20% 才行。
  6. 一个 100x100 的点阵,里面的每个坐标全为质数的点之间都有连线。

    上面两个留作练习。

怎样用 MetaPost?

MetaPost 和 LaTeX 结合使用非常简单。

  1. 首先编辑一个 MetaPost 文件,假设句名 fig.mp。里面有你的绘图语句:
    beginfig(1);
    ........
    .......
    endfig;
    beginfig(2);
    ...........
    .........
    endfig;
    ..........
    ..........
    ..........
    end
    
  2. 然后运行
    mpost fig.mp
    
    就会得到几个文件: fig.1, fig.2, ... 它们都是 eps 文件。你把它们用
    \includegraphics{fig.1}
    
    这样的语句插入 LaTeX 文档就可以了。

怎样在 MetaPost 中使用汉字?

MetaPost 缺省使用的是 plain TeX 来处理括在 btex...etex 之间的标号 但是我们可以想一个办法使用 LaTeX 来处理这些,这样我们就可以方便的使用 CJK.

其实每一个纯 TeX 文件的第一行如果出现以下内容:

%&latex                                        

TeX 就会读入 latex 格式

所以利用这个小把戏就可以这样做:

  1. 在mp文件开头加入:
    verbatimtex
    %&latex
    \documentclass{article}
    \usepackage{CJK}       
    \begin{CJK}{GBK}{song}
    \begin{document}      
    etex            
    
  2. 在最后加入:
    verbatimtex
    \end{CJK}  
    \end{document}
    etex          
    
  3. 标号时把标号都用 btex...etex 括起来。比如:
    label.top(btex 原点 etex, origin);         
    

给个完整的例子,我已经测试通过了:

verbatimtex                                          
%&latex 
\documentclass{article}
\usepackage{CJK}       
\begin{CJK}{GBK}{song}
\begin{document}      
etex            

beginfig(1);
drawarrow origin--(0,100);
drawarrow origin--(100,0);
drawdot origin withpen pencircle scaled 2pt;
label.bot(btex 原点 etex, origin);
label.lft(btex $y$ etex, (0,100));
label.lrt(btex $x$ etex, (100,0));
endfig;

verbatimtex
\end{CJK}
\end{document}
etex

end

结果是这样:

其实

mpost -tex=latex file.mp
或者把环境变量 TEX 设为 latex,也可以让 MetaPost 使用 LaTeX.

怎样才能使 gsview 显示含有 label 的 MetaPost 图形?

原文:bbs.ctex.org。原作者 cxterm。

经我修改后如下:

metapost 是一个很好的绘图工具。但如果在其中使用了 TeX 字体或中文字体的话, 在 gsview 中不能显示,有些不方便。下面介绍一种方法,可让这些 metapost 图形 正常的在 gsview 中显示。

造成 gsview 无法显示的主要原因是 gs 找不到相应的字体。所以,首先要让 gs 能够找到所需的字体。现在提供两种办法:

  1. 办法1. 适用于懒人:只需要设置环境变量
    export GS_FONTPATH=$TEXMF/fonts/type1/bluesky/cm
    

    如果你有另外的字体目录,用:(冒号)分开路径。 不过这样会使gs每次启动时检查目录里所有字体文件。启动速度变慢。 而且似乎对中文字体有点问题。 不过对于经常改变字体的情况确实很方便。

  2. 办法2. 适用于勤快人(启动更快): 可以在你的每个TeX字体目录下编辑一个叫 Fontmap 的文件,里面记录一些字体映射关系。比如,

    文件 $TEXMF/fonts/type1/bluesky/cm/Fontmap 里有如下内容:

    /CMB10 (cmb10.pfb);
    /CMBSY10 (cmbsy10.pfb);
    /CMBX10 (cmbx10.pfb);
    /CMBX12 (cmbx12.pfb);
    /CMBX5 (cmbx5.pfb);
    ...
    
    (注意大小写!)

    文件 $TEXMFLOCAL/fonts/type1/chinese/gbksong/Fontmap 里有如下内容:

    /gbksong01 (gbksong01.pfb);
    /gbksong02 (gbksong02.pfb);
    /gbksong03 (gbksong03.pfb);
    /gbksong04 (gbksong04.pfb);
    /gbksong05 (gbksong05.pfb);
    /gbksong06 (gbksong06.pfb);
    ...
    

    然后把这些路径都放到 GS_LIB 环境变量里:

    export GS_LIB=/usr/share/ghostscript/7.05/lib
     :$TEXMFMAIN/fonts/type1/bluesky/cm:$TEXMFLOCAL/fonts/type1/chinese/gbksong
     :$TEXMFLOCAL/fonts/type1/chinese/gbkstcaiyun
    
    (为了美观,断了行,在你的 ~/.bashrc 里可不能这样!)

    以后在 mp 文件最开头加入 prologues:=1; 搞定。

    这里说的环境变量设置都是 UNIX 的 Bourne Shell 用法。csh 和 ksh 用户以及 Windows 用户可以灵活变通一下。

    如何在MetaPost图形里插入已有的EPS图片

    使用 epsincl 可以往MetaPost里插入已有的EPS图片。epsincl 由两个文件epsincl.mp和epsincl.awk 组成。epsincl.mp 放在 MetaPost 的系统目录里。epsincl.awk 是AWK语言脚本,需要 AWK 解释执行。

    下面是一个例子。use_eps会用EPS文件构造一个picture类型的对象。它可以被作为 label 放在任何地方。

    input epsincl;
    
    tertiarydef p scaledto x = p scaled (x/xpart(urcorner(p)-llcorner(p))) enddef;
    
    beginfig(100);
    
      dist:=5cm;
      z0=origin;
      z1=z0+(dist,0);
    
      picture tex,meta;
      tex=use_eps("tex.eps");
      meta=use_eps("meta.eps");
      
      label(tex scaledto 100, z0);
      label(meta scaledto 100, z1);
    
      label(btex $\sqrt{x^2+y^2}$ etex rotated 60 scaled 1.44, .5[z0,z1]);
      
    endfig;
    
    end

    之后运行 MetaPost,再运行AWK 脚本处理生成的文件。假设这里我们的文件是 texmeta.mp。那么用这样的命令行:

    mpost texmeta.mp
    gawk -f epsincl.awk texmeta.100 >texmeta.eps
    

    之后得到的图片会是这个样子:

    怎样在 MetaPost 里绘制数学函数和数据文件图形?

    使用 mpgraph 宏包

    MetaPost 提供了一个 graph 宏包可以根据一个数据文件绘制 2D plot。

    graph 有非常多样的样式,你并不受它的约束,你只是拥有了更多的处理外部数据的方式,而且有了“自动坐标缩放”。

    你自己看看说明就知道了。你可以用 graph.mp 提供的 gdata 宏来从任意的数据文件读取数据:

    首先你需要

    input graph;
    

    你必须把你画图表的语句都放在

    draw begingraph(图表宽度,图表高度);
    ...
    endgraph;
    

    之间。

    比如你的一个数据文件 countries.d 有这些内容:

    20.910 75.7 US
    1.831 66.7 Alg
    1.694 70.9 Arg
    16.430 77.3 Aus
    0.180 53.0 Ban
    ...
    

    你写一个 MetaPost 文件来绘制这样一幅图:

    把第3个域的那个国家名字,比如 "US", "Alg", ... 标在前两个域指出的坐标的位置。

    你可以这样写:

    beginfig(5);
    draw begingraph(3in,2in);
    gdata("countries.d", s, glabel(s3,s1,s2););
    endgraph;
    endfig;
    
    其中,
    gdata("文件名", 一个变量名,你的操作);
    

    可以对这些数据进行任意的操作。“一个变量名”是你任意给的名字,比如你给一个 s, 那么数据文件的第一列 会被赋值给 s1, 第二列 s2, 第3列 s3, ...

    其实 gdata 功能太强了,大部分时候 gdraw 已经能满足你的需要。

    gdraw("文件名"); 
    

    就可以把你存在一个文件里的像这样的数据:

    00 4.011
    01 3.969
    02 3.806
    ...
    

    画成图表,太简单了!你还可以在 gdraw("文件名") 之后使用任何 MetaPost 绘图选项,比如 withcolor ... withpen ...。你还可以用一个特别的参数 plot,比如:

    gdraw("file") plot btex $\bullet$ etex; 
    

    就可以在每个数据点上画上一个 TeX 的 $\bullet$ 符号。看下面这些就是用同样的数据点画出的不同样式的图:

    mpgraph 缺省会把数据文件里所有的数据点都画出来。如果你想 限制数据点的范围可以用 setrange, 但是 setrange 只是把图片的 外框修改了一下,实际上曲线会超出外框显示。这个问题直接用 graph 宏包提供的接口无法解决,必须进行 hack. 请看这里

    使用其它工具输出 MetaPost

    某些绘图工具可以输出 MetaPost 图形文件。你还可以自己修改这个文件,得到非常好的效果。比如 gnuplot 就可以输出 MetaPost 图形文件。你只要在 gnuplot 里输入:

    set terminal mp
    set output "gnuplot.mp"
    set isosamples 40
    splot [-2.5:2.5] [-2.5:2.5] (x**2+3*y**2)*exp(1-(x**2+y**2))
    

    就可以把输出写到一个叫做 gnuplot.mp 的 MetaPost 文件里。由 MetaPost 处理生成 eps 再插入 LaTeX 文档。下面就是一个例子,里面的标号是我手工修改的。

    其实 gnuplot 可以画出很多好看的数学图形。请参考 这个网页

    MetaPost 里怎样才能显示程序生成的数字呢?

    有人想要用

    u:=.5cm;
    for p=1 upto 100: label(btex $10^p$ etex, (p*u,p*u)); endfor
    
    这样的语句在100个位置标注 101, 102, 103 ... 这样的标号。这是不行的。因为 btex ... etex 之间的部分是原封不动交给 TeX 处理的,这样你得到的是100个10p。如果你要使用很大的数字,想要用科学计数法表示,那么可以用 format 宏包,如果你想使用这些大数字计算,可以用 sarith 宏包来计算。
    1. format 宏包。

      format 宏包就可以处理数字的格式化,你首先 input format, 然后你可以用

      init_numbers(s, m, x, sn, e); 
      

      设定你的 TeX 数字格式,其中:

      s: 表示负数开头用什么符号表示它是负数。一般这个符号是 btex$-$etex
      m: 一个TeX的一位数,随便给一个数字就行,比如 btex$1$etex
      x: 科学计数法里面怎样表示 x10^{...}, 通常这样做: btex${\times}10$etex
      sn: 上标的负号怎么表示? 一般是 btex${}^-$etex
      e: 上标的示例数,用来计算上标占的位置,随便给它一个 btex${}^2$etex 之类的就行。
      

      MetaPost 有一个缺省的设定是:

      init_numbers(btex$-$etex, btex$1$etex, btex${\times}10$etex,
         btex${}^-$etex, btex${}^2$etex);
      

      只有当你对缺省的格式不满意才需要改变 init_numbers 的设定。

      当你用 format(格式串,数字) 时 format 会为你生成漂亮的 TeX 数字。 格式串可以是 %e, %f, %g, %G 之一,可以在字母前加数字表示有效数字,比如 "%10f"。

      你可以试试:

      label.bot(format("%3g","6.022e23"), origin);
      
    2. sarith 宏包

      MetaPost 继承了 METAFONT 的定点运算方式。因为印在纸上的东西尺寸不会很大,所以 MetaPost 只允许输入 32768 以内的数。这不是一个很严重的限制,因为你用 MetaPost 画的只是一些很小的样例图片,你不会把一个 10000x10000 的矩阵印在纸上,对吧?但是如果你的图形里需要计算少量的大数,你可以用 sarith 宏包。

      sarith 宏包可以计算用字符串表示的数字,你可以用很多种方式表示你的数字,非常自由,比如 "6.022e23"。

      数字之间的操作必需用专门的 sarith 提供的运算符 Sadd, Ssub, Smul, Sdiv。Sabs 可以得到一个数字的绝对值。

      Scvnum 可以把一个字符串表示的数转换成 MetaPost 使用的 numeral,如果这不会使 MetaPost 的定点计算溢出的话。如果你的数字里没有 "e",就可以用 scantokens 来得到一个字符串表示的数字的值。因为 scantokens 把后面的字符串当成一段 MetaPost 的输入。

      下面是一个比较 hack 一点的例子,我把 format 内部的宏 dofmt_ 挖出来,这样就可以随便表示任意的上标数了。

      input format;
      input marith;
      
      init_numbers(btex$-$etex, btex$1$etex, btex${\times}10$etex,
         btex${}^-$etex, btex${}^2$etex);
      
      %Fe_plus:="*" infont defaultfont;
      beginfig(1);
      u:=2cm;
      n:=3;
      pickup pencircle scaled 1pt;
      for i=0 upto n: draw (0*u,i*u)--(n*u,i*u); endfor;
      for i=0 upto n: draw (i*u,0*u)--(i*u,n*u); endfor;
      
      string mylabel;
      for i=0 upto n-1:
      for j=0 upto n-1:
      l:=j*n+i+1;
      label(dofmt_.relax("%3f",(l*1000,l)), (i*u+0.5u,j*u+0.5u));
      endfor;
      endfor;
      
      a=Mlog_Str("3213000000");
      label.bot(Mformat("%e",a), (0,0));
      
      endfig;
      end
      

      还有不清楚的就自己看看 sarith 和 format 的 mp 文件。

    怎样从外部文件读入数据?

    很多人想要从外部文件得到数据进行绘图,可是 MetaPost manual 里没有介绍怎么做。因为这个功能是 0.60 版本以后才有的。

    你可以用

    readfrom "文件"
    

    得到一行数据。再次使用 readfrom 就会得到后面的行。如果到达文件末尾,readfrom 会返回一个空字符串 EOF,你可以根据这个来判断数据是否画完了。

    读入的数据都是作为一个字符串,如果你想要得到数字,你必须用 scantokens 把字符串 evaluate 一下。举个例子,比如你的一个文件 points.dat 里有这些内容:

    40,60,70
    ....
    

    你可以编辑一个 MetaPost 文件:

    beginfig(8);
    j:=1;
    draw
    for p=scantokens readfrom "points.dat":
    (j*10,p)--
    hide(j:=j+1)
    endfor (j*10,80);
    
    endfig;
    

    来根据这个文件里每行提供的离散数据画图。

    另外,你可以用

    write <string expression> to <file name>
    

    把字串内容写到一个外部文件。

    MetaPost 能绘制饼状图(piechart)和柱状图(bar chart)吗?

    显然 piechart 对于 MetaPost 来说是小事一桩,但是我没有找到专门用来画这种图的 MetaPost 宏包。因为对于大多数场合, piechart 都是一个不好的选择。心理学研究表明,piechart 不适合表示重要的数据,因为人脑对于 piechart 表示的数值关系不敏感,不能有深刻的印象。

    但是如果你的项目很少,比如 2–5 个,而且数值之间差别很大,而且你不是用在很正式的场合,你可以用一个小程序叫做 ascii_chart 来画 piechart,它用起来极其简单,可以输出很多种格式。ascii_chart 也可以作 bar chart.

    另外 piechart 程序可以作出有“拉出效果的” piechart。ascii_chart 和 piechart 都需要 GNU plotutil 库。你可以到 GNU 的 ftp 服务器下载。

    哪里有更多的 MetaPost 的材料?

    1. John Hobby 写的 MetaPost 说明书

      这当然是 MetaPost 的权威指南。

    2. MetaFun

      这是 ConTeXt 的 MetaFun 说明书 — 一个图文并茂,非常漂亮的全屏幕 PDF 文档。前面讲的都是 MetaPost,有很多实例。特别注意的是,这个文档本身的背景,按钮,……都是 MetaPost 绘制的。

    3. METAFONTbook

      D. E. Knuth 的大作 The METAFONTbook. 由于 MetaPost 实际上用了 MetaFont 的语言,学习 MetaPost 实际上学会了 MetaFont 的一部分。MetaFont 是 Knuth 跟 TeX 配套设计的一种用来制造字体的程序语言。MATAFONTbook 可以在这里下载 PDF 版本, 请[点击]。

    4. 大量实例

      一个法国人提供的大量实例。你也可以看看我这里的“本地版”,我美化过的 :)

    5. 用 MetaPost 做三维动画

      http://www.loria.fr/~roegel/animations.html介绍怎样用MetaPost制作三维动画。

    6. 用MetaPost画Feynman图

      一个用来绘制专业级质量 Feynman 图的 MetaPost 宏包,它已经被 CERN(欧洲原子能研究中心)采用。用它可以用简简单单几行字就画出非常漂亮的 Feynman 图:

      我不懂粒子物理,根本不知道这些图什么意思 //blush …… 如果你有兴趣可以看看这个说明

    7. MetaGraf: 一个Java程序,可以“随手”画出 MetaPost 图形。这个程序可以弥补 MetaPost 的太精确的缺点,你可以用 MetaGraf 画出一些随手图形插到你的精确的文件里 :)

    8. 其它资料请参考John Hobby 的主页

    给几个例子吧

    下面给出一些我用 MetaPost 画出的简单图形。点击图片可以看到对应的 MetaPost 源文件。我开始时用 MetaPost 还不是很熟,有时会用一些“笨”办法来画东西,但是我每次看 MetaPost 说明书都会发现新的东西。我能用不同的方法,更好的方法完成我曾经做过的事情。这恐怕是所有优秀工具的特点。

    1. 我的本科毕业论文里的 Linux 内核 vfs 结构示意图:

    2. 我上次作的有关串扰布线的报告里的图片。

      第一幅电路图的所有元件都是我自己设计的 MetaPost 宏产生的,我可以多次复用这些宏,而且可以随意制造自己需要的元件图。

    3. 我的VLSI布图理论考试答案里的图片

      时延树

      迷宫算法

      steiner 树

      最小生成树

    4. 我的计算几何作业里的图片

    5. Scheme 48 的模块名字解析示意图

    6. DrScheme 的标志

    7. 我用程序生成的一个图片

      显示了 rectilinear Delaunay triangulation 对应的 minimum spanning tree 和 triple center


    返回