## @file
# Implementation of Chart::Pie
#
# written and maintained by
# @author Chart Group at Geodetic Fundamental Station Wettzell (Chart@fs.wettzell.de)
# @date 2014-10-14
# @version 2.4.8
#
## @class Chart::Pie
# @brief Pie class derived class for Chart to implement pies
#
package Chart::Pie;
use Chart::Base '2.4.8';
use GD;
use Carp;
use Chart::Constants;
use strict;
@Chart::Pie::ISA = qw(Chart::Base);
$Chart::Pie::VERSION = '2.4.8';
#>>>>>>>>>>>>>>>>>>>>>>>>>>#
# public methods go here #
#<<<<<<<<<<<<<<<<<<<<<<<<<<#
#>>>>>>>>>>>>>>>>>>>>>>>>>>>#
# private methods go here #
#<<<<<<<<<<<<<<<<<<<<<<<<<<<#
## @fn private _draw_data
# @brief
# finally get around to plotting the data
#
# @details
# The user may define the kind of labelling the data by setting\n
# 'label_values' to 'percent' if she wants to have the percentages\n
# 'label_values' to 'values' if she wants to have the absolut values\n
# 'label_values' to 'both' if she wants to have absolut values and percentages\n
# 'label_values' to 'none' if she wants to have neither absolute values nor percentages\n
# 'ring' to a number less then 1 to define a ring as output;
# if 'ring' is 1 ore greater a full pie is plotted\n
#
sub _draw_data
{
my $self = shift;
my $data = $self->{'dataref'};
my $misccolor = $self->_color_role_to_index('misc');
my $textcolor = $self->_color_role_to_index('text');
my $background = $self->_color_role_to_index('background');
my ( $width, $height, $centerX, $centerY, $diameter, $diameter_previous, $text_diameter );
my $dataset_sum;
my ( $start_degrees, $end_degrees, $label_degrees, $label_old_degrees, $labelY_repeat_count );
my ( $pi, $rd2dg, $dg2rd );
my ( $font, $fontW, $fontH, $labelX, $labelY );
my $label;
my ( $i, $j, $color );
my $label_length = 0;
my $degrees = 0;
my $insidecolor;
my $forbidden_degrees = 0; # last occupied degree
my %labelinfo;
my $max_val_len = 0;
my $max_label_len = 0;
# set up initial constant values
$pi = Chart::Constants::PI;
$dg2rd = $pi / 180; # Degree to Radians
$rd2dg = 180 / $pi; # Radian to Degree
$start_degrees = 0;
$end_degrees = 0;
$font = $self->{'legend_font'};
$fontW = $self->{'legend_font'}->width;
$fontH = $self->{'legend_font'}->height;
$label_degrees = $labelY_repeat_count = 0;
# init the imagemap data field if they wanted it
if ( $self->true( $self->{'imagemap'} ) )
{
$self->{'imagemap_data'} = [];
}
# find width and height of the plotting area
$width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
$height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
# 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 :)
$dataset_sum = 0;
for $j ( 0 .. $self->{'num_datapoints'} )
{
if ( defined $data->[1][$j] && $data->[1][$j] > 0 )
{
#add to sum
$dataset_sum += $data->[1][$j];
#don't allow negativ values
if ( $data->[1][$j] < 0 )
{
croak "We need positiv data for a pie chart (which is not true for data[$j])!";
}
}
}
# find the longest label
# first we need the length of the values
$max_label_len = 1;
for $j ( 0 .. ( $self->{'num_datapoints'} - 1 ) )
{
# don't try to draw anything if there's no data
$labelinfo{$j}{data} = 'undefined';
if ( defined( $data->[1][$j] ) && $data->[1][$j] > 0 )
{
$labelinfo{$j}{data} = $data->[1][$j];
$label = $data->[0][$j];
$labelinfo{$j}{labeldata} = $label;
if ( defined $self->{'label_values'} )
{
if ( $self->{'label_values'} =~ /^percent$/i )
{
$label = sprintf( "%s %4.2f%%", $label, $data->[1][$j] / ( $dataset_sum || 1 ) * 100 );
}
elsif ( $self->{'label_values'} =~ /^value$/i )
{
if ( $data->[1][$j] =~ /\./ )
{
$label = sprintf( "%s %.2f", $label, $data->[1][$j] );
}
else
{
$label = sprintf( "%s %d", $label, $data->[1][$j] );
}
}
elsif ( $self->{'label_values'} =~ /^both$/i )
{
if ( $data->[1][$j] =~ /\./ )
{
$label =
sprintf( "%s %4.2f%% %.2f", $label, $data->[1][$j] / ( $dataset_sum || 1 ) * 100, $data->[1][$j] );
}
else
{
$label =
sprintf( "%s %4.2f%% %d", $label, $data->[1][$j] / ( $dataset_sum || 1 ) * 100, $data->[1][$j] );
}
}
elsif ( $self->{'label_values'} =~ /^none$/i )
{
$label = sprintf( "%s", $label );
}
}
$label_length = length($label);
$labelinfo{$j}{labelstring} = $label, $labelinfo{$j}{labellength} = $label_length;
}
$max_label_len = $label_length if ( $max_label_len < $label_length );
}
$max_label_len *= $fontW;
# 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'};
# @details
# always draw a circle, which means the diameter will be the smaller
# of the width and height. let enough space for the labels.\n
# Calculate the space needed for the labels by taking
# into account the angles where the labels will be plotted
my $labeldistance = 2 * $self->maximum( $fontW, $fontH );
$start_degrees = 0;
$end_degrees = 0;
my $max_radius = $self->minimum( $width, $height ) / 2;
my $radius = $max_radius;
for $j ( 0 .. ( $self->{'num_datapoints'} - 1 ) )
{
# So, get the degree offset for this dataset
$end_degrees = $start_degrees + ( $data->[1][$j] / ( $dataset_sum || 1 ) * 360 );
$degrees = $start_degrees + ( $end_degrees - $start_degrees ) / 2;
# stick the label in the middle of the slice
$label_degrees = ( $start_degrees + $end_degrees ) / 2;
#print "Vor Modulo: label_degrees = $label_degrees\n";
$label_degrees = $self->modulo( $label_degrees, 360.0 );
#print "Nach Modulo: label_degrees = $label_degrees\n";
$label = $labelinfo{$j}{labelstring};
$label_length = $labelinfo{$j}{labellength} || 0;
#!!DEBUG!!!!
#print "Text=".$label.", StartDegrees=".$start_degrees.
# ", end=".$end_degrees.
# ", label_degree=".$label_degrees."\n";
# 0 degrees means east
# 90 degrees means south
# 180 degrees means west
# 270 degrees means north
# 360 degrees means east again
if ( ( $label_degrees >= 270 && $label_degrees <= 360 )
|| ( $label_degrees >= 0 && $label_degrees <= 90 ) )
{
# right side of the circle
{
# $x value in respect of label arc
my $x = $radius * sin( $dg2rd * $label_degrees );
my $y = $radius * cos( $dg2rd * $label_degrees );
#!!DEBUG!!!!
#print "Text=".$label.": x=$x, y=$y\n";
# i.e. the startpoint is at $centerX+$x, $centerY-$y
#test
#$self->{'gd_obj'}->rectangle( $centerX, $centerY, $centerX+$x, $centerY-$y, $misccolor );
# theoretical right value in respect to radius and length of label
my $right_pos = $centerX + $x + $label_length * $fontW;
if ( $right_pos > $self->{'curr_x_max'} )
{
# too far right, correct x
$right_pos = $self->{'curr_x_max'};
$x = $right_pos - $centerX - $label_length * $fontW;
#!!DEBUG!!!!
#print "too far right: Text=".$label.": x=$x, y=$y\n";
}
# theoretical top position in respect to radius and height of label
# (Remark: direction to the top of the picture counts backwards!)
my $top_pos = $centerY - $y - $fontH;
if ( $top_pos < $self->{'curr_y_min'} )
{
# too far up, correct $y
$top_pos = $self->{'curr_y_min'};
$y = $centerY - $top_pos - $fontH;
#!!DEBUG!!!!
#print "too far up: Text=".$label.": x=$x, y=$y\n";
}
my $down_pos = $centerY + $y + $fontH;
if ( $down_pos > $self->{'curr_y_max'} )
{
$down_pos = $self->{'curr_y_max'};
$y = $down_pos - $centerY - $fontH;
#!!DEBUG!!!!
#print "too far down: Text=".$label.": x=$x, y=$y\n";
}
#test
#$self->{'gd_obj'}->rectangle( $centerX+$x, $centerY-$y, $right_pos, $top_pos, $textcolor );
#$self->{'gd_obj'}
# ->rectangle( $self->{'curr_x_min'}, $self->{'curr_y_min'}, $self->{'curr_x_max'}, $self->{'curr_y_max'}, $misccolor );
#return;
#!!DEBUG!!!!
#print "before Line 270: label_degrees=$label_degrees, cos()=". cos( $dg2rd * $label_degrees ). ", sin()=". sin( $dg2rd * $label_degrees )."\n";
if ( $label_degrees == 0 )
{
$radius = $self->minimum( $radius, abs( $y / cos( $dg2rd * $label_degrees ) ) );
}
else
{
$radius =
$self->minimum( $radius, $x / sin( $dg2rd * $label_degrees ),
abs( $y / cos( $dg2rd * $label_degrees ) ) );
}
$radius = int( $radius + 0.5 );
#!!DEBUG!!
#$self->{'gd_obj'}->line( $centerX, $centerY, $centerX+$radius, $centerY, gdBrushed );
}
if ( $radius <= 0 ) { croak "radius < 0!"; }
}
else
{ # left side of the circle
# as 0 degrees means east
if ( abs($label_degrees) < 0.1 )
{
# too small angle
$radius = $self->{'curr_x_max'} - $label_length * $fontW;
}
else
{
# $x value in respect of label arc
my $x = $radius * sin( $dg2rd * $label_degrees );
my $y = $radius * cos( $dg2rd * $label_degrees );
# i.e. the startpoint is at $centerX-$x, $centerY+$y
#test
#$self->{'gd_obj'}->rectangle( $centerX, $centerY, $centerX-$x, $centerY+$y, $misccolor );
# theoretical right value in respect to radius and length of label
my $left_pos = $centerX - $x - $label_length * $fontW;
if ( $left_pos < $self->{'curr_x_min'} )
{
# too far right, correct x
$left_pos = $self->{'curr_x_min'};
$x = $centerX - $left_pos - $label_length * $fontW;
}
# theoretical top position in respect to radius and height of label
# (Remark: direction to the top of the picture counts backwards!)
my $top_pos = $centerY + $y - $fontH;
if ( $top_pos < $self->{'curr_y_min'} )
{
# too far up, correct $y
$top_pos = $self->{'curr_y_min'};
$y = $centerY - $top_pos - $fontH;
}
my $down_pos = $centerY - $y + $fontH;
if ( $down_pos > $self->{'curr_y_max'} )
{
$down_pos = $self->{'curr_y_max'};
$y = $centerY + $fontH - $down_pos;
}
#test
#$self->{'gd_obj'}->rectangle( $centerX-$x, $centerY+$y, $left_pos, $top_pos, $textcolor );
#$self->{'gd_obj'}
# ->rectangle( $self->{'curr_x_min'}, $self->{'curr_y_min'}, $self->{'curr_x_max'}, $self->{'curr_y_max'}, $misccolor );
#return;
$radius =
$self->minimum( $radius, $x / sin( $dg2rd * $label_degrees ), abs( $y / cos( $dg2rd * $label_degrees ) ) );
$radius = int( $radius + 0.5 );
#test
#$self->{'gd_obj'}->line( $centerX, $centerY, $centerX+$radius, $centerY, gdBrushed );
}
if ( $radius <= 0 ) { croak "radius < 0!"; }
}
# reset starting point for next dataset and continue.
$start_degrees = $end_degrees;
}
$diameter = $radius * 2 - 2 * $labeldistance;
$text_diameter = $diameter + $labeldistance;
$self->{'gd_obj'}->arc( $centerX, $centerY, $diameter, $diameter, 0, 360, $misccolor );
# for DEBUG!!
#$self->{'gd_obj'}->arc($centerX, $centerY, $text_diameter, $text_diameter,
# 0, 360, $misccolor);
# for DEBUG!!
#print "-------------------------------------------\n";
# @details
# Plot the pies
$start_degrees = 0;
$end_degrees = 0;
for $j ( 0 .. ( $self->{'num_datapoints'} - 1 ) )
{
next if $labelinfo{$j}{data} eq 'undefined';
# get the color for this datapoint, take the color of the datasets
$color = $self->_color_role_to_index( 'dataset' . $j );
$label = $labelinfo{$j}{labelstring};
$label_length = $labelinfo{$j}{labellength};
# 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 + ( $data->[1][$j] / ( $dataset_sum || 1 ) * 360 );
$degrees = ( $start_degrees + $end_degrees ) / 2;
# stick the label in the middle of the slice
$label_degrees = $degrees;
$label_degrees = $self->modulo( $label_degrees, 360.0 );
if ( $start_degrees < $end_degrees )
{
# draw filled Arc
# test
#print "centerX=$centerX, centerY=$centerY, diameter=$diameter, $start_degrees, ".int($start_degrees) . ", $end_degrees\n";
#$self->{'gd_obj'}->filledArc( $centerX, $centerY, $diameter, $diameter, $start_degrees, $end_degrees, $color );
$self->{'gd_obj'}->filledArc(
$centerX, $centerY, $diameter, $diameter,
int( $start_degrees - 0.5 ),
int( $end_degrees + 0.5 ), $color
);
}
# Figure out where to place the label
# $forbidden_degrees = angle of the center, representing the height of the label
if ( $j == 0 )
{
$forbidden_degrees = $rd2dg * atan2( $fontH, 0.5 * $text_diameter );
$label_old_degrees = 0;
}
else
{
my $winkel;
my $h;
if ( ( $label_old_degrees <= 90.0 && $label_degrees > 90.0 )
|| ( $label_old_degrees <= 270.0 && $label_degrees > 270.0 ) )
{
# at 90 degrees there the reference point to the text changes
# from the beginning to the back
$forbidden_degrees = 0;
}
$label_degrees = $self->maximum( $label_degrees, $forbidden_degrees );
$label_old_degrees = $label_degrees; # remember old label_degrees
$winkel = cos( $label_degrees * $dg2rd );
$winkel = abs($winkel);
if ( abs($winkel) < 0.01 )
{
$h = 0;
}
else
{
$h = $fontH / $winkel;
}
my $atan = atan2( $h, 0.5 * $text_diameter ); # -pi ... +pi
$forbidden_degrees = $label_degrees + $rd2dg * $atan;
# for debugging
#printf("Index=%2d winkel=%6.2f, H=%3d atan=%5.2f label=%6.2f forbidden=%6.2f\n",
# $j, $winkel*$dg2rd,$h, $atan*$rd2dg, $label_degrees,$forbidden_degrees);
# end for debugging
}
$labelX = $centerX + $text_diameter * 0.5 * cos( $label_degrees * $dg2rd );
$labelY = $centerY + $text_diameter * 0.5 * sin( $label_degrees * $dg2rd );
#!!DEBUG!!!!
#print "Text=".$label.": labelX=$labelX, y=$labelY\n";
# # For debugging
# # Draw Point
# # reset the brush for points
# my $brush = $self->_prepare_brush($color, 'point',
# $self->{'pointStyle' . '0'});
# $self->{'gd_obj'}->setBrush($brush);
#
# # draw the point
# $self->{'gd_obj'}->line($labelX, $labelY, $labelX, $labelY, gdBrushed);
# # end for debugging
# 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 being overlapped. It ain't perfect,
# but it does a pretty good job.
if ( ( $label_degrees >= 270 && $label_degrees <= 360 )
|| ( $label_degrees >= 0 && $label_degrees <= 90 ) )
{
# right side of the circle
# as 0 degrees means east
# $textcolor marks everything black
$self->{'gd_obj'}->string( $font, $labelX, $labelY - $fontH * 0.5, $label, $textcolor );
}
else
{
# $textcolor marks everything black
$self->{'gd_obj'}->string( $font, $labelX - length($label) * $fontW, $labelY - $fontH * 0.5, $label, $textcolor );
}
if ( $self->true( $self->{'legend_lines'} ) )
{
$self->{'gd_obj'}->line(
$centerX + 0.5 * $diameter * cos( $degrees * $dg2rd ),
$centerY + 0.5 * $diameter * sin( $degrees * $dg2rd ),
$labelX, $labelY, $color
);
}
# reset starting point for next dataset and continue.
$start_degrees = $end_degrees;
} # end for $j
# print "Center $centerX, $centerY\n";
# print "Durchmesser $diameter\n";
# print "Hintergrund $background\n";
if ( defined( $self->{'ring'} ) && abs( $self->{'ring'} ) < 1 )
{
# print "bground $bground\n";
my $hole = ( 1 - abs( $self->{'ring'} ) );
if ( $self->true( $self->{'grey_background'} ) )
{
$insidecolor = $self->_color_role_to_index('grey_background');
}
else
{
$insidecolor = $background;
}
$self->{'gd_obj'}->filledArc( $centerX, $centerY, $hole * $diameter, $hole * $diameter, 0, 360, $insidecolor );
$self->{'gd_obj'}->arc( $centerX, $centerY, $hole * $diameter, $hole * $diameter, 0, 360, $misccolor );
}
# 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;
}
## @fn private _draw_right_legend
# Overwrite the legend methods to get the right legend
sub _draw_right_legend
{
my $self = shift;
my $data = $self->{'dataref'};
my @labels = @{ $data->[0] };
my ( $x1, $x2, $x3, $y1, $y2, $width, $color, $misccolor, $w, $h, $brush );
my $font = $self->{'legend_font'};
my $l1 = 0;
my $l2 = 0;
my ( $i, $j, $label, $dataset_sum );
my $max_label_len = 1;
# make sure we're using a real font
unless ( ( ref($font) ) eq 'GD::Font' )
{
croak "The 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 what the sum of all datapoits is, needed for the Labels with percent
$dataset_sum = 0;
for my $j ( 0 .. $self->{'num_datapoints'} )
{
if ( defined $data->[1][$j] )
{
$dataset_sum += $data->[1][$j];
}
}
# find out how who wide the largest label text is
foreach (@labels)
{
if ( length($_) > $l1 )
{
$l1 = length($_);
}
}
for ( my $i = 0 ; $i < ( $self->{'num_datapoints'} ) ; $i++ )
{
if ( length( $data->[1][$i] ) > $l2 )
{
$l2 = length( $data->[1][$i] );
}
}
if ( $self->{'legend_label_values'} =~ /^value$/i )
{
$max_label_len = $l1 + $l2 + 1;
}
elsif ( $self->{'legend_label_values'} =~ /^percent$/i )
{
$max_label_len = $l1 + 7;
}
elsif ( $self->{'legend_label_values'} =~ /^both$/i )
{
$max_label_len = $l1 + $l2 + 9;
}
else
{
$max_label_len = $l1;
}
# find out how wide the largest label is
$width = ( 2 * $self->{'text_space'} )
#+ ($self->{'max_legend_label'} * $w)
+ $max_label_len * $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_datapoints'} * ( $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
$color = $self->_color_role_to_index( 'dataset' . $_ );
# 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;
if ( defined $data->[1][$_] )
{
if ( $self->{'legend_label_values'} =~ /^value$/i )
{
$self->{'gd_obj'}->string( $font, $x2, $y2, $labels[$_] . ' ' . $data->[1][$_], $color );
}
elsif ( $self->{'legend_label_values'} =~ /^percent$/i )
{
$label = sprintf( "%s %4.2f%%", $labels[$_], $data->[1][$_] / ( $dataset_sum || 1 ) * 100 );
$self->{'gd_obj'}->string( $font, $x2, $y2, $label, $color );
}
elsif ( $self->{'legend_label_values'} =~ /^both$/i )
{
if ( $data->[1][$_] =~ /\./ )
{
$label =
sprintf( "%s %4.2f%% %.2f", $labels[$_], $data->[1][$_] / ( $dataset_sum || 1 ) * 100, $data->[1][$_] );
}
else
{
$label =
sprintf( "%s %4.2f%% %d", $labels[$_], $data->[1][$_] / ( $dataset_sum || 1 ) * 100, $data->[1][$_] );
}
$self->{'gd_obj'}->string( $font, $x2, $y2, $label, $color );
}
else
{
$self->{'gd_obj'}->string( $font, $x2, $y2, $labels[$_], $color );
}
}
}
# mark off the used space
$self->{'curr_x_max'} -= $width;
# and return
return 1;
}
## @fn private _draw_left_legend
# put the legend on the left of the chart
sub _draw_left_legend
{
my $self = shift;
my $data = $self->{'dataref'};
my @labels = @{ $data->[0] };
my ( $x1, $x2, $x3, $y1, $y2, $width, $color, $misccolor, $w, $h, $brush );
my $font = $self->{'legend_font'};
my $max_label_len = 1;
my $l1 = 0;
my $l2 = 0;
my ( $dataset_sum, $label );
# make sure we're using a real font
unless ( ( ref($font) ) eq 'GD::Font' )
{
croak "The 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 what the sum of all datapoits is, needed for the Labels with percent
$dataset_sum = 0;
for my $j ( 0 .. $self->{'num_datapoints'} )
{
if ( defined $data->[1][$j] )
{
$dataset_sum += $data->[1][$j];
}
}
# find out how who wide the largest label text is
foreach (@labels)
{
if ( length($_) > $l1 )
{
$l1 = length($_);
}
}
for ( my $i = 0 ; $i < ( $self->{'num_datapoints'} ) ; $i++ )
{
if ( length( $data->[1][$i] ) > $l2 )
{
$l2 = length( $data->[1][$i] );
}
}
if ( $self->{'legend_label_values'} =~ /^value$/i )
{
$max_label_len = $l1 + $l2 + 1;
}
elsif ( $self->{'legend_label_values'} =~ /^percent$/i )
{
$max_label_len = $l1 + 7;
}
elsif ( $self->{'legend_label_values'} =~ /^both$/i )
{
$max_label_len = $l1 + $l2 + 9;
}
else
{
$max_label_len = $l1;
}
# find out how wide the largest label is
$width =
( 2 * $self->{'text_space'} ) +
( $max_label_len * $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_datapoints'} * ( $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
$color = $self->_color_role_to_index( 'dataset' . $_ );
# 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;
if ( $self->{'legend_label_values'} =~ /^value$/i )
{
$self->{'gd_obj'}->string( $font, $x2, $y2, $labels[$_] . ' ' . $data->[1][$_], $color );
}
elsif ( $self->{'legend_label_values'} =~ /^percent$/i )
{
$label = sprintf( "%s %4.2f%%", $labels[$_], $data->[1][$_] / ( $dataset_sum || 1 ) * 100 );
$self->{'gd_obj'}->string( $font, $x2, $y2, $label, $color );
}
elsif ( $self->{'legend_label_values'} =~ /^both$/i )
{
if ( $data->[1][$_] =~ /\./ )
{
$label =
sprintf( "%s %4.2f%% %.2f", $labels[$_], $data->[1][$_] / ( $dataset_sum || 1 ) * 100, $data->[1][$_] );
}
else
{
$label = sprintf( "%s %4.2f%% %d", $labels[$_], $data->[1][$_] / ( $dataset_sum || 1 ) * 100, $data->[1][$_] );
}
$self->{'gd_obj'}->string( $font, $x2, $y2, $label, $color );
}
else
{
$self->{'gd_obj'}->string( $font, $x2, $y2, $labels[$_], $color );
}
}
# mark off the used space
$self->{'curr_x_min'} += $width;
# and return
return 1;
}
## @fn private _draw_bottom_legend
# put the legend on the bottom of the chart
sub _draw_bottom_legend
{
my $self = shift;
my $data = $self->{'dataref'};
my @labels = @{ $data->[0] };
my ( $x1, $y1, $x2, $x3, $y2, $empty_width, $max_label_width, $cols, $rows, $color, $brush );
my ( $col_width, $row_height, $r, $c, $index, $x, $y, $w, $h );
my $font = $self->{'legend_font'};
my $max_label_len;
my $l1 = 0;
my $l2 = 0;
my ( $dataset_sum, $j );
my $label;
# make sure we're using a real font
unless ( ( ref($font) ) eq 'GD::Font' )
{
croak "The font you specified isn\'t a GD Font object";
}
# get the size of the font
( $h, $w ) = ( $font->height, $font->width );
# find the base x values
$x1 = $self->{'curr_x_min'} + $self->{'graph_border'};
# + ($self->{'y_tick_label_length'} * $self->{'tick_label_font'}->width)
# + $self->{'tick_len'} + (3 * $self->{'text_space'});
$x2 = $self->{'curr_x_max'} - $self->{'graph_border'};
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'};
}
#find out what the sum of all datapoits is, needed for the Labels with percent
$dataset_sum = 0;
for $j ( 0 .. $self->{'num_datapoints'} )
{
if ( defined $data->[1][$j] )
{
$dataset_sum += $data->[1][$j];
}
}
# find out how who wide the largest label text is, especially look what kind of
# label is needed
foreach (@labels)
{
if ( length($_) > $l1 )
{
$l1 = length($_);
}
}
for ( my $i = 0 ; $i < ( $self->{'num_datapoints'} ) ; $i++ )
{
if ( length( $data->[1][$i] ) > $l2 )
{
$l2 = length( $data->[1][$i] );
}
}
if ( $self->{'legend_label_values'} =~ /^value$/i )
{
$max_label_len = $l1 + $l2 + 1;
}
elsif ( $self->{'legend_label_values'} =~ /^percent$/i )
{
$max_label_len = $l1 + 7;
}
elsif ( $self->{'legend_label_values'} =~ /^both$/i )
{
$max_label_len = $l1 + $l2 + 9;
}
else
{
$max_label_len = $l1;
}
# figure out how wide the columns need to be, and how many we
# can fit in the space available
$empty_width = ( $x2 - $x1 ) - ( 2 * $self->{'legend_space'} );
$max_label_width = $max_label_len * $w
#$self->{'max_legend_label'} * $w
+ ( 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, remember how tall they are
$rows = int( $self->{'num_datapoints'} / $cols );
unless ( ( $self->{'num_datapoints'} % $cols ) == 0 )
{
$rows++;
}
unless ($rows)
{
$rows = 1;
}
$row_height = $h + $self->{'text_space'};
# box the legend 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_role_to_index('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
for $r ( 0 .. $rows - 1 )
{
for $c ( 0 .. $cols - 1 )
{
$index = ( $r * $cols ) + $c; # find the index in the label array
if ( $labels[$index] )
{
# get the color
$color = $self->_color_role_to_index( 'dataset' . $index );
# get the x-y coordinate for the start of the example line
$x = $x1 + ( $col_width * $c );
$y = $y1 + ( $row_height * $r ) + $h / 2;
# now draw the example line
$self->{'gd_obj'}->line( $x, $y, $x + $self->{'legend_example_size'}, $y, $color );
# reset the brush for points
$brush = $self->_prepare_brush( $color, 'point', $self->{ 'pointStyle' . $index } );
$self->{'gd_obj'}->setBrush($brush);
# draw the point
$x3 = int( $x + $self->{'legend_example_size'} / 2 );
$self->{'gd_obj'}->line( $x3, $y, $x3, $y, gdBrushed );
# adjust the x-y coordinates for the start of the label
$x += $self->{'legend_example_size'} + ( 2 * $self->{'text_space'} );
$y = $y1 + ( $row_height * $r );
# now draw the label
if ( $self->{'legend_label_values'} =~ /^value$/i )
{
$self->{'gd_obj'}->string( $font, $x, $y, $labels[$index] . ' ' . $data->[1][$index], $color );
#$self->{'gd_obj'}->stringTTF($color, FONT, 10, 0, $x, $y+10, $labels[$index].' '.$data->[1][$index]); ############
}
elsif ( $self->{'legend_label_values'} =~ /^percent$/i )
{
$label = sprintf( "%s %4.2f%%", $labels[$index], $data->[1][$index] / ( $dataset_sum || 1 ) * 100 );
$self->{'gd_obj'}->string( $font, $x, $y, $label, $color );
}
elsif ( $self->{'legend_label_values'} =~ /^both$/i )
{
if ( $data->[1][$index] =~ /\./ )
{
$label = sprintf( "%s %4.2f%% %.2f",
$labels[$index],
$data->[1][$index] / ( $dataset_sum || 1 ) * 100,
$data->[1][$index] );
}
else
{
$label = sprintf( "%s %4.2f%% %d",
$labels[$index],
$data->[1][$index] / ( $dataset_sum || 1 ) * 100,
$data->[1][$index] );
}
$self->{'gd_obj'}->string( $font, $x, $y, $label, $color ); ###
# $self->{'gd_obj'}->stringTTF($color, FONT, 10, 0, $x, $y, $label);
}
else
{
$self->{'gd_obj'}->string( $font, $x, $y, $labels[$index], $color );
}
}
}
}
# mark off the space used
$self->{'curr_y_max'} -= ( $rows * $row_height ) + $self->{'text_space'} + ( 2 * $self->{'legend_space'} );
# now return
return 1;
}
## @fn private _draw_top_legend
# put the legend on top of the chart
sub _draw_top_legend
{
my $self = shift;
my $data = $self->{'dataref'};
my ($max_label_len);
my $l1 = 0;
my $l2 = 0;
my @labels = @{ $data->[0] };
my ( $x1, $y1, $x2, $x3, $y2, $empty_width, $max_label_width, $cols, $rows, $color, $brush );
my ( $col_width, $row_height, $r, $c, $index, $x, $y, $w, $h, $dataset_sum, $label );
my $font = $self->{'legend_font'};
# make sure we're using a real font
unless ( ( ref($font) ) eq 'GD::Font' )
{
croak "The font you specified isn\'t a GD Font object";
}
# get the size of the font
( $h, $w ) = ( $font->height, $font->width );
#find out what the sum of all datapoits is, needed for the Labels with percent
$dataset_sum = 0;
for my $j ( 0 .. $self->{'num_datapoints'} )
{
if ( defined $data->[1][$j] )
{
$dataset_sum += $data->[1][$j];
}
}
# get some base x coordinates
$x1 = $self->{'curr_x_min'} + $self->{'graph_border'};
# + $self->{'y_tick_label_length'} * $self->{'tick_label_font'}->width
# + $self->{'tick_len'} + (3 * $self->{'text_space'});
$x2 = $self->{'curr_x_max'} - $self->{'graph_border'};
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'};
}
# find out how who wide the largest label text is
foreach (@labels)
{
if ( length($_) > $l1 )
{
$l1 = length($_);
}
}
for ( my $i = 0 ; $i < ( $self->{'num_datapoints'} ) ; $i++ )
{
if ( length( $data->[1][$i] ) > $l2 )
{
$l2 = length( $data->[1][$i] );
}
}
if ( $self->{'legend_label_values'} =~ /^value$/i )
{
$max_label_len = $l1 + $l2 + 1;
}
elsif ( $self->{'legend_label_values'} =~ /^percent$/i )
{
$max_label_len = $l1 + 7;
}
elsif ( $self->{'legend_label_values'} =~ /^both$/i )
{
$max_label_len = $l1 + $l2 + 9;
}
else
{
$max_label_len = $l1;
}
# figure out how wide the columns can be, and how many will fit
$empty_width = ( $x2 - $x1 ) - ( 2 * $self->{'legend_space'} );
$max_label_width = ( 4 * $self->{'text_space'} ) + $max_label_len * $w + $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 remember how tall they are
$rows = int( $self->{'num_datapoints'} / $cols );
unless ( ( $self->{'num_datapoints'} % $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_role_to_index('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
for $r ( 0 .. $rows - 1 )
{
for $c ( 0 .. $cols - 1 )
{
$index = ( $r * $cols ) + $c; # find the index in the label array
if ( $labels[$index] )
{
# get the color
$color = $self->_color_role_to_index( 'dataset' . $index );
# find the x-y coords
$x = $x1 + ( $col_width * $c );
$y = $y1 + ( $row_height * $r ) + $h / 2;
# draw the line first
$self->{'gd_obj'}->line( $x, $y, $x + $self->{'legend_example_size'}, $y, $color );
# reset the brush for points
$brush = $self->_prepare_brush( $color, 'point', $self->{ 'pointStyle' . $index } );
$self->{'gd_obj'}->setBrush($brush);
# draw the point
$x3 = int( $x + $self->{'legend_example_size'} / 2 );
$self->{'gd_obj'}->line( $x3, $y, $x3, $y, gdBrushed );
# now the label
$x += $self->{'legend_example_size'} + ( 2 * $self->{'text_space'} );
$y -= $h / 2;
if ( $self->{'legend_label_values'} =~ /^value$/i )
{
$self->{'gd_obj'}->string( $font, $x, $y, $labels[$index] . ' ' . $data->[1][$index], $color );
}
elsif ( $self->{'legend_label_values'} =~ /^percent$/i )
{
$label = sprintf( "%s %4.2f%%", $labels[$index], $data->[1][$index] / ( $dataset_sum || 1 ) * 100 );
$self->{'gd_obj'}->string( $font, $x, $y, $label, $color );
}
elsif ( $self->{'legend_label_values'} =~ /^both$/i )
{
if ( $data->[1][$index] =~ /\./ )
{
$label = sprintf( "%s %4.2f%% %.2f",
$labels[$index],
$data->[1][$index] / ( $dataset_sum || 1 ) * 100,
$data->[1][$index] );
}
else
{
$label = sprintf( "%s %4.2f%% %d",
$labels[$index],
$data->[1][$index] / ( $dataset_sum || 1 ) * 100,
$data->[1][$index] );
}
$self->{'gd_obj'}->string( $font, $x, $y, $label, $color );
}
else
{
$self->{'gd_obj'}->string( $font, $x, $y, $labels[$index], $color );
}
}
}
}
# mark off the space used
$self->{'curr_y_min'} += ( $rows * $row_height ) + $self->{'text_space'} + 2 * $self->{'legend_space'};
# now return
return 1;
}
## @fn private _draw_x_ticks
# Override the ticks methods for the pie charts.\n
# Here: do nothing
sub _draw_x_ticks
{
my $self = shift;
return;
}
## @fn private _draw_y_ticks
# Override the ticks methods for the pie charts.\n
sub _draw_y_ticks
{
my $self = shift;
return;
}
## @fn private _find_y_scale
# Override the find_y_scale methods for the pie charts.\n
# Here: do nothing
sub _find_y_scale
{
my $self = shift;
return;
}
## be a good module and return 1
1;