#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#
#  Chart::Composite           #
#                             #
#  written by david bonner    #
#  dbonner@cs.bu.edu          #
#                             #
#  maintained by peter clark  #
#  ninjaz@webexpress.com      #
#                             #
#  theft is treason, citizen  #
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<#

package Chart::Composite;

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

@Chart::Composite::ISA = qw(Chart::Base);
$Chart::Composite::VERSION = 0.99;

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

## have to override set, so we can pass the options to the 
## sub-objects later
sub set {
  my $self = shift;
  my %opts = @_;

  # basic error checking on the options, just warn 'em
  unless ($#_ % 2) {
    carp "Whoops, some option to be set didn't have a value.\n",
         "You might want to look at that.\n";
  }

  # store the options they gave us
  unless ($self->{'opts'}) {
    $self->{'opts'} = {};
  }

  # now set 'em
  for (keys %opts) {
    $self->{$_} = $opts{$_};
    $self->{'opts'}{$_} = $opts{$_};
  }

  # now return
  return;
}


##  get the information to turn the chart into an imagemap
##  had to override it to reassemble the @data array correctly
sub imagemap_dump {
  my $self = shift;
  my $ref = [];
  my ($i, $j);
 
  # croak if they didn't ask me to remember the data, or if they're asking
  # for the data before I generate it
  unless (($self->{'imagemap'} =~ /^true$/i) && $self->{'imagemap_data'}) {
    croak "You need to set the imagemap option to true, and then call the gif method, before you can get the imagemap data";
  }

  # make a copy for them, and reorder it
  for $i (0..$#{$self->{'sub0'}->{'imagemap_data'}}) {
    for $j (0..$#{$self->{'sub0'}->{'imagemap_data'}->[$i]}) {
      $ref->[$self->{'composite_info'}->[0][1][$i]][$j] = 
          [ @{ $self->{'sub0'}->{'imagemap_data'}->[$i][$j] } ];
    }
  }
  for $i (0..$#{$self->{'sub1'}->{'imagemap_data'}}) {
    for $j (0..$#{$self->{'sub1'}->{'imagemap_data'}->[$i]}) {
      $ref->[$self->{'composite_info'}->[1][1][$i]][$j] = 
          [ @{ $self->{'sub1'}->{'imagemap_data'}->[$i][$j] } ];
    }
  }

  # return their copy
  return $ref;
}


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

