NAME

Chart::Plot - Plot two dimensional data in an image. Version 0.10.

SYNOPSIS

use Chart::Plot; 

my $img = Chart::Plot->new(); 
my $anotherImg = Chart::Plot->new ($image_width, $image_height); 

$img->setData (\@dataset) or die( $img->error() );
$img->setData (\@xdataset, \@ydataset);
$img->setData (\@anotherdataset, 'red_dashedline_points'); 
$img->setData (\@xanotherdataset, \@yanotherdataset, 
               'Blue SolidLine NoPoints');

my ($xmin, $ymin, $xmax, $ymax) = $img->getBounds();

$img->setGraphOptions ('horGraphOffset' => 75,
			    'vertGraphOffset' => 100,
			    'title' => 'My Graph Title',
			    'horAxisLabel' => 'my X label',
			    'vertAxisLabel' => 'my Y label' );

print $img->draw();

DESCRIPTION

I wrote Chart::Plot to create images of some simple graphs of two dimensional data. The other graphing interface modules to GD.pm I saw on CPAN either could not handle negative data, or could only chart evenly spaced horizontal data. (If you have evenly spaced or nonmetric horizontal data and you want a bar or pie chart, I have successfully used the GIFgraph and Chart::* modules, available on CPAN.)

Chart::Plot will plot multiple data sets in the same graph, each with some negative or positive values in the independent or dependent variables. Each dataset can be a scatter graph (data are represented by graph points only) or with lines connecting successive data points, or both. Colors and dashed lines are supported, as is scientific notation (1.7E10). Axes are scaled and positioned automatically and 5-10 ticks are drawn and labeled on each axis.

You must have already installed the GD.pm library by Lincoln Stein, available on CPAN or at http://stein.cshl.org/WWW/software/GD/ Versions of GD below 1.19 supported only gif image format. Versions between 1.20 and 1.26 support only png format. GD version 1.27 supports either png or jpg image formats. Chart::Plot will draw whichever format your version of GD will draw. (See below for a method to determine which format your version supports.)

USAGE

Create an image object: new()

use Chart::Plot; 

my $img = Chart::Plot->new; 
my $img = Chart::Plot->new ( $image_width, $image_height ); 
my $anotherImg = new Chart::Plot; 

Create a new empty image with the new() method. It will be transparent and interlaced if your version of GD supports gif format. png does not yet support either. If image size is not specified, the default is 400 x 300 pixels, or you can specify a different image size. You can also create more than one image in the same script.

Acquire a dataset: setData()

$img->setData (\@data);
$img->setData (\@xdata, \@ydata);
$img->setData (\@data, 'red_dashedline_points'); 
$img->setData (\@xdata, \@ydata, 'blue solidline');

The setData() method reads in a two-dimensional dataset to be plotted into the image. You can pass the dataset either as one flat array containing the paired x,y data or as two arrays, one each for the x and y data.

As a single array, in your script construct a flat array of the form (x0, y0, ..., xn, yn) containing n+1 x,y data points . Then plot the dataset by passing a reference to the data array to the setData() method. (If you do not know what a reference is, just put a backslash (\) in front of the name of your data array when you pass it as an argument to setData().) Like this:

my @data = qw( -3 9   -2 4   -1 1   0 0   1 1  2 4  3 9);
$img->setData (\@data);

Or, you may find it more convenient to construct two equal length arrays, one for the horizontal and one for the corresponding vertical data. Then pass references to both arrays (horizontal first) to setData():

my @xdata = qw( -3  -2  -1  0  1  2  3 );
my @ydata = qw(  9   4   1  0  1  4  9 );
$img->setData (\@xdata, \@ydata);

In the current version, if you pass a reference to a single, flat array to setData(), then only a reference to the data array is stored internally in the plot object, not a copy of the array. The object does not modify your data, but you can and the modified data will be drawn. On the other hand, if you pass references to two arrays, then copies of the data are stored internally, and you cannot modify them from within your script. This inconsistent behavior is probably a bug, though it might be useful from time to time.

You can also plot multiple datasets in the same graph by calling $img->setData() repeatedly on different datasets.

Error checking: The setData() method returns a postive integer on success and 0 on failure. If setData() fails, you can recover an error message about the most recent failure with the error() method. The error string returned will either be "The data set does not contain an equal number of x and y values." or "The data element ... is non-numeric."

