2023年12月26日发(作者:)
用Perl-Tk实现数据可视化(本地运行)
使用Perl的标准GUI工具箱构建自定义绘图工具
Philipp K. Janert, Ph.D.
2003年10月01日发布
在直观显示数据时,人类的眼睛在识别复杂的行为、运动轨迹和图案时表现得出奇地好。如果
一个数据集的数据超过12个点,那么用图形表示就会有所帮助;如果数据集里的数据超过了
数千个点,那么使用图形表示就是必需的了。
对于简单的x-y平面图,gnuplot经常是第一选择。对更复杂的问题来说,您可以使用xmgrace
或者其他的绘制工具。但是大多数简单的曲线绘图仪在绘制二维数据方面或者在复杂的图形中
进行极为精细的控制方面显得很不足。复杂图形的例子包括专业化的条形图和须状图,带有完
整误差的时间序列条形图,颜色编码和密度图,以及许多其他可能的图。
这就是Perl/Tk的闪光之处。前提是,您已经使用Perl来进行数据操纵和提取。Perl/Tk提 供了与Perl绑定的Tk GUI工具集,并且它可能是最容易使用的部件之一。
在本文中,不会关注Perl/Tk的实际用户界面部分(例如,复选框和下拉菜单),只探讨它的
绘图能力。
作为一个GUI工具箱,Perl/Tk提供一个其他Perl图形扩展包(例如,极好的GD包)中 没有的附加工具:即,动画和交互式数据探索的能力。在接下来的示例中我将向您展示这些应
用。
Perl/Tk程序剖析
为了介绍Perl/Tk编程的基本概念,先让我们考虑大家熟知的“Hello, world”程序,在Perl/Tk
中,该程序类似如下:
清单
1. Perl/Tk
风格的“Hello, world”程序
1#!/usr/bin/perl -w
2use Tk;
3$mw = MainWindow->new;
4$mw->Label( -text => "Hello, World!" )->pack;
5$mw->Button( -text => "Exit", -command => sub { exit } )->pack;
6MainLoop;
这个程序首先打开一个新窗口,然后显示文本和一个“Exi/按钮。单击这个按钮,终止程序。
(曾经使用过的GUI工具箱有比这个程序更容易的吗?)
语句use Tk;声明在程序中要引用Tk模块。整个Tk工具集汲取了大多数Perl面向对象 的特性。您经常可以看到使用-> 符号基于对象对方法进行调用。
每个Perl/Tk应用程序必须明确地对应一个主窗口。它是一个“项层”部件,意思是它必须在一
个单独的窗口中打开。我们已经使用其构造函数new来说明了这一点。
下一步,我们将在这个主窗口中创建标签和按钮对象,作为主窗口的子部件。每个部件(除了
顶层部件外,比如主窗口)必须是某个确切父部件的子部件。
我们为标签和按钮部件指定一些参数,然后调用pack,该语句是Perl/Tk的几何管理器之一。
几何管理器的职责是在父窗口中将子部件有规则地排列起来。除了语句pack外,还有语句
grid和place,它们除了布局部件外,更可以对部件进行出色的纹理控制。
在Perl/Tk中,部件的参数用它们的名字来指定(和管理器工具语句pack的参数一样),通
常开头带有一个短横。注意那些围绕参数名的引号不是必需的(因此通常被省略掉),因为操
作符=>将它左边的裸单词(barewords)看作是被引用的字符串。参数-command用来定义回
调,如果部件接收到一个用户事件,那么就会触发对将被调用函数的引用(例如,在当前例子
中,用户点击按钮)。由于这个回调程序体很小,所以我们将整个函数内嵌为一个匿名子例程
来完成。下面您将看到独立子例程被注册为回调程序的示例。
现在我们的部件已经设置完成,并且程序准备接收用户事件。程序的最后一行调用MainLoop
语句,这个语句的作用是开始监听用户事件,并且把它们分派给合适的回调。所有的事件都已
被对应的能够产生事件的部件注册。
整个程序就是这样!注意初学者通常犯的错误是忘了调用pack或者调用MainLoop。如果程 序运行时应用程序窗口不能正常显示,原因可能就是由于其中之一或者两者都被遗漏掉所造成
的。另外要记住的一点是新窗口的实际大小和位置是由窗口管理器控制的(比如Gnome,KDE, IceWM),而不是由程序本身控制。
在画布上创建图形
如果我们要输出图形,Tk提供了画布部件。一旦创建了画布部件实例,其上就可以创建一些
标准的图形对象(例如,直线、圆形、长方形等)。以下是示例程序:
清单2.创建了画布部件实例并在其上放置对象
#!/usr/bin/perl -w
2
use Tk;
1
3
my ( $size, $step ) = ( 200, 10 );
# Create MainWindow and configure:
5
my $mw = MainWindow->new;
6
$mw->configure( -width=>$size, -height=>$size );
4
$mw->resizable( 0, 0 ); # not resizable in any direction
8
# Create and configure the canvas:
7
my $canvas = $mw->Canvas( -cursor=>〃crosshair〃, -background=>〃white〃,
10-width=>$size, -height=>$size )->pack;
11# Place objects on canvas:
12$canvas->createRectangle( $step, $step, $size-$step, $size-$step, -fill=>〃red〃 );
13for( my $i=$step; $i<$size-$step; $i+=$step ) {
14my $val = 255*$i/$size;
15my $color = sprintf( 〃#%02x%02x%02x〃, $val, $val, $val );
16$canvas->createRectangle( $i, $i, $i+$step, $i+$step, -fill=>$color );
17)
18MainLoop;
9
在这个程序中,和前一程序一样,首先创建一个主窗口,并且设置窗口的宽和高为$size像
素。resizable( $in_x_direction, $in_y_direction )方法用于固定顶层窗口的尺寸。该 方法带有两个布尔变量,用于决定部件在x方向或者y方向是否可以调节。这里我们完全禁 止调节尺寸。
下一步是创建画布部件并使之充满整个主窗口。我们已经将画布清理干净(将画布的背景色设
置为“白色”),并且当鼠标移至画布时,光标变为交叉(在X11中有78种标准的鼠标光标 形状,其名称可以在头文件cursorfont.h中找到,典型安装时,这个头文件可以在 /说"*11/:1腕1血0灰11/目录中找到。)
注意在末尾要调用pack,它使得画布对象的实例在主窗口中显示出来。
在注释语句"# Place objects on canvas:"之后,我们准备在画布上绘制内容了。我们已经创建 了一个大的红色的长方形,并且有一排较小的灰色长方形斜穿过它。注意画布对象使用了标准 的“图形坐标系统”,x轴指向右边,y轴指向下面,所以坐标系统的原点(两轴交接的地方) 位于窗口的左上角。
图1.放置在画布上的长方形对象
这段代码演示了在Perl/Tk中指定颜色的两种办法。一种是使用在文件 (通常这个文 件可以在目录/usr/X11/lib/X11/下找到)中预定义的颜色名称,比如“red”或者“PapayaWhip”。 另一种
方法是指定各个RGB (red/green/blue)值,这个值以“#”打头,后面跟着三个两位的 十六进制数的字符串。注意如果某个十六进制数只有个位数,那么在其左边必须补上一个零。 如果RGB三个值相等(正如本例所示),显示的颜色就是灰色。
长方形(其他形状也一样)有两种颜色,一种是填充色,另一个是边框色。由于我们没有指定
后者,所以默认为黑色。要去除这些边框,设置-outline属性为与填充色相同即可;要加宽 边框,使用-width属性来指定宽度(用像素表示)。
最后,要注意图形元素(像长方形、直线、圆形)都不是部件!诸如createRectangle这样 的函数都不会返回一个对象,然而每个图形元素都有一个ID (确切地说,是一个数字)来标 识。要移动、修改或者删除图形元素,该ID将作为参数传递给包含画布对象的各个成员函数。 例如,为了删除红色正方形,我们使用$canvas->delete( $id ),其中的$id就是第一次 调用函数createRectangle的返回值。
与用户交互
从某种意义上来讲,GUI工具箱(例如Tk)的整个出发点就是为了实现与用户交互。换句 话说,工具箱应该使得应用程序响应各种事件变得很容易,例如鼠标点击或者键盘输入。
在Perl/Tk中,大多数部件都有-command属性,正如上面所提及的,该属性允许部件注册一 个子例程(“回调”),如果这个部件被用户激活,那么该子例程将被调用。我们已经在清单1 中看到了一个这样的例子,其中回调是一个匿名子例程:-command => sub { exit }。如果 不采用这种方法的话,我们要想注册一个子例程(用sub method_name{ ... }来定义),那 么必须注册一个到这个子例程的引用,类似于:-command=>&method_name。
画布部件则不同,它不带-command属性。为了使画布能够响应用户的交互,我们需要使用
Tk::bind函数显式地将回调绑定到事件。(注意显式命名空间Tk::在这里是必需的,因为 画布部件定义了自己的bind函数是隐含继承的)。
函数bind带有两个变量:第一,要响应的事件序列;第二,回调和它的参数。事件序列是放 在尖括号中的字符串,例如,
o (Tk响应的事件是类似于 X11窗口系统定义的事件集,但是不完全相同。请查阅Tk::bind的参考资料来获得事件和修 饰符的完整列表。)
函数bind的第二个变量是被调用的回调。我们已经看到如何使用匿名的子例程或者对指定子
例程的引用。当回调需要参数时,我们将这个回调指定为匿名列表,首先是子例程引用,然后
是后续列表项参数:bind( 〃
注意方括号(不是圆括号)中需要组成一个匿名列表引用。
使用bind指派的回调的第一个变量总是一个对产生事件的部件的引用。然后才是用户定义的
参数。这是一个非常容易忘记的细节!
作为最后的难点,Ev()工具允许检索和使用调用回调的事件细节。例如,事件发生的位置坐
标(参照$canvas原点)可以通过Ev('x')和Ev('y')获得。
所以如果我们增加以下一行:
$canvas->Tk::bind( 〃
1Ev('y')]);
放到我们第二个示例程序中的MainLoop调用之前,那么当我们在画布窗口中单击鼠标左键时,
画布坐标将显示在屏幕上。(注意,在参数列表中,我们省略了第一个条目$_[0]。这是上 面提到的对调用部件的引用,在本例中,就是$canvas。)
一个图形例子
作为展示绘制图形和数据可视化能力的重要例子,设想我们面临以下带有两个变量x和y的 方程:
f(
x,
y) = cos(2
a) cos(4
b) + cos(5
a) cos(3
b) + cos(7
a) cos(1
b)
其中(pi = 3.1415…,sqrt()表示平方根):
a = pi (x- sqrt(3.0)
y )
b
= pi ( 3
x + sqrt(3.0)
y )
仅从研究方程本身很难得到该函数行为的任何想法。止匕外,这个行为十分复杂,以致即使绘制
大量的一维图形(也就是说固定y为某些值,仅仅绘制x函数的值)也不能揭示该方程的底 层结构。然而,该函数的简单二维密度绘图马上给我们一种清晰的感觉,那就是在原公式中包 含的简单而漂亮的对称结构。
图2.二维密度图
点击查看大图
您可以在本文后面的参考资料中下载这个程序,并完成绘图。根据前面的讨论,这个程序应
该容易理解,虽然在这里一步一步地讲解这个程序会过于冗长。注意,这个程序关于处理实际
绘图和用户交互的部分十分简短。程序的绝大部分花费在创建和设置各种GUI部件,以及完
成从函数的坐标系统到屏幕坐标系统的转换上。这是所有绘图程序的典型特征(并且如果我们
允许用户输入要绘制的函数,程序将进一步扩大,这是由于代码必须完成必需的输入验证。)
保存工作
在屏幕上看到数据是一回事,将它保存到磁盘中(或者打印出来)又完全是另一回事。Perl/Tk
已经显示了很强的绘图能力和灵活性,但是不可思议的是它竟然没有一个标准的模块用于将图
形存储到位图文件中。
有一个工具用于将画布内容存储到PostScript文件中,这就是通过调用 一
$canvas->postscript( -file=>"file_")。这将只能捕获在画布中实际显示的内容。
因此,要注意的是一定要确保画布对象已经全部渲染到屏幕上;否则输出文件将是空的。函数
update。用于(在任何部件上)强制渲染并且等待所有进行的事件全部完成。
另一种可能是将每一屏幕像素的RGB值输出为由三部分组成的字节,并直接写入任何文件句
柄。一些图形程序可以处理用这种方法生成的rgb文件。可能最为强大的是来自ImageMagick 软
件包的转换实用程序,它能够将rgb文件转换成任何常用的图形文件格式。
最后,从显示窗口获取图形文件的最简易方法是直接拷屏。来自ImageMagick软件包的导入
工具十分灵活方便,并且可以生成多种文件格式。
相关主题
您可以参阅本文在developerWorks全球站点上的 英文原文.
•
要创建图2所示的密度图,请下载。
•
由 Stephen O. Lidie 和 Nancy Walsh 合著的书
Mastering Perl/Tk (O'Reilly & Associates, 2002)
提供了通俗易懂的介绍。它是第2版,对Nancy Walsh所著的Learning Perl/Tk(O'Reilly &
Associates, 1999)进行了大量扩充。然而该书组织得不是很好,而且没有覆盖标准发行版的 所有组件。
•
也许最能够及时更新并且全面的Perl/Tk文档是手册页了。输入manTk (或者perldoc Tk),
就可以看到简单的和复杂的部件的概述和详细列表。
•
在本文中,作者没有提到的一个问题是颜色选择。原因是Perl/Tk仅支持RGB颜色规范(和 符号名称)。对于大多数人来说,RGB值很不直观,基于 HSV (Hue/Saturation/ValueBrightness)
色彩模型的颜色选择要更加容易一些。然而,在不同颜色空间之间进行转换却是十分复杂的。
这方面的例子可以参阅Color FAQ或者Colour Space Conversions FAQ。简单但是很有用的概
述可以参阅 Introduction to Color。
•
有关如何从命令行使用ImageMagick的概述请参阅"通过命令行处理图形"(developerWorks,
2003 年 7 月)。
•
ImageMagick主页是下载ImageMagick软件的地方。
•如果您需要操纵TIFF图像,学习如何通过调用libtiff来使用TIFF的ANSI C实现,可以 参阅developerWorks的两部分系列文章。用libtiff进行图形编程,第1部分讨论了 TIFF 的不足之处,并指导您使用libtiff库来处理黑白图像。用libtiff进行图形编程,第2部分 进一步讲解如何使用libtiff库处理灰度和彩色图像。
•字符和图形的Perl用户界面列在CPAN上。
•在CPAN上您可以找到更多的信息,GD图形库上的Perl界面。
•
SFGraph是IBM alphaWorks框架,用于压缩和查看高分辨率的大图像。SFGraph的编码器使
用wavelet技术来压缩大图像,这些图像可以是PGM、PPM、PNM、JPEG、GIF,以及原始RGB 颜色和灰度级别格式。
•
在developerWorks的Linux专区可以找到更多的linux开发者资源。
•
在developerWorks的开放源代码专区,可以浏览更多的 开放源代码资源
•
二
发布者:admin,转转请注明出处:http://www.yc00.com/news/1703585098a1304384.html
评论列表(0条)