##  make sure the data isn't really weird
##  and collect some basic info about it
sub _check_data {
  my $self = shift;
  my $length = 0;

  # first things first, make sure we got the composite_info
  unless (($self->{'composite_info'}) && ($#{$self->{'composite_info'}} == 1)) {
    croak "Chart::Composite needs to be told what kind of components to use";
  }

  # make sure we don't end up dividing by zero if they ask for
  # just one y_tick
  if ($self->{'y_ticks'} == 1) {
    $self->{'y_ticks'} = 2;
    carp "The number of y_ticks displayed must be at least 2";
  }

  # remember the number of datasets
  $self->{'num_datasets'} = $#{$self->{'dataref'}};

  # remember the number of points in the largest dataset
  $self->{'num_datapoints'} = 0;
  for (0..$self->{'num_datasets'}) {
    if (scalar(@{$self->{'dataref'}[$_]}) > $self->{'num_datapoints'}) {
      $self->{'num_datapoints'} = scalar(@{$self->{'dataref'}[$_]});
    }
  }

  # find the longest x-tick label, and remember how long it is
  for (@{$self->{'dataref'}[0]}) {
    if (length ($_) > $length) {
      $length = length ($_);
    }
  }
  $self->{'x_tick_label_length'} = $length;

  # now split the data into sub-objects
  $self->_split_data;

  return;
}


## create sub-objects for each type, store the appropriate
## data sets in each one, and stick the correct values into
## them (ie. 'gd_obj');
sub _split_data {
  my $self = shift;
  my @types = ($self->{'composite_info'}[0][0],$self->{'composite_info'}[1][0]);
  my ($ref, $i, $j);

  # we can only do two at a time
  if ($self->{'composite_info'}[2]) {
    croak "Sorry, Chart::Composite can only do two chart types at a time";
  }

  # load the individual modules
  require "Chart/".$types[0].".pm";
  require "Chart/".$types[1].".pm"; 

  # create the sub-objects
  $self->{'sub_0'} = ("Chart::".$types[0])->new();
  $self->{'sub_1'} = ("Chart::".$types[1])->new();

  # set the options (set the min_val, max_val, and y_ticks
  # options intelligently so that the sub-objects don't get
  # confused)
  $self->{'sub_0'}->set (%{$self->{'opts'}});
  $self->{'sub_1'}->set (%{$self->{'opts'}});
  if (defined ($self->{'opts'}{'min_val1'})) {
    $self->{'sub_0'}->set ('min_val' => $self->{'opts'}{'min_val1'});
  }
  if (defined ($self->{'opts'}{'max_val1'})) {
    $self->{'sub_0'}->set ('max_val' => $self->{'opts'}{'max_val1'});
  }
  if (defined ($self->{'opts'}{'min_val2'})) {
    $self->{'sub_1'}->set ('min_val' => $self->{'opts'}{'min_val2'});
  }
  if (defined ($self->{'opts'}{'max_val2'})) {
    $self->{'sub_1'}->set ('max_val' => $self->{'opts'}{'max_val2'});
  }
  if ($self->{'opts'}{'y_ticks1'}) {
    $self->{'sub_0'}->set ('y_ticks' => $self->{'opts'}{'y_ticks1'});
  } 
  if ($self->{'opts'}{'y_ticks2'}) {
    $self->{'sub_1'}->set ('y_ticks' => $self->{'opts'}{'y_ticks2'});
  }

  # replace the gd_obj fields
  $self->{'sub_0'}->{'gd_obj'} = $self->{'gd_obj'};
  $self->{'sub_1'}->{'gd_obj'} = $self->{'gd_obj'};

  # let the sub-objects know they're sub-objects
  $self->{'sub_0'}->{'component'} = 'true';
  $self->{'sub_1'}->{'component'} = 'true';

  # give each sub-object its data
  $self->{'component_datasets'} = [];
  for $i (0..1) {
    $ref = [];
    $self->{'component_datasets'}[$i] = $self->{'composite_info'}[$i][1];
    push @{$ref}, $self->{'dataref'}[0];
    for $j (@{$self->{'composite_info'}[$i][1]}) {
      push @{$ref}, $self->{'dataref'}[$j];
    }
    $self->{'sub_'.$i}->_copy_data ($ref);
  }

  # and let them check it
  $self->{'sub_0'}->_check_data;
  $self->{'sub_1'}->_check_data;

  # realign the y-axes if they want
  if ($self->{'same_y_axes'} =~ /^true$/i) {
    if ($self->{'sub_0'}{'min_val'} < $self->{'sub_1'}{'min_val'}) {
      $self->{'sub_1'}{'min_val'} = $self->{'sub_0'}{'min_val'};
    }
    else {
      $self->{'sub_0'}{'min_val'} = $self->{'sub_1'}{'min_val'};
    }

    if ($self->{'sub_0'}{'max_val'} > $self->{'sub_1'}{'max_val'}) {
      $self->{'sub_1'}{'max_val'} = $self->{'sub_0'}{'max_val'};
    }
    else {
      $self->{'sub_0'}{'max_val'} = $self->{'sub_1'}{'max_val'};
    }

    $self->{'sub_0'}->_check_data;
    $self->{'sub_1'}->_check_data;
  }
	
  # find out how big the y-tick labels will be from sub_0 and sub_1
  $self->{'y_tick_label_length1'} = $self->{'sub_0'}->{'y_tick_label_length'};
  $self->{'y_tick_label_length2'} = $self->{'sub_1'}->{'y_tick_label_length'};

  # now return
  return;
}


## put the legend on the top of the data plot
sub _draw_top_legend {
  my $self = shift;
  my @labels = @{$self->{'legend_labels'}};
  my ($x1, $y1, $x2, $y2, $empty_width, $max_label_width, $cols, $rows, $color);
  my ($col_width, $row_height, $i, $j, $r, $c, $index, $x, $y, $sub, $w, $h);
  my $font = $self->{'legend_font'};
  my @colors = qw(light_purple pink peach olive plum turquoise mauve brown);

  # copy the current boundaries into the sub-objects
  $self->_sub_update;

  # modify the dataset color table entries to avoid duplicating
  # dataset colors (this limits the number of possible data sets
  # for each component to 8)
  for (0..$#colors) {
    $self->{'sub_1'}{'color_table'}{'dataset'.$_} 
      = $self->{'color_table'}{$colors[$_]};
  }

  # 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 some base x coordinates
  $x1 = $self->{'curr_x_min'} + $self->{'graph_border'}
          + $self->{'y_tick_label_length1'} * $self->{'tick_label_font'}->width
	  + $self->{'tick_len'} + (3 * $self->{'text_space'});
  $x2 = $self->{'curr_x_max'} - $self->{'graph_border'}
          - $self->{'y_tick_label_length2'} * $self->{'tick_label_font'}->width
	  - $self->{'tick_len'} - (3 * $self->{'text_space'});
  if ($self->{'y_label'}) {
    $x1 += $self->{'label_font'}->height + 2 * $self->{'text_space'};
  }
  if ($self->{'y_label2'}) {
    $x2 -= $self->{'label_font'}->height + 2 * $self->{'text_space'};
  }

  # figure out how wide the widest label is, then figure out how many
  # columns we can fit into the allotted space
  $empty_width = $x2 - $x1 - (2 * $self->{'legend_space'});
  $max_label_width = $self->{'max_legend_label'} 
    * $self->{'legend_font'}->width + 4 * $self->{'text_space'}
    + $self->{'legend_example_size'};
  $cols = int ($empty_width / $max_label_width);
  unless ($cols) {
    $cols = 1;
  }
  $col_width = $empty_width / $cols;

  # figure out how many rows we need and how tall they are
  $rows = int ($self->{'num_datasets'} / $cols);
  unless (($self->{'num_datasets'} % $cols) == 0) {
    $rows++;
  }
  unless ($rows) {
    $rows = 1;
  }
  $row_height = $h + $self->{'text_space'};

  # box the legend off
  $y1 = $self->{'curr_y_min'};
  $y2 = $self->{'curr_y_min'} + $self->{'text_space'}
          + ($rows * $row_height) + (2 * $self->{'legend_space'});
  $self->{'gd_obj'}->rectangle($x1, $y1, $x2, $y2, 
                               $self->{'color_table'}{'misc'});

  # leave some space inside the legend
  $x1 += $self->{'legend_space'} + $self->{'text_space'};
  $x2 -= $self->{'legend_space'};
  $y1 += $self->{'legend_space'} + $self->{'text_space'};
  $y2 -= $self->{'legend_space'} + $self->{'text_space'};

  # draw in the actual legend
  $r = 0; # current row
  $c = 0; # current column
  for $i (0..1) {
    for $j (0..$#{$self->{'component_datasets'}[$i]}) {
      # get the color
      $color = $self->{'sub_'.$i}->{'color_table'}{'dataset'.$j};
      $index = $self->{'component_datasets'}[$i][$j] - 1; # index in label list

      # find the x-y coordinates for the beginning of the example line
      $x = $x1 + ($col_width * $c);
      $y = $y1 + ($row_height * $r) + $h/2;

      # draw the example line
      $self->{'gd_obj'}->line ($x, $y,
                               $x + $self->{'legend_example_size'}, $y,
			       $color);

      # find the x-y coordinates for the beginning of the label
      $x += $self->{'legend_example_size'} + 2 * $self->{'text_space'};
      $y -= $h/2;
      
      # now draw the label
      $self->{'gd_obj'}->string($font, $x, $y, 
                                $labels[$index], $color);

      # keep track of which row/column we're using
      $r = ($r + 1) % $rows;
      if ($r == 0) {
	$c++;
      }
    }
  }
      
      
  # mark of the space used
  $self->{'curr_y_min'} += ($rows * $row_height)
  			      + $self->{'text_space'}
			      + 2 * $self->{'legend_space'}; 

  return;
}


## put the legend on the right of the chart
sub _draw_right_legend {
  my $self = shift;
  my @labels = @{$self->{'legend_labels'}};
  my ($x1, $x2, $x3, $y1, $y2, $width, $color, $misccolor, $w, $h);
  my $font = $self->{'legend_font'};
  my (%colors, @datasets, $i);

  # copy the current boundaries and colors into the sub-objects
  $self->_sub_update;

  # modify the dataset color table entries to avoid duplicating
  # dataset colors (this limits the number of possible data sets
  # for each component to 8)
  for (0..7) {
    $self->{'sub_1'}{'color_table'}{'dataset'.$_}
      = $self->{'color_table'}{'dataset'.($_+8)};
  }

  # make sure we use the right colors for the legend
  @datasets = @{$self->{'composite_info'}[0][1]};
  $i = 0;
  for (0..$#datasets) {
    $colors{$datasets[$_]-1} = $self->{'color_table'}{'dataset'.($i)};
    $i++;
  }
  @datasets = @{$self->{'composite_info'}[1][1]};
  $i = 0;
  for (0..$#datasets) {
    $colors{$datasets[$_]-1} = $self->{'color_table'}{'dataset'.($i+8)};
    $i++;
  }

  # 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_table'}{'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'});

  # box the thing off
  $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'});
  $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
    $color = $colors{$_};

    # 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);

    # now the label
    $x2 = $x3 + (2 * $self->{'text_space'});
    $y2 -= $h/2;
    $self->{'gd_obj'}->string ($font, $x2, $y2, $labels[$_], $color);
  }

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

  # and return
  return;
}


