Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Data visualization using Perl/Tk

Build custom graphing tools with the standard GUI toolkit for Perl

Philipp Janert, Ph.D. (janert@ieee.org), Software Project Consultant, IEEE.org
Philipp K. Janert is a Software Project Consultant, server programmer, and architect. His specific interest is the identification, establishment, and transmission of software engineering's best practices. He maintains the BeyondCode.org Web site, and his articles have appeared in IEEE Software, Linux Journal, and the O'Reilly Network site. He holds a Ph.D. in Theoretical Physics from the University of Washington in Seattle. You can contact Philipp at janert-at-ieee.org.

Summary:  Generating a visual representation is often the best way to understand large data sets, but standard tools such as gnuplot often fall short. This article shows how to use Perl/Tk, the standard GUI toolkit for Perl, to quickly build custom plotting and graphing tools.

Date:  27 Aug 2003
Level:  Introductory
Also available in:   Japanese

Activity:  34250 views
Comments:  

The human eye is incredibly good at recognizing complex behavior and spotting trends and patterns in visually displayed data. If any dataset is larger than about a dozen points, a graph is helpful; if the dataset exceeds a few thousand points, a graph becomes a necessity.

For simple x-y plots, gnuplot is often the first choice. For more complicated problems you can use xmgrace and other plotting tools. However, most simple curve plotters are insufficient for plotting two-dimensional data or for exerting detailed control over a complicated graph. Examples of complicated graphs include specialized bar-and-whiskers plots, time-series with sophisticated error bars, color encodings and density plots, and many other possibilities.

This is where Perl/Tk shines. Chances are, you're already using Perl for data manipulation and extraction. Perl/Tk provides Perl bindings for the Tk GUI toolset and is possibly one of the easiest-to-use widget sets around.

In this article, I'm not as interested in the actual user-interface parts of Perl/Tk (such as checkboxes and drop-down menus) as I am in its graphing capabilities.

Being a GUI-toolkit, Perl/Tk provides an additional facility, which is absent from other graphing extensions to Perl (such as the excellent GD package): namely, the ability for animations and interactive data exploration. I'll show you applications for this in the examples below.

Anatomy of a Perl/Tk program

To introduce the basic concepts of programming with Perl/Tk, let's consider the proverbial "Hello, world" program, which in Perl/Tk looks like this:


Listing 1. "Hello, world," Perl/Tk style
#!/usr/bin/perl -w

use Tk;

$mw = MainWindow->new;
$mw->Label( -text => "Hello, World!" )->pack;
$mw->Button( -text => "Exit", -command => sub { exit } )->pack;

MainLoop;

This program opens a new window, displaying the text and an "Exit" button. Clicking the button terminates the program. (Was there ever a GUI-toolkit that was easier to program than this?)

The use Tk; declaration makes the Tk module available to our program. The entire Tk toolset draws heavily on Perl's object-oriented features; you'll frequently see methods being called on objects using the -> notation.

Every Perl/Tk application must have exactly one MainWindow. It is a "top-level" widget, meaning that it opens in a separate window. We've instantiated it using its constructor, new.

Next, we've created Label and Button objects as child widgets inside the MainWindow. Each widget (except for top-level ones, such as MainWindow) must be the child of exactly one parent.

We've specified some parameters to the Label and Button widgets and then called pack, one of Perl/Tk's geometry managers, on each of them. The geometry manager's responsibility is to line up and arrange child widgets within their parent window. In addition to pack, there are also grid and place, which provide finer-grained control over the way widgets are laid out.

Parameters to widgets (as well as parameters to manager facilities such as pack) are specified using their names, which, in Perl/Tk, conventionally start with a dash ("-"). Note that quotes around the parameter names are not required (and are therefore commonly omitted), because the => operator forces barewords on its left to be treated as quoted strings. The -command parameter is used to define the callback, a reference to the function that will be called if this widget receives a user event (for example, if the button is clicked by the user, in the present case). Since the body of the callback is trivial here, we've defined the entire function inline as an anonymous subroutine. Below, you'll see cases where stand-alone subroutines are registered as callbacks.

