Chart-Plot
view release on metacpan or search on metacpan
#===================#
# initialization
# this contains a record of all private data except class variables, up top
sub _init {
my $self = shift;
# create an image object
if ($#_ == 1) {
$self->{'_im'} = new GD::Image($_[0], $_[1]);
$self->{'_imx'} = $_[0];
$self->{'_imy'} = $_[1];
}
else {
$self->{'_im'} = new GD::Image(400,300);
$self->{'_imx'} = 400;
$self->{'_imy'} = 300;
}
# find format(s) supported by GD
unless (@_image_types) {
for ( qw(png gif jpeg) ) {
push @_image_types, $_ if $self->{'_im'}->can($_);
}
}
# set graph offset; graph will be centered this many pixels within image
$self->{'_horGraphOffset'} = 50;
$self->{'_vertGraphOffset'} = 50;
# create an empty hash for the datsets
# data sets and their styles are hashes whose keys are 1 ... _numDataSets
# and values are refs to flat data arrays or style strings, respectively
$self->{'_data'} = {};
$self->{'_dataStyle'} = {};
$self->{'_numDataSets'} = 0;
# calculated by _getMinMax and used in translating _data2pxl()
$self->{'_xmin'} = 0; $self->{'_xmax'} = 0; # among all datasets
$self->{'_ymin'} = 0; $self->{'_ymax'} = 0;
$self->{'_xslope'} = 0; $self->{'_yslope'} = 0; # for _data2pxl()
$self->{'_ax'} = 0; $self->{'_ay'} = 0;
$self->{"_omx"} = 0; $self->{"_omy"} = 0; # for axis ticks
$self->{'_validMinMax'} = 0; # last calculated min and max still valid
# initialize text
($self->{'_horAxisLabel'}, $self->{'_vertAxisLabel'}) = ('','');
$self->{'_title'} = '';
$self->{'_errorMessage'} = '';
# initialize custom tick labels
($self->{'_xTickLabels'}, $self->{'_yTickLabels'}) = (0,0);
# allocate some colors
$self->{'_white'} = $self->{'_im'}->colorAllocate(255,255,255);
$self->{'_black'} = $self->{'_im'}->colorAllocate(0,0,0);
$self->{'_red'} = $self->{'_im'}->colorAllocate(255,0,0);
$self->{'_blue'} = $self->{'_im'}->colorAllocate(0,0,255);
$self->{'_green'} = $self->{'_im'}->colorAllocate(0,255,0);
# make the background transparent and interlaced
$self->{'_im'}->transparent($self->{'_white'});
$self->{'_im'}->interlaced('true');
# Put a black frame around the picture
$self->{'_im'}->rectangle( 0, 0,
$self->{'_imx'}-1, $self->{'_imy'}-1,
$self->{'_black'});
# undocumented: in script, use as $plotObject->{'_debugging'} = 1;
$self->{'_debugging'} = 0;
}
# sets min and max values of all data (_xmin, _ymin, _xmax, _ymax);
# also sets _xslope, _yslope, _ax and _ay used in _data2pxl;
# usage: $self->_getMinMax
sub _getMinMax {
my $self = shift;
my ($i, $arrayref);
my ($xmin, $ymin, $xmax, $ymax);
# if no data, set arbitrary bounds
($xmin, $ymin, $xmax, $ymax) = (0,0,1,1) unless keys %{$self->{'_data'}} > 0;
# initialize to zero
$xmin = $xmax = $ymin = $ymax = 0;
# # or to first data point of an arbitrary dataset
# foreach (keys %{$self->{'_data'}}) {
# $arrayref = $self->{'_data'}->{$_};
# $xmin = $xmax = ($self->{'_noZeroX'} ? $$arrayref[0] : 0);
# $ymin = $ymax = ($self->{'_noZeroY'} ? $$arrayref[1] : 0);
# last; # skip any other datasets
# }
# cycle through the datasets looking for min and max values
foreach (keys %{$self->{'_data'}}) {
$arrayref = $self->{'_data'}->{$_};
for ($i=0; $i<$#{$arrayref}; $i++) {
$xmin = ($xmin > $$arrayref[$i] ? $$arrayref[$i] : $xmin);
$xmax = ($xmax < $$arrayref[$i] ? $$arrayref[$i] : $xmax);
$i++;
$ymin = ($ymin > $$arrayref[$i] ? $$arrayref[$i] : $ymin);
$ymax = ($ymax < $$arrayref[$i] ? $$arrayref[$i] : $ymax);
}
}
# set axes data ranges as decimal order of magnitude of widest dataset
($self->{'_xmin'}, $self->{'_xmax'}) = $self->_getOM ('x', $xmin,$xmax);
($self->{'_ymin'}, $self->{'_ymax'}) = $self->_getOM ('y', $ymin,$ymax);
# calculate conversion constants for _data2pxl()
$self->{'_xslope'} = ($self->{'_imx'} - 2 * $self->{'_horGraphOffset'})
/ ($self->{'_xmax'} - $self->{'_xmin'});
$self->{'_yslope'} = ($self->{'_imy'} - 2 * $self->{'_vertGraphOffset'})
/ ($self->{'_ymax'} - $self->{'_ymin'});
$self->{'_ax'} = $self->{'_horGraphOffset'};
$self->{'_ay'} = $self->{'_imy'} - $self->{'_vertGraphOffset'};
=head1 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();
=head1 DESCRIPTION
I wrote B<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.)
B<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 B<GD.pm> library by Lincoln Stein,
available on B<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.)
=head1 USAGE
=head2 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.
=head2 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
C<$img-E<gt>setData()> repeatedly on different datasets.
B<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 ($),
( run in 2.607 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )