之前,在「会画画的乌龟」一文中已对 Gnuplot 作了简单介绍。本文讲述如何使用 Gnuplot 将一组数据以散点图、折线图以及直方图的形式可视化。
数据的准备
下面这组数据是我的一篇论文中的实验数据……为了得到这组数据,折腾了一个多月。第一行文本是注释。第 1 列是 7 份数据的编号。第 2 列是数据量(点数)。第 3~5 列分别是三种数据处理算法所耗时间(秒)。
#ID N CR-tree RR*-tree MOO R-tree
a 408707 132.169812 61.785783 33.539202
b 1017746 301.820305 149.091773 85.920577
c 1320154 369.331256 178.966130 99.004964
d 1565930 497.775453 226.263013 133.925556
e 1904034 598.490553 326.580407 179.858509
f 2798395 908.095829 531.302097 297.512237
g 3209425 1041.512695 560.594926 327.782766
散点图
将上一节的数据保存到 time-consuming.dat 文件中,并在同一目录创建 time-consuming.gnu 文件,内容如下:
set xlabel "样点数量(百万) "
set ylabel "建树时间(秒)"
file = "time-consuming.dat"
plot file using 2:3 title "CR-tree", \
file using 2:4 title "RR*-tree", \
file using 2:5 title "MOO R-tree"
然后,打开终端,进入 time-consuming.gnu 文件所在目录,执行以下命令,进入 gnuplot 命令行交互环境,并载入 time-consuming.gnu 脚本文件:
$ gnuplot
G N U P L O T
Version 5.0 patchlevel 4 (Gentoo revision r0) last modified 2016-07-21
Copyright (C) 1986-1993, 1998, 2004, 2007-2016
Thomas Williams, Colin Kelley and many others
gnuplot home: http://www.gnuplot.info
faq, bugs, etc: type "help FAQ"
immediate help: type "help" (plot window: hit 'h')
Terminal type set to 'x11'
gnuplot> load "time-consuming.gnu"
然后会跳出一个窗口显示数据绘制结果——基于 time-consuming.dat 中的数据绘制的散点图。
你会发现,上面的 gnuplot 脚本中对横轴与纵轴设置的文字标注:
set xlabel "样点数量(百万) "
set ylabel "建树时间(秒)"
在绘制结果中变成了乱码。不要纠结此事,这是古老的 X11 窗口系统对中文支持不够友好所致。
绘制散点图的语句为:
plot file using 2:3 title "CR-tree"
其意为:将数据文件中的第 2 列与第 3 列数据分别作为横轴数据与纵轴数据进行绘图,绘制结果的图例标注为 CR-tree
。由于我需要在同一幅图中绘制三种 R 树变体的构建时间的散点图,所以还需要将数据文件中的第 4 列与第 5 列数据与第 2 列数据组合,继续绘制两组散点。gnuplot 支持在同一条 plot
命令中绘制多组数据,但是每组数据的绘制语句之间要以逗号间隔,即:
plot file using 2:3 title "CR-tree", file using 2:4 title "RR*-tree", file using 2:5 title "MOO R-tree"
为了清晰起见,可使用续行符进行断行:
plot file using 2:3 title "CR-tree", \
file using 2:4 title "RR*-tree", \
file using 2:5 title "MOO R-tree"
注意,上述代码中的file
可以是同一份数据文件名,也可以是不同的数据文件名。
图形终端
Gnuplot 支持多种图形终端。在 Linux 桌面环境中,Gnuplot 默认使用的图形终端是 X11 的窗口系统。由于 X11 的窗口系统起源于 20 世纪 80 年代,对不同地区所用的文字与字体的支持极差。虽然有办法让 X11 窗口系统显示中文字符,但过程有些曲折,而且现在也没必要再碰触这些已经过时的知识了。再者说,X11 图形终端的数据绘制结果也不适合在论文中使用。
若仅仅是希望绘图结果能够显示中文字符,可使用 png 终端,即让 Gnuplot 将图形绘制结果输出为 png 图片。例如,将 time-consuming.gnu 修改为:
set terminal png
set output "time-consuming.png"
set xlabel "样点数量(百万) "
set ylabel "建树时间(秒)"
file = "time-consuming.dat"
plot file using 2:3 title "CR-tree", \
file using 2:4 title "RR*-tree", \
file using 2:5 title "MOO R-tree"
在终端中执行:
$ gnuplot -c time-consuming.gnu
然后在同一目录下便可获得 time-consuming.png 图片文件:
现在横轴与纵轴的文本标注不再是乱码了,都成了方块。这是因为 png 终端默认使用的字体未包含中文字符所致。可在 Gnuplot 脚本中设置特定的中文字体及字号。例如,将 png 终端字体设置为 9 号微软雅黑字体:
set terminal png font "Microsoft YaHei, 9"
所得结果为:
可能你的系统中没有微软雅黑字体,可以使用以下 fc-list
命令从系统中搜索其他中文字体来用:
$ fc-list :lang=zh
图例
现在,虽然已将 time-consuming.dat 中的数据以散点图的形式呈现了出来,但是散点图的图例的位置并不理想。图例中的散点符号与数据图的散点符号太近了。数据图的左侧位置有大片的空白,若将图例说明移到左侧,会更好一些。
使用 set key
命令可以对图例进行调整。例如,将图例放到左侧:
set terminal png font "Microsoft YaHei, 9"
set output "time-consuming.png"
set key left
set xlabel "样点数量"
set ylabel "建树时间(秒)"
file = "time-consuming.dat"
plot file using 2:3 title "CR-tree", \
file using 2:4 title "RR*-tree", \
file using 2:5 title "MOO R-tree"
结果得到:
图例放到左侧之后,将图例文本与散点符号对调一下位置会更符合从左向右的阅读逻辑:
set key left reverse
结果为:
对调了图例文本与散点符号的位置之后,让图例文本呈左对齐会更美观一些:
set key left reverse Left
结果为:
再让图例的各行间距更大一些:
set key left reverse Left spacing 1.2
结果为:
折线图
在 plot
命令中使用 「with linespoints
」选项,便可将散点图变成折线图。例如:
set terminal png font "Microsoft YaHei, 9"
set output "time-consuming.png"
set key left reverse Left spacing 1.2
set xlabel "样点数量"
set ylabel "建树时间(秒)"
file = "time-consuming.dat"
plot file using 2:3 with linespoint title "CR-tree", \
file using 2:4 with linespoint title "RR*-tree", \
file using 2:5 with linespoint title "MOO R-tree"
结果为:
点的形状与线的类型
无论是散点图中的点,还是折线图中的点,Gnuplot 默认使用的形状可能不遂我们之意。那么,Gnuplot 支持提供了哪些点的形状可选呢?
不同的 Gnuplot 图形终端中的点的形状是不同的。要查看某一特定终端支持哪些点类型,可以使用 test
命令。例如,查看 png 终端中的点类型:
set terminal png
set output "test.png"
test
结果得到:
上图中的右侧一列便为点的形状及其标号。在 plot
命令中,使用 pointtype 点的形状标号
选项,Gnuplot 便会按照指定的形状绘制点。例如:
set terminal png font "Microsoft YaHei, 9"
set output "time-consuming.png"
set key left reverse Left spacing 1.2
set xlabel "样点数量"
set ylabel "建树时间(秒)"
file = "time-consuming.dat"
plot file using 2:3 with linespoint pointtype 3 title "CR-tree", \
file using 2:4 with linespoint pointtype 4 title "RR*-tree", \
file using 2:5 with linespoint pointtype 5 title "MOO R-tree"
结果为:
若要控制点的大小,可在 plot
命令中使用「pointsize 点的大小
」选项。例如:
plot file using 2:3 with linespoint pointtype 3 pointsize 2 title "CR-tree", \
file using 2:4 with linespoint pointtype 4 pointsize 2 title "RR*-tree", \
file using 2:5 with linespoint pointtype 5 pointsize 2 title "MOO R-tree"
若同一幅图内的各条折线上的点的大小相同,可使用 set
命令统一设定,即:
set pointsize 2
plot file using 2:3 with linespoint pointtype 3 title "CR-tree", \
file using 2:4 with linespoint pointtype 4 title "RR*-tree", \
file using 2:5 with linespoint pointtype 5 title "MOO R-tree"
结果为:
Gnuplot 的 test
命令输出的结果中,不仅给出了点的形状集,也给出了线的类型集。例如,图中的 dashtype
给出了 png 终端支持的虚线类型。看了之后,你会发现,png 终端并不支持虚线,仅支持用户选择线宽与线的颜色。
在 plot
命令中,使用「linewidth 宽度
」修改线宽。例如:
plot file using 2:3 with linespoint pointtype 3 linewidth 2 title "CR-tree", \
file using 2:4 with linespoint pointtype 4 linewidth 2 title "RR*-tree", \
file using 2:5 with linespoint pointtype 5 linewidth 3 title "MOO R-tree"
结果为:
至于线的颜色的设定,需要在 plot
命令中使用「linecolor 颜色标号
」选项。颜色标号,从 test
命令输出结果的右侧的点的形状集合中选择。例如:
plot file using 2:3 with linespoint pointtype 3 linewidth 2 linecolor 2 title "CR-tree", \
file using 2:4 with linespoint pointtype 4 linewidth 2 linecolor 4 title "RR*-tree", \
file using 2:5 with linespoint pointtype 5 linewidth 3 linecolor 7 title "MOO R-tree"
结果为:
在熟悉了 plot
命令格式「plog 数据文件名 with ... title 图例
」中的各个选项之后,可以使用它们的简称以简化 plot
命令。上一节最后给出的 plot
命令,可简化为:
plot file u 2:3 w lp pt 3 lw 2 lc 2 t "CR-tree", \
file u 2:4 w lp pt 4 lw 2 lc 4 t "RR*-tree", \
file u 2:5 w lp pt 5 lw 3 lc 7 t "MOO R-tree"
直方图
前文中绘制的数据图,描述的是基于三种 R 树变体为 7 份采样点集构建索引结构耗费的总时间。对于每份采样点集,现在我想统计样点插入索引结构中的平均耗时,亦即将 time-consuming.dat 文件中的第 3~5 列数据分别处以第 2 列数据。
Gnuplot 支持数据按列运算。对于上述需求,可将 time-consuming.dat 修改为:
set terminal png font "Microsoft YaHei, 9"
set output "time-consuming.png"
set key left reverse Left spacing 1.2
set xlabel "样点数量"
set ylabel "样点插入过程平均耗时(秒)"
file = "time-consuming.dat"
plot file u 2:($3/$2) w lp pt 3 lc 2 t "CR-tree", \
file u 2:($4/$2) w lp pt 4 lc 4 t "RR*-tree", \
file u 2:($5/$2) w lp pt 5 lc 7 t "MOO R-tree"
结果为:
可将这种数据绘制为直方图(柱状图)。例如:
set terminal png font "Microsoft YaHei, 9"
set output "time-consuming.png"
set key left reverse Left spacing 1.2
set xlabel "采样点集"
set ylabel "样点插入过程平均耗时(秒)"
file = "time-consuming.dat"
set style data histogram
set style histogram clustered gap 2
set style fill pattern 1 border
plot file u ($3/$2):xticlabels(1) t "CR-tree", \
file u ($4/$2):xticlabels(1) t "RR*-tree", \
file u ($5/$2):xticlabels(1) t "MOO R-tree"
在直方图中,三种 R 树变体的构建效率高下立见:
以下语句用于将数据图形设为直方图风格,并设置直方图间隔与填充模式:
set style data histogram
set style histogram clustered gap 2
set style fill pattern 1
对于直方图,用于绘制散点图或折线图的 plot
命令不再适用。绘制直方图的 plot
命令只需要一组纵轴数据,然后将横轴用文本进行标注。在上述 plot
命令中,($3/$2):xticlabels(1)
的意思是:将数据文件中的第 3 列的数据除以第 2 列的数据,并将结果作为纵轴数据,然后将数据文件中的第 1 列文本作为横轴上的标注文本。
在 Gnuplot 的 test
命令的输出结果中,也包含着与直方图相关的填充样式,主要是填充图案与颜色。使用 fillstyle
设定填充样式。例如,将 MOOS R 树的直方图设置为纯色填充,将另外两种 R 树变体分别用图案 1 与 2 填充:
set terminal png font "Microsoft YaHei, 9"
set output "time-consuming.png"
set key left reverse Left spacing 1.2
set xlabel "采样点集"
set ylabel "样点插入过程平均耗时(秒)"
file = "time-consuming.dat"
set style data histogram
set style histogram clustered gap 2
plot file u ($3/$2):xticlabels(1) fillstyle pattern 4 t "CR-tree", \
file u ($4/$2):xticlabels(1) fillstyle pattern 5 t "RR*-tree", \
file u ($5/$2):xticlabels(1) fillstyle solid 0.5 t "MOO R-tree"
结果为:
pattern
选项用于设定填充图案,Gnuplot 对不同图形终端支持的填充图案可在 Gnuplot 的 test
命令输出结果中查看。solid
选项用于指定纯色填充,其后的数字用于指定填充颜色的深浅,该数字的取值范围为 [0, 1],其值越大,颜色越深。
至于直方图的颜色设置,与折线图的颜色设置相同,皆使用 linecolor
选项,在此不再赘述。
直方图相关绘图选项也有相应的简称,例如:
plot file u ($3/$2):xtic(1) fs p 4 t "CR-tree", \
file u ($4/$2):xtic(1) fs p 5 t "RR*-tree", \
file u ($5/$2):xtic(1) fs s 0.5 t "MOO R-tree"
图例的绝对定位
上一节绘制的直方图,图例的位置又出现了问题,它与直方图出现了重叠。需要再将它挪移到一个合适的位置上。采样点集 c 的直方图上方是比较理想的位置。这时,需要使用图例中心的绝对定位功能将其移动到目标位置。
用于设定图例中心绝对位置的命令为「set key center at x, y
」,其中 x
与 y
为绘图区域内某个位置的坐标值。如何获得采样点集 c 的上方理想位置的坐标值呢?此时,可以借助 Gnuplot 的 X11 终端。因为在 X11 的终端窗口中,光标(鼠标)的位置会即时的显示于窗口的左下角。使用这种办法,可以很顺利的确定 (1.65, 0.000325)
是比较理想的目标位置,将其定为图例的中心:
set key center at 1.65, 0.000325
结果为:
ConTeXt 终端
Gnuplot 可将绘制的图形输出为 ConTeXt 文档,然后使用 context
命令将 ConTeXt 文档「编译」为 PDF 文档。这样做的好处是,PDF 文档中的数据图是以矢量图的形式存在的。矢量图的好处是,可以随意缩放而不失真,适合作为论文中的插图。png 格式的图片,是像素图,对图片进行放大或缩小会致其模糊,因而只适合作为网络文档中的插图,例如本文中的所有插图皆为 png 格式。
要使用 Gnuplot 的 ConTeXt 终端,前提是你的系统中已经安装 ConTeXt 环境。若未安装,可参考「睦邻友好的 ConTeXt Standalone」与「zhfonts:ConTeXt MkIV 中文支持的 Hacking」这两份文档进行安装,然后按照 http://wiki.contextgarden.net... 的说明为 ConTeXt 安装 gnuplot 模块。做好这些准备工作之后,便可实现 gnuplot 与 ConTexT 的协作。
time-consuming.gnu 的 ConTeXt 终端版本如下:
set term context standalone size 12cm,8cm header '\usemodule[zhfonts]'
set output 'time-consuming.tex'
set key left reverse Left spacing 1.2
set key center at 1.65, 0.000325
set xlabel "采样点集"
set ylabel "样点插入过程平均耗时(秒)"
file = "time-consuming.dat"
set style data histogram
set style histogram clustered gap 2
plot file u ($3/$2):xtic(1) fs p 1 t "CR-tree", \
file u ($4/$2):xtic(1) fs p 2 t "RR*-tree", \
file u ($5/$2):xtic(1) fs s 0.5 t "MOO R-tree"
在终端中执行以下命令便可生成 time-consuming.pdf:
$ gnuplot -c time-consuming.gnu
$ context time-consuming
结果如下图所示:
由于字体尺寸以及终端环境的不同,原来适用于 png 终端的图例,现在不适于 ConTeXt 终端了。更糟糕的是,现在整幅图没有合适的位置放置图例。要解决这一问题,需要将纵轴的尺度增大,从而制造出足够的空间。
使用 set yrange
可以设置纵轴的尺度。例如:
set yrange [0.00005:0.00040]
结果如下图所示:
现在,纵向的空加足够安放图例了,但是需要重新设定图例的中心,让图例向上偏移一些。此外,轴的数据,目前的单位是秒,若使用毫秒为单位,能够让数据图的宽度减小一些。将这方方面面考虑周全,最终让我满意的代码如下:
set term context standalone size 12cm,8cm header '\usemodule[zhfonts]'
set output 'time-consuming.tex'
set key left reverse Left spacing 1.2
set key center at 1.4, 0.37
set xlabel "采样点集"
set ylabel "样点插入过程平均耗时(毫秒)"
set yrange [0.05:0.42]
file = "time-consuming.dat"
set style data histogram
set style histogram clustered gap 2
plot file u (1000*$3/$2):xtic(1) fs p 1 t "CR-tree", \
file u (1000*$4/$2):xtic(1) fs p 2 t "RR*-tree", \
file u (1000*$5/$2):xtic(1) fs s 1 t "MOO R-tree"
最终得到了我的论文中的第一幅实验数据图:
结语
最终之所以采用 ConTeXt 终端生成直方图,是为了获得矢量图。可能有些同学是使用 MS Word 写论文,认为 Gnuplot 与 ConTeXt 所做的这一切都与 MS Word 无关。事实上并非如此。由 ConTeXt 终端生成的 PDF 文档,可经由 inkscape 转换为 svg 格式。在 Windows 平台下,MS Visio 可以导入 svg 图形,然后再转存为 emf 格式的图形文件,这是 MS Word 支持的矢量图格式。对于打算投 SCI 期刊的论文,由于许多 SCI 期刊使用 LaTeX 排版。PDF 文档格式的图片,可直接在 LaTeX 文档中使用。
事实上,Gnuplot 也提供了 pdfcairo 终端,可以直接生成 PDF 文档格式的图片,但是图形的绘制结果有些粗糙。使用 ConTeXt 终端不仅能够得到比较精美的图形,而且可以充分利用 ConTeXt 对图形中的文本标注进行更好的排版,而且也可以插入数学公式。