The setup of our widget is now complete, and the program is ready to receive user events. The last line calls MainLoop, which starts listening for user events and dispatches them to the appropriate callbacks, which have been registered with each widget that can generate events.

That's all there is to it! Note that a common beginner's mistake is to forget the call to pack or the call to MainLoop. If the application window fails to display when the program is run, chances are that one or both of these omissions is the cause. Also keep in mind that the actual size and placement of the new window is controlled by the window manager (such as Gnome, KDE, IceWM), not by the program itself.


Committing it to Canvas

If we want to generate graphical output, Tk offers the Canvas widget. Once a Canvas widget has been instantiated, several standard graphical objects (such as lines, circles, rectangles, and others) can be created on it. The following program demonstrates this:


Listing 2. Instantiating and placing objects on the Canvas widget
#!/usr/bin/perl -w

use Tk;

my ( $size, $step ) = ( 200, 10 );

# Create MainWindow and configure:
my $mw = MainWindow->new;
$mw->configure( -width=>$size, -height=>$size );
$mw->resizable( 0, 0 ); # not resizable in any direction

# Create and configure the canvas:
my $canvas = $mw->Canvas( -cursor=>"crosshair", -background=>"white",
              -width=>$size, -height=>$size )->pack;

# Place objects on canvas:
$canvas->createRectangle( $step, $step, $size-$step, $size-$step, -fill=>"red" );

for( my $i=$step; $i<$size-$step; $i+=$step ) {
  my $val = 255*$i/$size;
  my $color = sprintf( "#%02x%02x%02x", $val, $val, $val );
  $canvas->createRectangle( $i, $i, $i+$step, $i+$step, -fill=>$color );
}

MainLoop;

Here we've created a MainWindow as before and configured it to be $size pixels wide and high. The resizable( $in_x_direction, $in_y_direction ) method is used to fix the size of the top-level window. The method takes two boolean arguments, determining whether the widget is resizable in the x- or y-direction. Here, we prohibit resizing entirely.

The next step has created a Canvas widget and had it fill the entire MainWindow. We've cleared the canvas (by setting its background color to "white") and changed the mouse-cursor to a cross-hair when the mouse is over the Canvas. (There are 78 standard mouse cursor shapes in X11, the names of which can be found in the header file cursorfont.h, which in a typical installation can be found in the /usr/X11/include/X11/ directory.)

Notice the trailing call to pack, which has made the instantiated Canvas object visible in the MainWindow.

At "# Place objects on canvas:" we are ready to draw items onto the canvas. We've created a large, red rectangle, with a row of smaller, grey rectangles running diagonally across it. Notice that the Canvas object uses the standard "graphics coordinate system," with the x-axis pointing to the right and the y-axis pointing down, so that the origin of the coordinate system (where both axes meet) lies in the upper left corner.


Figure 1. Rectangular objects placed on canvas
Objects placed on canvas

This code demonstrates the two ways to specify color in Perl/Tk. One possibility is to use one of the pre-defined color names such as "red" or "PapayaWhip" from the file rgb.txt (usually found in /usr/X11/lib/X11/). The alternative is to specify the individual RGB (red/green/blue) values as two-digit hexadecimal values in a string beginning with a "#". Note that single hexadecimal digits must be zero-padded on the left. If all three values are equal (as in this example), the color will appear grey.

Rectangles (as well as other shapes) have both a fill color and an outline color. Since we have not specified the latter, it defaults to black. To remove these borders, set the -outline property to be the same as the fill color; to widen the borders, use the -width property to specify their width in pixels.

Lastly, note that graphical elements such as rectangles, lines, and circles are not widgets! Functions such as createRectangle do not return an object, but rather an ID (really, a number) by which each graphic element can be identified. To move, modify, or delete a graphics element, this ID is passed as a parameter to the respective member function on the containing Canvas object. For instance, to delete the red square, we would use $canvas->delete( $id ), where $id is the return value of the first call to createRectangle.


