#============================#
#                            #
#  Chart::Mountain           #
#                            #
#  Inspired by Chart::Lines  #
#  by davidb bonner          #
#  dbonner@cs.bu.edu         #
#                            #
#  Updated for               #
#  compatibility with        #
#  changes to Chart::Base    #
#  by peter clark            #
#  ninjaz@webexpress.com     #
#                            #
#============================#

# Copyright 1998, 1999 by James F. Miner.
# All rights reserved. 
# This program is free software; you can redistribute it 
# and/or modify it under the same terms as Perl itself. 

package Chart::Mountain;

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

@Chart::Mountain::ISA = qw ( Chart::Base );
@Chart::Mountain::VERSION = '0.03';

my $DEBUG = 0;

##  Some Mountain chart details:
#
#   The effective y data value for a given x point and dataset
#   is the sum of the actual y data values of that dataset and 
#   all datasets "below" it (i.e., with higher dataset indexes).
# 
#   If the y data value in any dataset is undef or negative for 
#   a given x, then all datasets are treated as missing for that x.
#   
#   The y minimum is always forced to zero.
# 
#   To avoid a dataset area "cutting into" the area of the dataset below
#   it, the y pixel for each dataset point will never be below the y pixel for
#   the same point in the dataset below the dataset.

#   This probably should have a custom legend method, because each 
#   dataset is identified by the fill color (and optional pattern)
#   of its area, not just a line color.  So the legend shou a square
#   of the color and pattern for each dataset.

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

sub _find_y_range {
    my $self = shift;
    
    #   This finds the maximum point-sum over all x points,
    #   where the point-sum is the sum of the dataset values at that point.
    #   If the y value in any dataset is undef for a given x, then all datasets
    #   are treated as missing for that x.
    
    my $data = $self->{'dataref'};
    my $max = undef;
    for my $i (0..$#{$data->[0]}) {
	my $y_sum = $data->[1]->[$i];
	if ( defined $y_sum && $y_sum >= 0 ) {
	    for my $dataset ( @$data[2..$#$data] ) { # order not important
		my $datum = $dataset->[$i];
		if ( defined $datum && $datum >= 0 ) { 
		    $y_sum += $datum 
		}
		else { # undef or negative, treat all at same x as missing.
		    $y_sum = undef;  
		    last 
		}
	    }
	}
	if ( defined $y_sum ) {
	    $max = $y_sum unless defined $max && $y_sum <= $max;
	}
    }

## new _find_y_scale does this:
#   my $tmp = ($max) ? 10 ** (int (log ($max) / log (10))) : 10;
#   $max = $tmp * (int ($max / $tmp) + 1);

    (0, $max);
}


sub _draw_data {
    my $self = shift;
    my $data = $self->{'dataref'};
    
    my @patterns = @{ $self->{'patterns'} || [] };
    
    # Calculate array of x pixel positions (@x).
    my $x_step = ($self->{'curr_x_max'} - $self->{'curr_x_min'}) / $self->{'num_datapoints'};
    my $x_min = $self->{'curr_x_min'} + $x_step / 2;
    my $x_max = $self->{'curr_x_max'} - $x_step / 2;
    my @x = map { $_ * $x_step + $x_min } 0..$self->{'num_datapoints'}-1;
    
    # Calculate array of y pixel positions for upper boundary each dataset (@y).
    
    my $map = ($self->{'max_val'})
 		? ($self->{'curr_y_max'} - $self->{'curr_y_min'}) / $self->{'max_val'}
                : ($self->{'curr_y_max'} - $self->{'curr_y_min'}) / 10;

    my $y_max = $self->{'curr_y_max'}; # max pixel point (lower y values)
    
    my @y;
    for my $j (0..$#{$data->[0]}) {
	my $sum = 0;
	for my $i (reverse 1..$#{$data}) { # bottom to top of chart
	    my $datum = $data->[$i][$j];
	    if ( defined $datum && $datum >= 0 ) {
		$sum += $datum;
		$y[$i-1][$j] = $y_max - $map * $sum;
                if ( $y[$i-1][$j] < $self->{'curr_y_min'} ) {
                   $y[$i-1][$j] = $self->{'curr_y_min'};
                }
	    }
	    else { # missing value, force all to undefined
		foreach my $k (1..$#{$data}) { $y[$k-1][$j] = undef }
		last;
	    }
	}
    }
    
    # Find first and last x where y is defined in the bottom dataset.
    my $x_begin = 0;
    my $x_end = $self->{'num_datapoints'}-1;
    while ( $x_begin <= $x_end && ! defined $y[-1]->[$x_begin] ) { $x_begin++ }
    while ( $x_begin <= $x_end && ! defined $y[-1]->[$x_end] ) { $x_end-- }
   
    # For each dataset, generate a polygon for the dataset's area of the chart,
    # and fill the polygon with the dataset's color/pattern.
    
    my $poly = GD::Polygon->new;
    $poly->addPt($x[$x_end], $y_max); # right end of x axis
    $poly->addPt($x[$x_begin], $y_max); # left end of x axis (right-to-left)
    
    for my $dataset (reverse 0..@y-1) {
	my $y_ref = $y[$dataset];
    
	# Append points for this dataset to polygon, direction depends on $dataset % 2.
	my $last_vertex_count = $poly->length;
	if ( (@y - 1 - $dataset) % 2 ) { # right-to-left
	    for (reverse $x_begin..$x_end) { 
		$poly->addPt($x[$_], $y_ref->[$_]) if defined $y_ref->[$_] 
	    }
	}
	else { # left-to-right
	    for ($x_begin..$x_end) { 
		$poly->addPt($x[$_], $y_ref->[$_]) if defined $y_ref->[$_] 
	    }
	}
		
	# draw the polygon
	my $color = $self->_color_role_to_index('dataset'.$dataset);
	if ( $patterns[$dataset] ) {
	    $self->{'gd_obj'}->filledPolygon($poly, $color) 
		if $patterns[$dataset]->transparent >= 0;
	    $self->{'gd_obj'}->setTile($patterns[$dataset]);
	    $self->{'gd_obj'}->filledPolygon($poly, gdTiled);
	}
	else {
	    $self->{'gd_obj'}->filledPolygon($poly, $color);
	}
	
	# delete previous dataset's points from the polygon, update $last_vertex_count.
	unless ( $dataset == 0 ) { # don't bother do delete points after last area
	    while ( $last_vertex_count ) { $poly->deletePt(0); $last_vertex_count-- }
	}
    }

    # Enclose the plots
    $self->{'gd_obj'}->rectangle(
	$self->{'curr_x_min'}, $self->{'curr_y_min'},
	$self->{'curr_x_max'}, $self->{'curr_y_max'},
	$self->_color_role_to_index('misc')
    );

}    


###############################################################

### Fix a bug in GD::Polygon.  
### A patch has been submitted to Lincoln Stein.

require GD;
unless ( defined &GD::Polygon::deletePt ) { 
    *GD::Polygon::deletePt  = sub {
	my($self,$index) = @_;
	unless (($index >= 0) && ($index < @{$self->{'points'}})) {
	    warn "Attempt to set an undefined polygon vertex";
	    return undef;
	}
	my($vertex) = splice(@{$self->{'points'}},$index,1);
	$self->{'length'}--;
	return @$vertex;
    }
}

###############################################################

1;
