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 )