The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#============================#
#                            #
#  Chart::Points	     #
#  written by davidb bonner  #
#  dbonner@cs.bu.edu         #
#                            #
#============================#

package Chart::Points;

use Chart::Base;
use GD;
use Carp;
use strict;

@Chart::Points::ISA = qw ( Chart::Base );

#==================#
#  public methods  #
#==================#



#===================#
#  private methods  #
#===================#

sub find_range {
    my $obj = shift;
    my $dataref = $obj->{'data'};
    my $max = 0;
    my ($tmp, $i);
    
    for (1..$#{$dataref}) {
	for $i (0..$#{$dataref->[$_]}) {
	    if ($dataref->[$_][$i] > $max) {
		$max = $dataref->[$_][$i];
	    }
	}
    }

    $tmp = ($max) ? 10 ** (int (log ($max) / log (10))) : 10;
    $max = $tmp * (int ($max / $tmp) + 1);
    $obj->{'max_val'} = $max;
}

sub draw_ticks {
    my $obj = shift;
    my $dataref = $obj->{'data'};
    my $black = $obj->get_color ('black');
    my $grey = $obj->get_color ('grey');
    my ($y_step, $y_diff, $x_step, $val, $str_len);
    my ($h, $w) = (gdSmallFont->height, gdSmallFont->width);
    my $str_max = 0;
    my ($x_min, $x_max, @dec);
    my $stag = 0;
    my @ticks;
    
    #===============================#
    #  check for custom tick array  #
    #===============================#

    if ($obj->{'custom_x_ticks'}) {
	@ticks = sort {$Chart::Points::a <=> $Chart::Points::b} 
	           @{$obj->{'custom_x_ticks'}};
    }

    #==========================#
    #  draw the y tick labels  #  
    #==========================#

    $y_diff = ($obj->{'stagger_x_labels'}) 
	? 2 * $h + $obj->{'text_space'} 
        : $h + $obj->{'text_space'};
    $y_step = (($obj->{'y_max'} - 
		($obj->{'y_min'} + $obj->{'tick_len'} + $y_diff)) 
	       / $obj->{'y_ticks'});

    for (0..$obj->{'y_ticks'}) {
	$val = (($obj->{'max_val'} / $obj->{'y_ticks'}) * $_);
        @dec = split /\./, $val;
        if ($dec[1] && length($dec[1]) > 3) { $val = sprintf ("%.3f", $val) }
	$str_len = length($val);
	
	if ($str_len > $str_max) {
	    $str_max = $str_len;
	}
    }

    
    for (0..$obj->{'y_ticks'}) {
	$val = ($obj->{'max_val'} / $obj->{'y_ticks'}) * $_;
        @dec = split /\./, $val;
        if ($dec[1] && length($dec[1]) > 3) { $val = sprintf ("%.3f", $val) }
	$str_len = length($val);
	$obj->{'im'}->string (gdSmallFont,
			      $obj->{'x_min'} + ($str_max - $str_len) * $w 
			          + 2 * $obj->{'text_space'},
			      $obj->{'y_max'} - $y_step * $_ - $h / 2
			         - $obj->{'tick_len'} - $y_diff,
			      $val,
			      $black);
    }

    $obj->{'x_min'} += ($str_max * $w) + 3 * $obj->{'text_space'};
    
    #==========================#
    #  draw the x tick labels  #
    #==========================#

    ($x_min, $x_max) = ($obj->{'x_min'} + 10 + $obj->{'tick_len'},
			$obj->{'x_max'} - 10);
    $x_step = ($x_max - $x_min) / $#{$dataref->[0]};
    

    if (@ticks) {  #custom ticks
	for (@ticks) {
	    $val = $dataref->[0][$_];
	    $str_len = length($val) * ($w/2);

	    my $y;
	    if ($obj->{'stagger_x_labels'} eq 'true') {
		$y = ($stag++ % 2) ? $obj->{'y_max'} - (2 * $h)
		    : $obj->{'y_max'} - ($h);
	    }
	    else {
		$y = $obj->{'y_max'} - (1.5 * $h);
	    }
	    
	    $obj->{'im'}->string (gdSmallFont,
				  $x_min - $str_len + ($x_step * $_),
				  $y,
				  $val,
				  $black);
	}
    }
    elsif ($obj->{'skip_x_ticks'}) {  #every n ticks
	for (0..$#{$dataref->[0]}) {
	    $val = $dataref->[0][$_];
	    $str_len = length($val) * ($w/2);
	    if ($_ % $obj->{'skip_x_ticks'} == 0) {
		my $y;
		if ($obj->{'stagger_x_labels'} eq 'true') {
		    $y = ($stag++ % 2) ? $obj->{'y_max'} - (2 * $h)
			: $obj->{'y_max'} - ($h);
		}
		else {
		    $y = $obj->{'y_max'} - (1.5 * $h);
		}
		
		$obj->{'im'}->string (gdSmallFont,
				      $x_min - $str_len + ($x_step * $_),
				      $y,
				      $val,
				      $black);
	    }
	}
    }
    else {  #all the ticks
	for (0..$#{$dataref->[0]}) {
	    $val = $dataref->[0][$_];
	    $str_len = length($val) * ($w/2);

	    my $y;
	    if ($obj->{'stagger_x_labels'} eq 'true') {
		$y = ($stag++ % 2) ? $obj->{'y_max'} - (2 * $h)
		    : $obj->{'y_max'} - ($h);
	    }
	    else {
		$y = $obj->{'y_max'} - (1.5 * $h);
	    }
		

	    $obj->{'im'}->string (gdSmallFont,
				  $x_min - $str_len + ($x_step * $_),
				  $y,
				  $val,
				  $black);
	}
    }

    $obj->{'y_max'} -= ($obj->{'stagger_x_labels'} eq 'true') 
	? 2 * $h + $obj->{'text_space'} 
        : $h + $obj->{'text_space'};

    #==================#
    #  draw the ticks  #
    #==================#

    $obj->{'x_min'} += $obj->{'tick_len'};
    $obj->{'y_max'} -= $obj->{'tick_len'};

    $y_step = ($obj->{'y_max'} - $obj->{'y_min'}) / $obj->{'y_ticks'};
    ($x_min, $x_max) = ($obj->{'x_min'} + 10, $obj->{'x_max'} - 10);
    $x_step = ($x_max - $x_min) / $#{$dataref->[0]};

    if (@ticks) {  #custom ticks
	for (@ticks) {
	    $obj->{'im'}->line ($x_min + ($x_step * $_),
				$obj->{'y_max'},
				$x_min + $x_step * $_,
				$obj->{'y_max'} + $obj->{'tick_len'},
				$black);
            if ($obj->{'grid_lines'} && $obj->{'grid_lines'} eq 'true') {
                $obj->{'im'}->line ($x_min + $x_step * $_,
                                    $obj->{'y_max'},
                                    $x_min + $x_step * $_,
                                    $obj->{'y_min'},
                                    $grey);
            }
	}
    }
    elsif ($obj->{'skip_x_ticks'}) {  #every n ticks
	for (0..$#{$dataref->[0]}) {
	    if ($_ % $obj->{'skip_x_ticks'} == 0) {
		$obj->{'im'}->line ($x_min + ($x_step * $_),
				    $obj->{'y_max'},
				    $x_min + $x_step * $_,
				    $obj->{'y_max'} + $obj->{'tick_len'},
				    $black);
                if ($obj->{'grid_lines'} && $obj->{'grid_lines'} eq 'true') {
                    $obj->{'im'}->line ($x_min + $x_step * $_,
                                        $obj->{'y_max'},
                                        $x_min + $x_step * $_,
                                        $obj->{'y_min'},
                                        $grey);
                }
	    }
	}
    }
    else {
	for (0..$#{$dataref->[0]}) {
	    $obj->{'im'}->line ($x_min + ($x_step * $_),
				$obj->{'y_max'},
				$x_min + $x_step * $_,
				$obj->{'y_max'} + $obj->{'tick_len'},
				$black);
            if ($obj->{'grid_lines'} && $obj->{'grid_lines'} eq 'true') {
                $obj->{'im'}->line ($x_min + $x_step * $_,
                                    $obj->{'y_max'},
                                    $x_min + $x_step * $_,
                                    $obj->{'y_min'},
                                    $grey);
            }
	}
    }

    for (0..$obj->{'y_ticks'}-1) {
	$obj->{'im'}->line ($obj->{'x_min'},
			    $obj->{'y_min'} + $y_step * $_,
			    $obj->{'x_min'} - $obj->{'tick_len'},
			    $obj->{'y_min'} + $y_step * $_,
			    $black);
        if ($obj->{'grid_lines'} && $obj->{'grid_lines'} eq 'true') {
            $obj->{'im'}->line ($obj->{'x_min'},
                                $obj->{'y_min'} + $y_step * $_,
                                $obj->{'x_max'},
                                $obj->{'y_min'} + $y_step * $_,
                                $grey);
        }
    }
}

