#====================================================================
#  Chart::HorizontalBars
#
#  written by Chart-Group
#
#  maintained by the Chart Group
#  Chart@wettzell.ifag.de
#
#---------------------------------------------------------------------
# History:
#----------
# $RCSfile: HorizontalBars.pm,v $ $Revision: 1.2 $ $Date: 2003/02/14 14:04:40 $
# $Author: dassing $
# $Log: HorizontalBars.pm,v $
# Revision 1.2  2003/02/14 14:04:40  dassing
# First setup
#
#====================================================================

package Chart::HorizontalBars;

use Chart::Base 2.0;
use GD;
use Carp;
use strict;

@Chart::HorizontalBars::ISA = qw(Chart::Base);
$Chart::HorizontalBars::VERSION = '2.2';

#>>>>>>>>>>>>>>>>>>>>>>>>>>#
#  public methods go here  #
#<<<<<<<<<<<<<<<<<<<<<<<<<<#



#>>>>>>>>>>>>>>>>>>>>>>>>>>>#
#  private methods go here  #
#<<<<<<<<<<<<<<<<<<<<<<<<<<<#
#draw x_ticks and their labels
sub _draw_x_ticks {
 my $self = shift;
 my $data = $self->{'dataref'};
 my $font = $self->{'tick_label_font'};
 my $textcolor = $self->_color_role_to_index('text');
 my $misccolor = $self->_color_role_to_index('misc');
 my ($h, $w, $x1, $y1, ,$y2, $x2, $delta, $width, $label);
 my @labels = @{$self->{'y_tick_labels'}};

 $self->{'grid_data'}->{'x'} = [];
 
 #make sure we have a real font
 unless ((ref $font) eq 'GD::Font') {
  croak "The tick label font you specified isn't a GD font object";
 }
 
 #get height and width of the font
 ($h, $w) = ($font->height, $font->width);
 
 #get the right x-value and width
 if ( $self->{'y_axes'} =~ /^right$/i ){
  $x1 = $self->{'curr_x_min'};
  $width = $self->{'curr_x_max'} - $x1 -$self->{'tick_len'} - $self->{'text_space'}
           - $w * $self->{'x_tick_label_length'};
 }
 elsif ( $self->{'y_axes'} =~ /^both$/i) {
  $x1 = $self->{'curr_x_min'} + $self->{'text_space'} + $w* $self->{'x_tick_label_length'}
        + $self->{'tick_len'};
  $width = $self->{'curr_x_max'} - $x1 - $self->{'tick_len'} - $self->{'text_space'}
           - $w * $self->{'x_tick_label_length'};
 }
 else {
  $x1 = $self->{'curr_x_min'} + $self->{'text_space'} + $w* $self->{'x_tick_label_length'}
        + $self->{'tick_len'};
  $width = $self->{'curr_x_max'} - $x1;
 }

 #get the delta value
 $delta = $width / ($self->{'y_ticks'} -1) ;

 #draw the labels
 $y2 =$y1;
 
 if ($self->{'x_ticks'} =~ /^normal/i ) {  #just normal ticks
   #get the point for updating later
   $y1 = $self->{'curr_y_max'} - 2*$self->{'text_space'} -$h - $self->{'tick_len'};
   #get the start point
   $y2 = $y1  + $self->{'tick_len'} + $self->{'text_space'};
   for (0..$#labels){
     $label = $self->{'y_tick_labels'}[$_];
     $x2 = $x1 + ($delta * $_) - ($w* length( $label)/2) ;
     $self->{'gd_obj'}->string($font, $x2, $y2 , $label , $textcolor);
   }
 }
 elsif ($self->{'x_ticks'} =~ /^staggered/i ) {  #staggered ticks
   #get the point for updating later
   $y1 = $self->{'curr_y_max'} - 3*$self->{'text_space'} - 2*$h - $self->{'tick_len'};

   for (0..$#labels) {
   $label = $self->{'y_tick_labels'}[$_];
     $x2 = $x1 + ($delta * $_) - ($w* length( $label)/2);
     unless ($_%2) {
      $y2 = $y1  + $self->{'text_space'} + $self->{'tick_len'};
       $self->{'gd_obj'}->string($font, $x2, $y2 , $label, $textcolor);
     }
     else {
     $y2 = $y1  + $h + 2*$self->{'text_space'} + $self->{'tick_len'};
       $self->{'gd_obj'}->string($font, $x2, $y2 , $label, $textcolor);
     }
   }

 }

 elsif ($self->{'x_ticks'} =~ /^vertical/i ) {  #vertical ticks
   #get the point for updating later
   $y1 = $self->{'curr_y_max'} - 2*$self->{'text_space'} -$w* $self->{'y_tick_label_length'} - $self->{'tick_len'};


   for (0..$#labels){
     $label = $self->{'y_tick_labels'}[$_];
     #get the start point
     $y2 = $y1  + $self->{'tick_len'} + $w* length($label) + $self->{'text_space'};

     $x2 = $x1 + ($delta * $_) - ($h /2);
     $self->{'gd_obj'}->stringUp($font, $x2, $y2 , $label , $textcolor);
   }

 }
 
 else {
  carp "I don't understand the type of x-ticks you specified";
 }
 #update the curr x and y max value
 $self->{'curr_y_max'} = $y1;
 $self->{'curr_x_max'} = $x1 + $width;
 
 #draw the ticks
 $y1 =$self->{'curr_y_max'};
 $y2 =$self->{'curr_y_max'} + $self->{'tick_len'};
 for(0..$#labels ) {
   $x2 = $x1 + ($delta * $_);
   $self->{'gd_obj'}->line($x2, $y1, $x2, $y2, $misccolor);
     if (($self->{'grid_lines'} =~ /^true$/i) or ($self->{'x_grid_lines'} =~ /^true$/i)) {
        $self->{'grid_data'}->{'x'}->[$_] = $x2;
     }
 }
 
 return 1;
}



