Chart
view release on metacpan or search on metacpan
lib/Chart/Base.pm view on Meta::CPAN
# does only calculate in integers
# @param[in] a a in a%b
# @param[in] b b in a%b
# @return $a % $b in float
sub modulo {
my $pkg = shift;
my $a = shift;
my $b = shift;
my $erg = 0.0;
if ( !defined($a) || !defined($b) || $b == 0 )
{
die "Modulo needs valid parameters!"
#return $erg;
}
my $div = $a / $b;
$erg = $a - int($div) * $b;
return $erg;
}
#>>>>>>>>>>>>>>>>>>>>>>>>>>>#
# private methods go here #
#<<<<<<<<<<<<<<<<<<<<<<<<<<<#
## @fn private int _init($x,$y)
# Initialize all default options here
# @param[in] x Width of the final image in pixels (Default: 400)
# @param[in] y Height of the final image in pixels (Default: 300)
#
sub _init {
my $self = shift;
my $x = shift || 400; # give them a 400x300 image
my $y = shift || 300; # unless they say otherwise
# get the gd object
# Reference to new GD::Image
$self->{'gd_obj'} = GD::Image->new( $x, $y );
# start keeping track of used space
# actual current y min Value
$self->{'curr_y_min'} = 0;
$self->{'curr_y_max'} = $y; # maximum pixel in y direction (down)
$self->{'curr_x_min'} = 0;
$self->{'curr_x_max'} = $x; # maximum pixel in x direction (right)
# use a 10 pixel border around the whole png
$self->{'png_border'} = 10;
# leave some space around the text fields
$self->{'text_space'} = 2;
# and leave some more space around the chart itself
$self->{'graph_border'} = 10;
# leave a bit of space inside the legend box
$self->{'legend_space'} = 4;
# set some default fonts
$self->{'title_font'} = gdLargeFont,
$self->{'sub_title_font'} = gdLargeFont,
$self->{'legend_font'} = gdSmallFont,
$self->{'label_font'} = gdMediumBoldFont,
$self->{'tick_label_font'} = gdSmallFont;
# put the legend on the bottom of the chart
$self->{'legend'} = 'right';
# default to an empty list of labels
$self->{'legend_labels'} = [];
# use 20 pixel length example lines in the legend
$self->{'legend_example_size'} = 20;
# Set the maximum & minimum number of ticks to use.
$self->{'y_ticks'} = 6,
$self->{'min_y_ticks'} = 6,
$self->{'max_y_ticks'} = 100,
$self->{'x_number_ticks'} = 1,
$self->{'min_x_ticks'} = 6,
$self->{'max_x_ticks'} = 100;
# make the ticks 4 pixels long
$self->{'tick_len'} = 4;
# no custom y tick labels
$self->{'y_tick_labels'} = undef;
# no patterns
$self->{'patterns'} = undef;
# let the lines in Chart::Lines be 6 pixels wide
$self->{'brush_size'} = 6;
# let the points in Chart::Points and Chart::LinesPoints be 18 pixels wide
$self->{'pt_size'} = 18;
# use the old non-spaced bars
$self->{'spaced_bars'} = 'true';
# use the new grey background for the plots
$self->{'grey_background'} = 'true';
# don't default to transparent
$self->{'transparent'} = 'false';
# default to "normal" x_tick drawing
$self->{'x_ticks'} = 'normal';
# we're not a component until Chart::Composite says we are
$self->{'component'} = 'false';
# don't force the y-axes in a Composite chare to be the same
$self->{'same_y_axes'} = 'false';
# plot rectangeles in the legend instead of lines in a composite chart
$self->{'legend_example_height'} = 'false';
# don't force integer y-ticks
$self->{'integer_ticks_only'} = 'false';
# don't forbid a false zero scale.
$self->{'include_zero'} = 'false';
# don't waste time/memory by storing imagemap info unless they ask
$self->{'imagemap'} = 'false';
# default for grid_lines is off
$self->{grid_lines} = 'false',
$self->{x_grid_lines} = 'false',
$self->{y_grid_lines} = 'false',
$self->{y2_grid_lines} = 'false';
# default for no_cache is false. (it breaks netscape 4.5)
$self->{no_cache} = 'false';
# default value for skip_y_ticks for the labels
$self->{skip_y_ticks} = 1;
# default value for skip_int_ticks only for integer_ticks_only
$self->{skip_int_ticks} = 1;
# default value for precision
$self->{precision} = 3;
# default value for legend label values in pie charts
$self->{legend_label_values} = 'value';
# default value for the labels in a pie chart
$self->{label_values} = 'percent';
# default position for the y-axes
$self->{y_axes} = 'left';
# copies of the current values at the x-ticks function
$self->{temp_x_min} = 0;
$self->{temp_x_max} = 0;
$self->{temp_y_min} = 0;
$self->{temp_y_max} = 0;
# Instance for a sum
$self->{sum} = 0;
# Don't sort the data unless they ask
$self->{'sort'} = 'false';
# The Interval for drawing the x-axes in the split module
$self->{'interval'} = undef;
# The start value for the split chart
$self->{'start'} = undef;
# How many ticks do i have to draw at the x-axes in one interval of a split-plot?
$self->{'interval_ticks'} = 6;
# Draw the Lines in the split-chart normal
$self->{'scale'} = 1;
# Make a x-y plot
$self->{'xy_plot'} = 'false';
# min and max for xy plot
$self->{'x_min_val'} = 1;
$self->{'x_max_val'} = 1;
# use the same error value in ErrorBars
$self->{'same_error'} = 'false';
# Set the minimum and maximum number of circles to draw in a direction chart
$self->{'min_circles'} = 4, $self->{'max_circles'} = 100;
# set the style of a direction diagramm
$self->{'point'} = 'true', $self->{'line'} = 'false', $self->{'arrow'} = 'false';
# The number of angel axes in a direction Chart
$self->{'angle_interval'} = 30;
# dont use different 'x_axes' in a direction Chart
$self->{'pairs'} = 'false';
# polarplot for a direction Chart (not yet tested)
$self->{'polar'} = 'false';
# guiding lines in a Pie Chart
$self->{'legend_lines'} = 'false';
# Ring Chart instead of Pie
$self->{'ring'} = 1; # width of ring; i.e. normal pie
# stepline for Lines, LinesPoints
$self->{'stepline'} = 'false';
$self->{'stepline_mode'} = 'end'; # begin, end
# used function to transform x- and y-tick labels to strings
$self->{f_x_tick} = \&_default_f_tick, $self->{f_y_tick} = \&_default_f_tick, $self->{f_z_tick} = \&_default_f_tick;
# default color specs for various color roles.
# Subclasses should extend as needed.
my $d = 0;
$self->{'colors_default_spec'} = {
background => 'white',
misc => 'black',
text => 'black',
y_label => 'black',
y_label2 => 'black',
grid_lines => 'black',
grey_background => 'gray90',
(
map { 'dataset' . $d++ => $_ }
qw (flamescarlet forestgreen navy olive lightseagreen purple
orangepeel gold2 chartreuse3 cornflowerblue mediumpurple2 deeppink2
galaxyblue hazelnut pottersclay BlueViolet
PaleGreen1 DarkBlue orange2 chocolate1 LightGreen
pink light_purple light_blue plum yellow turquoise light_green brown
PaleGreen2 MediumPurple PeachPuff1 orange3 chocolate2
olive light_purple light_blue yellow turquoise light_green brown
DarkOrange PaleGreen3 SlateBlue BlueViolet PeachPuff2 orange4
chocolate3 LightGreen light_purple light_blue light_green
snow1 honeydew3 SkyBlue1 cyan3 DarkOliveGreen1 IndianRed3
orange1 LightPink3 MediumPurple1 snow3 LavenderBlush1 SkyBlue3
DarkSlateGray1 DarkOliveGreen3 sienna1 orange3 PaleVioletRed1
MediumPurple3 seashell1 LavenderBlush3 LightSkyBlue1)
),
};
# get default color specs for some color roles from alternate role.
# Subclasses should extend as needed.
$self->{'colors_default_role'} = {
'x_grid_lines' => 'grid_lines',
'y_grid_lines' => 'grid_lines',
'y2_grid_lines' => 'grid_lines', # should be added by Chart::Composite...
};
# Define style to plot dots in Points and Lines
$self->{'brushStyle'} = 'FilledCircle';
# and return
return 1;
}
## @fn private int _copy_data($extern_ref)
# Copy external data via a reference to internal memory.
#
# Remember the external reference.\n
lib/Chart/Base.pm view on Meta::CPAN
# find good min and max y-values for the plot
$self->_find_y_scale();
# find the longest x-tick label
$length = 0;
for ( @{ $self->{'dataref'}->[0] } ) {
next if !defined($_);
if ( length( $self->{f_x_tick}->($_) ) > $length ) {
$length = length( $self->{f_x_tick}->($_) );
}
}
if ( $length <= 0 ) { $length = 1; } # make sure $length is positive and greater 0
# now store it in the object
$self->{'x_tick_label_length'} = $length;
# find x-scale, if a x-y plot is wanted
# makes only sense for some charts
if ( $self->true( $self->{'xy_plot'} ) && ( $self->isa('Chart::Lines')
|| $self->isa('Chart::Points')
|| $self->isa('Chart::LinesPoints')
|| $self->isa('Chart::Split')
|| $self->isa('Chart::ErrorBars') ) ) {
$self->_find_x_scale;
}
return 1;
}
## @fn private int _draw
# Plot the chart to the gd object\n
# Calls:
# @see _draw_title
# @see _draw_sub_title
# @see _sort_data
# @see _plot
#
# @return status
sub _draw {
my $self = shift;
# leave the appropriate border on the png
$self->{'curr_x_max'} -= $self->{'png_border'};
$self->{'curr_x_min'} += $self->{'png_border'};
$self->{'curr_y_max'} -= $self->{'png_border'};
$self->{'curr_y_min'} += $self->{'png_border'};
# draw in the title
$self->_draw_title() if $self->{'title'};
# have to leave this here for backwards compatibility
$self->_draw_sub_title() if $self->{'sub_title'};
# sort the data if they want to (mainly here to make sure
# pareto charts get sorted)
$self->_sort_data() if ( $self->true( $self->{'sort'} ) );
# start drawing the data (most methods in this will be
# overridden by the derived classes)
# include _draw_legend() in this to ensure that the legend
# will be flush with the chart
$self->_plot();
# and return
return 1;
}
## @fn private int _set_colors
# specify my colors
# @return status
sub _set_colors {
my $self = shift;
my $index = $self->_color_role_to_index('background'); # allocate GD color
if ( $self->true( $self->{'transparent'} ) )
{
$self->{'gd_obj'}->transparent($index);
}
# all other roles are initialized by calling $self->_color_role_to_index(ROLENAME);
# and return
return 1;
}
## @fn private int _color_role_to_index
# return a (list of) color index(es) corresponding to the (list of) role(s)
#
# @details wantarray
# is a special keyword which returns a flag indicating
# which context your subroutine has been called in.
# It will return one of three values.
#
# @li true: If your subroutine has been called in list context
# @li false: If your subroutine has been called in scalar context
# @li undef: If your subroutine has been called in void context
#
# @return a (list of) color index(es) corresponding to the (list of) role(s) in \\\@_.
sub _color_role_to_index {
my $self = shift;
# Return a (list of) color index(es) corresponding to the (list of) role(s) in @_.
my @result = map {
my $role = $_;
my $index = $self->{'color_table'}->{$role};
unless ( defined $index ) {
my $spec =
$self->{'colors'}->{$role}
|| $self->{'colors_default_spec'}->{$role}
|| $self->{'colors_default_spec'}->{ $self->{'colors_default_role'}->{$role} };
my @rgb = $self->_color_spec_to_rgb( $role, $spec );
my $string = sprintf " RGB(%d,%d,%d)", map { $_ + 0 } @rgb;
$index = $self->{'color_table'}->{$string};
unless ( defined $index ) {
$index = $self->{'gd_obj'}->colorAllocate(@rgb);
$self->{'color_table'}->{$string} = $index;
}
lib/Chart/Base.pm view on Meta::CPAN
{
# it's worth looking for integers
if ( $datum !~ /^[\-\+]?\d+$/ )
{
$flag_all_integers = 0; # No
}
}
if ( $datum =~ /^[\-\+]?\s*[\d\.eE\-\+]+/ )
{
if ( defined $max && $max =~ /^[\-\+]{0,}\s*[\d\.eE\-\+]+/ )
{
if ( $datum > $max ) { $max = $datum; }
elsif ( !defined $min ) { $min = $datum; }
elsif ( $datum < $min ) { $min = $datum; }
}
else { $min = $max = $datum }
}
}
}
}
# Return:
( $min, $max, $flag_all_integers );
}
## @fn private array _find_x_range()
# Find minimum and maximum value of x data sets
# @return ( min, max )
sub _find_x_range
{
my $self = shift;
my $data = $self->{'dataref'};
my $max = undef;
my $min = undef;
for my $datum ( @{ $data->[0] } )
{
if ( defined $datum && $datum =~ /^[\-\+]{0,1}\s*[\d\.eE\-\+]+/ )
{
if ( defined $max && $max =~ /^[\-\+]{0,1}\s*[\d\.eE\-\+]+/ )
{
if ( $datum > $max ) { $max = $datum }
elsif ( $datum < $min ) { $min = $datum }
}
else { $min = $max = $datum }
}
}
return ( $min, $max );
}
## @fn private int _plot()
# main sub that controls all the plotting of the actual chart
# @return status
sub _plot
{
my $self = shift;
# draw the legend first
$self->_draw_legend();
# mark off the graph_border space
$self->{'curr_x_min'} += $self->{'graph_border'};
$self->{'curr_x_max'} -= $self->{'graph_border'};
$self->{'curr_y_min'} += $self->{'graph_border'};
$self->{'curr_y_max'} -= $self->{'graph_border'};
# draw the x- and y-axis labels
$self->_draw_x_label if $self->{'x_label'};
$self->_draw_y_label('left') if $self->{'y_label'};
$self->_draw_y_label('right') if $self->{'y_label2'};
# draw the ticks and tick labels
$self->_draw_ticks();
# give the plot a grey background if they want it
$self->_grey_background if ( $self->true( $self->{'grey_background'} ) );
#draw the ticks again if grey_background has ruined it in a Direction Chart.
if ( $self->true( $self->{'grey_background'} ) && $self->isa("Chart::Direction") )
{
$self->_draw_ticks;
}
$self->_draw_grid_lines if ( $self->true( $self->{'grid_lines'} ) );
$self->_draw_x_grid_lines if ( $self->true( $self->{'x_grid_lines'} ) );
$self->_draw_y_grid_lines if ( $self->true( $self->{'y_grid_lines'} ) );
$self->_draw_y2_grid_lines if ( $self->true( $self->{'y2_grid_lines'} ) );
# plot the data
$self->_draw_data();
# and return
return 1;
}
## @fn private int _draw_legend()
# let the user know what all the pretty colors mean.\n
# The user define the position of the legend by setting option
# 'legend' to 'top', 'bottom', 'left', 'right' or 'none'.
# The legend is positioned at the defined place, respectively.
# @return status
sub _draw_legend {
my $self = shift;
my $length;
# check to see if legend type is none..
if ( $self->{'legend'} =~ /^none$/ || length( $self->{'legend'} ) == 0 )
{
return 1;
}
# check to see if they have as many labels as datasets,
# warn them if not
if ( ( $#{ $self->{'legend_labels'} } >= 0 )
&& ( ( scalar( @{ $self->{'legend_labels'} } ) ) != $self->{'num_datasets'} ) )
{
carp "The number of legend labels and datasets doesn\'t match";
}
# init a field to store the length of the longest legend label
unless ( $self->{'max_legend_label'} )
{
$self->{'max_legend_label'} = 0;
}
# fill in the legend labels, find the longest one
for ( 1 .. $self->{'num_datasets'} )
{
unless ( $self->{'legend_labels'}[ $_ - 1 ] )
{
$self->{'legend_labels'}[ $_ - 1 ] = "Dataset $_";
}
$length = length( $self->{'legend_labels'}[ $_ - 1 ] );
if ( $length > $self->{'max_legend_label'} )
{
$self->{'max_legend_label'} = $length;
}
}
# different legend types
if ( $self->{'legend'} eq 'bottom' )
{
$self->_draw_bottom_legend;
}
elsif ( $self->{'legend'} eq 'right' )
{
$self->_draw_right_legend;
}
elsif ( $self->{'legend'} eq 'left' )
{
$self->_draw_left_legend;
}
elsif ( $self->{'legend'} eq 'top' )
{
$self->_draw_top_legend;
}
elsif ( $self->{'legend'} eq 'none' || length( $self->{'legend'} ) == 0 )
{
$self->_draw_none_legend;
}
else
{
carp "I can't put a legend there (at " . $self->{'legend'} . ")\n";
}
# and return
return 1;
}
## @fn private int _draw_bottom_legend()
# put the legend on the bottom of the chart
# @return status
sub _draw_bottom_legend {
my $self = shift;
my @labels = @{ $self->{'legend_labels'} };
my ( $x1, $y1, $x2, $x3, $y2 );
my ( $empty_width, $max_label_width, $cols, $rows, $color, $brush );
my ( $col_width, $row_height, $r, $c, $index, $x, $y, $w, $h, $axes_space );
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 the base x values
$axes_space =
( $self->{'y_tick_label_length'} * $self->{'tick_label_font'}->width ) +
$self->{'tick_len'} +
( 3 * $self->{'text_space'} );
$x1 = $self->{'curr_x_min'} + $self->{'graph_border'};
$x2 = $self->{'curr_x_max'} - $self->{'graph_border'};
if ( $self->{'y_axes'} =~ /^right$/i )
{
$x2 -= $axes_space;
}
elsif ( $self->{'y_axes'} =~ /^both$/i )
{
$x2 -= $axes_space;
$x1 += $axes_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 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 = $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_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_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'};
my $text_color = $self->_color_role_to_index( 'text' );
# 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', 'dataset' . $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
$self->{'gd_obj'}->string( $font, $x, $y, $labels[$index], $text_color );
}
}
}
# mark off the space used
$self->{'curr_y_max'} -= $rows * $row_height + 2 * $self->{'text_space'} + 2 * $self->{'legend_space'};
# now return
return 1;
}
## @fn private int _draw_right_legend()
# put the legend on the right of the chart
# @return status
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
$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
my $offset = 0;
( $brush, $offset ) = $self->_prepare_brush( $color, 'point', 'dataset' . $_ );
$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
$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 int _draw_top_legend()
# put the legend on top of the chart
# @return status
sub _draw_top_legend {
my $self = shift;
my @labels = @{ $self->{'legend_labels'} };
my ( $x1, $y1, $x2, $x3, $y2, $empty_width, $max_label_width );
my ( $cols, $rows, $color, $brush );
my ( $col_width, $row_height, $r, $c, $index, $x, $y, $w, $h, $axes_space );
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 );
# find the base x values
$axes_space =
( $self->{'y_tick_label_length'} * $self->{'tick_label_font'}->width ) +
$self->{'tick_len'} +
( 3 * $self->{'text_space'} );
$x1 = $self->{'curr_x_min'} + $self->{'graph_border'};
$x2 = $self->{'curr_x_max'} - $self->{'graph_border'};
if ( $self->{'y_axes'} =~ /^right$/i )
{
$x2 -= $axes_space;
}
elsif ( $self->{'y_axes'} =~ /^both$/i )
{
$x2 -= $axes_space;
$x1 += $axes_space;
}
# 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'} ) + ( $self->{'max_legend_label'} * $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_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_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', 'dataset' . $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;
$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 int _draw_left_legend()
# put the legend on the left of the chart
# @return status
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
$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', 'dataset' . $_ );
$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
$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 int _draw_none_legend()
# no legend to draw..
# Just return in this case. This routine may be overwritten by
# subclasses.
# @return 1
sub _draw_none_legend {
my $self = shift;
my $status = 1;
return $status;
}
## @fn private int _draw_x_label()
# draw the label for the x-axis
#
# Get font for labels\n
# Get the color of x_label or text\n
# Get size of font\n
# and write x-Label
#
# @return status
sub _draw_x_label {
my $self = shift;
my $label = $self->{'x_label'};
my $font = $self->{'label_font'};
my $color;
my ( $h, $w, $x, $y );
#get the right color
if ( defined $self->{'colors'}->{'x_label'} )
{
$color = $self->_color_role_to_index('x_label');
}
else
{
$color = $self->_color_role_to_index('text');
}
# make sure it's a real GD Font object
unless ( ( ref($font) ) eq 'GD::Font' )
{
croak "The x-axis label font you specified isn\'t a GD Font object";
}
# get the size of the font
( $h, $w ) = ( $font->height, $font->width );
# make sure it goes in the right place
$x = ( $self->{'curr_x_max'} - $self->{'curr_x_min'} ) / 2 + $self->{'curr_x_min'} - ( length($label) * $w ) / 2;
$y = $self->{'curr_y_max'} - ( $self->{'text_space'} + $h );
# now write it
$self->{'gd_obj'}->string( $font, $x, $y, $label, $color );
# mark the space written to as used
$self->{'curr_y_max'} -= $h + 2 * $self->{'text_space'};
# and return
return 1;
}
## @fn private int _draw_y_label()
# draw the label for the y-axis
# @return status
sub _draw_y_label
{
( run in 1.048 second using v1.01-cache-2.11-cpan-437f7b0c052 )