$p->setData (\@data) or die( $p->error() );

In the current version, only numerals, decimal points (apologies to Europeans), minus signs, and more generally, scientific notation (+1.7E-10 or -.298e+17) are supported. Commas (,), currencies ($), time (11:23am) or dates (23/05/98) are not yet supported and will generate errors. I hope to figure these out sometime in the future.

Be cautious with scientific notation, since the axis tick labels will probably become unwieldy. Consider rescaling your data by orders of magnitude or using logarithmic transforms before plotting them. Or experiment with image size and graph offset.

Style options: You can also specify certain graphing style options for each dataset by passing an optional final string argument to setData() with a concatenated list of selections from each of the following groups:

BLACK           SOLIDLINE            POINTS    
red		    dashedline           nopoints  
green 	    noline         
blue

The capitalized options in each group are the default for that group. If you do not specify any options, you will get black solid lines connecting successive data points with dots at each data point ('black_solidline_points'). If you want a red scatter plot (red dots but no lines) you could specify either

$p->setData (\@data, 'redNOLINE'); 
$p->setData (\@xdata, \@ydata, 'Points Noline Red');

Options are detected by a simple regexp match, so order does not matter in the option string, options are not case sensitive and extraneous characters between options are ignored. There is no harm in specifying a default. There is also no error checking.

Obtain current graph boundaries: getBounds()

my ($xmin, $ymin, $xmax, $ymax) = $img->getBounds;

This method returns the data values of the lower left corner and upper right corner of the graph, based on the datasets so far set. If you have only positive data, then $xmin and $ymin will be 0. The upper values will typically not be the data maxima, since axis tick ranges are usually a little beyond the range of the data. If you add another dataset, these values may become inaccurate, so you will need to call the method again. As an example, I use this to draw a least squares regression line (using Statistics::OLS) through a scatter plot of the data, running from the edges of the graph rather than from the bounds of the data.