sub _draw_y_ticks {
  my $self = shift;
  my $side = shift || 'left';
  my $data = $self->{'dataref'};
  my $font = $self->{'tick_label_font'};
  my $textcolor = $self->_color_role_to_index ('text');
  my $misccolor = $self->_color_role_to_index ('misc');
  my ($h, $w, $x1, $x2,  $y1, $y2);
  my ($width, $height, $delta);

  $self->{'grid_data'}->{'y'} =[];
  
  #make sure that is a real font
  unless ((ref $font) eq 'GD::Font') {
    croak "The tick label font isn't a GD Font object!";
  }

  #get the size of the font
  ($h, $w) = ($font->height, $font->width);

  #figure out, where to draw
  if ($side =~ /^right$/i) {
    #get the right startposition
    $x1 = $self->{'curr_x_max'};
    $y1 = $self->{'curr_y_max'} - $h/2;
    
    #get the delta values
    $height =  $self->{'curr_y_max'} - $self->{'curr_y_min'} ;
    $delta = ($height) / ($self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1);
    $y1 -= ($delta/2 );

    #look if skipping is desired
    if (!defined($self->{'skip_y_ticks'})) {
       $self->{'skip_y_ticks'} =1;
    }
    
    #draw the labels
    for(0.. int (($self->{'num_datapoints'} - 1) / $self->{'skip_y_ticks'})) {
       $y2 = $y1 - ($delta) * ($_ * $self->{'skip_y_ticks'});
       $x2 = $x1 + $self->{'tick_len'} + $self->{'text_space'};
       $self->{'gd_obj'}->string($font, $x2, $y2,
                              $self->{f_y_tick}->($data->[0][$_*$self->{'skip_y_ticks'}]), $textcolor);
    }
    
    #draw the ticks
    $x1 = $self->{'curr_x_max'};
    $x2 = $self->{'curr_x_max'} + $self->{'tick_len'};
    $y1 += $h/2;
    for(0..($self->{'num_datapoints'} -1 / $self->{'skip_y_ticks'})) {
           $y2 = $y1 - ($delta * $_);
           $self->{'gd_obj'}->line($x1,$y2,$x2,$y2,$misccolor);
           if ($self->{'grid_lines'} =~ /^true$/i or $self->{'x_grid_lines'} =~ /^true$/i ) {
              $self->{'grid_data'}->{'y'}->[$_] = $y2;
           }
    }
    
  }
  elsif ($side =~ /^both$/i) {
    #get the right startposition
    $x1 = $self->{'curr_x_max'};
    $y1 = $self->{'curr_y_max'} - $h/2;

    #get the delta values
    $height =  $self->{'curr_y_max'} - $self->{'curr_y_min'} ;
    $delta = ($height) / ($self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1);
    $y1 -= ($delta/2 );

    #look if skipping is desired
    if (!defined($self->{'skip_y_ticks'})) {
       $self->{'skip_y_ticks'} =1;
    }

    #first draw the right labels
    for(0.. int (($self->{'num_datapoints'} - 1) / $self->{'skip_y_ticks'})) {
       $y2 = $y1 - ($delta) * ($_ * $self->{'skip_y_ticks'});
       $x2 = $x1 + $self->{'tick_len'} + $self->{'text_space'};
       $self->{'gd_obj'}->string($font, $x2, $y2,
                              $self->{f_y_tick}->($data->[0][$_*$self->{'skip_y_ticks'}]), $textcolor);
    }

    #then draw the right ticks
    $x1 = $self->{'curr_x_max'};
    $x2 = $self->{'curr_x_max'} + $self->{'tick_len'};
    $y1 += $h/2;
    for(0..($self->{'num_datapoints'} -1 / $self->{'skip_y_ticks'})) {
           $y2 = $y1 - ($delta * $_);
           $self->{'gd_obj'}->line($x1,$y2,$x2,$y2,$misccolor);
           if ($self->{'grid_lines'} =~ /^true$/i or $self->{'x_grid_lines'} =~ /^true$/i ) {
              $self->{'grid_data'}->{'y'}->[$_] = $y2;
           }
    }

    #get the right startposition
    $x1 = $self->{'curr_x_min'} ;
    $y1 = $self->{'curr_y_max'} -$h/2 ;

    #get the delta values for positioning
    $height =  $self->{'curr_y_max'} - $self->{'curr_y_min'} ;
    $delta = ($height) / ($self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1);
    $y1 -= ($delta/2 );

    #then draw the left labels
    for(0.. int (($self->{'num_datapoints'} - 1) / $self->{'skip_y_ticks'})) {
       $y2 = $y1 - ($delta) * ($_ * $self->{'skip_y_ticks'});
       $x2 = $x1 - $w * length($self->{f_y_tick}->($data->[0][$_*$self->{'skip_y_ticks'}])) #print the Labels right-sided
             + $w * $self->{'x_tick_label_length'};
       $self->{'gd_obj'}->string($font, $x2, $y2,
                              $self->{f_y_tick}->($data->[0][$_*$self->{'skip_y_ticks'}]), $textcolor);
    }

    #update the curr_x_min val
    $self->{'curr_x_min'} = $x1 + $self->{'text_space'} + $w* $self->{'x_tick_label_length'}
                           + $self->{'tick_len'};

    #finally draw the left ticks
    $x1 = $self->{'curr_x_min'};
    $x2 = $self->{'curr_x_min'} - $self->{'tick_len'};
    $y1 += $h/2;
    for(0..($self->{'num_datapoints'} -1 / $self->{'skip_y_ticks'})) {
           $y2 = $y1 - ($delta * $_);
           $self->{'gd_obj'}->line($x1,$y2,$x2,$y2,$misccolor);
           if ($self->{'grid_lines'} =~ /^true$/i or $self->{'x_grid_lines'} =~ /^true$/i ) {
              $self->{'grid_data'}->{'y'}->[$_] = $y2;
           }
    }
  }

  else {
    #get the right startposition
    $x1 = $self->{'curr_x_min'} ;
    $y1 = $self->{'curr_y_max'} -$h/2 ;

    #get the delta values for positioning
    $height =  $self->{'curr_y_max'} - $self->{'curr_y_min'} ;
    $delta = ($height) / ($self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1);
    $y1 -= ($delta/2 );
  
    if (!defined($self->{'skip_y_ticks'})) {
       $self->{'skip_y_ticks'} =1;
    }

    #draw the labels
    for(0.. int (($self->{'num_datapoints'} - 1) / $self->{'skip_y_ticks'})) {
       $y2 = $y1 - ($delta) * ($_ * $self->{'skip_y_ticks'});
       $x2 = $x1 - $w * length($self->{f_y_tick}->($data->[0][$_*$self->{'skip_y_ticks'}])) #print the Labels right-sided
             + $w * $self->{'x_tick_label_length'};
       $self->{'gd_obj'}->string($font, $x2, $y2,
                              $self->{f_y_tick}->($data->[0][$_*$self->{'skip_y_ticks'}]), $textcolor);
    }
    
    #update the curr_x_min val
    $self->{'curr_x_min'} = $x1 + $self->{'text_space'} + $w* $self->{'x_tick_label_length'}
                           + $self->{'tick_len'};
  
    #draw the ticks
    $x1 = $self->{'curr_x_min'};
    $x2 = $self->{'curr_x_min'} - $self->{'tick_len'};
    $y1 += $h/2;
    for(0..($self->{'num_datapoints'} -1 / $self->{'skip_y_ticks'})) {
           $y2 = $y1 - ($delta * $_);
           $self->{'gd_obj'}->line($x1,$y2,$x2,$y2,$misccolor);
           if ($self->{'grid_lines'} =~ /^true$/i or $self->{'x_grid_lines'} =~ /^true$/i ) {
              $self->{'grid_data'}->{'y'}->[$_] = $y2;
           }
    }
  }
  #now return
  return 1;
}