User interaction

In a sense, the entire point of a GUI-toolkit (such as Tk) is to enable user interaction. In other words, the toolkit must make it easy for the application to respond to all kinds of "events," such as mouse clicks or key presses.

Most widgets in Perl/Tk take a -command attribute, which, as mentioned above, allows a widget to register a subroutine (a "callback") that will be called if the widget is activated by the user. We have already seen an example of this in Listing 1, where the callback was an anonymous subroutine: -command => sub { exit }. If instead we wanted to register an arbitrary subroutine (defined using sub method_name{ ... }), we would have to register a reference to this subroutine like so: -command=>\&method_name.

The Canvas widget is different, in that it does not take a -command attribute. To make a Canvas respond to user interaction, we need to explicitly bind a callback to an event, using the Tk::bind function. (Note that the explicit namespace Tk:: is necessary here, since the Canvas widget defines its own bind function that hides the inherited one.)

The bind function takes two arguments: first, the event sequence to respond to, and second, the callback and its parameters. The event sequence is a string enclosed in angle brackets, such as <Motion> or <Shift-Button-3>. (The events Tk responds to are similar, but not completely equivalent, to the set of events defined by the X11 window system. Refer to the Tk::bind reference for the complete list of events and modifiers.)

The second argument to bind is the callback to be invoked. We've already seen how to use an anonymous subroutine or a reference to a named subroutine. When the callback requires parameters, we specify the callback as an anonymous list, with the subroutine reference first and the parameters as subsequent list entries: bind( "<Motion>", [ \&method_name, parameter1, parameter2 ] ). Note the brackets (not parentheses), which are required to compose an anonymous list reference.

The first argument to a callback assigned using bind is always a reference to the widget that generated the event. Only then follow the user-defined parameters. This is an easily forgotten detail!

As a final twist, the Ev() facility allows us to retrieve and use details about the event that invoked the callback. For instance, the coordinates (with respect to the $canvas origin) of the location at which the event occurred are available through Ev('x') and Ev('y').

So, if we add the following line:

$canvas->Tk::bind( "<Button-1>", [ sub { print "$_[1] $_[2]\n"; }, Ev('x'), Ev('y') ] );

to our second example program just before the call to MainLoop, the program will print out the canvas coordinates to the console whenever we click the left mouse button over the canvas window. (Note that we ignore the first entry, $_[0], in the parameter list. This is the reference to the invoking widget mentioned earlier, in this case, the $canvas.)


A graphic example

As a non-trivial example of the power of graphing and data visualization, imagine that we are faced with the following equation of two variables, x and y:

f(x, y) = cos(2a) cos(4b) + cos(5a) cos(3b) + cos(7a) cos(1b)

where (with pi = 3.1415... and sqrt() denoting the square root):

a = pi ( x - sqrt(3.0) y ) b = pi ( 3 x + sqrt(3.0) y )

It is very difficult to obtain any idea about the behavior of this function simply from studying the equation. Furthermore, the behavior is sufficiently complex that even a large number of one-dimensional plots (in other words, fixing y at some value and plotting the equation as a function of x only) will not reveal the underlying structure. However, a simple two-dimensional density plot of this function immediately gives us a good feeling for the simple and beautiful symmetry contained in the original formula.


Figure 2. Two-dimensional density plot
Two-dimensional density plot

You can download the program to perform this plot from Resources later in this article. With the foregoing discussion, it should be easy to understand, although the program is too lengthy to give a step-by-step explanation here. Note that the parts of the program that handle the actual plotting and user interaction are remarkably small. The lion's share of the program is spent creating and configuring the various GUI widgets and performing the transformation from the coordinate system of the function to screen coordinates. This is quite typical of any plotting program (and if we allowed the user to enter the function to be plotted, the program would balloon further, due to the code having to perform the now required input validation.)


Saving the work

Viewing data onscreen is one thing, saving it to disk (and printing it) is another matter entirely. Given the power and flexibility of Perl/Tk, it may come as a considerable surprise that there are no standard modules to save graphics to a bitmap file.