Graph-wide options: setGraphOptions()

    $img->setGraphOptions ('title' => 'My Graph Title',
		         'horAxisLabel' => 'my X label',
		         'vertAxisLabel' => 'my Y label' 
			 'horGraphOffset' => $numHorPixels,
	                 'vertGraphOffset' => $numvertPixels);

    my %xTickLabels = qw (1 One o'clock 2 Two o'clock 3 Three o'clock);
    my %yTickLabels = qw (1 Jan 2 Feb 3 Mar);
    $img->setGraphOptions ('xTickLabels' => \%xTickLabels,
                           'yTickLabels' => \%yTickLabels)
       or die ($img->error);

This method and each of its arguments are optional. You can call it with one, some or all options, or you can call it repeatedly to set or change options. This method will also accept a hash.

In the current version, Chart::Plot is a little smarter about placement of text, but is still not likely to satisfy everyone, If you are not constructing images on the fly, you might consider leaving these blank and using a paint program to add text by hand. Or place descriptive text in a caption outside the image.

Titles and Axis labels are blank, by default. The title will be centered in the margin space below the graph. A little extra vertical offset space (the margin between the edges of the graph proper and the image) is added to allow room. There is no support for multi-line strings. You can specify empty strings for one or the other of the axis labels. The vertical label will be centered or left justified above the vertical axis; the horizontal label will be placed below the end of the horizontal axis, centered or right justified.

By default, the graph will be centered within the image, with 50 pixels offset distance from its edges to the edges of the image (though a title will increase the vertical offset). Axis and tick labels and the title will appear in this margin (assuming all data are positive). You can obtain more space for a title or a horizontal label by increasing the image size (method new() ) and adjusting the offset.

Custom Tick Labels: Normally, Chart::Plot will draw five to ten ticks on each axis and label them with their corresponding data values. You can override this and supply your own custom tick labels to either axis in a hash reference, in which the axis position (eg, the axis data coordinate) is the key and the label at that distance along the axis is its value. If a key is not a number, an error message is set to the effect that a tick label is non-numeric. If you supply an empty hash reference, all ticks will be suppressed.

Draw the image: draw()

$img->draw();
$img->draw('jpeg') or die "$img->error()";

This method draws the image and returns it as a string, which you can print to a file or to STDOUT. (This should be the last method called from the $img object.) You will generally need to know which image format your version of GD supports: if it supports png, then to save the image in a file:

open (WR,'>plot.png') or die ("Failed to write file: $!");
binmode WR;            # for DOSish platforms
print WR $img->draw();
close WR;

Or, to return the graph from a cgi script:

print "Content-type: image/png\n\n";
print  $img->draw();

Or, to pipe it to a viewing program which accepts STDIN (such as xv on Unix)

open (VIEWER,'| /usr/X11R6/bin/xv -') or die ("Failed to open viewer: $!");
print VIEWER $img->draw();
close VIEWER;

Of course, if you have a version of GD which supports only gif, change the file names and types to gif. GD version 1.19 and below supported only gif image format. Versions between 1.20 and 1.26 support only png format. If you are not sure, or suspect the supported formats may change in the future, you can use

$extension = $img->image_type();
open (WR,">plot.$extension");

to obtain the type, 'png' or 'gif'. Often, you must know the type to write the correct file extension or to return the correct content type from a cgi script.

GD version 1.27 supports both png and jpeg image formats. For this version, draw() will default to 'png' unless you supply 'jpeg' as the argument. image_type() will return 'png' in scalar context and the list of all supported formats (png,jpeg) in array context.

If the argument to draw() is not a supported image format by the local version of GD, draw() will return undef and an error message will be set. error() will return 'The image format ... is not supported by this version ... of GD.'

Accessing the GD object directly

Chart::Plot is a front end to GD and creates an internal GD object. You can access the GD object directly, to use GD methods to draw on it in ways that Chart::Plot does not anticipate. The getGDobject() method in Chart::Plot returns the object reference to its internal GD object.

my $GDobject = $img->getGDobject();
my ($GDobject, $black, $white, $red, $green, $blue) 
    = $img->getGDObject();

In scalar context, this method returns only the reference to the GD object. It can also return a list containing the reference to the image object and the colors already created by Chart::Plot for that GD onject, in the order specified above. If you do not obtain these colors, you will need to allocate your own colors before drawing, example below.

When you call the draw() method of Chart::Plot (typically the last step in your script) any drawing you have done in your script with GD on the GD object will also be drawn.

Since Chart::Plot works with data values and GD works with pixel values, you will need the data2pxl() method of Chart::Plot to translate (x,y) pairs of data values to (px,py) pairs of pixel values. (You call this method on the Chart::Plot object, not the GD object.) You must call this method only after all datasets have been registered with the setData() method, since the graph scaling and this translation may change with each new dataset.

Here is a brief example which draws small blue circles around each data point in the chart.

   use Chart::Plot; 
   my $img = Chart::Plot->new; 
   my @data = qw( 10 11 11 12 12 13 13 14 14 15);
   $img->setData (\@data);
   
   # draw circles around each data point, diameter 15 pixels
   my $gd = $img->getGDobject;
   my $blue = $gd->colorAllocate(0,0,255); # or use $img's blue 
   my ($px,$py); 
   for (my $i=0; $i<$#data; $i+=2) {
     ($px,$py) = $img->data2pxl ($data[$i], $data[$i+1]);
     $gd->arc($px,$py,15,15,0,360,$blue);
   }

   # draw the rest of the chart, and print it 
   open (OUT,">plot.gif"); 
   binmode OUT; 
   print OUT $img->draw(); 
   close OUT; 

BUGS AND TO DO

If your data is bunched tightly but far away from the origin, then you will obtain a better chart if the graph is clipped away from the origin. I have not yet found a useful way to do this, but I am still thinking. You may be able to use Custom Tick Labels to improve your chart in the meantime.

You will probably be unhappy with axis tick labels running together if you use scientific notation. Controlling tick label formatting and length for scientific notation seems doable but challenging.

Future versions might incorporate a legend, control of font size, word wrap and dynamic adjustment of axis labels and title. Better code, a better pod page.

AUTHOR

Copyright (c) 1998-2000 by Sanford Morton <smorton@pobox.com> All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

This work is dedicated to the memory of Dr. Andrew Morton, who requested it. Requiescat in pace, my friend.

SEE ALSO

GD::Graph(1) (formerly GIFgraph(1)) and Chart(1) are other front end modules to GD(1). All can be found on CPAN.