#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#
#  Chart::StackedBars            #
#                                #
#  written by david bonner       #
#  dbonner@cs.bu.edu             #
#                                #
#  maintained by the Chart Group #
#  Chart@wettzell.ifag.de        #
#                                #
#  theft is treason, citizen     #
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<#

package Chart::StackedBars;

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

@Chart::StackedBars::ISA = qw(Chart::Base);
$Chart::StackedBars::VERSION = '2.4.1';

#>>>>>>>>>>>>>>>>>>>>>>>>>>#
#  public methods go here  #
#<<<<<<<<<<<<<<<<<<<<<<<<<<#

#>>>>>>>>>>>>>>>>>>>>>>>>>>>#
#  private methods go here  #
#<<<<<<<<<<<<<<<<<<<<<<<<<<<#

## override check_data to make sure we don't get datasets with positive
## and negative values mixed
sub _check_data {
  my $self = shift;
  my $data = $self->{'dataref'};
  my $length = 0;
  my ($i, $j, $posneg);
  my $composite;
  # remember the number of datasets
  if (defined $self->{'composite_info'}) { 
    if ($self->{'composite_info'}[0][0] =~ /^StackedBars$/i) {
      $composite=0; 
      }
    if ($self->{'composite_info'}[1][0] =~ /^StackedBars$/i) {
      $composite=1;
      }
 # $self->{'num_datasets'} = $#{$data};     ###
  
  $self->{'num_datasets'} = ($#{$self->{'composite_info'}[$composite][1]})+1;
  }
  else {
  $self->{'num_datasets'} = $#{$data}; 
  }
  # remember the number of points in the largest dataset
  $self->{'num_datapoints'} = 0;
  for (0..$self->{'num_datasets'}) { 
  if (scalar(@{$data->[$_]}) > $self->{'num_datapoints'}) {
      $self->{'num_datapoints'} = scalar(@{$data->[$_]});
    }
  }

  # make sure the datasets don't mix pos and neg values
  for $i (0..$self->{'num_datapoints'}-1) {
    $posneg = '';
    for $j (1..$self->{'num_datasets'}) {
      if ($data->[$j][$i] > 0) {
	if ($posneg eq 'neg') {
	  croak "The values for a Chart::StackedBars data point must either be all positive or all negative";
	}
	else {
	  $posneg = 'pos';
	}
      }
      elsif ($data->[$j][$i] < 0) {
	if ($posneg eq 'pos') {
	  croak "The values for a Chart::StackedBars data point must either be all positive or all negative";
	}
	else {
	  $posneg = 'neg';
	}
      }
    }
  }

  # find good min and max y-values for the plot
  $self->_find_y_scale;

  # find the longest x-tick label
  for (@{$data->[0]}) {
    if (length($_) > $length) {
      $length = length ($_);
    }
  }

  # now store it in the object
  $self->{'x_tick_label_length'} = $length;

  
  return;
}


sub _find_y_range {
  my $self = shift;
  
  #   This finds the minimum and maximum point-sum over all x points,
  #   where the point-sum is the sum of the dataset values for that point.
  #   If the y value in any dataset is undef for a given x, it simply
  #   adds nothing to the sum.
  
  
  my $data = $self->{'dataref'};
  my $max = undef;
  my $min = undef;
  for my $i (0..$#{$data->[0]}) { # data point
    my $sum = $data->[1]->[$i] || 0;
    for my $dataset ( @$data[2..$#$data] ) { # order not important
      my $datum = $dataset->[$i];
      $sum += $datum if defined $datum;
    }
    if ( defined $max ) {
      if ( $sum > $max ) { $max = $sum }
      elsif ( $sum < $min ) { $min = $sum }
    }
    else { $min = $max = $sum }
  }


  # make sure all-positive or all-negative charts get anchored at
  # zero so that we don't cut out some parts of the bars
  if (($max > 0) && ($min > 0)) {
    $min = 0;
  }
  if (($min < 0) && ($max < 0)) {
    $max = 0;
  }

	($min, $max);
}


# ## override _find_y_scale to account for stacked bars
# sub _find_y_scale {
#   my $self = shift;
#   my $raw = $self->{'dataref'};
#   my $data = [@{$raw->[1]}];
#   my ($i, $j, $max, $min);
#   my ($order, $mult, $tmp);
#   my ($range, $delta, @dec, $y_ticks);
#   my $labels = [];
#   my $length = 0;
# 
#   # use realy weird max and min values
#   $max = -999999999999;
#   $min = 999999999999;
# 
#   # go through and stack them
#   for $i (0..$self->{'num_datapoints'}-1) {
#     for $j (2..$self->{'num_datasets'}) {
#       $data->[$i] += $raw->[$j][$i];
#     }
#   }
# 
#   # get max and min values
#   for $i (0..$self->{'num_datapoints'}-1) {
#     if ($data->[$i] > $max) {
#       $max = $data->[$i];
#     }
#     if ($data->[$i] < $min) {
#       $min = $data->[$i];
#     }
#   }
# 
#   # make sure all-positive or all-negative charts get anchored at
#   # zero so that we don't cut out some parts of the bars
#   if (($max > 0) && ($min > 0)) {
#     $min = 0;
#   }
#   if (($min < 0) && ($max < 0)) {
#     $max = 0;
#   }
# 
#   # calculate good max value
#   if ($max < -10) {
#     $tmp = -$max;
#     $order = int((log $tmp) / (log 10));
#     $mult = int ($tmp / (10 ** $order));
#     $tmp = ($mult - 1) * (10 ** $order);
#     $max = -$tmp;
#   }
#   elsif ($max < 0) {
#     $max = 0;
#   }
#   elsif ($max > 10) {
#     $order = int((log $max) / (log 10));
#     $mult = int ($max / (10 ** $order));
#     $max = ($mult + 1) * (10 ** $order);
#   }
#   elsif ($max >= 0) {
#     $max = 10;
#   }
# 
#   # now go for a good min
#   if ($min < -10) {
#     $tmp = -$min;
#     $order = int((log $tmp) / (log 10));
#     $mult = int ($tmp / (10 ** $order));
#     $tmp = ($mult + 1) * (10 ** $order);
#     $min = -$tmp;
#   }
#   elsif ($min < 0) {
#     $min = -10;
#   }
#   elsif ($min > 10) {
#     $order = int ((log $min) / (log 10));
#     $mult = int ($min / (10 ** $order));
#     $min = $mult * (10 ** $order);
#   }
#   elsif ($min >= 0) {
#     $min = 0;
#   }
# 
#   # put the appropriate min and max values into the object if necessary
#   unless (defined ($self->{'max_val'})) {
#     $self->{'max_val'} = $max;
#   }
#   unless (defined ($self->{'min_val'})) {
#     $self->{'min_val'} = $min;
#   }
# 
#   # generate the y_tick labels, store them in the object
#   # figure out which one is going to be the longest
#   $range = $self->{'max_val'} - $self->{'min_val'};
#   $y_ticks = $self->{'y_ticks'} - 1;
#   ## Don't adjust y_ticks if the user specified custom labels
#   if ($self->{'integer_ticks_only'} =~ /^true$/i && ! $self->{'y_tick_labels'}) {
#     unless (($range % $y_ticks) == 0) {
#       while (($range % $y_ticks) != 0) {
# 	$y_ticks++;
#       }
#       $self->{'y_ticks'} = $y_ticks + 1;
#     }
#   }
#     
#   $delta = $range / $y_ticks;
#   for (0..$y_ticks) {
#     $tmp = $self->{'min_val'} + ($delta * $_);
#     @dec = split /\./, $tmp;
#     if ($dec[1] && (length($dec[1]) > 3)) {
#       $tmp = sprintf("%.3f", $tmp);
#     }
#     $labels->[$_] = $tmp;
#     if (length($tmp) > $length) {
#       $length = length($tmp);
#     }
#   }
# 
#   # store it in the object
#   $self->{'y_tick_labels'} = $labels;
#   $self->{'y_tick_label_length'} = $length;
#  
#   # and return
#   return;
# }


## finally get around to plotting the data
sub _draw_data {
  my $self = shift;
  my $raw = $self->{'dataref'};
  my $data = [];
  my $misccolor = $self->_color_role_to_index('misc');
  my ($width, $height, $delta, $map, $mod);
  my ($x1, $y1, $x2, $y2, $x3, $y3, $i, $j, $color, $cut);
  my $pink = $self->{'gd_obj'}->colorAllocate(255,0,255);

  # init the imagemap data field if they want it
  if ($self->{'imagemap'} =~ /^true$/i) {
    $self->{'imagemap_data'} = [];
  }
   
  # width and height of remaining area, delta for width of bars, mapping value
  $width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
   
  if ($self->{'spaced_bars'} =~ /^true$/i) {
   $delta = ($width / ($self->{'num_datapoints'} * 2));
    }
  else {
    $delta = $width / $self->{'num_datapoints'};
    }
  $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
  $map = $height / ($self->{'max_val'} - $self->{'min_val'});

  # get the base x and y values
  $x1 = $self->{'curr_x_min'};
  if ($self->{'min_val'} >= 0) {
    $y1 = $self->{'curr_y_max'};
    $mod = $self->{'min_val'};
  }
  elsif ($self->{'max_val'} <= 0) {
    $y1 = $self->{'curr_y_min'};
    $mod = $self->{'max_val'};
  }
  else {
    $y1 = $self->{'curr_y_min'} + ($map * $self->{'max_val'});
    $mod = 0;
    $self->{'gd_obj'}->line ($self->{'curr_x_min'}, $y1,
                             $self->{'curr_x_max'}, $y1,
			     $misccolor);
  }

  
  # create another copy of the data, but stacked
  $data->[1] = [@{$raw->[1]}];
  for $i (0..$self->{'num_datapoints'}-1) {
    for $j (2..$self->{'num_datasets'}) {
      $data->[$j][$i] = $data->[$j-1][$i] + $raw->[$j][$i];
    }
  }
 
  # draw the damn bars
  for $i (0..$self->{'num_datapoints'}-1) {
    # init the y values for this datapoint
    $y2 = $y1;
    
    
    for $j (1..$self->{'num_datasets'}) {
      # get the color
      $color = $self->_color_role_to_index('dataset'.($j-1));
      
      # set up the geometry for the bar
      if ($self->{'spaced_bars'} =~ /^true$/i) {
        $x2 = $x1 + (2 * $i * $delta) + ($delta / 2);
       	$x3 = $x2 + $delta;
	
      }
      else {
        $x2 = $x1 + ($i * $delta);
        $x3 = $x2 + $delta;
      }
      $y3 = $y1 - (($data->[$j][$i] - $mod) * $map);
     
      #cut the bars off, if needed
      if ($data->[$j][$i] > $self->{'max_val'}) {
           $y3 = $y1 - (($self->{'max_val'} - $mod ) * $map) ;
           $cut = 1;
      }
      elsif  ($data->[$j][$i] < $self->{'min_val'}) {
           $y3 = $y1 - (($self->{'min_val'} - $mod ) * $map) ;
           $cut = 1;
      }
      else {
           $cut = 0;
      }
      
      # draw the bar
      ## y2 and y3 are reversed in some cases because GD's fill
      ## algorithm is lame
      if ($data->[$j][$i] > 0) {
        $self->{'gd_obj'}->filledRectangle ($x2, $y3, $x3, $y2, $color);
	if ($self->{'imagemap'} =~ /^true$/i) {
	  $self->{'imagemap_data'}->[$j][$i] = [ $x2, $y3, $x3, $y2 ];
	}
      }
      else {
        $self->{'gd_obj'}->filledRectangle ($x2, $y2, $x3, $y3, $color);
	if ($self->{'imagemap'} =~ /^true$/i) {
	  $self->{'imagemap_data'}->[$j][$i] = [ $x2, $y2, $x3, $y3 ];
	}
      }

      # now outline it. outline red if the bar had been cut off
      unless ($cut){
	  $self->{'gd_obj'}->rectangle ($x2, $y2, $x3, $y3, $misccolor);
      }
      else {
          $self->{'gd_obj'}->rectangle ($x2, $y2, $x3, $y3, $misccolor);
          $self->{'gd_obj'}->rectangle ($x2, $y1, $x3, $y3, $pink);
      }

      # now bootstrap the y values
      $y2 = $y3; 
    }
  }

  
  # and finaly box it off 
  $self->{'gd_obj'}->rectangle ($self->{'curr_x_min'},
  				$self->{'curr_y_min'},
 				$self->{'curr_x_max'},
 				$self->{'curr_y_max'},
 				$misccolor);
  return;
  
}



sub _draw_left_legend {
  my $self = shift;
  my @labels = @{$self->{'legend_labels'}};
  my ($x1, $x2, $x3, $y1, $y2, $width, $color, $misccolor, $w, $h, $brush);
  my $font = $self->{'legend_font'};
 
  # make sure we're using a real font
  unless ((ref ($font)) eq 'GD::Font') {
    croak "The subtitle font you specified isn\'t a GD Font object";
  }

  # get the size of the font
  ($h, $w) = ($font->height, $font->width);

  # get the miscellaneous color
  $misccolor = $self->_color_role_to_index('misc');

  # find out how wide the largest label is
  $width = (2 * $self->{'text_space'})
    + ($self->{'max_legend_label'} * $w)
    + $self->{'legend_example_size'}
    + (2 * $self->{'legend_space'});

  # get some base x-y coordinates
  $x1 = $self->{'curr_x_min'};
  $x2 = $self->{'curr_x_min'} + $width;
  $y1 = $self->{'curr_y_min'} + $self->{'graph_border'} ;
  $y2 = $self->{'curr_y_min'} + $self->{'graph_border'} + $self->{'text_space'}
          + ($self->{'num_datasets'} * ($h + $self->{'text_space'}))
	  + (2 * $self->{'legend_space'});

  # box the legend off
  $self->{'gd_obj'}->rectangle ($x1, $y1, $x2, $y2, $misccolor);

  # leave that nice space inside the legend box
  $x1 += $self->{'legend_space'};
  $y1 += $self->{'legend_space'} + $self->{'text_space'};

  # now draw the actual legend
  for (0..$#labels) {
    # get the color
    my $c = $self->{'num_datasets'}-$_-1;
    # color of the datasets in the legend
    if ($self->{'dataref'}[1][0] <0) {
        $color = $self->_color_role_to_index('dataset'.$_);
    }
    else {
        $color = $self->_color_role_to_index('dataset'.$c);
    }

    # find the x-y coords
    $x2 = $x1;
    $x3 = $x2 + $self->{'legend_example_size'};
    $y2 = $y1 + ($_ * ($self->{'text_space'} + $h)) + $h/2;

    # do the line first
    $self->{'gd_obj'}->line ($x2, $y2, $x3, $y2, $color);

    # reset the brush for points
    $brush = $self->_prepare_brush($color, 'point',
				$self->{'pointStyle' . $_});
    $self->{'gd_obj'}->setBrush($brush);
    # draw the point
    $self->{'gd_obj'}->line(int(($x3+$x2)/2), $y2,
				int(($x3+$x2)/2), $y2, gdBrushed);
    
    # now the label
    $x2 = $x3 + (2 * $self->{'text_space'});
    $y2 -= $h/2;
    # order of the datasets in the legend
    if ($self->{'dataref'}[1][0] <0) {
        $self->{'gd_obj'}->string ($font, $x2, $y2, $labels[$_], $color);
    }
    else {
        $self->{'gd_obj'}->string ($font, $x2, $y2, $labels[$c], $color);
    }
  }

  # mark off the used space
  $self->{'curr_x_min'} += $width;

  # and return
  return 1;
}


sub _draw_right_legend {
  my $self = shift;
  my @labels = @{$self->{'legend_labels'}};
  my ($x1, $x2, $x3, $y1, $y2, $width, $color, $misccolor, $w, $h, $brush);
  my $font = $self->{'legend_font'};
 
  # make sure we're using a real font
  unless ((ref ($font)) eq 'GD::Font') {
    croak "The subtitle font you specified isn\'t a GD Font object";
  }

  # get the size of the font
  ($h, $w) = ($font->height, $font->width);

  # get the miscellaneous color
  $misccolor = $self->_color_role_to_index('misc');

  # find out how wide the largest label is
  $width = (2 * $self->{'text_space'})
    + ($self->{'max_legend_label'} * $w)
    + $self->{'legend_example_size'}
    + (2 * $self->{'legend_space'});

  # get some starting x-y values
  $x1 = $self->{'curr_x_max'} - $width;
  $x2 = $self->{'curr_x_max'};
  $y1 = $self->{'curr_y_min'} + $self->{'graph_border'} ;
  $y2 = $self->{'curr_y_min'} + $self->{'graph_border'} + $self->{'text_space'}
          + ($self->{'num_datasets'} * ($h + $self->{'text_space'}))
	  + (2 * $self->{'legend_space'});

  # box the legend off
  $self->{'gd_obj'}->rectangle ($x1, $y1, $x2, $y2, $misccolor);

  # leave that nice space inside the legend box
  $x1 += $self->{'legend_space'};
  $y1 += $self->{'legend_space'} + $self->{'text_space'};

  
  # now draw the actual legend
  for (0..$#labels) {
    # get the color
    my $c = $self->{'num_datasets'}-$_-1;
    # color of the datasets in the legend  
     
   
    if ($self->{'dataref'}[1][0] < 0) {
        $color = $self->_color_role_to_index('dataset'.$_);
    }
    else {	
        $color = $self->_color_role_to_index('dataset'.$c);
    }

    # find the x-y coords
    $x2 = $x1;
    $x3 = $x2 + $self->{'legend_example_size'};
    $y2 = $y1 + ($_ * ($self->{'text_space'} + $h)) + $h/2;

    # do the line first
    $self->{'gd_obj'}->line ($x2, $y2, $x3, $y2, $color);

    # reset the brush for points
    $brush = $self->_prepare_brush($color, 'point',
				$self->{'pointStyle' . $_});
    $self->{'gd_obj'}->setBrush($brush);
    # draw the point
    $self->{'gd_obj'}->line(int(($x3+$x2)/2), $y2,
				int(($x3+$x2)/2), $y2, gdBrushed);

    # now the label
    $x2 = $x3 + (2 * $self->{'text_space'});
    $y2 -= $h/2;
    # order of the datasets in the legend
    if ($self->{'dataref'}[1][0] <0) {
        $self->{'gd_obj'}->string ($font, $x2, $y2, $labels[$_], $color);
    }
    else {
        $self->{'gd_obj'}->string ($font, $x2, $y2, $labels[$c], $color);
    }
  }

  # mark off the used space
  $self->{'curr_x_max'} -= $width;

  # and return
  return 1;
}

## be a good module and return 1
1;