There is a facility to save the contents of a Canvas to a PostScript file, by calling $canvas->postscript( -file=>"file_name.ps" ). This will capture only the actually displayed contents of the Canvas. Therefore, take care that the Canvas object in fact has been rendered to the screen; otherwise the output file will be empty. The update() function can be used (on any widget) to force rendering and to wait until all pending events have been processed.

Another possibility is to explicitly write the RGB values for each screen pixel as a byte-triple to any filehandle. Several graphics programs can handle the rgb files generated in this way. Possibly the most powerful of these is the convert utility from the ImageMagick package, which is able to transform an rgb file into any one of the commonly used graphics file formats.

Finally, the easiest way to obtain a graphics file from a displayed window is to simply take a screenshot. The import tool from the ImageMagick package is quite flexible and is able to generate a large number of file formats.



Download

NameSizeDownload method
l-datavis/hexa-plot.pl HTTP

Information about download methods


Resources

Despite its power and ease of use, Perl/Tk has unfortunately been somewhat of a backwater, and up-to-date and comprehensive information is often hard to find. Below are some of the author's recommendations.

  • To create the density plot shown in Figure 2, download hexa-plot.pl.

  • You'll find a wealth of information at the official Perl/Tk homepage. Its FAQ is somewhat dated but still useful.

  • The book Mastering Perl/Tk (O'Reilly & Associates, 2002) by Stephen O. Lidie and Nancy Walsh provides an easy-to-read introduction. It is the 2nd and much expanded version of Learning Perl/Tk (O'Reilly & Associates, 1999) by Nancy Walsh. However, it is not particularly well organized and does not cover all of the widgets in the standard distribution.

  • Probably the most up-to-date and comprehensive documentation for Perl/Tk are the man pages. Start with man Tk (or perldoc Tk), for an overview and a comprehensive list of simple and complex widgets.

  • One issue the author has not touched on in this article is color selection. The reason is that Perl/Tk only supports RGB color specification (and symbolic names). For most people, RGB values are not particularly intuitive, and color selection based on an HSV (Hue/Saturation/Value=Brightness) color model is much easier. However, the transformation between different color spaces has complex implications. Check, for instance, the Color FAQ or the Colour Space Conversions FAQ. For a simple but useful overview, consider Introduction to Color.

  • For an overview of using ImageMagick from the command line, read "Graphics from the command line" (developerWorks, July 2003).

  • The ImageMagick home page is the place to download ImageMagick and get more information on its various programming interfaces.

  • If you need to manipulate TIFF images, learn how to use an ANSI C implementation of TIFF called libtiff in this two-part series on developerWorks. Part 1 discusses some pitfalls with TIFF and guides you through use of the libtiff library for black-and-white images. Part 2 takes the next logical step, showing how to use libtiff for grayscale and color images.

  • Character and graphical Perl user interfaces are listed on CPAN.

  • You'll also find on CPAN more information about GD.pm, the Perl interface to the GD graphics library.

  • SFGraph is an IBM alphaWorks framework for compressing and viewing large, high-resolution images. SFGraph's encoder uses wavelet technology to compress large images in the PGM, PPM, PNM, JPEG, GIF, and raw RGB color and gray-scale formats.

  • Find more resources for Linux developers in the developerWorks Linux zone.

  • Browse more Open source resources in the developerWorks Open source zone.

About the author

Philipp K. Janert is a Software Project Consultant, server programmer, and architect. His specific interest is the identification, establishment, and transmission of software engineering's best practices. He maintains the BeyondCode.org Web site, and his articles have appeared in IEEE Software, Linux Journal, and the O'Reilly Network site. He holds a Ph.D. in Theoretical Physics from the University of Washington in Seattle. You can contact Philipp at janert-at-ieee.org.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. You may update your IBM account at any time.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux, Open source
ArticleID=11335
ArticleTitle=Data visualization using Perl/Tk
publish-date=08272003
author1-email=janert@ieee.org
author1-email-cc=