##  draw the legend on the bottom of the data plot
sub _draw_bottom_legend {
  my $self = shift;
  my @labels = @{$self->{'legend_labels'}};
  my ($x1, $y1, $x2, $y2, $empty_width, $max_label_width, $cols, $rows, $color);
  my ($col_width, $row_height, $i, $j, $r, $c, $index, $x, $y, $sub, $w, $h);
  my $font = $self->{'legend_font'};
  my @colors = qw(light_purple pink peach olive plum turquoise mauve brown);

  # copy the current boundaries and colors into the sub-objects
  $self->_sub_update;

  # modify the dataset color table entries to avoid duplicating
  # dataset colors (this limits the number of possible data sets
  # for each component to 8)
  for (0..$#colors) {
    $self->{'sub_1'}{'color_table'}{'dataset'.$_} 
      = $self->{'color_table'}{$colors[$_]};
  }

  # 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);

  # figure out how many columns we can fit
  $x1 = $self->{'curr_x_min'} + $self->{'graph_border'}
          + $self->{'y_tick_label_length1'} * $self->{'tick_label_font'}->width
	  + $self->{'tick_len'} + (3 * $self->{'text_space'});
  $x2 = $self->{'curr_x_max'} - $self->{'graph_border'}
          - $self->{'y_tick_label_length2'} * $self->{'tick_label_font'}->width
	  - $self->{'tick_len'} - (3 * $self->{'text_space'});
  if ($self->{'y_label'}) {
    $x1 += $self->{'label_font'}->height + 2 * $self->{'text_space'};
  }
  if ($self->{'y_label2'}) {
    $x2 -= $self->{'label_font'}->height + 2 * $self->{'text_space'};
  }
  $empty_width = $x2 - $x1 - (2 * $self->{'legend_space'});
  $max_label_width = $self->{'max_legend_label'} 
    * $self->{'legend_font'}->width + 4 * $self->{'text_space'}
    + $self->{'legend_example_size'};
  $cols = int ($empty_width / $max_label_width);
  unless ($cols) {
    $cols = 1;
  }
  $col_width = $empty_width / $cols;

  # figure out how many rows we need
  $rows = int ($self->{'num_datasets'} / $cols);
  unless (($self->{'num_datasets'} % $cols) == 0) {
    $rows++;
  }
  unless ($rows) {
    $rows = 1;
  }
  $row_height = $h + $self->{'text_space'};

  # box it off
  $y1 = $self->{'curr_y_max'} - $self->{'text_space'}
          - ($rows * $row_height) - (2 * $self->{'legend_space'});
  $y2 = $self->{'curr_y_max'};
  $self->{'gd_obj'}->rectangle($x1, $y1, $x2, $y2, 
                               $self->{'color_table'}{'misc'});
  $x1 += $self->{'legend_space'} + $self->{'text_space'};
  $x2 -= $self->{'legend_space'};
  $y1 += $self->{'legend_space'} + $self->{'text_space'};
  $y2 -= $self->{'legend_space'} + $self->{'text_space'};

  # draw in the actual legend
  $r = 0;
  $c = 0;
  for $i (0..1) {
    for $j (0..$#{$self->{'component_datasets'}[$i]}) {
      $color = $self->{'sub_'.$i}->{'color_table'}{'dataset'.$j};
      $index = $self->{'component_datasets'}[$i][$j] - 1;

      $x = $x1 + ($col_width * $c);
      $y = $y1 + ($row_height * $r) + $h/2;
      $self->{'gd_obj'}->line ($x, $y,
                               $x + $self->{'legend_example_size'}, $y,
			       $color);

      $x += $self->{'legend_example_size'} + 2 * $self->{'text_space'};
      $y -= $h/2;
      $self->{'gd_obj'}->string($font, $x, $y, 
                                $labels[$index], $color);

      # keep track of which row/column we're using
      $r = ($r + 1) % $rows;
      if ($r == 0) {
	$c++;
      }
    }
  }
      
      
  # mark of the space used
  $self->{'curr_y_max'} -= ($rows * $row_height)
  			      + $self->{'text_space'}
			      + 2 * $self->{'legend_space'}; 

  return;
}