sub draw_data {
    my $obj = shift;
    my $dataref = $obj->{'data'};
    my ($x_step, $ref);
    my ($dataset, $color, @data);
    my ($x_min, $x_max);

    if (!($obj->{'max_val'})) { $obj->find_range ($dataref); }
    $obj->draw_ticks ($dataref);

    ($x_min, $x_max) = ($obj->{'x_min'} + 10, $obj->{'x_max'} - 10);
    $x_step = ($x_max - $x_min) / $#{$dataref->[0]};

    $ref = $obj->data_map ($dataref);
    
    for $dataset (0..$#{$ref}) {
	$color = $obj->data_color ($dataset);
	@data = @{$ref->[$dataset]};
	for (0..$#data) {
	    $obj->{'im'}->filledRectangle (($_) * $x_step + $x_min
					   - ($obj->{'pt_size'} / 2),
					   $data[$_] - ($obj->{'pt_size'} / 2),
					   ($_) * $x_step + $x_min
					   + ($obj->{'pt_size'} / 2),
					   $data[$_] + ($obj->{'pt_size'} / 2),
					   $color) if defined ($data[$_]);
	}
    }

    $obj->draw_axes;
}    

sub data_map {
    my $obj = shift;
    my $dataref = $obj->{'data'};
    my ($ref, $map, $i, $j);
    
    $map = ($obj->{'max_val'})
                ? ($obj->{'y_max'} - $obj->{'y_min'}) / $obj->{'max_val'}
                : ($obj->{'y_max'} - $obj->{'y_min'}) / 10;

    for $i (1..$#{$dataref}) {
	for $j (0..$#{$dataref->[$i]}) {
	    $ref->[$i-1][$j] = (defined ($dataref->[$i][$j]))
	    			? $obj->{'y_max'} - $map * $dataref->[$i][$j]
				: undef;
	}
    }

    return $ref;
}

1;