#overwrite the find_y_scale function, only to get the right f_x_ticks !!!!!
sub _find_y_scale
{
	my $self = shift;

	# Predeclare vars.
	my ($d_min, $d_max);		# Dataset min & max.
	my ($p_min, $p_max);		# Plot min & max.
	my ($tickInterval, $tickCount);
	my @tickLabels;				# List of labels for each tick.
	my $maxtickLabelLen = 0;	# The length of the longest tick label.

	# Find the datatset minimum and maximum.
	($d_min, $d_max) = $self->_find_y_range();

	# Force the inclusion of zero if the user has requested it.
	if( $self->{'include_zero'} =~ m!^true$!i )
	{
		if( ($d_min * $d_max) > 0 )	# If both are non zero and of the same sign.
		{
			if( $d_min > 0 )	# If the whole scale is positive.
			{
				$d_min = 0;
			}
			else				# The scale is entirely negative.
			{
				$d_max = 0;
			}
		}
	}

        if ( $self->{'integer_ticks_only'} =~ /^\d$/ ) {
           if ( $self->{'integer_ticks_only'} == 1 ) {
              $self->{'integer_ticks_only'} = 'true';
           } else {
              $self->{'integer_ticks_only'} = 'false';
           }
        }
	if( $self->{'integer_ticks_only'} =~ m!^true$!i )
	{
                # Allow the dataset range to be overidden by the user.
	        # f_min/max are booleans which indicate that the min & max should not be modified.
	        my $f_min = defined $self->{'min_val'};
	        $d_min = $self->{'min_val'} if $f_min;

	        my $f_max = defined $self->{'max_val'};
	        $d_max = $self->{'max_val'} if $f_max;

	        # Assert against the min is larger than the max.
	        if( $d_min > $d_max )
	        {
	         croak "The the specified 'min_val' & 'max_val' values are reversed (min > max: $d_min>$d_max)";
	        }
		# The user asked for integer ticks, force the limits to integers.
		# & work out the range directly.
		$p_min = $self->_round2Tick($d_min, 1, -1);
		$p_max = $self->_round2Tick($d_max, 1, 1);

		my $skip = $self->{skip_int_ticks};

		$tickInterval = $skip;
		$tickCount = ($p_max - $p_min ) /$skip +1;

             	# Now sort out an array of tick labels.

		for( my $labelNum = $p_min; $labelNum<=$p_max; $labelNum+=$tickInterval )
		{
			my $labelText;

			if( defined $self->{f_x_tick} )
			{
                        	# Is _default_f_tick function used?
                        	if ( $self->{f_x_tick} == \&_default_f_tick) {
			   	$labelText = sprintf("%d", $labelNum);
                        	}
				else {
					$labelText = $self->{f_x_tick}->($labelNum);
                        	}
			}

			else
			{
				$labelText = sprintf("%d", $labelNum);
			}

		#print "labelText = $labelText\n";
		push @tickLabels, $labelText;
		$maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
		}

	}
	else
	{
	    # Allow the dataset range to be overidden by the user.
	    # f_min/max are booleans which indicate that the min & max should not be modified.
	    my $f_min = defined $self->{'min_val'};
	    $d_min = $self->{'min_val'} if $f_min;

	    my $f_max = defined $self->{'max_val'};
	    $d_max = $self->{'max_val'} if $f_max;

	    # Assert against the min is larger than the max.
	    if( $d_min > $d_max )
	    {
	     croak "The the specified 'min_val' & 'max_val' values are reversed (min > max: $d_min>$d_max)";
	     }

	     # Calculate the width of the dataset. (posibly modified by the user)
	     my $d_width = $d_max - $d_min;

	     # If the width of the range is zero, forcibly widen it
	     # (to avoid division by zero errors elsewhere in the code).
	     if( 0 == $d_width )
	         {
		$d_min--;
		$d_max++;
		$d_width = 2;
	          }

             # Descale the range by converting the dataset width into
             # a floating point exponent & mantisa pair.
             my( $rangeExponent, $rangeMantisa ) = $self->_sepFP( $d_width );
	     my $rangeMuliplier = 10 ** $rangeExponent;

	     # Find what tick
	     # to use & how many ticks to plot,
	     # round the plot min & max to suatable round numbers.
	     ($tickInterval, $tickCount, $p_min, $p_max)
		= $self->_calcTickInterval($d_min/$rangeMuliplier, $d_max/$rangeMuliplier,
				$f_min, $f_max,
				$self->{'min_y_ticks'}, $self->{'max_y_ticks'});
	     # Restore the tickInterval etc to the correct scale
	     $_ *= $rangeMuliplier foreach($tickInterval, $p_min, $p_max);

	     #get teh precision for the labels
	     my $precision = $self->{'precision'};

	     # Now sort out an array of tick labels.
	     for( my $labelNum = $p_min; $labelNum<=$p_max; $labelNum+=$tickInterval )
	     {
		my $labelText;

		if( defined $self->{f_x_tick} )
		{
                        # Is _default_f_tick function used?
                        if ( $self->{f_x_tick} == \&_default_f_tick) {
			   $labelText = sprintf("%.".$precision."f", $labelNum);
                        } else {
			   $labelText = $self->{f_x_tick}->($labelNum);
                        }
		}
		else
		{
			$labelText = sprintf("%.".$precision."f", $labelNum);
		}
		#print "labelText = $labelText\n";
		push @tickLabels, $labelText;
		$maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
	     }
	}

	# Store the calculated data.
	$self->{'min_val'} = $p_min;
	$self->{'max_val'} = $p_max;
	$self->{'y_ticks'} = $tickCount;
	$self->{'y_tick_labels'} = \@tickLabels;
	$self->{'y_tick_label_length'} = $maxtickLabelLen;

	# and return.
	return 1;
}