## draw the ticks and tick labels
sub _draw_ticks {
  my $self = shift;

  # draw the x ticks
  $self->_draw_x_ticks;

  # update the boundaries in the sub-objects
  $self->_boundary_update ($self, $self->{'sub_0'});
  $self->_boundary_update ($self, $self->{'sub_1'}); 

  # now the y ticks
  $self->_draw_y_ticks;

  # then return
  return;
}


## draw the 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_table'}{'text'};
  my $misccolor = $self->{'color_table'}{'misc'};
  my ($h, $w);
  my ($x1, $x2, $y1, $y2);
  my ($width, $delta);
  my ($stag);

  $self->{'grid_data'}->{'x'} = [];

  # make sure we got a real font
  unless ((ref $font) eq 'GD::Font') {
    croak "The tick label font you specified isn\'t a GD Font object";
  }

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

  # allow for the amount of space the y-ticks will push the
  # axes over to the right and to the left
  $x1 = $self->{'curr_x_min'} + ($w * $self->{'y_tick_label_length1'})
         + (2 * $self->{'text_space'}) + $self->{'tick_len'};
  $x2 = $self->{'curr_x_max'} - ($w * $self->{'y_tick_label_length2'})
         - (2 * $self->{'text_space'}) - $self->{'tick_len'};
  $y1 = $self->{'curr_y_max'} - $h - $self->{'text_space'};

  # get the delta value, figure out how to draw the labels
  $width = $x2 - $x1;
  $delta = $width / $self->{'num_datapoints'};
  if ($delta <= ($self->{'x_tick_label_length'} * $w)) {
    unless ($self->{'x_ticks'} =~ /^vertical$/i) {
      $self->{'x_ticks'} = 'staggered';
    }
  }

  # now draw the labels
  if ($self->{'x_ticks'} =~ /^normal$/i) { # normal ticks
    if ($self->{'skip_x_ticks'}) {
      for (0..int(($self->{'num_datapoints'}-1)/$self->{'skip_x_ticks'})) {
        $x2 = $x1 + ($delta/2) + ($delta*($_*$self->{'skip_x_ticks'})) 
	      - ($w*length($data->[0][$_* $self->{'skip_x_ticks'}])) / 2; 
        $self->{'gd_obj'}->string($font, $x2, $y1, 
	                          $data->[0][$_*$self->{'skip_x_ticks'}], 
				  $textcolor);
      }
    }
    elsif ($self->{'custom_x_ticks'}) {
      for (@{$self->{'custom_x_ticks'}}) {
        $x2 = $x1 + ($delta/2) + ($delta*$_) - ($w*length($data->[0][$_])) / 2; 
        $self->{'gd_obj'}->string($font, $x2, $y1, $data->[0][$_], $textcolor);
      }
    }
    else {
      for (0..$self->{'num_datapoints'}-1) {
        $x2 = $x1 + ($delta/2) + ($delta*$_) - ($w*length($data->[0][$_])) / 2; 
        $self->{'gd_obj'}->string($font, $x2, $y1, $data->[0][$_], $textcolor);
      }
    }
  }
  elsif ($self->{'x_ticks'} =~ /^staggered$/i) { # staggered ticks
    if ($self->{'skip_x_ticks'}) {
      $stag = 0;
      for (0..int(($self->{'num_datapoints'}-1)/$self->{'skip_x_ticks'})) {
        $x2 = $x1 + ($delta/2) + ($delta*($_*$self->{'skip_x_ticks'})) 
	        - ($w*length($data->[0][$_*$self->{'skip_x_ticks'}])) / 2;
        if (($stag % 2) == 1) {
          $y1 -= $self->{'text_space'} + $h;
        }
        $self->{'gd_obj'}->string($font, $x2, $y1, 
                                  $data->[0][$_*$self->{'skip_x_ticks'}], 
                                  $textcolor);
        if (($stag % 2) == 1) {
          $y1 += $self->{'text_space'} + $h;
        }
	$stag++;
      }
    }
    elsif ($self->{'custom_x_ticks'}) {
      $stag = 0;
      for (sort (@{$self->{'custom_x_ticks'}})) {
        $x2 = $x1 + ($delta/2) + ($delta*$_) - ($w*length($data->[0][$_])) / 2;
        if (($stag % 2) == 1) {
          $y1 -= $self->{'text_space'} + $h;
        }
        $self->{'gd_obj'}->string($font, $x2, $y1, $data->[0][$_], $textcolor);
        if (($stag % 2) == 1) {
          $y1 += $self->{'text_space'} + $h;
        }
	$stag++;
      }
    }
    else {
      for (0..$self->{'num_datapoints'}-1) {
        $x2 = $x1 + ($delta/2) + ($delta*$_) - ($w*length($data->[0][$_])) / 2;
        if (($_ % 2) == 1) {
          $y1 -= $self->{'text_space'} + $h;
        }
        $self->{'gd_obj'}->string($font, $x2, $y1, $data->[0][$_], $textcolor);
        if (($_ % 2) == 1) {
          $y1 += $self->{'text_space'} + $h;
        }
      }
    }
  }
  elsif ($self->{'x_ticks'} =~ /^vertical$/i) { # vertical ticks
    $y1 = $self->{'curr_y_max'} - $self->{'text_space'};
    if ($self->{'skip_x_ticks'}) {
      for (0..int(($self->{'num_datapoints'}-1)/$self->{'skip_x_ticks'})) {
        $x2 = $x1 + ($delta/2) + ($delta*($_*$self->{'skip_x_ticks'})) - $h/2;
        $y2 = $y1 - (($self->{'x_tick_label_length'} 
	              - length($data->[0][$_*$self->{'skip_x_ticks'}])) * $w);
        $self->{'gd_obj'}->stringUp($font, $x2, $y2, 
	                            $data->[0][$_*$self->{'skip_x_ticks'}], 
				    $textcolor);
      }
    }
    elsif ($self->{'custom_x_ticks'}) {
      for (@{$self->{'custom_x_ticks'}}) {
        $x2 = $x1 + ($delta/2) + ($delta*$_) - $h/2;
        $y2 = $y1 - (($self->{'x_tick_label_length'} - length($data->[0][$_])) 
                      * $w);
        $self->{'gd_obj'}->stringUp($font, $x2, $y2, 
	                            $data->[0][$_], $textcolor);
      }
    }
    else {
      for (0..$self->{'num_datapoints'}-1) {
        $x2 = $x1 + ($delta/2) + ($delta*$_) - $h/2;
        $y2 = $y1 - (($self->{'x_tick_label_length'} - length($data->[0][$_])) 
                      * $w);
        $self->{'gd_obj'}->stringUp($font, $x2, $y2, 
	                            $data->[0][$_], $textcolor);
      }
    }
  }
  else { # error time
    carp "I don't understand the type of x-ticks you specified";
  }

  # update the current y-max value
  if ($self->{'x_ticks'} =~ /^normal$/i) {
     $self->{'curr_y_max'} -= $h + (2 * $self->{'text_space'});
  } 
  elsif ($self->{'x_ticks'} =~ /^staggered$/i) {
    $self->{'curr_y_max'} -= (2 * $h) + (3 * $self->{'text_space'});
  }
  elsif ($self->{'x_ticks'} =~ /^vertical$/i) {
    $self->{'curr_y_max'} -= ($w * $self->{'x_tick_label_length'})
                               + (2 * $self->{'text_space'});
  }

  # now plot the ticks
  $y1 = $self->{'curr_y_max'};
  $y2 = $self->{'curr_y_max'} - $self->{'tick_len'};
  if ($self->{'skip_x_ticks'}) {
    for (0..int(($self->{'num_datapoints'}-1)/$self->{'skip_x_ticks'})) {
      $x2 = $x1 + ($delta/2) + ($delta*($_*$self->{'skip_x_ticks'}));
      $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;
      }
    }
  }
  elsif ($self->{'custom_x_ticks'}) {
    for (@{$self->{'custom_x_ticks'}}) {
      $x2 = $x1 + ($delta/2) + ($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;
      }
    }
  }
  else {
    for (0..$self->{'num_datapoints'}-1) {
      $x2 = $x1 + ($delta/2) + ($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;
      }
    }
  }

  # update the current y-max value
  $self->{'curr_y_max'} -= $self->{'tick_len'};

  # and return
  return;
}


