The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package ScatterPlot;

#use 5.008001;
use strict;
use warnings;

require Exporter;

our @ISA = qw(Exporter);

# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.

# This allows declaration	use SCatterPlot ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = ( 'all' => [ qw(
	
) ] );

our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );

our @EXPORT = qw(
	
);

our $VERSION = '0.01';


# constructor
sub new {
    # declare the class name and assign it the input parameter
    my ($class_name) = @_;

    # create the new variable, its a hash
    my ($self) = [];

    # bless it to be an object within class $class_name
    bless ($self, $class_name);

    # return the hash and exit
    return $self;
}


# draw an ASCII plot
sub draw {
    # declare local copy of self and sport, assigning with input paramters
    my ($self, $xy_points, $x_size, $y_size, $x_label, $y_label, $char, $type) = @_;

    # default variables
    unless ($type) {
        $type = 'text';
    }
    unless ($char) {
        $char = 'o';
    }
    unless ($y_label) {
        $y_label = '';
    }
    unless ($x_label) {
        $x_label = '';
    }
    unless ($y_size) {
        $y_size = 26;
    } else {
        $y_size--;
    }
    unless ($x_size) {
        $x_size = 60;
    }
    unless ($xy_points) {
        my @xy_points = ();
        my $i_max = 20;
        for (my $i=0; $i<$i_max; $i++) {
            $xy_points[$i][0] = ($i - ($i_max - 1) / 2 ) * 6 / $i_max;
            $xy_points[$i][1] = ($xy_points[$i][0] + 2) * ($xy_points[$i][0] - 2) * $xy_points[$i][0];
        }
        $xy_points = \@xy_points;
    }



    # find the number of points to plot
    my $num_points = @$xy_points;

    # loop thru the points and find min/max values
    my $i = 0;
    my $x_min;
    my $x_max;
    my $y_min;
    my $y_max;
    while ((!$x_min or !$y_min) and ($i < $num_points)) {
        $x_min = $$xy_points[$i][0];
        $x_max = $$xy_points[$i][0];
        $y_min = $$xy_points[$i][1];
        $y_max = $$xy_points[$i][1];
        $i++;
    }
    for (my $j=$i; $j<$num_points; $j++) {
        if ($$xy_points[$j][0] and $$xy_points[$j][1]) {
            if ($$xy_points[$j][0] < $x_min) {
                $x_min = $$xy_points[$j][0];
            }
            if ($$xy_points[$j][0] > $x_max) {
                $x_max = $$xy_points[$j][0];
            }
            if ($$xy_points[$j][1] < $y_min) {
                $y_min = $$xy_points[$j][1];
            }
            if ($$xy_points[$j][1] > $y_max) {
                $y_max = $$xy_points[$j][1];
            }
        }
    }

    # calculate the scale and offset values
    my $x_scale = 0;
    if ($x_max - $x_min != 0) {
        $x_scale = $x_size / ($x_max - $x_min);
    }
    my $x_offset = -$x_min;
    my $y_scale = 0;
    if ($y_max - $y_min != 0) {
        $y_scale = $y_size / ($y_max - $y_min);
    }
    my $y_offset = -$y_min;

    # clear the graph
    for (my $x=0; $x<$x_size; $x++) {
        for (my $y=0; $y<=$y_size; $y++) {
            $$self[$x][$y] = ' ';
        }
    }

    # draw the axes
    my $x_axis = int($x_scale * $x_offset);
    my $y_axis = int($y_scale * $y_offset);
    if (($y_axis >= 0) and ($y_axis < $y_size)) {
        for (my $x=0; $x<$x_size; $x++) {
            $$self[$x][$y_axis] = '-';
        }
    }
    if (($x_axis >= 0) and ($x_axis < $x_size)) {
        for (my $y=0; $y<$y_size; $y++) {
            $$self[$x_axis][$y] = '|';
        }
    }
    if (($x_axis >= 0) and ($x_axis < $x_size) and($y_axis >= 0) and ($y_axis < $y_size)) {
        $$self[$x_axis][$y_axis] = '+';
    }

    # plot the points
    for (my $i=0; $i<$num_points; $i++) {
        my $x_pos = 0;
        if ($$xy_points[$i][0]) {
            $x_pos = int($x_scale * ($$xy_points[$i][0] + $x_offset));
        }
        if ($x_pos < 0) {
            $x_pos = 0;
        } elsif ($x_pos > $x_size - 1) {
            $x_pos = $x_size - 1;
        }
        my $y_pos = 0;
        if ($$xy_points[$i][1]) {
            $y_pos = int($y_scale * ($$xy_points[$i][1] + $y_offset));
        }
        if ($y_pos < 0) {
            $y_pos = 0;
        } elsif ($y_pos > $y_size - 1) {
            $y_pos = $y_size - 1;
        }
        if ($$xy_points[$i][0] and $$xy_points[$i][1]) {
            $$self[$x_pos][$y_pos] = $char;
        }
    }

    # add the axes limits
    # left label
    my $y_pos = 1;
    if ($y_axis < 1) {
        $y_pos = 1;
    } elsif ($y_axis > $y_size) {
        $y_pos = $y_size;
    } else {
        $y_pos = $y_axis;
    }
    my $label = sprintf("%0.1f ", $x_min);
    my $l = length($label);
    for (my $i=0; $i<$l; $i++) {
        $$self[$i][$y_pos] = substr($label, $i, 1);
    }
    # right label
    $label = sprintf(" %0.1f", $x_max);
    $l = length($label);
    my $x_label_pos = $x_size - $l;
    for (my $i=0; $i<$l; $i++) {
        $$self[$x_label_pos+$i][$y_pos] = substr($label, $i, 1);
    }
    # bottom label
    $label = sprintf("%0.1f", $y_min);
    $l = length($label);
    my $y_label_pos = $x_axis - int($l/2);
    if ($y_label_pos < 0) {
        $y_label_pos = 0;
    } elsif ($y_label_pos + $l > $x_size) {
        $y_label_pos = $x_size - $l;
    }
    for (my $i=0; $i<$l; $i++) {
        $$self[$y_label_pos+$i][0] = substr($label, $i, 1);
    }
    # top label
    $label = sprintf("%0.1f", $y_max);
    $l = length($label);
    $y_label_pos = $x_axis - int($l/2);
    if ($y_label_pos < 0) {
        $y_label_pos = 0;
    } elsif ($y_label_pos + $l > $x_size) {
        $y_label_pos = $x_size - $l;
    }
    for (my $i=0; $i<$l; $i++) {
        $$self[$y_label_pos+$i][$y_size-1] = substr($label, $i, 1);
    }

    # add the labels
    # x label
    $l = length($x_label);
    $x_label_pos = $x_size - $l;
    for (my $i=0; $i<$l; $i++) {
        $$self[$x_label_pos+$i][$y_pos+1] = substr($x_label, $i, 1);
    }
    # y label
    $l = length($y_label);
    $y_label_pos = $x_axis - int($l/2);
    if ($y_label_pos < 0) {
        $y_label_pos = 0;
    } elsif ($y_label_pos + $l > $x_size) {
        $y_label_pos = $x_size - $l;
    }
    for (my $i=0; $i<$l; $i++) {
        $$self[$y_label_pos+$i][$y_size] = substr($y_label, $i, 1);
    }

    # print the $self
    my $prefix = "\n";
    my $newline = "\n";
    my $postfix = "\n";
    if (($type eq 'html') or ($type eq 'HTML')) {
        $prefix = "<pre>\n";
        $newline = "\n";
        $postfix = "</pre>\n";
    }
    print $prefix;
    for (my $y=$y_size; $y>=0; $y--) {
        for (my $x=0; $x<$x_size; $x++) {
            print $$self[$x][$y];
        }
        print $newline;
    }
    print $postfix;

    return 1;
}


