The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#
#  Chart::Pie                 #
#                             #
#  written by karlon west     #
#  karlon@netcom.com          #
#  theft is treason, citizen  #
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<#

package Chart::Pie;

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

@Chart::Pie::ISA = qw(Chart::Base);
$Chart::Pie::VERSION = 0.90;

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



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

# Override the ticks methods for the pie charts
# as they do not always make sense.
sub _draw_x_ticks {
  my $self = shift;

  # draw the x ticks, if we're supposed to
  $self->SUPER::_draw_x_ticks unless $self->{'x_ticks'} =~ /^none$/i;

  # then return
  return;
}

sub _draw_y_ticks {
  my $self = shift;

  # draw the y ticks, if we're supposed to
  $self->SUPER::_draw_y_ticks unless $self->{'y_ticks'} =~ /^none$/i;

  # then return
  return;
}

sub _find_y_scale {
  my $self = shift;

  # find y scale, if we're supposed to
  $self->SUPER::_find_y_scale unless $self->{'y_ticks'} =~ /^none$/i;

  # then return
  return;
}

## finally get around to plotting the data
sub _draw_data {
  my $self = shift;
  my $data = $self->{'dataref'};
  my $misccolor = $self->{'color_table'}{'misc'};
  my $background = $self->{'color_table'}{'background'};
  my ($width, $height, $centerX, $centerY, $diameter);
  my ($sum_total, $dataset_sum);
  my ($start_degrees, $end_degrees, $label_degrees);
  my ($pi, $font, $fontW, $fontH, $labelX, $labelY, $label_offset);
  my ($last_labelX, $last_labelY, $label);
  my ($i, $j, $color);

  # set up initial constant values
  $pi = 3.14159265;
  $start_degrees=0;
  $end_degrees=0;
  $font = $self->{'legend_font'};
  $label_offset = .6;
  $fontW = $self->{'legend_font'}->width;
  $fontH = $self->{'legend_font'}->height;
  $last_labelX = 0;
  $last_labelY = 0;

  # init the imagemap data field if they wanted it
  if ($self->{'imagemap'} =~ /^true$/i) {
    $self->{'imagemap_data'} = [];
  }

  # find width and height
  $width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
  $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};

  # find center point, from which the pie will be drawn around
  $centerX = int($width/2)  + $self->{'curr_x_min'};
  $centerY = int($height/2) + $self->{'curr_y_min'};

  # always draw a circle, which means the diameter will be the smaller
  # of the width and height.
  $diameter  = ($centerX < $centerY ? $centerX : $centerY);

  # okay, add up all the numbers of all the datasets, to get the
  # sum total. This will be used to determine the percentage 
  # of each dataset. Obviously, negative numbers might be bad :)
  $sum_total=0;
  for $i (1..$self->{'num_datasets'}) {
     for $j (0..$self->{'num_datapoints'}) {
        if(defined $data->[$i][$j])
        {
           $sum_total += $data->[$i][$j];
        }
     }
  }

  # draw the bars
  for $i (1..$self->{'num_datasets'}) {

    # get the color for this dataset
    $color = $self->{'color_table'}{'dataset'.($i-1)};

    # Add up the sum for this dataset
    $dataset_sum=0;
    for $j (0..$self->{'num_datapoints'}) {
      # don't try to draw anything if there's no data
      if (defined ($data->[$i][$j])) {
         $dataset_sum += $data->[$i][$j];
      }
    }

    $label = $self->{'legend_labels'}[$i-1];
    if(defined $self->{'label_values'})
    {
       if($self->{'label_values'} =~ /^percent$/i)
       {
          $label = sprintf("%s %%%4.2f",$label,$dataset_sum / $sum_total * 100);
       }
       elsif($self->{'label_values'} =~ /^value$/i)
       {
          $label = sprintf("%s %d",$label,$dataset_sum);
       }
       elsif($self->{'label_values'} =~ /^both$/i)
       {
          $label = sprintf("%s %%%4.2f %d",$label,
                                          $dataset_sum / $sum_total * 100,
                                          $dataset_sum);
       }
    }    

    # The first value starts at 0 degrees, each additional dataset
    # stops where the previous left off, and since I've already 
    # calculated the sum_total for the whole graph, I know that
    # the final pie slice will end at 360 degrees.

    # So, get the degree offset for this dataset
    $end_degrees = $start_degrees + ($dataset_sum / $sum_total * 360);

    # stick the label in the middle of the slice
    $label_degrees = ($start_degrees + $end_degrees) / 2;

    # The following drawings are in a very specific ordering, and are not
    # intuitive as to why they are being done this way, but it is basically
    # because the GD module doesn't provide a filledArc() method. So, I
    # developed my own, below.
 
    # First, draw an arc, in black, from the starting offset, all the 
    # way to 360 degrees.
    $self->{'gd_obj'}->arc($centerX,$centerY,
                    $diameter, $diameter,
                    $start_degrees, 360,
                    $misccolor);

    # This is tricky, but draw a short line in the desired color, along the
    # path that will be the end of this pie slice of data. But, make sure not
    # to extend this line to intersect with the boundary of the arc. This
    # is crucial.
    $self->{'gd_obj'}->line($centerX, $centerY,
                    $centerX + .4*$diameter*cos($end_degrees*$pi/180),
                    $centerY + .4*$diameter*sin($end_degrees*$pi/180),
                    $color);

    # Draw the radius of the beginning side of the pie slice, in black
    $self->{'gd_obj'}->line($centerX,$centerY,
                    $centerX + .5*$diameter*cos($start_degrees*$pi/180),
                    $centerY + .5*$diameter*sin($start_degrees*$pi/180),
                    $misccolor);

    # Now, execute fillToBorder, starting from a point on the end line, in the
    # desired pie slice color, and fill until a black pixel if encountered.
    # What this means, is that a series of pie slices is drawn, each starting
    # at the correct location, but each ending at 360 degrees. 
    $self->{'gd_obj'}->fillToBorder(
                    $centerX + .4*$diameter*cos($end_degrees*$pi/180),
                    $centerY + .4*$diameter*sin($end_degrees*$pi/180),
                    $misccolor,$color);

    # Figure out where to place the label
    $labelX = $centerX+$label_offset*$diameter*cos($label_degrees*$pi/180);
    $labelY = $centerY+$label_offset*$diameter*sin($label_degrees*$pi/180);

    # If label is to the left of the pie chart, make sure the label doesn't
    # bleed into the chart. So, back it up the length of the label
    if($labelX < $centerX)
    {
       $labelX -= (length($label) * $fontW);
    }

    # Same thing if the label is above the chart. Don't go too low.
    if($labelY < $centerY)
    {
       $labelY -= $fontH;
    }

    # Okay, if a bunch of very small datasets are close together, they can
    # overwrite each other. The following if statement is to help keep
    # labels of neighbor datasets from beong overlapped. It ain't perfect,
    # but it des a pretty good job.
    if($label_degrees <= 90 || $label_degrees >= 270)
    {
       if(($labelY - $last_labelY) < $fontH                                      && 
          sqrt(($labelY-$last_labelY)**2 + ($labelX-$last_labelX)**2) < $fontH*2 &&
           $last_labelY > 0)
       {
          $labelY = $last_labelY + $fontH;
       }
    }
    else
    {
       if(($last_labelY - $labelY) < $fontH                                      &&
          sqrt(($labelY-$last_labelY)**2 + ($labelX-$last_labelX)**2) < $fontH*2 &&
          $last_labelY > 0)
       {
          $labelY = $last_labelY - $fontH;
       }
    }

    # Now, draw the label for this pie slice
    $self->{'gd_obj'}->string($font, $labelX, $labelY, $label, $misccolor);

    # reset starting point for next dataset and continue.
    $start_degrees = $end_degrees;
    $last_labelX = $labelX;
    $last_labelY = $labelY;
  }

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