## draw the y-ticks and their labels
sub _draw_y_ticks {
  my $self = shift;

  # let the first guy do his
  $self->{'sub_0'}->_draw_y_ticks ('left');

  # and update the other two objects
  $self->_boundary_update ($self->{'sub_0'}, $self);
  $self->_boundary_update ($self->{'sub_0'}, $self->{'sub_1'});

  # now draw the other ones
  $self->{'sub_1'}->_draw_y_ticks ('right');

  # and update the other two objects
  $self->_boundary_update ($self->{'sub_1'}, $self);
  $self->_boundary_update ($self->{'sub_1'}, $self->{'sub_0'});

  # then return
  return;
}


## finally get around to plotting the data
sub _draw_data {
  my $self = shift;

  # do a grey background if they want it
  if ($self->{'grey_background'} =~ /^true$/i) {
    $self->_grey_background;
    $self->{'sub_0'}->{'grey_background'} = 'false';
    $self->{'sub_1'}->{'grey_background'} = 'false';
  }

  # draw grid again if necessary (if grey background ruined it..)
  unless ($self->{grey_background} !~ /^true$/i) {
    $self->_draw_grid_lines if ($self->{grid_lines} =~ /^true$/i);
    $self->_draw_x_grid_lines if ($self->{x_grid_lines} =~ /^true$/i);
    $self->_draw_y_grid_lines if ($self->{y_grid_lines} =~ /^true$/i);
    $self->_draw_y2_grid_lines if ($self->{y2_grid_lines} =~ /^true$/i);
  }


  # do a final bounds update
  $self->_boundary_update ($self, $self->{'sub_0'});
  $self->_boundary_update ($self, $self->{'sub_1'});

  # now let the component modules go to work
  $self->{'sub_0'}->_draw_data;
  $self->{'sub_1'}->_draw_data;
      
  return;
}


