gnuplot多曲线绘图进阶:线型配色、坐标轴图例与批量出图
真正把 gnuplot 用顺手,多数人不是在画第一条曲线时卡住,而是在画第二条、第三条的时候。第一篇里那条孤零零的 sin(x) 跑通,只能证明环境、终端、字体这几块地基是好的;等你手上拿到一份三列、两千行的实测记录,要把三组数据叠在一张图上,塞进一页 A4 版面的报告,麻烦才会成片冒出来:第五条曲线跟第一条撞色、图例正好压在峰值上、x 轴刻度标签挤成一条黑带、导出 PNG 后字号小得像蚂蚁、中文标题变成一排方框、CSV 文件读进去整张图是空的。这些正是 gnuplot 使用教程里绕不开的第二关。
这篇接着往下写,全部围绕"图能看、能用、敢交出去"这一步展开:plot 命令的扩展写法、线型配色与标记点、坐标轴刻度与图例排布、数据文件读取的细节陷阱、以及批量出图脚本。gnuplot 的默认出图确实谈不上好看,但它的可控程度极高,几乎每一个像素都能用一条 set 命令改掉,前提是你得知道那条命令叫什么、放在哪一行。下面这些内容基本都是我这些年反复改图改出来的东西,能直接抄,也标了哪些地方踩过坑。
1. plot 命令的第二次进化:从一条线到一张能读懂数据的图
第一篇里 plot 只带一个表达式,这其实是最省事的形态。一旦进入多曲线阶段,plot 就从一个"画图命令"变成了一个"描述一张图的完整句子":谁画、用什么样式画、叫什么名字、挂在哪根轴上,全部要在这一行里交代清楚。这一节先把 plot 的扩展语法拆开,再讲怎么把重复的设置抽出去,避免每条曲线都重写一遍。
1.1 多曲线同框的三种写法与它们各自的适用边界
最直接的做法是一条 plot 后面用逗号把多个元素串起来:
plot "run_a.dat" using 1:2 with lines title "方案A", \ "run_b.dat" using 1:2 with lines title "方案B", \ "run_c.dat" using 1:2 with lines title "方案C"反斜杠是续行符,行尾不能有空格,这一点比想象中更容易翻车——编辑器里一个看不见的尾随空格就能让下一行被当成独立命令,然后报一堆莫名其妙的错。逗号分隔写法的一个硬性约束是:每一个元素后面的with、title、axes只对当前这一个元素生效,不会向后继承。也就是说,如果你写了六条曲线,就得写六遍样式,除非用下面说的默认样式。
第二种是set style data lines这类全局默认:
set style data linespoints set style function lines plot "a.dat", "b.dat", "c.dat"它把with省掉了,代价是三条曲线样式完全一致,只能靠颜色区分。这种写法适合"同质对比",比如三条同类型的对照组,读者不需要靠样式区分它们,靠图例就够了。我一般会在曲线数不超过四条、且量级相近的时候用它,省事且不易出错。
第三种是replot累加,早期版本里很常见,现在依然能用:
plot "a.dat" with lines title "方案A" replot "b.dat" with lines title "方案B"它的好处是可以在两条曲线之间插一条set命令,比如画完第一组后把 y 轴改成对数再叠第二组。但它有个副作用:每执行一次replot都会重新解析前面所有的 plot 元素,数据文件会被重读一遍,数据大的时候明显变慢。所以真要做"边画边改设置",我更推荐先把设置写全,再用一条 plot 收尾。
| 写法 | 适合曲线数 | 优点 | 主要缺点 |
|---|---|---|---|
| 逗号串接 | 2 到 6 条 | 每条独立控制样式,语义清晰 | 长、需要续行符,易被空格坑 |
| set style data 默认 | 2 到 4 条 | 极简,改动一处全局生效 | 样式无法区分,只能靠颜色 |
| replot 累加 | 2 到 3 条 | 可在中间插入设置 | 重复读文件,脚本一长就乱 |
1.2 用内联数据块把参考线和标注点写进脚本
gnuplot 5.0 之后支持内联数据块,语法是$名称 << EOD ... EOD。这个特性在做图时非常实用,因为它让你能把"只出现一次的几条参考点"直接写进脚本,不用为了三行数据专门建一个文件:
$ref << EOD 0.0 1.00 2.5 1.00 2.5 3.20 5.0 3.20 EOD plot "measure.dat" using 1:2 with lines lw 2 title "实测", \ $ref using 1:2 with lines dt 2 lw 1.5 lc rgb "#888888" title "工艺上限"这里画出来的是典型的阶梯状参考线,用dt 2(虚线)和灰色,视觉上自然退到背景层,不会跟主数据抢注意力。有一个必须注意的细节:结束标记EOD必须顶格写在行首,前面不能有任何空格或制表符,否则 gnuplot 会认不出来,然后一直往下读到文件结尾。我第一次用的时候在 EOD 前面缩进了四个空格,脚本直接卡在那里等输入,排查了十分钟。
内联数据块另一个常见用途是给图加手工标注的散点,比如"标出三个工况点",这些点往往连不成线,用with points单独画一层就行。数据块的名字可以自定义,$ref、$peaks、$anno都行,只要前后一致。
1.3 把反复出现的设置抽成可复用的脚本片段
做到第三张图的时候,你就会发现自己在一遍遍抄同样的十几行set。gnuplot 提供了load命令来加载脚本片段,把公共设置单独存成一个.gp文件,主脚本里一行load "style.gp"就够:
# style.gp —— 公共样式,所有图共用 set border 3 set tics nomirror set grid xtics ytics lc rgb "#dddddd" lw 0.6 set key top left box spacing 1.15 samplen 3 set xlabel font ",12" set ylabel font ",12" set tics font ",10"注意set border 3配set tics nomirror这个组合:前者只保留左边和下边的边框(3 = 1+2,二进制位掩码),后者让刻度线只朝内不朝外。这两条一起用,出图立刻从"gnuplot 默认风格"变成"能进报告的风格",成本只有两行。
片段里我故意没有写set terminal和set output,因为这两项跟输出格式强相关,不同用途的图要的不一样,混进公共样式里反而会互相干扰。这一点后面第 5 节还会展开。另外load是逐行执行的,如果片段里有reset,它会把之前所有的设置清空,所以公共片段里千万别放reset——这个坑我在多子图排版时踩过,当时整张图只剩一个轴框,数据全没了。
2. 线型、颜色与标记点:让五条曲线在一张黑白打印稿里也能分辨
颜色在屏幕上好用,一旦落到打印稿、投影仪、或者色觉辨识度不高的读者眼里,就未必了。gnuplot 的线型系统(linetype)同时管理颜色、线宽、虚线模式和标记点,理解它的分配逻辑,才能保证"第几条曲线长什么样"是可控的,而不是每次画图都靠运气。
2.1 默认 colorsequence 的分配规则与撞色问题
gnuplot 5.2 之后默认使用一套叫podo的配色序列,共 8 种颜色,分别绑定在 linetype 1 到 8 上。超过八条曲线,序列会从头循环,于是第九条和第一条同色——在多曲线对比图里这是灾难,因为读者会以为它们是同一组数据。诊断方法很简单,在脚本末尾加一行test,它会画出一张包含所有线型、标记点、虚线样式的参考图,一眼就能看出哪几种被重复用了。
test只解决"看见",真正要解决问题得显式定义。我习惯在脚本开头直接把 1 到 8 号线型重定义一遍,颜色全部从一套固定的低饱和度色板里取:
set linetype 1 lc rgb "#4C78A8" lw 2 pt 7 ps 1.1 set linetype 2 lc rgb "#F58518" lw 2 pt 5 ps 1.1 set linetype 3 lc rgb "#54A24B" lw 2 pt 9 ps 1.1 set linetype 4 lc rgb "#B279A2" lw 2 pt 11 ps 1.1 set linetype 5 lc rgb "#E45756" lw 2 pt 13 ps 1.1 set linetype 6 lc rgb "#72B7B2" lw 2 pt 4 ps 1.1 set linetype 7 lc rgb "#EECA3B" lw 2 pt 6 ps 1.1 set linetype 8 lc rgb "#9D755D" lw 2 pt 8 ps 1.1用十六进制 rgb 而不是颜色名(red、blue)的好处是可复现:不同机器、不同版本上颜色值绝对一致,改图时也不会因为某个颜色名在别的终端里不认而退回默认值。还有一个开关值得知道:set colorsequence classic,它会让配色退回老版本的八色方案,红绿蓝青紫黄黑,饱和度很高。除非你要和旧图保持一致,否则不建议用,因为红绿在打印和色弱读者面前的区分度很差。
真正决定"黑白也分得清"的其实是虚线模式。如果这张图有可能被黑白打印,我会在脚本里补一条注释掉的备选行,随时切换:
# set for-monochrome: 关闭颜色差异,全靠 dashtype 区分 # set linetype 1 lc rgb "#000000" dt 1 # set linetype 2 lc rgb "#000000" dt 2 # set linetype 3 lc rgb "#000000" dt 32.2 dashtype 与 pointtype 的搭配清单
gnuplot 内置的 dashtype 编号有限,1 是实线,2 到 5 依次是长划、点划、短划等,超过 5 就基本看不出差别了。需要更多区分度就自定义:
set dashtype 11 (8,3) set dashtype 12 (2,2) set dashtype 13 (8,3,2,3)括号里的数字是"实线段长度、空白段长度"交替排列,单位是字符宽度的倍数,可以给四个甚至六个数字做出双划线效果。这套自定义 dashtype 在 pdfcairo 和 pngcairo 终端里支持得很好,但在dumb(纯文本)终端里完全无效——那个终端只有 ASCII 字符,谈不上虚线,这一点后面讲出图时还会提。
标记点(pointtype)是散点图和多曲线折线图的另一个区分维度。常用的几个我基本固定这么用:
| pt 编号 | 形状 | 适用场景 |
|---|---|---|
| 4 | 空心方块 | 离散采样点,空心不遮挡线条 |
| 5 | 实心方块 | 强调重点组 |
| 6 | 空心圆 | 与 4 号搭配做对照组 |
| 7 | 实心圆 | 默认折线图最稳妥的选择 |
| 9 | 实心上三角 | 需要第三个区分维度时 |
| 11 | 实心下三角 | 与 9 号成对使用 |
| 13 | 空心菱形 | 数据点稀疏时辨识度高 |
ps(pointsize)的取值不能凭感觉。经验值是:输出宽度在 1200 像素左右时ps 1.0到1.2比较合适;如果输出是 8 英寸宽的 PDF,ps 0.8就够,再大点会盖住相邻点。线宽lw同理,像素级 PNG 用lw 2,矢量 PDF 用lw 1.5甚至1.2,因为 PDF 里的"1 点"是物理尺寸,跟屏幕像素不是一回事。
2.3 用变量驱动颜色,把第三列数据编码进图形
数据表里第三列往往是个类别标签或者正负状态,这种信息用颜色编码最直观。gnuplot 支持lc rgb variable,让每一点的颜色由数据列算出来:
plot "samples.dat" using 1:2:(($3 > 0) ? 0xE45756 : 0x4C78A8) \ with points pt 7 ps 1.2 lc rgb variable notitle括号里是三元表达式,0xE45756这种写法是十六进制整数,gnuplot 会把它当成 RGB 值。注意这里必须是整数,写成字符串会报错。notitle是因为这种按点着色的图层没法给出一个有意义的图例条目,与其让图例里出现一行莫名其妙的色块,不如干脆去掉,在正文里用一句话说明颜色含义。
如果要连续色阶而不是二分类,用调色板更合适:
set palette defined (0 "#4C78A8", 0.5 "#F2F2F2", 1 "#E45756") plot "grid.dat" using 1:2:3 with points pt 7 ps 1.4 lc palette set colorbox verticalset palette defined里给的是归一化位置和颜色,中间自动插值。set colorbox会画出右侧色标条,但默认位置往往压住数据,需要配合set colorbox user origin ... size ...手动挪开。这类图我在实际项目里的教训是:色标条的刻度一定要和正文里的数值区间对上,否则读者会误读,而 gnuplot 默认只给两端标数值,中间是空的。
3. 坐标轴、刻度与图例:那些不调就一直别扭的默认值
gnuplot 的默认轴设置是为了"能出图",不是为了"能交付"。刻度间隔是自动算的,往往会给出 2.5、7.5 这种不整的数;图例默认放在右上角,那里恰好是很多数据曲线的峰值区;标签字号对 A4 版面来说偏小。这一节专门处理这些"不调就一直别扭"的地方,每一条都对应一个具体的返工理由。
3.1 刻度密度、数值格式与标签旋转
刻度数量失控是最常见的问题。x 轴范围 0 到 100 的时候,gnuplot 可能给你 20 个刻度标签,全部挤在一起。手动指定间隔是最省事的办法:
set xtics 10 set ytics 0.5 set mxtics 2 set mytics 2set mxtics 2是次刻度,在每个主刻度之间插一个短线但不带标签,视觉上能让人更准确地读数值,又不增加文字量。这个组合我用得最多,几乎每张图都带。
数值格式靠set format控制。%g会自动选择科学计数法或普通小数,%.1f固定一位小数,%.3g保留三位有效数字。做实验数据图时我倾向于%.3g,因为它对 0.0012 和 1200 这类跨量级数据都能给出合理表示。对数轴要用另一套格式:
set logscale y set format y "10^{%L}"%L在 logscale 下代表指数值,10^{%L}配合enhanced终端会渲染成真正的上标,比1e-3这种写法好看太多。注意10^{...}这种数学标记只有在enhanced模式打开时才生效,如果终端没开 enhanced,你会在图上看到原样的花括号。
标签旋转是中文用户的刚需。中文标签比英文宽,五六个标签就撑满一行:
set xtics rotate by -30 right font ",10"rotate by -30是逆时针转 30 度(负角度往下倾斜),right让旋转后的标签右端对齐刻度点。这两个参数必须一起用,只写 rotate 的话标签会以中心对齐,导致错位。如果标签是中文,还得单独指定一个含中文字形的字体,这一点在第 5 节讲终端时会详细说。
时间轴是另一套体系,需要三件事配合:
set xdata time set timefmt "%Y-%m-%d %H:%M" set format x "%m-%d\n%H:%M"timefmt是读入格式,format x是显示格式,两者可以不一样——原始数据带年份,但图上只显示月日和时刻,这样标签能短一半。\n在标签里是真正的换行,能把一条长标签折成两行,是在不旋转标签的前提下解决拥挤的另一个办法。
3.2 双 y 轴、对数轴与"伪断轴"
双 y 轴在论文图里很常见,但也是误读高发区。gnuplot 的做法是先声明第二根轴的刻度和标签,再在 plot 里用axes指定某条曲线挂到哪根轴上:
set ylabel "温度 (摄氏度)" set y2label "压力 (千帕)" set y2tics 20 set ytics nomirror set y2tics nomirror plot "t.dat" using 1:2 axes x1y1 with lines lw 2 title "温度", \ "p.dat" using 1:2 axes x1y2 with lines lw 2 dt 2 title "压力"set y2tics nomirror这一条很容易漏,漏了之后右边框上会出现两套刻度线,一套朝内一套朝外,看起来像渲染错误。另一个必须提醒自己的原则:双轴的量级差最好控制在一个数量级以内。如果一条曲线的值在 0.1 到 1 之间,另一条在 1000 到 5000 之间,用双轴强行放在一起,读者几乎必然会把两条线的交叉点当成"两者相等"的时刻,而实际上毫无关系。这种情况我宁可拆成两张上下排列的子图。
对数轴除了格式串,还有几个细节。set logscale y之后,set yrange的下界不能写 0,写 0 会报错,最小值给到1e-6这种量级就行。如果数据里有 0 或负值,set datafile missing处理掉之前,图上会出现断线或者直接画不出来。
gnuplot 没有原生的断轴(broken axis)功能,网上流传的做法是用multiplot拼两块,上块画高值区间、下块画低值区间,中间用set border去掉相邻的边框,再用set size和set origin手动对齐。这个方案能用,但维护成本高,改一次数据范围就要重新调一遍比例。我的实际选择是:能不断就不去断,改用量级变换(比如对数值取 log 后作为新列)反而更省事,也更好解释。
3.3 key 的排布、外面放法与自动取列头
图例(gnuplot 里叫 key)的默认位置在右上角,而右上角往往是数据的高值区。几条命令就能把它挪开:
set key top left box spacing 1.1 samplen 2.5 width -4box给图例加一个细边框,在数据线密集的图里能明显提升可读性;spacing控制行距;samplen是样例线段的长度,默认偏长,中文标题短的时候显得空;width -4是负值,表示允许自动加宽以容纳长标题,正数则是强制固定宽度。这个参数的符号很容易记混,负值才是"自动"。
如果图例条目太多,比如八条曲线,放哪儿都挡数据,就用外置:
set key outside right center set rmargin at screen 0.72outside right会把图例整体挪到绘图区右侧,但绘图区宽度不会自动缩,所以必须配合set rmargin给它腾地方。at screen 0.72是按画布比例定位,0.72 表示绘图区右边界在画布从左往右 72% 的位置,右边 28% 留给图例。这个数值需要试两次,标题长的时候给到 0.68 甚至 0.65。我一般先在交互终端里调好这个数,再写进脚本,比盲猜快得多。
标题自动取自数据文件列头也是个省事的做法:
set key autotitle columnhead plot "a.dat" using 1:2 with lines, "b.dat" using 1:3 with lines它会跳过每个数据文件的第一行,把列名拿来当图例文字。用这个的前提是你的文件确实有列头,而且列名不长——列名是"实验组A-高温-第三次重复"这种,图例宽度会直接吃满半张图。这种情况下老老实实手写title "高温A"更靠谱。
4. 数据文件读取的暗礁:using、分隔符与缺失值
gnuplot 最让人抓狂的失败模式是"静默出错":不报错、不警告,图就是空的,或者画出来是一条毫无意义的斜线。九成以上的原因都出在数据解析这一层。这一节把using、分隔符、注释符、缺失值这几个开关的联动关系理一遍,顺便给出两个查数据的工具。
4.1 using 的列表达式与列号错位的排查
using 1:2是最基本的形式,意思是"第 1 列当 x,第 2 列当 y"。但真实数据很少这么规整,常见的变形有这几种:
plot "d.dat" using 1:($2*1000) # 单位换算,毫秒转微秒 plot "d.dat" using ($1/3600):2 # x 轴从秒换算成小时 plot "d.dat" using 0:2 # 第 0 列是行号,当索引 plot "d.dat" using 2:xticlabels(1) # 用第 1 列文本当刻度标签括号里的表达式可以调用 gnuplot 的内置函数,log、sqrt、abs、column都在里面。using 2:xticlabels(1)这个写法在画分类数据时特别有用:第一列是"周一""周二"这种文字,直接拿来当 x 轴标签,不用另外set xtics手工指定。
列号错位是最隐蔽的坑。数据文件有三列(时间、温度、压力),脚本里写了using 1:3想画压力,但实际文件因为某次导出多了个序号列,压力变成了第 4 列,using 1:3画出来的是温度——图能出来,坐标轴数字也合理,就是内容错了,肉眼极难发现。我的对策是画图前先跑一次plot "d.dat" using 1:2 title "col2",把第 2 列单独画出来,确认形状符合预期再改成正式脚本。另一个办法是在数据文件里给列头起个能一眼认出的名字,用columnhead系列函数按名字取列,虽然书写麻烦,但不会错位。
4.2 分隔符、注释符、缺失值这三个开关必须一起调
CSV 文件的第一件事是告诉 gnuplot 分隔符变了:
set datafile separator ","这个设置是全局的,一旦改了,脚本里所有plot都会按逗号切分。如果脚本要同时读一个 CSV 和一个空格分隔的文件,就必须在读第二个之前改回来,或者干脆用using里的csv修饰(5.2 之后支持)。这一点在混用数据源的项目里很容易忘,我见过脚本里改了一次分隔符,后面三张图全空的例子——图确实画了,只是每行被当成了一个字段,y 值取不到。
注释符默认是#,CSV 里有时用%或者其他符号做表头标记:
set datafile commentschars "#%"改成"#%"的意思是"这两个字符开头的行都当注释跳过"。注意这个设置和分隔符是独立的两件事,改了分隔符不会自动调整注释符,得自己盯着。
缺失值处理是这张表里最容易引发误读的一环。gnuplot 遇到解析不了的字段会直接中断绘图并报错,而很多实测数据里缺失值是NaN、NA、-或者干脆是空:
set datafile missing "NaN" set datafile missing "NA"这两行可以连续写,会把两种写法都识别成缺失。识别之后,默认行为是"跳过这一点并断开折线",也就是图上会出现一个缺口。如果这个缺口刚好在数据中段,视觉上就是一条断线;如果不希望断开,可以加set datafile missing后配合with linespoints并接受断线,或者用smooth系列做插值填补(第 4.3 节会讲)。我的偏好是保留断线——图上有个缺口,读者就知道那里没有数据,这比插值出来的平滑曲线诚实得多。
还有一个常被忽略的细节:CSV 里带引号的字段。gnuplot 不解析引号,"1,234"这种带千分位逗号的值会被拆成两段,整行列数就乱了。这种情况只能先做预处理,用一行脚本把引号去掉或者把逗号换成别的分隔符,不要在 gnuplot 里硬扛。
4.3 用 stats 体检数据、用 set table 做重采样
动手画之前先给数据做个体检,能省掉大量试错。stats命令会读一遍数据并算出一堆统计量:
stats "d.dat" using 2 nooutput set yrange [STATS_min*0.95 : STATS_max*1.05] print "min=", STATS_min, " max=", STATS_max, " mean=", STATS_meannooutput是让结果只进变量不打印,避免交互终端被刷屏。可用的变量包括STATS_min、STATS_max、STATS_mean、STATS_median、STATS_stddev,还有STATS_index_min给出最小值所在的行号。用这些变量自动设yrange并留 5% 余量,比手写set yrange [0:100]稳妥得多——数据一换,图不会因为越界被裁掉。
set table则是反向操作:让 gnuplot 把计算结果输出成文本文件,而不是画出来:
set table "smoothed.dat" plot "raw.dat" using 1:2 smooth csplines unset table这段能对原始数据做三次样条插值,然后把插值后的点写进文件。用途是把一百个稀疏采样点变成五百个平滑点,再拿去做别的处理。smooth的其他选项里,bezier更平滑但可能过冲,frequency会改变 x 轴稀疏度,unique会去掉重复的 x 值。这里有个必须注意的顺序问题:set table会临时接管输出,在这段代码之后如果不unset table,后面所有的plot都不会出图,只会往文件里写文本——这个坑我踩过一次,排查了半天才发现问题出在前面那三行。
5. 出图与批处理:terminal、多子图与循环生成
命令行里plot一下就能看到图,那只是预览。真正交付的时候,图要变成 PNG 塞进文档、变成 PDF 交给排版、变成 SVG 放进网页。这一层的关键是 terminal 的概念——它是 gnuplot 的"输出后端",换一个 terminal,同一个脚本就能产出完全不同格式的图,但尺寸、字体、线宽的合理取值全都会跟着变。
5.1 terminal 选型与尺寸、字体、内边距的配合
常用的几个 terminal 各有适用面:
| terminal | 输出 | 适用场景 | 需要注意 |
|---|---|---|---|
| pngcairo | PNG | 网页、文档插图 | 需带 size 和 font,否则分辨率偏低 |
| pdfcairo | 论文、印刷 | 尺寸单位是英寸,lw 要调小 | |
| svg | SVG | 网页矢量图、后续编辑 | 字体可能被转成路径,文字不可选 |
| epscairo | EPS | 传统排版流程 | 中文支持依赖字体嵌入 |
| dumb | ASCII 文本 | 无图形环境下的快速预览 | 没有颜色、没有虚线 |
| qt / wxt | 屏幕窗口 | 交互调试 | 不能用于批处理输出 |
一张常见的配置长这样:
set terminal pngcairo size 1600,1000 enhanced font "Noto Sans CJK SC,14" lw 2 set output "fig-01.png" plot "d.dat" using 1:2 with lines lw 2 title "实测" unset output三条顺序不能乱:先设 terminal,再设 output,画完之后unset output。最后这一步极其重要——它负责把文件句柄关闭、缓冲区刷盘。不写unset output的话,PNG 文件可能只写了一部分,用图片查看器打开会显示"文件损坏"或者下半截是灰的。这个问题在单张图的时候不一定暴露,一旦用循环连续出十张图,中间几张必定残缺。我现在的习惯是把unset output当成一个固定的收尾动作,跟右括号一样不能少。
尺寸的换算值得单独说。A4 纸正文单栏宽度大约 8.3 厘米,等于 3.27 英寸;双栏满宽大约 17 厘米,等于 6.7 英寸。如果最终排版是单栏,PDF 图的尺寸就该设size 3.27,2.2,这时候脚本里的字号必须相应放大到 8 到 9 点,否则缩到版面上根本看不清。反过来,如果先按 1600 像素宽出图再缩小到 8.3 厘米,等效分辨率足够,但字号会同步缩小,这是很多人"屏幕上看着挺好,印出来像蚂蚁"的根本原因。矢量格式(PDF、SVG)没有这个问题,因为缩放不损失信息,所以正式交付优先用 PDF。
中文乱码的处理只有一条路:指定一个真正含中文字形的字体。font "Noto Sans CJK SC,14"在 Linux 上通常可用,Windows 上换成系统自带的中文字体名。判断字体有没有生效,最快的办法是画一个中文标题看一眼,成了方块就说明字体没找到——gnuplot 不会为字体缺失报错,这是它一贯的静默风格。
5.2 multiplot 排版与共享轴的对齐
多子图用set multiplot layout:
set terminal pdfcairo size 7,5 font "Noto Sans CJK SC,11" set output "panel.pdf" set multiplot layout 2,2 title "四组工况对比" font ",13" set title "工况一" plot "a1.dat" using 1:2 with lines lw 1.5 notitle set title "工况二" plot "a2.dat" using 1:2 with lines lw 1.5 notitle # ... 另两张同理 unset multiplot unset outputlayout 2,2会自动切分画布,每个plot填一格。要在四个子图之间做对比,最关键的一步是提前把xrange和yrange固定住:
set xrange [0:60] set yrange [0:1.2]不固定的话,gnuplot 会为每个子图单独算范围,四张图的轴刻度各不相同,读者根本没法横向比较,整张拼图就失去了意义。这一条是我在做对比图时最强调的:宁可留白,也不要让子图各自的轴自动缩放。
另一个坑是子图里的公共设置。multiplot期间每次plot都会沿用当前的全局设置,所以set title这类会互相覆盖的东西必须在每个子图前重新设一遍。反过来,像set border、set grid这种希望全图统一的,就在set multiplot之前设一次。如果中间不小心执行了reset,所有设置会被清空回到默认值,包括 terminal 的设置——这时候后续的输出会退回屏幕终端,文件里什么都没有。所以multiplot段落里我从不写reset。
5.3 do for 循环与参数化出图脚本
一批二十个样本、每个画一张图,手工复制粘贴脚本是自找麻烦。gnuplot 的do for循环可以直接生成文件名:
set terminal pngcairo size 1200,800 font "Noto Sans CJK SC,13" do for [i=1:20] { fname = sprintf("sample_%02d.dat", i) oname = sprintf("fig_%02d.png", i) set output oname set title sprintf("样本 %02d", i) plot fname using 1:2 with lines lw 2 title "测量值" unset output }sprintf的%02d会把 1 补成01,这样文件名排序才正确,不会出现sample_10排在sample_2前面。循环体里set output和unset output必须成对出现,每一轮都要写一遍——只在循环外面写一次是不行的,因为set output打开的是一个新文件,第二次plot会往同一个文件里追加一页,最后你拿到一个只有第一张图的文件,或者一个包含二十页的奇怪 PNG。
如果文件名的规律不是数字而是字符串,可以用列表形式:
do for [name in "alpha beta gamma"] { set output name . ".png" plot name . ".dat" using 1:2 with lines title name unset output }脚本参数化还有一条更彻底的路子:把脚本写成接受命令行参数的形式,用gnuplot -c script.gp 参数1 参数2调用,在脚本里用ARG1、ARG2取值。这种方式适合把出图逻辑嵌进更大的流程里,比如一批数据跑完之后自动出图。参数传进来是字符串,需要参与数值计算的时候记得用int()或real()转一下,直接拿字符串做算术会报类型错误。
调试这类批处理脚本,我的做法是先用set terminal dumb size 80,24跑一遍完整流程。dumb 终端把图渲染成 ASCII 字符画,输出直接打在终端上,速度快、不产生文件,能立刻看出范围对不对、有几条线、标题有没有生效。等逻辑确认无误,再换成pngcairo出正式图。这个"先用字符画验流程、再出正式图"的两段式,替我省下的时间大概比任何一个单项技巧都多。
最后分享一个实际操作里的小体会:gnuplot 脚本最忌讳边写边改、一路加命令。我现在的习惯是先在一个干净的交互会话里把图调到满意,然后用save "fig.gp"把当前所有设置导出成脚本,再手工删掉那些调试期留下的set和临时plot。这样得到的脚本短、干净、可复用,也不会出现"某一行不知道为什么要写但删了就出错"的情况——那种行往往就是当初随手加的某条范围设置,删掉之后自动缩放接管,图的形状就变了。