## finally get around to plotting the data
sub _draw_data {
  my $self = shift;
  my $data = $self->{'dataref'};
  my $misccolor = $self->_color_role_to_index('misc');
  my ($x1, $x2, $x3, $y1, $y2, $y3);
  my $cut = 0;
  my ($width, $height, $delta1, $delta2, $map, $mod, $pink);
  my ($i, $j, $color);

  # init the imagemap data field if they wanted it
  if ($self->{'imagemap'} =~ /^true$/i) {
    $self->{'imagemap_data'} = [];
  }

  # find both delta values ($delta1 for stepping between different
  # datapoint names, $delta2 for setpping between datasets for that
  # point) and the mapping constant
  $width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
  $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
  $delta1 = $height / ($self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1);
  $map = $width / ($self->{'max_val'} - $self->{'min_val'});
  if ($self->{'spaced_bars'} =~ /^true$/i) {
    $delta2 = $delta1 / ($self->{'num_datasets'} + 2);
  }
  else {
    $delta2 = $delta1 / $self->{'num_datasets'};
  }

  # get the base x-y values
  $y1 = $self->{'curr_y_max'} - $delta2;
  if ($self->{'min_val'} >= 0) {
    $x1 = $self->{'curr_x_min'} ;
    $mod = $self->{'min_val'};
  }
  elsif ($self->{'max_val'} <= 0) {
    $x1 = $self->{'curr_x_max'};
    $mod = $self->{'max_val'};
  }
  else {
   $x1 = $self->{'curr_x_min'} + abs($map * $self->{'min_val'});
   $mod = 0;
   $self->{'gd_obj'}->line ($x1, $self->{'curr_y_min'},
                            $x1, $self->{'curr_y_max'},
                           $misccolor);
  }
  
  # draw the bars
  for $i (1..$self->{'num_datasets'}) {
    # get the color for this dataset
    $color = $self->_color_role_to_index('dataset'.($i-1));
    
    # draw every bar for this dataset
    for $j (0..$self->{'num_datapoints'}) {
      # don't try to draw anything if there's no data
      if (defined ($data->[$i][$j])) {
	# find the bounds of the rectangle
        if ($self->{'spaced_bars'} =~ /^true$/i) {
           $y2 = $y1 - ($j * $delta1) - ($self->{'num_datasets'} * $delta2) + (($i-1) * $delta2);
	}
	else {
           $y2 = $y1 - ($j * $delta1) - ($self->{'num_datasets'} * $delta2) + (($i) * $delta2);
	}
	$x2 = $x1;
	$y3 = $y2 + $delta2;

        #cut the bars off, if needed
        if ($data->[$i][$j] > $self->{'max_val'}) {
           $x3 = $x1 + (($self->{'max_val'} - $mod ) * $map) -1;
           $cut = 1;
        }
        elsif  ($data->[$i][$j] < $self->{'min_val'}) {
           $x3 = $x1 + (($self->{'min_val'} - $mod ) * $map) +1;
           $cut = 1;
        }
        else {
           $x3 = $x1 + (($data->[$i][$j] - $mod) * $map);
           $cut = 0;
        }
        
	# draw the bar
	## y2 and y3 are reversed in some cases because GD's fill
	## algorithm is lame
	if ($data->[$i][$j] < 0) {
          $self->{'gd_obj'}->filledRectangle ($x3, $y2, $x2, $y3, $color);
          if ($self->{'imagemap'} =~ /^true$/i) {
	       $self->{'imagemap_data'}->[$i][$j] = [$x3, $y2, $x2, $y3];
          }

          $self->{'gd_obj'}->filledRectangle ($x3, $y2, $x2, $y3, $color);
          if ($self->{'imagemap'} =~ /^true$/i) {
              $self->{'imagemap_data'}->[$i][$j] = [$x3, $y2, $x2, $y3];
          }
	}
	else {
	  $self->{'gd_obj'}->filledRectangle ($x2, $y2, $x3, $y3, $color);
	  if ($self->{'imagemap'} =~ /^true$/i) {
	    $self->{'imagemap_data'}->[$i][$j] = [$x2, $y2, $x3, $y3];
	  }
	}

	# now outline it. outline red if the bar had been cut off
        unless ($cut){
	  $self->{'gd_obj'}->rectangle ($x2, $y3, $x3, $y2, $misccolor);
        }
        else {
          $pink = $self->{'gd_obj'}->colorAllocate(255,0,255);
          $self->{'gd_obj'}->rectangle ($x2, $y3, $x3, $y2, $pink);
        }
        
      } else {
	  if ($self->{'imagemap'} =~ /^true$/i) {
            $self->{'imagemap_data'}->[$i][$j] = [undef(), undef(), undef(), undef()];
          }
      }
    }
  }
      
  # 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;

}

## be a good module and return 1
1;