## update all the necessary information in the sub-objects
sub _sub_update {
  my $self = shift;
  my $sub0 = $self->{'sub_0'};
  my $sub1 = $self->{'sub_1'};

  # update the boundaries
  $self->_boundary_update ($self, $sub0);
  $self->_boundary_update ($self, $sub1);

  # copy the color tables
  $sub0->{'color_table'} = { %{$self->{'color_table'}} };
  $sub1->{'color_table'} = { %{$self->{'color_table'}} };

  # now return
  return;
}


## copy the current gd_obj boundaries from one object to another
sub _boundary_update {
  my $self = shift;
  my $from = shift;
  my $to = shift;

  $to->{'curr_x_min'} = $from->{'curr_x_min'};
  $to->{'curr_x_max'} = $from->{'curr_x_max'};
  $to->{'curr_y_min'} = $from->{'curr_y_min'};
  $to->{'curr_y_max'} = $from->{'curr_y_max'};

  return;
}

sub _draw_y_grid_lines {
	my ($self) = shift;
	$self->{'sub_0'}->_draw_y_grid_lines();
	return;
}

sub _draw_y2_grid_lines {
	my ($self) = shift;
	$self->{'sub_1'}->_draw_y2_grid_lines();
	return;
}

## be a good module and return 1
1;