1;
__END__

=head1 NAME

ScatterPlot - Perl extension for drawing ASCII scatter plots

=head1 SYNOPSIS

  use ScatterPlot;

=head1 DESCRIPTION

This module will draw a quick and easy ASCII scatter plot.  It has only two functions, new() and draw().  new() takes no arguments and creates a new ScatterPlot object.  draw() can be called with no arguments to draw a sample test plot.  You can call draw like this:  

    draw($xy_points);

where $xy_points is a reference to an array of (x,y) pairs.  See the file ScatterPlot.pl for an example.  The full call to draw is: 

    draw($xy_points, $x_size, $y_size, $x_label, $y_label, $char, $type);

where $xy_points is a reference to an array of (x,y) pairs, $x_size is an integer describing the width of the plot in characters, $y_size is an integer describing the height of the plot in characters, $x_label is a string for the horizontal axis label, $y_label is a string for the vertical axis lable, $char is the plot character, and $type is either 'text', 'html', or 'HTML'.  If you are using CGI or sending the plot output to a web page, then use $type='html' or $type='HTML'.  

The method draw() will automatically scale the plot to fit your data and draw the axes labels accordingly.  The size of the output text will be $y_size lines of text, each of which is $x_size long in characters (plus line terminator).  In text mode the plot begins with "\n" and ends with "\n", while in html mode the plot begins with "<pre>" and ends with "<\pre>".  

=head2 EXPORT

none


=head1 SEE ALSO

The example file ScatterPlot.pl contains an example of how to use the ScatterPlot module.  

=head1 AUTHOR

Les Hall, E<lt>inventor-66@comcast.netE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2007 by Les Hall

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself. 

=cut