## @file
# Implementation of Chart::Base
#
# written by
# @author david bonner (dbonner@cs.bu.edu)
#
# maintained by the
# @author Chart Group at Geodetic Fundamental Station Wettzell (Chart@fs.wettzell.de)
# @date 2012-10-03
# @version 2.4.6

## @mainpage Chart::Base
#
# Basic Class of Chart from which all the other classes are derived.

## @class Chart::Base
# @brief Base class for Chart; all other classes derived from here
#
# Base class from which all other classes are derived.
# This class provides all functions which are common for
# all classes
package Chart::Base;

# Uses
# GD
# Carp
# FileHandle
use GD;
use Carp;
use FileHandle;
use Chart::Constants;
use GD::Image;

$Chart::Base::VERSION = '2.4.6';

use vars qw(%named_colors);
use strict;

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

## @cmethod object new()
# @return A new object.
#
# @brief
# Standard normal constructor.\n
# Calls
# @see _init
sub new
{
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self  = {};

    bless $self, $class;
    $self->_init(@_);

    return $self;
}

## @method int set(%opts)
# Set all options
#
# @details
#  main method for customizing the chart, lets users
#  specify values for different parameters\n
#  The options are saved locally to be able to output them
#  via @see getopts()
#
# @param[in] %opts Hash of options to the Chart
# @return ok or croak
#
sub set
{
    my $self = shift;
    my %opts = @_;

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

    # set the options
    for ( keys %opts )
    {
        $self->{$_} = $opts{$_};
        $self->{saveopts}->{$_} = $opts{$_};

        # if someone wants to change the grid_lines color, we should set all
        # the colors of the grid_lines
        if ( $_ =~ /^colors$/ )
        {
            my %hash = %{ $opts{$_} };
            foreach my $key ( sort keys %hash )
            {
                if ( $key =~ /^grid_lines$/ )
                {

                    # ORIG:
                    #$self->{'colors'}{'y_grid_lines'}    = $hash{'grid_lines'},
                    #  $self->{'colors'}{'x_grid_lines'}  = $hash{'grid_lines'},
                    #  $self->{'colors'}{'y2_grid_lines'} = $hash{'grid_lines'};
                    #
                    # NEW!!!!!!!!!!!!!!!!!!
                    if ( ref( $hash{'grid_lines'} ) eq 'ARRAY' )
                    {
                        my @aLocal = ( $hash{'grid_lines'}[0], $hash{'grid_lines'}[1], $hash{'grid_lines'}[2] );
                        $self->{'colors'}{'y_grid_lines'}  = [@aLocal];
                        $self->{'colors'}{'x_grid_lines'}  = [@aLocal];
                        $self->{'colors'}{'y2_grid_lines'} = [@aLocal];
                    }
                    elsif ( ref( \$hash{'grid_lines'} ) eq 'SCALAR' )
                    {
                        my $sLocal = $hash{'grid_lines'};
                        $self->{'colors'}{'y_grid_lines'}  = $sLocal;
                        $self->{'colors'}{'x_grid_lines'}  = $sLocal;
                        $self->{'colors'}{'y2_grid_lines'} = $sLocal;
                    }
                    else
                    {
                        carp "colors{'grid_lines'} is not SCALAR and not ARRAY\n";
                    }
                }
            }
        }
    }

    # now return
    return 1;
}

## @method hash getopts()
# @return hash of all set options so far
#
# @brief
# get all options
#
# @details
# @return set options as a hash
sub getopts
{
    my $self = shift;
    my %opts = ();

    foreach ( keys %{ $self->{saveopts} } )
    {
        $opts{$_} = $self->{saveopts};
    }
    return %opts;
}

## @method int add_pt(@data)
# Graph API\n
# Add one dataset (as a list) to the dataref
#
# @param @data Dataset to add

## @method add_pt(\@data)
# Graph API\n
# Add one dataset (as a reference to a list) to the dataref
# via
# <pre>
# for ( 0 .. $#data )
# {
#    push @{ $self->{'dataref'}->[$_] }, $data[$_];
# }
# </pre>
#
# @param \@data Dataset to add
#
sub add_pt
{
    my $self = shift;
    my @data = ();

    if ( ( ref $_[0] ) =~ /^ARRAY/ )
    {
        my $rdata = shift;
        @data = @$rdata if defined @$rdata;
    }
    elsif ( ( ref \$_[0] ) =~ /^SCALAR/ )
    {
        if ( defined $_[0] )
        {
            @data = @_;
        }
    }
    else
    {
        croak "Not an array or reference to array";
    }

    # error check the data (carp, don't croak)
    if ( $self->{'dataref'} && ( $#{ $self->{'dataref'} } != $#data ) )
    {
        carp "New point to be added has an incorrect number of data sets";
        return 0;
    }

    # copy it into the dataref
    for ( 0 .. $#data )
    {
        push @{ $self->{'dataref'}->[$_] }, $data[$_];
    }

    # now return
    return 1;
}

## @method int add_dataset(@data)
#  Graph API\n
# Add many datasets (implemented as a list)
# to the dataref,
#
# @param @data Dataset (list) to add

## @method int add_dataset(\@data)
#  Graph API\n
# Add many datasets (implemented as a references to alist)
# to the dataref,
#
# @param \@data Dataset (reference to a list) to add
sub add_dataset
{
    my $self = shift;
    my @data = ();

    if ( ( ref $_[0] ) =~ /^ARRAY/ )
    {
        my $rdata = shift;
        @data = @$rdata if defined @$rdata;
    }
    elsif ( ( ref \$_[0] ) =~ /^SCALAR/ )
    {
        if ( defined $_[0] )
        {
            @data = @_;
        }
    }
    else
    {
        croak "Not an array or reference to array";
        return;
    }

    # error check the data (carp, don't croak)
    if ( $self->{'dataref'} && ( $#{ $self->{'dataref'}->[0] } != $#data ) )
    {
        carp "New data set to be added has an incorrect number of points";
    }

    # copy it into the dataref
    push @{ $self->{'dataref'} }, [@data];

    # now return
    return 1;
}

## @method int add_datafile($filename,$format)
#  Graph API\n
# it's also possible to add a complete datafile\n
# Uses
# @see add_pt
# @see add_dataset
#
# @param[in] $filename Name of file which contents is to be added
# @param[in] $format 'pt' or 'set' to distiguish between function add_pt() in case of 'pt'
#                 or function add_dataset() in case of 'set'
sub add_datafile
{
    my $self     = shift;
    my $filename = shift;
    my $format   = shift;
    my ( $File, @array );

    # do some ugly checking to see if they gave me
    # a filehandle or a file name
    if ( ( ref \$filename ) eq 'SCALAR' )
    {

        # they gave me a file name
        open( $File, $filename ) or croak "Can't open the datafile: $filename.\n";
    }
    elsif ( ( ref \$filename ) =~ /^(?:REF|GLOB)$/ )
    {

        # either a FileHandle object or a regular file handle
        $File = $filename;
    }
    else
    {
        carp "I'm not sure what kind of datafile you gave me,\n", "but it wasn't a filename or a filehandle.\n";
    }

    #add the data
    while (<$File>)
    {
        @array = split;
        if ( $#array > -1 )
        {
            if ( $format =~ m/^pt$/i )
            {
                $self->add_pt(@array);
            }
            elsif ( $format =~ m/^set$/i )
            {
                $self->add_dataset(@array);
            }
            else
            {
                carp "Tell me what kind of file you gave me: 'pt' or 'set'\n";
            }
        }
    }
    close($File);
}

## @method int clear_data()
# Clear Graph API (by undefining 'dataref'
# @return Status of function
sub clear_data
{
    my $self = shift;

    # undef the internal data reference
    $self->{'dataref'} = undef;

    # now return
    return 1;
}

## @method arrayref get_data()
#  Get array of data of the last graph
# @return Reference to data set of the last graph
sub get_data
{
    my $self = shift;
    my $ref  = [];
    my ( $i, $j );

    # give them a copy, not a reference into the object
    for $i ( 0 .. $#{ $self->{'dataref'} } )
    {
        @{ $ref->[$i] } = @{ $self->{'dataref'}->[$i] }
## speedup, compared to...
          #   for $j (0..$#{$self->{'dataref'}->[$i]}) {
          #     $ref->[$i][$j] = $self->{'dataref'}->[$i][$j];
          #   }
    }

    # return it
    return $ref;
}

## @method int png($file, $dataref)
# Produce the graph of options set in png format.
#
# called after the options are set, this method
# invokes all my private methods to actually
# draw the chart and plot the data
# @see _set_colors
# @see _copy_data
# @see _check_data
# @see _draw
# @param[in] $file Name of file to write graph to
# @param[in] $dataref Reference to external data space
# @return Status of the plot
sub png
{
    my $self    = shift;
    my $file    = shift;
    my $dataref = shift;
    my $fh;

    # do some ugly checking to see if they gave me
    # a filehandle or a file name
    if ( ( ref \$file ) eq 'SCALAR' )
    {

        # they gave me a file name
        # Try to delete an existing file
        if ( -f $file )
        {
            my $number_deleted_files = unlink $file;
            if ( $number_deleted_files != 1 )
            {
                croak "Error: File \"$file\" did already exist, but it failed to delete it";
            }
        }
        $fh = FileHandle->new(">$file");
        if ( !defined $fh )
        {
            croak "Error: File \"$file\" could not be created!\n";
        }
    }
    elsif ( ( ref \$file ) =~ /^(?:REF|GLOB)$/ )
    {

        # either a FileHandle object or a regular file handle
        $fh = $file;
    }
    else
    {
        croak "I'm not sure what you gave me to write this png to,\n", "but it wasn't a filename or a filehandle.\n";
    }

    # allocate the background color
    $self->_set_colors();

    # make sure the object has its copy of the data
    $self->_copy_data($dataref);

    # do a sanity check on the data, and collect some basic facts
    # about the data
    $self->_check_data();

    # pass off the real work to the appropriate subs
    $self->_draw();

    # now write it to the file handle, and don't forget
    # to be nice to the poor ppl using nt
    binmode $fh;

    print $fh $self->{'gd_obj'}->png();

    # now exit
    return 1;
}

## @method int cgi_png($dataref)
# Produce the graph of options set in png format to be directly
# written for CGI.
#
# called after the options are set, this method
# invokes all my private methods to actually
# draw the chart and plot the data
# @param $dataref
# @return Status of the plot
sub cgi_png
{
    my $self    = shift;
    my $dataref = shift;

    # allocate the background color
    $self->_set_colors();

    # make sure the object has its copy of the data
    $self->_copy_data($dataref);

    # do a sanity check on the data, and collect some basic facts
    # about the data
    $self->_check_data();

    # pass off the real work to the appropriate subs
    $self->_draw();

    # print the header (ripped the crlf octal from the CGI module)
    if ( $self->true( $self->{no_cache} ) )
    {
        print "Content-type: image/png\015\012Pragma: no-cache\015\012\015\012";
    }
    else
    {
        print "Content-type: image/png\015\012\015\012";
    }

    # now print the png, and binmode it first so Windows-XX likes us
    binmode STDOUT;
    print STDOUT $self->{'gd_obj'}->png();

    # now exit
    return 1;
}

## @method int scalar_png($dataref)
# Produce the graph of options set in PNG format to be directly returned
#
# called after the options are set, this method
# invokes all my private methods to actually
# draw the chart and return the image to the caller
#
# @param $dataref
# @return returns the png image as a scalar value, so that
#         the programmer-user can do whatever the heck
#         s/he wants to with it
sub scalar_png
{
    my $self    = shift;
    my $dataref = shift;

    #allocate the background color
    $self->_set_colors();

    # make sure the object has its copy of the data
    $self->_copy_data($dataref);

    # do a sanity check on the data, and collect some basic facts
    # about the data
    $self->_check_data();

    # pass off the real work to the appropriate subs
    $self->_draw();

    # returns the png image as a scalar value, so that
    # the programmer/user can do whatever the she/he wants to with it
    return $self->{'gd_obj'}->png();
}

## @method int jpeg($file,$dataref)
# Produce the graph of options set in JPG format to be directly plotted.\n
#
# Called after the options are set, this method
# invokes all my private methods to actually
# draw the chart and plot the data.
# The output has the jpeg format in opposite to png format produced by
# @see png
#
# Uses the following private functions:\n
# @see _set_colors
# @see _copy_data
# @see _check_data
# @see _draw
#
# @param[in] $file Name of file to write graph to
# @param[in] $dataref Reference to external data space
# @return Status of the plot
#
sub jpeg
{
    my $self    = shift;
    my $file    = shift;
    my $dataref = shift;
    my $fh;

    # do some ugly checking to see if they gave me
    # a filehandle or a file name
    if ( ( ref \$file ) eq 'SCALAR' )
    {

        # they gave me a file name
        # Try to delete an existing file
        if ( -f $file )
        {
            my $number_deleted_files = unlink $file;
            if ( $number_deleted_files != 1 )
            {
                croak "Error: File \"$file\" did already exist, but it fails to delete it";
            }
        }
        $fh = FileHandle->new(">$file");
        if ( !defined $fh )
        {
            croak "Error: File \"$file\" could not be created!\n";
        }
    }
    elsif ( ( ref \$file ) =~ /^(?:REF|GLOB)$/ )
    {

        # either a FileHandle object or a regular file handle
        $fh = $file;
    }
    else
    {
        croak "I'm not sure what you gave me to write this jpeg to,\n", "but it wasn't a filename or a filehandle.\n";
    }

    # allocate the background color
    $self->_set_colors();

    # make sure the object has its copy of the data
    $self->_copy_data($dataref);

    # do a sanity check on the data, and collect some basic facts
    # about the data
    $self->_check_data;

    # pass off the real work to the appropriate subs
    $self->_draw();

    # now write it to the file handle, and don't forget
    # to be nice to the poor ppl using Windows-XX
    binmode $fh;
    print $fh $self->{'gd_obj'}->jpeg( [100] );    # high quality need

    # now exit
    return 1;
}

## @method int cgi_jpeg($dataref)
# Produce the graph of options set in JPG format to be directly
# for CGI.
#
# called after the options are set, this method
# invokes all my private methods to actually
# draw the chart and plot the data
# @param $dataref
# @return Status of the plot
sub cgi_jpeg
{
    my $self    = shift;
    my $dataref = shift;

    # allocate the background color
    $self->_set_colors();

    # make sure the object has its copy of the data
    $self->_copy_data($dataref);

    # do a sanity check on the data, and collect some basic facts
    # about the data
    $self->_check_data();

    # pass off the real work to the appropriate subs
    $self->_draw();

    # print the header (ripped the crlf octal from the CGI module)
    if ( $self->true( $self->{no_cache} ) )
    {
        print "Content-type: image/jpeg\015\012Pragma: no-cache\015\012\015\012";
    }
    else
    {
        print "Content-type: image/jpeg\015\012\015\012";
    }

    # now print the jpeg, and binmode it first so Windows-XX likes us
    binmode STDOUT;
    print STDOUT $self->{'gd_obj'}->jpeg( [100] );

    # now exit
    return 1;
}

## @method int scalar_jpeg($dataref)
# Produce the graph of options set in JPG format to be directly returned
#
# called after the options are set, this method
# invokes all my private methods to actually
# draw the chart and return the image to the caller
#
# @param $dataref
# @return returns the jpeg image as a scalar value, so that
#         the programmer-user can do whatever the heck
#         s/he wants to with it
sub scalar_jpeg
{
    my $self    = shift;
    my $dataref = shift;

    # allocate the background color
    $self->_set_colors();

    # make sure the object has its copy of the data
    $self->_copy_data($dataref);

    # do a sanity check on the data, and collect some basic facts
    # about the data
    $self->_check_data();

    # pass off the real work to the appropriate subs
    $self->_draw();

    # returns the jpeg image as a scalar value, so that
    # the programmer-user can do whatever the heck
    # s/he wants to with it
    $self->{'gd_obj'}->jpeg( [100] );
}

## @method int make_gd($dataref)
# Produce the graph of options set in GD format to be directly
#
# called after the options are set, this method
# invokes all my private methods to actually
# draw the chart and plot the data
# @param $dataref
# @return Status of the plot
sub make_gd
{
    my $self    = shift;
    my $dataref = shift;

    # allocate the background color
    $self->_set_colors();

    # make sure the object has its copy of the data
    $self->_copy_data($dataref);

    # do a sanity check on the data, and collect some basic facts
    # about the data
    $self->_check_data();

    # pass off the real work to the appropriate subs
    $self->_draw();

    # return the GD::Image object that we've drawn into
    return $self->{'gd_obj'};
}

## @method imagemap_dump()
#  get the information to turn the chart into an imagemap
#
# @return Reference to an array of the image
sub imagemap_dump
{
    my $self = shift;
    my $ref  = [];
    my ( $i, $j );

    # croak if they didn't ask me to remember the data, or if they're asking
    # for the data before I generate it
    unless ( ( $self->true( $self->{'imagemap'} ) ) && $self->{'imagemap_data'} )
    {
        croak "You need to set the imagemap option to true, and then call the png method, before you can get the imagemap data";
    }

    # can't just return a ref to my internal structures...
    for $i ( 0 .. $#{ $self->{'imagemap_data'} } )
    {
        for $j ( 0 .. $#{ $self->{'imagemap_data'}->[$i] } )
        {
            $ref->[$i][$j] = [ @{ $self->{'imagemap_data'}->[$i][$j] } ];
        }
    }

    # return their copy
    return $ref;
}

## @method minimum(@array)
# determine minimum of an array of values
# @param @array List of numerical values
# @return Minimal value of list of values
sub minimum
{
    my $self  = shift;
    my @array = @_;

    return undef if !@array;
    my $min = $array[0];
    for ( my $iIndex = 0 ; $iIndex < scalar @array ; $iIndex++ )
    {
        $min = $array[$iIndex] if ( $min > $array[$iIndex] );
    }
    $min;
}

## @method maximum(@array)
# determine maximum of an array of values
# @param @array List of numerical values
# @return Maximal value of list of values
sub maximum
{
    my $self  = shift;
    my @array = @_;

    return undef if !@array;
    my $max = $array[0];
    for ( my $iIndex = 0 ; $iIndex < scalar @array ; $iIndex++ )
    {
        $max = $array[$iIndex] if ( $max < $array[$iIndex] );
    }
    $max;
}

## @method arccos($a)
# Function arccos(a)
# @param $a Value
# @return arccos(a)
sub arccos
{
    my $self = shift;
    my $a    = shift;

    return ( atan2( sqrt( 1 - $a * $a ), $a ) );
}

## @method arcsin($a)
# Function arcsin(a)
# @param $a Value
# @return arcsin(a)
sub arcsin
{
    my $self = shift;
    my $a    = shift;

    return ( atan2( $a, sqrt( 1 - $a * $a ) ) );
}

## @method true($b)
# determine true value of argument
# @param[in] $b Bool value to check for true
# @return 1 if argument is equal to TRUE, true, 1, t, T, and defined
sub true
{
    my $pkg = shift;
    my $arg = shift;

    if ( !defined($arg) )
    {
        return 0;
    }

    if (   $arg eq 'true'
        || $arg eq 'TRUE'
        || $arg eq 't'
        || $arg eq 'T'
        || $arg eq '1' )
    {
        return 1;
    }

    return 0;
}

## @method false($b)
# determine false value of argument
# @param[in] $b Bool value to check for true
# @return 1 if argument is equal to false, FALSE, 0, f, F or undefined
sub false
{
    my $pkg = shift;
    my $arg = shift;

    if ( !defined($arg) )
    {
        return 1;
    }

    if (   $arg eq 'false'
        || $arg eq 'FALSE'
        || $arg eq 'f'
        || $arg eq 'F'
        || $arg eq '0'
        || $arg eq 'none' )
    {
        return 1;
    }

    return 0;
}

#>>>>>>>>>>>>>>>>>>>>>>>>>>>#
#  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 => 'grey',
        (
            map { 'dataset' . $d++ => $_ }
              qw (red green blue purple peach orange mauve olive pink light_purple light_blue plum yellow turquoise light_green brown
              HotPink PaleGreen1 DarkBlue BlueViolet orange2 chocolate1 LightGreen pink light_purple light_blue plum yellow turquoise light_green brown
              pink PaleGreen2 MediumPurple PeachPuff1 orange3 chocolate2 olive pink light_purple light_blue plum yellow turquoise light_green brown
              DarkOrange PaleGreen3 SlateBlue BlueViolet PeachPuff2 orange4 chocolate3 LightGreen pink light_purple light_blue plum yellow turquoise light_green brown
              snow1 honeydew3 SkyBlue1 cyan3 DarkOliveGreen1 IndianRed3
              orange1 LightPink3 MediumPurple1 snow3 LavenderBlush1 SkyBlue3
              DarkSlateGray1 DarkOliveGreen3 sienna1 orange3 PaleVioletRed1
              MediumPurple3 seashell1 LavenderBlush3 LightSkyBlue1
              DarkSlateGray3 khaki1 sienna3 DarkOrange1 PaleVioletRed3
              thistle1 seashell3 MistyRose1 LightSkyBlue3 aquamarine1 khaki3
              burlywood1 DarkOrange3 maroon1 thistle3 AntiqueWhite1
              MistyRose3 SlateGray1 aquamarine3 LightGoldenrod1 burlywood3
              coral1 maroon3 AntiqueWhite3 azure1 SlateGray3 DarkSeaGreen1
              LightGoldenrod3 wheat1 coral3 VioletRed1 bisque1 azure3
              LightSteelBlue1 DarkSeaGreen3 LightYellow1 wheat3 tomato1
              VioletRed3 bisque3 SlateBlue1 LightSteelBlue3 SeaGreen1
              LightYellow3 tan1 tomato3 magenta1 PeachPuff1 SlateBlue3
              LightBlue1 SeaGreen3 yellow1 tan3 OrangeRed1 magenta3
              PeachPuff3 RoyalBlue1 LightBlue3 PaleGreen1 yellow3 chocolate1
              OrangeRed3 orchid1 NavajoWhite1 RoyalBlue3 LightCyan1
              PaleGreen3 gold1 chocolate3 red1 orchid3 NavajoWhite3 blue1
              LightCyan3 SpringGreen1 gold3 firebrick1 red3 plum1
              LemonChiffon1 blue3 PaleTurquoise1 SpringGreen3 goldenrod1
              firebrick3 DeepPink1 plum3 LemonChiffon3 DodgerBlue1
              PaleTurquoise3 green1 goldenrod3 brown1 DeepPink3
              MediumOrchid1 cornsilk1 DodgerBlue3 CadetBlue1 green3
              DarkGoldenrod1 brown3 HotPink1 MediumOrchid3 cornsilk3
              SteelBlue1 CadetBlue3 chartreuse1 DarkGoldenrod3 salmon1
              HotPink3 DarkOrchid1 ivory1 SteelBlue3 turquoise1 chartreuse3
              RosyBrown1 salmon3 pink1 DarkOrchid3 ivory3 DeepSkyBlue1
              turquoise3 OliveDrab1 RosyBrown3 LightSalmon1 pink3 purple1
              honeydew1 DeepSkyBlue3 cyan1 OliveDrab3 IndianRed1
              LightSalmon3 LightPink1 purple3 honeydew2 DeepSkyBlue4 cyan2
              OliveDrab4 IndianRed2 LightSalmon4 LightPink2 purple4 snow2
              honeydew4 SkyBlue2 cyan4 DarkOliveGreen2 IndianRed4 orange2
              LightPink4 MediumPurple2 snow4 LavenderBlush2 SkyBlue4
              DarkSlateGray2 DarkOliveGreen4 sienna2 orange4 PaleVioletRed2
              MediumPurple4 seashell2 LavenderBlush4 LightSkyBlue2
              DarkSlateGray4 khaki2 sienna4 DarkOrange2 PaleVioletRed4
              thistle2 seashell4 MistyRose2 LightSkyBlue4 aquamarine2 khaki4
              burlywood2 DarkOrange4 maroon2 thistle4 AntiqueWhite2
              MistyRose4 SlateGray2 aquamarine4 LightGoldenrod2 burlywood4
              coral2 maroon4 AntiqueWhite4 azure2 SlateGray4 DarkSeaGreen2
              LightGoldenrod4 wheat2 coral4 VioletRed2 bisque2 azure4
              LightSteelBlue2 DarkSeaGreen4 LightYellow2 wheat4 tomato2
              VioletRed4 bisque4 SlateBlue2 LightSteelBlue4 SeaGreen2
              LightYellow4 tan2 tomato4 magenta2 PeachPuff2 SlateBlue4
              LightBlue2 SeaGreen4 yellow2 tan4 OrangeRed2 magenta4
              PeachPuff4 RoyalBlue2 LightBlue4 PaleGreen2 yellow4 chocolate2
              OrangeRed4 orchid2 NavajoWhite2 RoyalBlue4 LightCyan2
              PaleGreen4 gold2 chocolate4 red2 orchid4 NavajoWhite4 blue2
              LightCyan4 SpringGreen2 gold4 firebrick2 red4 plum2
              LemonChiffon2 blue4 PaleTurquoise2 SpringGreen4 goldenrod2
              firebrick4 DeepPink2 plum4 LemonChiffon4 DodgerBlue2
              PaleTurquoise4 green2 goldenrod4 brown2 DeepPink4
              MediumOrchid2 cornsilk2 DodgerBlue4 CadetBlue2 green4
              DarkGoldenrod2 brown4 HotPink2 MediumOrchid4 cornsilk4
              SteelBlue2 CadetBlue4 chartreuse2 DarkGoldenrod4 salmon2
              HotPink4 DarkOrchid2 ivory2 SteelBlue4 turquoise2 chartreuse4
              RosyBrown2 salmon4 pink2 DarkOrchid4 ivory4 DeepSkyBlue2
              turquoise4 OliveDrab2 RosyBrown4 LightSalmon2 pink4 purple2)
        ),
    };

    # 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
# Therefore, this function can anly be called once!
# @param $extern_ref  Reference to external data space
sub _copy_data
{
    my $self       = shift;
    my $extern_ref = shift;
    my ( $ref, $i );

    # look to see if they used the other api
    if ( $self->{'dataref'} )
    {

        # we've already got a copy, thanks
        return 1;
    }
    else
    {

        # get an array reference
        $ref = [];

        # loop through and copy the external data to internal memory
        for $i ( 0 .. $#{$extern_ref} )
        {
            @{ $ref->[$i] } = @{ $extern_ref->[$i] };
            ## Speedup compared to:
            #  for $j (0..$#{$extern_ref->[$i]}) {
            #       $ref->[$i][$j] = $extern_ref->[$i][$j];
            #     }
        }

        # put it in the object
        $self->{'dataref'} = $ref;
        return 1;
    }
}

## @fn private int _check_data
# Check the internal data to be displayed.
#
# Make sure the data isn't really weird
#  and collect some basic info about it\n
# Not logical data is 'carp'ed.\n
# @return status of check
sub _check_data
{
    my $self   = shift;
    my $length = 0;

    # first make sure there's something there
    unless ( scalar( @{ $self->{'dataref'} } ) >= 2 )
    {
        croak "Call me again when you have some data to chart";
    }

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

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

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

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

## @var	    Hash named_colors RGB values of named colors
#
# see URL http://en.wikipedia.org/wiki/Web_colors#X11_color_names
our %named_colors = (
    'white'           => [ 255, 255, 255 ],
    'black'           => [ 0,   0,   0 ],
    'red'             => [ 200, 0,   0 ],
    'green'           => [ 0,   175, 0 ],
    'blue'            => [ 0,   0,   200 ],
    'orange'          => [ 250, 125, 0 ],
    'orange2'         => [ 238, 154, 0 ],
    'orange3'         => [ 205, 133, 0 ],
    'orange4'         => [ 139, 90,  0 ],
    'yellow'          => [ 225, 225, 0 ],
    'purple'          => [ 200, 0,   200 ],
    'light_blue'      => [ 0,   125, 250 ],
    'light_green'     => [ 125, 250, 0 ],
    'light_purple'    => [ 145, 0,   250 ],
    'pink'            => [ 250, 0,   125 ],
    'peach'           => [ 250, 125, 125 ],
    'olive'           => [ 125, 125, 0 ],
    'plum'            => [ 125, 0,   125 ],
    'turquoise'       => [ 0,   125, 125 ],
    'mauve'           => [ 200, 125, 125 ],
    'brown'           => [ 160, 80,  0 ],
    'grey'            => [ 225, 225, 225 ],
    'HotPink'         => [ 255, 105, 180 ],
    'PaleGreen1'      => [ 154, 255, 154 ],
    'PaleGreen2'      => [ 144, 238, 144 ],
    'PaleGreen3'      => [ 124, 205, 124 ],
    'PaleGreen4'      => [ 84,  138, 84 ],
    'DarkBlue'        => [ 0,   0,   139 ],
    'BlueViolet'      => [ 138, 43,  226 ],
    'PeachPuff'       => [ 255, 218, 185 ],
    'PeachPuff1'      => [ 255, 218, 185 ],
    'PeachPuff2'      => [ 238, 203, 173 ],
    'PeachPuff3'      => [ 205, 175, 149 ],
    'PeachPuff4'      => [ 139, 119, 101 ],
    'chocolate1'      => [ 255, 127, 36 ],
    'chocolate2'      => [ 238, 118, 33 ],
    'chocolate3'      => [ 205, 102, 29 ],
    'chocolate4'      => [ 139, 69,  19 ],
    'LightGreen'      => [ 144, 238, 144 ],
    'lavender'        => [ 230, 230, 250 ],
    'MediumPurple'    => [ 147, 112, 219 ],
    'DarkOrange'      => [ 255, 127, 0 ],
    'DarkOrange2'     => [ 238, 118, 0 ],
    'DarkOrange3'     => [ 205, 102, 0 ],
    'DarkOrange4'     => [ 139, 69,  0 ],
    'SlateBlue'       => [ 106, 90,  205 ],
    'BlueViolet'      => [ 138, 43,  226 ],
    'RoyalBlue'       => [ 65,  105, 225 ],
    'AntiqueWhite'    => [ 250, 235, 215 ],
    'AntiqueWhite1'   => [ 255, 239, 219 ],
    'AntiqueWhite2'   => [ 238, 223, 204 ],
    'AntiqueWhite3'   => [ 205, 192, 176 ],
    'AntiqueWhite4'   => [ 139, 131, 120 ],
    'CadetBlue'       => [ 95,  158, 160 ],
    'CadetBlue1'      => [ 152, 245, 255 ],
    'CadetBlue2'      => [ 142, 229, 238 ],
    'CadetBlue3'      => [ 122, 197, 205 ],
    'CadetBlue4'      => [ 83,  134, 139 ],
    'DarkGoldenrod'   => [ 184, 134, 11 ],
    'DarkGoldenrod1'  => [ 255, 185, 15 ],
    'DarkGoldenrod2'  => [ 238, 173, 14 ],
    'DarkGoldenrod3'  => [ 205, 149, 12 ],
    'DarkGoldenrod4'  => [ 139, 101, 8 ],
    'DarkOliveGreen'  => [ 85,  107, 47 ],
    'DarkOliveGreen1' => [ 202, 255, 112 ],
    'DarkOliveGreen2' => [ 188, 238, 104 ],
    'DarkOliveGreen3' => [ 162, 205, 90 ],
    'DarkOliveGreen4' => [ 110, 139, 61 ],
    'DarkOrange1'     => [ 255, 127, 0 ],
    'DarkOrchid'      => [ 153, 50,  204 ],
    'DarkOrchid1'     => [ 191, 62,  255 ],
    'DarkOrchid2'     => [ 178, 58,  238 ],
    'DarkOrchid3'     => [ 154, 50,  205 ],
    'DarkOrchid4'     => [ 104, 34,  139 ],
    'DarkSeaGreen'    => [ 143, 188, 143 ],
    'DarkSeaGreen1'   => [ 193, 255, 193 ],
    'DarkSeaGreen2'   => [ 180, 238, 180 ],
    'DarkSeaGreen3'   => [ 155, 205, 155 ],
    'DarkSeaGreen4'   => [ 105, 139, 105 ],
    'DarkSlateGray'   => [ 47,  79,  79 ],
    'DarkSlateGray1'  => [ 151, 255, 255 ],
    'DarkSlateGray2'  => [ 141, 238, 238 ],
    'DarkSlateGray3'  => [ 121, 205, 205 ],
    'DarkSlateGray4'  => [ 82,  139, 139 ],
    'DeepPink'        => [ 255, 20,  147 ],
    'DeepPink1'       => [ 255, 20,  147 ],
    'DeepPink2'       => [ 238, 18,  137 ],
    'DeepPink3'       => [ 205, 16,  118 ],
    'DeepPink4'       => [ 139, 10,  80 ],
    'DeepSkyBlue'     => [ 0,   191, 255 ],
    'DeepSkyBlue1'    => [ 0,   191, 255 ],
    'DeepSkyBlue2'    => [ 0,   178, 238 ],
    'DeepSkyBlue3'    => [ 0,   154, 205 ],
    'DeepSkyBlue4'    => [ 0,   104, 139 ],
    'DodgerBlue'      => [ 30,  144, 255 ],
    'DodgerBlue1'     => [ 30,  144, 255 ],
    'DodgerBlue2'     => [ 28,  134, 238 ],
    'DodgerBlue3'     => [ 24,  116, 205 ],
    'DodgerBlue4'     => [ 16,  78,  139 ],
    'HotPink1'        => [ 255, 110, 180 ],
    'HotPink2'        => [ 238, 106, 167 ],
    'HotPink3'        => [ 205, 96,  144 ],
    'HotPink4'        => [ 139, 58,  98 ],
    'IndianRed'       => [ 205, 92,  92 ],
    'IndianRed1'      => [ 255, 106, 106 ],
    'IndianRed2'      => [ 238, 99,  99 ],
    'IndianRed3'      => [ 205, 85,  85 ],
    'IndianRed4'      => [ 139, 58,  58 ],
    'LavenderBlush'   => [ 255, 240, 245 ],
    'LavenderBlush1'  => [ 255, 240, 245 ],
    'LavenderBlush2'  => [ 238, 224, 229 ],
    'LavenderBlush3'  => [ 205, 193, 197 ],
    'LavenderBlush4'  => [ 139, 131, 134 ],
    'LemonChiffon'    => [ 255, 250, 205 ],
    'LemonChiffon1'   => [ 255, 250, 205 ],
    'LemonChiffon2'   => [ 238, 233, 191 ],
    'LemonChiffon3'   => [ 205, 201, 165 ],
    'LemonChiffon4'   => [ 139, 137, 112 ],
    'LightBlue'       => [ 173, 216, 230 ],
    'LightBlue1'      => [ 191, 239, 255 ],
    'LightBlue2'      => [ 178, 223, 238 ],
    'LightBlue3'      => [ 154, 192, 205 ],
    'LightBlue4'      => [ 104, 131, 139 ],
    'LightCyan'       => [ 224, 255, 255 ],
    'LightCyan1'      => [ 224, 255, 255 ],
    'LightCyan2'      => [ 209, 238, 238 ],
    'LightCyan3'      => [ 180, 205, 205 ],
    'LightCyan4'      => [ 122, 139, 139 ],
    'LightGoldenrod'  => [ 238, 221, 130 ],
    'LightGoldenrod1' => [ 255, 236, 139 ],
    'LightGoldenrod2' => [ 238, 220, 130 ],
    'LightGoldenrod3' => [ 205, 190, 112 ],
    'LightGoldenrod4' => [ 139, 129, 76 ],
    'LightPink'       => [ 255, 182, 193 ],
    'LightPink1'      => [ 255, 174, 185 ],
    'LightPink2'      => [ 238, 162, 173 ],
    'LightPink3'      => [ 205, 140, 149 ],
    'LightPink4'      => [ 139, 95,  101 ],
    'LightSalmon'     => [ 255, 160, 122 ],
    'LightSalmon1'    => [ 255, 160, 122 ],
    'LightSalmon2'    => [ 238, 149, 114 ],
    'LightSalmon3'    => [ 205, 129, 98 ],
    'LightSalmon4'    => [ 139, 87,  66 ],
    'LightSkyBlue'    => [ 135, 206, 250 ],
    'LightSkyBlue1'   => [ 176, 226, 255 ],
    'LightSkyBlue2'   => [ 164, 211, 238 ],
    'LightSkyBlue3'   => [ 141, 182, 205 ],
    'LightSkyBlue4'   => [ 96,  123, 139 ],
    'LightSteelBlue'  => [ 176, 196, 222 ],
    'LightSteelBlue1' => [ 202, 225, 255 ],
    'LightSteelBlue2' => [ 188, 210, 238 ],
    'LightSteelBlue3' => [ 162, 181, 205 ],
    'LightSteelBlue4' => [ 110, 123, 139 ],
    'LightYellow'     => [ 255, 255, 224 ],
    'LightYellow1'    => [ 255, 255, 224 ],
    'LightYellow2'    => [ 238, 238, 209 ],
    'LightYellow3'    => [ 205, 205, 180 ],
    'LightYellow4'    => [ 139, 139, 122 ],
    'MediumOrchid'    => [ 186, 85,  211 ],
    'MediumOrchid1'   => [ 224, 102, 255 ],
    'MediumOrchid2'   => [ 209, 95,  238 ],
    'MediumOrchid3'   => [ 180, 82,  205 ],
    'MediumOrchid4'   => [ 122, 55,  139 ],
    'MediumPurple1'   => [ 171, 130, 255 ],
    'MediumPurple2'   => [ 159, 121, 238 ],
    'MediumPurple3'   => [ 137, 104, 205 ],
    'MediumPurple4'   => [ 93,  71,  139 ],
    'MistyRose'       => [ 255, 228, 225 ],
    'MistyRose1'      => [ 255, 228, 225 ],
    'MistyRose2'      => [ 238, 213, 210 ],
    'MistyRose3'      => [ 205, 183, 181 ],
    'MistyRose4'      => [ 139, 125, 123 ],
    'NavajoWhite'     => [ 255, 222, 173 ],
    'NavajoWhite1'    => [ 255, 222, 173 ],
    'NavajoWhite2'    => [ 238, 207, 161 ],
    'NavajoWhite3'    => [ 205, 179, 139 ],
    'NavajoWhite4'    => [ 139, 121, 94 ],
    'OliveDrab'       => [ 107, 142, 35 ],
    'OliveDrab1'      => [ 192, 255, 62 ],
    'OliveDrab2'      => [ 179, 238, 58 ],
    'OliveDrab3'      => [ 154, 205, 50 ],
    'OliveDrab4'      => [ 105, 139, 34 ],
    'OrangeRed'       => [ 255, 69,  0 ],
    'OrangeRed1'      => [ 255, 69,  0 ],
    'OrangeRed2'      => [ 238, 64,  0 ],
    'OrangeRed3'      => [ 205, 55,  0 ],
    'OrangeRed4'      => [ 139, 37,  0 ],
    'PaleGreen'       => [ 152, 251, 152 ],
    'PaleTurquoise'   => [ 175, 238, 238 ],
    'PaleTurquoise1'  => [ 187, 255, 255 ],
    'PaleTurquoise2'  => [ 174, 238, 238 ],
    'PaleTurquoise3'  => [ 150, 205, 205 ],
    'PaleTurquoise4'  => [ 102, 139, 139 ],
    'PaleVioletRed'   => [ 219, 112, 147 ],
    'PaleVioletRed1'  => [ 255, 130, 171 ],
    'PaleVioletRed2'  => [ 238, 121, 159 ],
    'PaleVioletRed3'  => [ 205, 104, 137 ],
    'PaleVioletRed4'  => [ 139, 71,  93 ],
    'RosyBrown'       => [ 188, 143, 143 ],
    'RosyBrown1'      => [ 255, 193, 193 ],
    'RosyBrown2'      => [ 238, 180, 180 ],
    'RosyBrown3'      => [ 205, 155, 155 ],
    'RosyBrown4'      => [ 139, 105, 105 ],
    'RoyalBlue1'      => [ 72,  118, 255 ],
    'RoyalBlue2'      => [ 67,  110, 238 ],
    'RoyalBlue3'      => [ 58,  95,  205 ],
    'RoyalBlue4'      => [ 39,  64,  139 ],
    'SeaGreen'        => [ 46,  139, 87 ],
    'SeaGreen1'       => [ 84,  255, 159 ],
    'SeaGreen2'       => [ 78,  238, 148 ],
    'SeaGreen3'       => [ 67,  205, 128 ],
    'SeaGreen4'       => [ 46,  139, 87 ],
    'SkyBlue'         => [ 135, 206, 235 ],
    'SkyBlue1'        => [ 135, 206, 255 ],
    'SkyBlue2'        => [ 126, 192, 238 ],
    'SkyBlue3'        => [ 108, 166, 205 ],
    'SkyBlue4'        => [ 74,  112, 139 ],
    'SlateBlue1'      => [ 131, 111, 255 ],
    'SlateBlue2'      => [ 122, 103, 238 ],
    'SlateBlue3'      => [ 105, 89,  205 ],
    'SlateBlue4'      => [ 71,  60,  139 ],
    'SlateGray'       => [ 112, 128, 144 ],
    'SlateGray1'      => [ 198, 226, 255 ],
    'SlateGray2'      => [ 185, 211, 238 ],
    'SlateGray3'      => [ 159, 182, 205 ],
    'SlateGray4'      => [ 108, 123, 139 ],
    'SpringGreen'     => [ 0,   255, 127 ],
    'SpringGreen1'    => [ 0,   255, 127 ],
    'SpringGreen2'    => [ 0,   238, 118 ],
    'SpringGreen3'    => [ 0,   205, 102 ],
    'SpringGreen4'    => [ 0,   139, 69 ],
    'SteelBlue'       => [ 70,  130, 180 ],
    'SteelBlue1'      => [ 99,  184, 255 ],
    'SteelBlue2'      => [ 92,  172, 238 ],
    'SteelBlue3'      => [ 79,  148, 205 ],
    'SteelBlue4'      => [ 54,  100, 139 ],
    'VioletRed'       => [ 208, 32,  144 ],
    'VioletRed1'      => [ 255, 62,  150 ],
    'VioletRed2'      => [ 238, 58,  140 ],
    'VioletRed3'      => [ 205, 50,  120 ],
    'VioletRed4'      => [ 139, 34,  82 ],
    'aquamarine'      => [ 127, 255, 212 ],
    'aquamarine1'     => [ 127, 255, 212 ],
    'aquamarine2'     => [ 118, 238, 198 ],
    'aquamarine3'     => [ 102, 205, 170 ],
    'aquamarine4'     => [ 69,  139, 116 ],
    'azure'           => [ 240, 255, 255 ],
    'azure1'          => [ 240, 255, 255 ],
    'azure2'          => [ 224, 238, 238 ],
    'azure3'          => [ 193, 205, 205 ],
    'azure4'          => [ 131, 139, 139 ],
    'bisque'          => [ 255, 228, 196 ],
    'bisque1'         => [ 255, 228, 196 ],
    'bisque2'         => [ 238, 213, 183 ],
    'bisque3'         => [ 205, 183, 158 ],
    'bisque4'         => [ 139, 125, 107 ],
    'blue1'           => [ 0,   0,   255 ],
    'blue2'           => [ 0,   0,   238 ],
    'blue3'           => [ 0,   0,   205 ],
    'blue4'           => [ 0,   0,   139 ],
    'brown1'          => [ 255, 64,  64 ],
    'brown2'          => [ 238, 59,  59 ],
    'brown3'          => [ 205, 51,  51 ],
    'brown4'          => [ 139, 35,  35 ],
    'burlywood'       => [ 222, 184, 135 ],
    'burlywood1'      => [ 255, 211, 155 ],
    'burlywood2'      => [ 238, 197, 145 ],
    'burlywood3'      => [ 205, 170, 125 ],
    'burlywood4'      => [ 139, 115, 85 ],
    'chartreuse'      => [ 127, 255, 0 ],
    'chartreuse1'     => [ 127, 255, 0 ],
    'chartreuse2'     => [ 118, 238, 0 ],
    'chartreuse3'     => [ 102, 205, 0 ],
    'chartreuse4'     => [ 69,  139, 0 ],
    'chocolate'       => [ 210, 105, 30 ],
    'coral'           => [ 255, 127, 80 ],
    'coral1'          => [ 255, 114, 86 ],
    'coral2'          => [ 238, 106, 80 ],
    'coral3'          => [ 205, 91,  69 ],
    'coral4'          => [ 139, 62,  47 ],
    'cornsilk'        => [ 255, 248, 220 ],
    'cornsilk1'       => [ 255, 248, 220 ],
    'cornsilk2'       => [ 238, 232, 205 ],
    'cornsilk3'       => [ 205, 200, 177 ],
    'cornsilk4'       => [ 139, 136, 120 ],
    'cyan'            => [ 0,   255, 255 ],
    'cyan1'           => [ 0,   255, 255 ],
    'cyan2'           => [ 0,   238, 238 ],
    'cyan3'           => [ 0,   205, 205 ],
    'cyan4'           => [ 0,   139, 139 ],
    'firebrick'       => [ 178, 34,  34 ],
    'firebrick1'      => [ 255, 48,  48 ],
    'firebrick2'      => [ 238, 44,  44 ],
    'firebrick3'      => [ 205, 38,  38 ],
    'firebrick4'      => [ 139, 26,  26 ],
    'gold'            => [ 255, 215, 0 ],
    'gold1'           => [ 255, 215, 0 ],
    'gold2'           => [ 238, 201, 0 ],
    'gold3'           => [ 205, 173, 0 ],
    'gold4'           => [ 139, 117, 0 ],
    'goldenrod'       => [ 218, 165, 32 ],
    'goldenrod1'      => [ 255, 193, 37 ],
    'goldenrod2'      => [ 238, 180, 34 ],
    'goldenrod3'      => [ 205, 155, 29 ],
    'goldenrod4'      => [ 139, 105, 20 ],
    'gray'            => [ 190, 190, 190 ],
    'gray1'           => [ 3,   3,   3 ],
    'gray2'           => [ 5,   5,   5 ],
    'gray3'           => [ 8,   8,   8 ],
    'gray4'           => [ 10,  10,  10 ],
    'green1'          => [ 0,   255, 0 ],
    'green2'          => [ 0,   238, 0 ],
    'green3'          => [ 0,   205, 0 ],
    'green4'          => [ 0,   139, 0 ],
    'grey1'           => [ 3,   3,   3 ],
    'grey2'           => [ 5,   5,   5 ],
    'grey3'           => [ 8,   8,   8 ],
    'grey4'           => [ 10,  10,  10 ],
    'honeydew'        => [ 240, 255, 240 ],
    'honeydew1'       => [ 240, 255, 240 ],
    'honeydew2'       => [ 224, 238, 224 ],
    'honeydew3'       => [ 193, 205, 193 ],
    'honeydew4'       => [ 131, 139, 131 ],
    'ivory'           => [ 255, 255, 240 ],
    'ivory1'          => [ 255, 255, 240 ],
    'ivory2'          => [ 238, 238, 224 ],
    'ivory3'          => [ 205, 205, 193 ],
    'ivory4'          => [ 139, 139, 131 ],
    'khaki'           => [ 240, 230, 140 ],
    'khaki1'          => [ 255, 246, 143 ],
    'khaki2'          => [ 238, 230, 133 ],
    'khaki3'          => [ 205, 198, 115 ],
    'khaki4'          => [ 139, 134, 78 ],
    'magenta'         => [ 255, 0,   255 ],
    'magenta1'        => [ 255, 0,   255 ],
    'magenta2'        => [ 238, 0,   238 ],
    'magenta3'        => [ 205, 0,   205 ],
    'magenta4'        => [ 139, 0,   139 ],
    'maroon'          => [ 176, 48,  96 ],
    'maroon1'         => [ 255, 52,  179 ],
    'maroon2'         => [ 238, 48,  167 ],
    'maroon3'         => [ 205, 41,  144 ],
    'maroon4'         => [ 139, 28,  98 ],
    'orange1'         => [ 255, 165, 0 ],
    'orchid'          => [ 218, 112, 214 ],
    'orchid1'         => [ 255, 131, 250 ],
    'orchid2'         => [ 238, 122, 233 ],
    'orchid3'         => [ 205, 105, 201 ],
    'orchid4'         => [ 139, 71,  137 ],
    'pink1'           => [ 255, 181, 197 ],
    'pink2'           => [ 238, 169, 184 ],
    'pink3'           => [ 205, 145, 158 ],
    'pink4'           => [ 139, 99,  108 ],
    'plum1'           => [ 255, 187, 255 ],
    'plum2'           => [ 238, 174, 238 ],
    'plum3'           => [ 205, 150, 205 ],
    'plum4'           => [ 139, 102, 139 ],
    'purple1'         => [ 155, 48,  255 ],
    'purple2'         => [ 145, 44,  238 ],
    'purple3'         => [ 125, 38,  205 ],
    'purple4'         => [ 85,  26,  139 ],
    'red1'            => [ 255, 0,   0 ],
    'red2'            => [ 238, 0,   0 ],
    'red3'            => [ 205, 0,   0 ],
    'red4'            => [ 139, 0,   0 ],
    'salmon'          => [ 250, 128, 114 ],
    'salmon1'         => [ 255, 140, 105 ],
    'salmon2'         => [ 238, 130, 98 ],
    'salmon3'         => [ 205, 112, 84 ],
    'salmon4'         => [ 139, 76,  57 ],
    'seashell'        => [ 255, 245, 238 ],
    'seashell1'       => [ 255, 245, 238 ],
    'seashell2'       => [ 238, 229, 222 ],
    'seashell3'       => [ 205, 197, 191 ],
    'seashell4'       => [ 139, 134, 130 ],
    'sienna'          => [ 160, 82,  45 ],
    'sienna1'         => [ 255, 130, 71 ],
    'sienna2'         => [ 238, 121, 66 ],
    'sienna3'         => [ 205, 104, 57 ],
    'sienna4'         => [ 139, 71,  38 ],
    'snow'            => [ 255, 250, 250 ],
    'snow1'           => [ 255, 250, 250 ],
    'snow2'           => [ 238, 233, 233 ],
    'snow3'           => [ 205, 201, 201 ],
    'snow4'           => [ 139, 137, 137 ],
    'tan'             => [ 210, 180, 140 ],
    'tan1'            => [ 255, 165, 79 ],
    'tan2'            => [ 238, 154, 73 ],
    'tan3'            => [ 205, 133, 63 ],
    'tan4'            => [ 139, 90,  43 ],
    'thistle'         => [ 216, 191, 216 ],
    'thistle1'        => [ 255, 225, 255 ],
    'thistle2'        => [ 238, 210, 238 ],
    'thistle3'        => [ 205, 181, 205 ],
    'thistle4'        => [ 139, 123, 139 ],
    'tomato'          => [ 255, 99,  71 ],
    'tomato1'         => [ 255, 99,  71 ],
    'tomato2'         => [ 238, 92,  66 ],
    'tomato3'         => [ 205, 79,  57 ],
    'tomato4'         => [ 139, 54,  38 ],
    'turquoise1'      => [ 0,   245, 255 ],
    'turquoise2'      => [ 0,   229, 238 ],
    'turquoise3'      => [ 0,   197, 205 ],
    'turquoise4'      => [ 0,   134, 139 ],
    'wheat'           => [ 245, 222, 179 ],
    'wheat1'          => [ 255, 231, 186 ],
    'wheat2'          => [ 238, 216, 174 ],
    'wheat3'          => [ 205, 186, 150 ],
    'wheat4'          => [ 139, 126, 102 ],
    'yellow1'         => [ 255, 255, 0 ],
    'yellow2'         => [ 238, 238, 0 ],
    'yellow3'         => [ 205, 205, 0 ],
    'yellow4'         => [ 139, 139, 0 ],
);

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

        #print STDERR "Role = $_\n";

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

            #print STDERR "spec = $spec\n";

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

            $self->{'color_table'}->{$role} = $index;
        }
        $index;
    } @_;

    #print STDERR "Result= ".$result[0]."\n";
    ( wantarray && @_ > 1 ? @result : $result[0] );
}

## @fn private array _color_spec_to_rgb($role,$spec)
# Return an array (list of) rgb values for spec
# @param[in] $role name of a role
# @param[in] $spec [r,g,b] or name
# @return array of rgb values as a list (i.e., \@rgb)
#
sub _color_spec_to_rgb
{
    my $self = shift;
    my $role = shift;    # for error messages
    my $spec = shift;    # [r,g,b] or name

    my @rgb;             # result
    if ( ref($spec) eq 'ARRAY' )
    {
        @rgb = @{$spec};
        croak "Invalid color RGB array (" . join( ',', @rgb ) . ") for $role\n"

          unless @rgb == 3 && grep( !m/^\d+$/ || $_ > 255, @rgb ) == 0;
    }
    elsif ( !ref($spec) )
    {
        croak "Unknown named color ($spec) for $role\n"
          unless $named_colors{$spec};
        @rgb = @{ $named_colors{$spec} };
    }
    else
    {
        croak "Unrecognized color for $role\n";
    }
    @rgb;
}

## @fn private int _brushStyles_of_roles
# return a (list of) brushStyles corresponding to the (list of) role(s)
#
# @param \@list_of_roles List of roles
# @return (list of) brushStyle(s) corresponding to the (list of) role(s) in \@_.
#
sub _brushStyles_of_roles
{
    my $self  = shift;
    my @roles = @_;

    my @results = ();
    foreach my $role (@roles)
    {
        my $brushStyle = $self->{'brushStyles'}->{$role};

        if ( !defined($brushStyle) )
        {
            $brushStyle = $self->{'brushStyle'};
        }
        push( @results, $brushStyle );
    }
    @results;
}

## @fn private int _draw_title
#  draw the title for the chart
#
# The title was defined by the user in set('title' => ....)\n
# The user may define some title lines by separating them via character '\\n';\n
# The used font is taken from 'title_font';\n
# The used color is calculated by function '_color_role_to_index'
# based on 'title' or 'text'\n
# @see _color_role_to_index
# @return status
sub _draw_title
{
    my $self = shift;
    my $font = $self->{'title_font'};
    my $color;
    my ( $h, $w, @lines, $x, $y );

    #get the right color
    if ( defined $self->{'colors'}{'title'} )
    {
        $color = $self->_color_role_to_index('title');
    }
    else
    {
        $color = $self->_color_role_to_index('text');
    }

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

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

    # split the title into lines
    @lines = split( /\\n/, $self->{'title'} );

    # write the first line
    $x = ( $self->{'curr_x_max'} - $self->{'curr_x_min'} ) / 2 + $self->{'curr_x_min'} - ( length( $lines[0] ) * $w ) / 2;
    $y = $self->{'curr_y_min'} + $self->{'text_space'};

    # Tests for Version 2.5
    #my $fontText = new GD::Text();
    # ttf are found in /usr/lib/jvm/java-6-sun*/
    # /var/share/fonts/truetype
    #$fontText->set_font('LiberationSans-Regular.ttf');
    #$fontText->set_text('Some text',14);
    #if ( GD::Text->can_do_ttf() )
    #{ carp "Can do ttf"; }
    #else
    #{ carp "No TTF"; }
    # ttf is in GD::Text!
    # #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    $self->{'gd_obj'}->string( $font, $x, $y, $lines[0], $color );

    # now loop through the rest of them
    # (the font is decreased in width and height by 1
    if ( $w > 1 ) { $w--; }
    if ( $h > 1 ) { $h--; }
    for ( 1 .. $#lines )
    {
        $self->{'curr_y_min'} += $self->{'text_space'} + $h;
        $x = ( $self->{'curr_x_max'} - $self->{'curr_x_min'} ) / 2 + $self->{'curr_x_min'} - ( length( $lines[$_] ) * $w ) / 2;
        $y = $self->{'curr_y_min'} + $self->{'text_space'};
        $self->{'gd_obj'}->string( $font, $x, $y, $lines[$_], $color );
    }

    # mark off that last space
    $self->{'curr_y_min'} += 2 * $self->{'text_space'} + $h;

    # and return
    return 1;
}

## @fn private int _draw_sub_title()
#  draw the sub-title for the chart
# @see _draw_title\n
# _draw_sub_title() is more or less obsolete as _draw_title() does the same
# by writing more than one line as the title.
# Both use decreased width and height of the font by one.
# @return status
sub _draw_sub_title
{
    my $self = shift;

    my $font = $self->{'sub_title_font'};
    my $text = $self->{'sub_title'};
    return 1 if length($text) == 0;    # nothing to plot

    #get the right color
    my $color;
    if ( defined $self->{'colors'}{'title'} )
    {
        $color = $self->_color_role_to_index('title');
    }
    else
    {
        $color = $self->_color_role_to_index('text');
    }

    my ( $h, $w, $x, $y );

    # 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 );
    if ( $h > 1 && $w > 1 ) { $h--, $w-- }

    # figure out the placement
    $x = ( $self->{'curr_x_max'} - $self->{'curr_x_min'} ) / 2 + $self->{'curr_x_min'} - ( length($text) * $w ) / 2;
    $y = $self->{'curr_y_min'};

    # now draw the subtitle
    $self->{'gd_obj'}->string( $font, $x, $y, $text, $color );

    # Adapt curr_y_min
    $self->{'curr_y_min'} += $self->{'text_space'} + $h;

    # and return
    return 1;
}

## @fn private int _sort_data()
#  sort the data nicely (mostly for the pareto charts and xy-plots)
# @return status
sub _sort_data
{
    my $self     = shift;
    my $data_ref = $self->{'dataref'};
    my @data     = @{ $self->{'dataref'} };
    my @sort_index;

    #sort the data with slices
    @sort_index = sort { $data[0][$a] <=> $data[0][$b] } ( 0 .. scalar( @{ $data[1] } ) - 1 );
    for ( 1 .. $#data )
    {
        @{ $self->{'dataref'}->[$_] } = @{ $self->{'dataref'}->[$_] }[@sort_index];
    }
    @{ $data_ref->[0] } = sort { $a <=> $b } @{ $data_ref->[0] };

    #finally return
    return 1;
}

## @fn private int _find_x_scale()
# For a xy-plot do the same for the x values, as '_find_y_scale' does for the y values!
# @see _find_y_scale
# @return status
sub _find_x_scale
{
    my $self = shift;
    my @data = @{ $self->{'dataref'} };
    my ( $i,     $j );
    my ( $d_min, $d_max );
    my ( $p_min, $p_max, $f_min, $f_max );
    my ( $tickInterval, $tickCount, $skip );
    my @tickLabels;
    my $maxtickLabelLen = 0;

    #look, if we have numbers
    #see also if we only have integers
    for $i ( 0 .. ( $self->{'num_datasets'} ) )
    {
        for $j ( 0 .. ( $self->{'num_datapoints'} - 1 ) )
        {

            # the following regular Expression matches all possible numbers, including scientific numbers
            # iff data is defined
            if ( defined $data[$i][$j] and $data[$i][$j] !~ m/^[\+\-]?((\.\d+)|(\d+\.?\d*))([eE][+-]?\d+)?[fFdD]?$/ )
            {
                croak "<$data[$i][$j]> You should give me numbers for drawing a xy plot!\n";
            }
        }
    }

    #find the dataset min and max
    ( $d_min, $d_max ) = $self->_find_x_range();

    # Force the inclusion of zero if the user has requested it.
    if ( $self->true( $self->{'include_zero'} ) )
    {
        if ( ( $d_min * $d_max ) > 0 )    # If both are non zero and of the same sign.
        {
            if ( $d_min > 0 )             # If the whole scale is positive.
            {
                $d_min = 0;
            }
            else                          # The scale is entirely negative.
            {
                $d_max = 0;
            }
        }
    }

    # Calculate the width of the dataset. (possibly modified by the user)
    my $d_width = $d_max - $d_min;

    # If the width of the range is zero, forcebly widen it
    # (to avoid division by zero errors elsewhere in the code).
    if ( 0 == $d_width )
    {
        $d_min--, $d_max++, $d_width = 2;
    }

    # Descale the range by converting the dataset width into
    # a floating point exponent & mantisa pair.
    my ( $rangeExponent, $rangeMantisa ) = $self->_sepFP($d_width);
    my $rangeMuliplier = 10**$rangeExponent;

    # Find what tick
    # to use & how many ticks to plot,
    # round the plot min & max to suatable round numbers.
    ( $tickInterval, $tickCount, $p_min, $p_max ) = $self->_calcXTickInterval(
        $d_min / $rangeMuliplier,
        $d_max / $rangeMuliplier,
        $f_min, $f_max,
        $self->{'min_x_ticks'},
        $self->{'max_x_ticks'}
    );

    # Restore the tickInterval etc to the correct scale
    $_ *= $rangeMuliplier foreach ( $tickInterval, $p_min, $p_max );

    #get the precision for the labels
    my $precision = $self->{'precision'};

    # Now sort out an array of tick labels.
    for ( my $labelNum = $p_min ; $labelNum < $p_max + $tickInterval / 2 ; $labelNum += $tickInterval )
    {
        my $labelText;

        if ( defined $self->{f_y_tick} )
        {

            # Is _default_f_tick function used?
            if ( $self->{f_y_tick} == \&_default_f_tick )
            {
                $labelText = sprintf( "%." . $precision . "f", $labelNum );
            }
            else
            {
                $labelText = $self->{f_y_tick}->($labelNum);
            }
        }
        else
        {
            $labelText = sprintf( "%." . $precision . "f", $labelNum );
        }

        push @tickLabels, $labelText;
        $maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
    }

    # Store the calculated data.
    $self->{'x_min_val'}             = $p_min,
      $self->{'x_max_val'}           = $p_max,
      $self->{'x_tick_labels'}       = \@tickLabels,
      $self->{'x_tick_label_length'} = $maxtickLabelLen,
      $self->{'x_number_ticks'}      = $tickCount;
    return 1;
}

## @fn private int _find_y_scale()
#  find good values for the minimum and maximum y-value on the chart
# @return status
#
# New version, re-written by David Pottage of Tao Group.\n
# This code is *AS IS* and comes with *NO WARRANTY*\n
#
# This Sub calculates correct values for the following class local variables,
# if they have not been set by the user.
#
# max_val, min_val: 	The maximum and minimum values for the y axis.\n
# y_ticks:		The number of ticks to plot on the y scale, including
#			the end points. e.g. If the scale runs from 0 to 50,
#			with ticks every 10, y_ticks will have the value of 6.\n
# y_tick_labels:	An array of strings, each is a label for the y axis.\n
# y_tick_labels_length:	The length to allow for B tick labels. (How long is
#                       the longest?)
sub _find_y_scale
{
    my $self = shift;

    # Predeclare vars.
    my ( $d_min,        $d_max );              # Dataset min & max.
    my ( $p_min,        $p_max );              # Plot min & max.
    my ( $tickInterval, $tickCount, $skip );
    my @tickLabels;                            # List of labels for each tick.
    my $maxtickLabelLen = 0;                   # The length of the longest tick label.
    my $prec_test       = 0;                   # Boolean which indicate if precision < |rangeExponent|
    my $temp_rangeExponent;

    my $flag_all_integers = 1;                 # assume true

    # Find the dataset minimum and maximum.
    ( $d_min, $d_max, $flag_all_integers ) = $self->_find_y_range();

    # Force the inclusion of zero if the user has requested it.
    if ( $self->true( $self->{'include_zero'} ) )
    {

        #print "include_zero = true\n";
        if ( ( $d_min * $d_max ) > 0 )         # If both are non zero and of the same sign.
        {
            if ( $d_min > 0 )                  # If the whole scale is positive.
            {
                $d_min = 0;
            }
            else                               # The scale is entirely negative.
            {
                $d_max = 0;
            }
        }
    }

    if ( $self->true( $self->{'integer_ticks_only'} ) )
    {

        # Allow the dataset range to be overidden by the user.
        # f_min/f_max are booleans which indicate that the min & max should not be modified.
        my $f_min = 0;
        if ( defined $self->{'min_val'} ) { $f_min = 1; }
        $d_min = $self->{'min_val'} if $f_min;

        my $f_max = 0;
        if ( defined $self->{'max_val'} ) { $f_max = 1; }
        $d_max = $self->{'max_val'} if $f_max;

        # Assert against defined min and max.
        if ( !defined $d_min || !defined $d_max )
        {
            croak "No min_val or max_val is defined";
        }

        # Assert against the min is larger than the max.
        if ( $d_min > $d_max )
        {
            croak "The specified 'min_val' & 'max_val' values are reversed (min > max: $d_min>$d_max)";
        }

        # The user asked for integer ticks, force the limits to integers.
        # & work out the range directly.
        #$p_min = $self->_round2Tick($d_min, 1, -1);
        #$p_max = $self->_round2Tick($d_max, 1, 1);

        $skip = $self->{skip_int_ticks};
        $skip = 1 if $skip < 1;

        $p_min = $self->_round2Tick( $d_min, 1, -1 );
        $p_max = $self->_round2Tick( $d_max, 1, 1 );
        if ( ( $p_max - $p_min ) == 0 )
        {
            $p_max++ if ( $f_max != 1 );                # p_max is not defined by the user
            $p_min-- if ( $f_min != 1 );                # p_min is not defined by the user
            $p_max++ if ( ( $p_max - $p_min ) == 0 );
        }

        $tickInterval = $skip;
        $tickCount    = ( $p_max - $p_min ) / $skip + 1;

        # Now sort out an array of tick labels.

        for ( my $labelNum = $p_min ; $labelNum < $p_max + $tickInterval / 3 ; $labelNum += $tickInterval )
        {
            my $labelText;

            if ( defined $self->{f_y_tick} )
            {

                # Is _default_f_tick function used?
                if ( $self->{f_y_tick} == \&_default_f_tick )
                {
                    $labelText = sprintf( "%d", $labelNum );
                }
                else
                {
                    $labelText = $self->{f_y_tick}->($labelNum);
                }
            }
            else
            {
                $labelText = sprintf( "%d", $labelNum );
            }

            push @tickLabels, $labelText;
            $maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
        }
    }
    else
    {

        # Allow the dataset range to be overidden by the user.
        # f_min/f_max are booleans which indicate that the min & max should not be modified.
        my $f_min = 0;
        if ( defined $self->{'min_val'} ) { $f_min = 1; }
        $d_min = $self->{'min_val'} if $f_min;

        my $f_max = 0;
        if ( defined $self->{'max_val'} ) { $f_max = 1; }
        $d_max = $self->{'max_val'} if $f_max;

        #  print "fmin $f_min fmax $f_max\n";
        #  print "dmin $d_min dmax $d_max\n";

        # Assert against defined min and max.
        if ( !defined $d_min || !defined $d_max )
        {
            croak "No min_val or max_val is defined";
        }

        # Assert against the min is larger than the max.
        if ( $d_min > $d_max )
        {
            croak "The the specified 'min_val' & 'max_val' values are reversed (min > max: $d_min>$d_max)";
        }

        # Calculate the width of the dataset. (possibly modified by the user)
        my $d_width = $d_max - $d_min;

        # If the width of the range is zero, forcibly widen it
        # (to avoid division by zero errors elsewhere in the code).
        if ( $d_width == 0 )
        {
            $d_min--, $d_max++, $d_width = 2;
        }

        # Descale the range by converting the dataset width into
        # a floating point exponent & mantisa pair.
        my ( $rangeExponent, $rangeMantisa ) = $self->_sepFP($d_width);
        my $rangeMuliplier = 10**$rangeExponent;

        # print "fmin $f_min fmax $f_max\n";
        # print "dmin $d_min dmax $d_max\n";

        # Find what tick
        # to use & how many ticks to plot,
        # round the plot min & max to suitable round numbers.
        ( $tickInterval, $tickCount, $p_min, $p_max ) = $self->_calcTickInterval(
            $d_min / $rangeMuliplier,
            $d_max / $rangeMuliplier,
            $f_min, $f_max,
            $self->{'min_y_ticks'},
            $self->{'max_y_ticks'}
        );

        # Restore the tickInterval etc to the correct scale
        $_ *= $rangeMuliplier foreach ( $tickInterval, $p_min, $p_max );

        # Is precision < |rangeExponent|?
        if ( $rangeExponent < 0 )
        {
            $temp_rangeExponent = -$rangeExponent;
        }
        else
        {
            $temp_rangeExponent = $rangeExponent;
        }

        # print "pmin $p_min pmax $p_max\n";
        # print "range exponent $rangeExponent\n";

        #get the precision for the labels
        my $precision = $self->{'precision'};

        if (   $temp_rangeExponent != 0
            && $rangeExponent < 0
            && $temp_rangeExponent > $precision )
        {
            $prec_test = 1;
        }

        # Now sort out an array of tick labels.
        for ( my $labelNum = $p_min ; $labelNum < $p_max + $tickInterval / 2 ; $labelNum += $tickInterval )
        {
            my $labelText;
            if ( defined $self->{f_y_tick} )
            {

                # Is _default_f_tick function used?
                if ( ( $self->{f_y_tick} == \&_default_f_tick ) && ( $prec_test == 0 ) )
                {
                    $labelText = sprintf( "%." . $precision . "f", $labelNum );
                }

                # If precision <|rangeExponent| print the labels whith exponents
                elsif ( ( $self->{f_y_tick} == \&_default_f_tick ) && ( $prec_test == 1 ) )
                {
                    $labelText = $self->{f_y_tick}->($labelNum);

                    #  print "precision $precision\n";
                    #  print "temp range exponent $temp_rangeExponent\n";
                    #  print "range exponent $rangeExponent\n";
                    #  print "labelText $labelText\n";

                }
                else
                {
                    $labelText = $self->{f_y_tick}->($labelNum);
                }
            }
            else
            {
                $labelText = sprintf( "%." . $precision . "f", $labelNum );
            }
            push @tickLabels, $labelText;
            $maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
        }    # end for
    }

    # Store the calculated data.
    #### begin debugging output
    #if ( defined $self->{'y_ticks'} )
    #{
    #    print "_find_y_scale: self->{'y_ticks'}=".$self->{'y_ticks'}."\n";
    #}
    #else
    #{
    #   print "_find_y_scale: self->{'y_ticks'}= NOT DEFINED\n";
    #}
    #if ( defined $self->{'min_val'} )
    #{
    #    print "_find_y_scale: self->{'min_val'}=".$self->{'min_val'}."\n";
    #}
    #else
    #{
    #    print "_find_y_scale: self->{'min_val'}=NOT DEFINED\n";
    #}
    #if ( defined $self->{'max_val'} )
    #{
    #    print "_find_y_scale: self->{'max_val'}=".$self->{'max_val'}."\n";
    #}
    #else
    #{
    #    print "_find_y_scale: self->{'max_val'}= NOT DEFINED\n";
    #}
    #### end debugging output

    $self->{'min_val'}               = $p_min,
      $self->{'max_val'}             = $p_max,
      $self->{'y_ticks'}             = $tickCount,
      $self->{'y_tick_labels'}       = \@tickLabels,
      $self->{'y_tick_label_length'} = $maxtickLabelLen;

    ##################
    #print statement is for debug only
    #print "_find_y_scale: min_val = $p_min, max_val=$p_max\n";
    ##################

    # and return.
    return 1;
}

## @fn private _calcTickInterval($dataset_min, $dataset_max, $flag_fixed_min, $flag_fixed_max, $minTicks, $maxTicks)
# @brief
# Calculate the Interval between ticks in y direction
#
# @details
# Calculate the Interval between ticks in y direction
# and compare the number of ticks to
# the user's given values min_y_ticks, max_y_ticks.
#
# @param[in] $dataset_min Minimal value in y direction
# @param[in] $dataset_max Maximal value in y direction
# @param[in] $flag_fixed_min Indicator whether the dataset_min value is fixed
# @param[in] $flag_fixed_max Indicator whether the dataset_max value is fixed
# @param[in] $minTicks Minimal number of ticks wanted
# @param[in] $maxTicks Maximal number of ticks wanted
# @return Array of ($tickInterval, $tickCount, $pMin, $pMax)
#
sub _calcTickInterval
{
    my $self = shift;

    my (
        $dataset_min,    $dataset_max,       # The dataset min & max.
        $flag_fixed_min, $flag_fixed_max,    # Indicates if those min/max are fixed.
        $minTicks,       $maxTicks,          # The minimum & maximum number of ticks.
    ) = @_;

# print "calcTickInterval dataset_min $dataset_min dataset_max $dataset_max flag_fixed_min $flag_fixed_min flag_mixed_max $flag_fixed_max\n";

    # Verify the supplied 'min_y_ticks' & 'max_y_ticks' are sensible.
    if ( $minTicks < 2 )
    {

        #print STDERR "Chart::Base::_calcTickInterval : Incorrect value for 'min_y_ticks', too small (less than 2).\n";
        $minTicks = 2;
    }

    if ( $maxTicks < 5 * $minTicks )
    {

        #print STDERR "Chart::Base::_calcTickInterval : Incorrect value for 'max_y_ticks', too small (<5*minTicks).\n";
        $maxTicks = 5 * $minTicks;
    }

    my $width = $dataset_max - $dataset_min;
    my @divisorList;

    for ( my $baseMul = 1 ; ; $baseMul *= 10 )
    {
      TRY: foreach my $tryMul ( 1, 2, 5 )
        {

            # Calc a fresh, smaller tick interval.
            my $divisor = $baseMul * $tryMul;

            # Count the number of ticks.
            my ( $tickCount, $pMin, $pMax ) = $self->_countTicks( $dataset_min, $dataset_max, 1 / $divisor );

            # Look a the number of ticks.
            if ( $maxTicks < $tickCount )
            {

                # If it is to high, Backtrack.
                $divisor = pop @divisorList;

                # just for security:
                if ( !defined($divisor) || $divisor == 0 ) { $divisor = 1; }
                ( $tickCount, $pMin, $pMax ) = $self->_countTicks( $dataset_min, $dataset_max, 1 / $divisor );

#print STDERR "\nChart::Base : Caution: Tick limit of $maxTicks exceeded. Backing of to an interval of ".1/$divisor." which plots $tickCount ticks\n";
                return ( 1 / $divisor, $tickCount, $pMin, $pMax );
            }
            elsif ( $minTicks > $tickCount )
            {

                # If it is too low, try again.
                next TRY;
            }
            else
            {

                # Store the divisor for possible later backtracking.
                push @divisorList, $divisor;

                # if the min or max is fixed, check they will fit in the interval.
                next TRY if ( $flag_fixed_min && ( int( $dataset_min * $divisor ) != ( $dataset_min * $divisor ) ) );
                next TRY if ( $flag_fixed_max && ( int( $dataset_max * $divisor ) != ( $dataset_max * $divisor ) ) );

                # If everything passes the tests, return.
                return ( 1 / $divisor, $tickCount, $pMin, $pMax );
            }
        }
    }

    die "can't happen!";
}

## @fn private int _calcXTickInterval($min,$max,$minF,$maxF,$minTicks,$maxTicks)
# @brief
# Calculate the Interval between ticks in x direction
#
# @details
# Calculate the Interval between ticks in x direction
# and compare the number of ticks to
# the user's given values minTicks, maxTicks.
#
# @param[in] $min Minimal value of dataset in x direction
# @param[in] $max Maximal value of dataset in x direction
# @param[in] $minF Inddicator if those min value is fixed
# @param[in] $maxF Inddicator if those max value is fixed
# @param[in] $minTicks Minimal number of tick in x direction
# @param[in] $maxTicks Maximal number of tick in x direction
# @return $tickInterval, $tickCount, $pMin, $pMax
sub _calcXTickInterval
{
    my $self = shift;
    my (
        $min,      $max,         # The dataset min & max.
        $minF,     $maxF,        # Indicates if those min/max are fixed.
        $minTicks, $maxTicks,    # The minimum & maximum number of ticks.
    ) = @_;

    # Verify the supplied 'min_y_ticks' & 'max_y_ticks' are sensible.
    if ( $minTicks < 2 )
    {

        #print STDERR "Chart::Base::_calcXTickInterval : Incorrect value for 'min_y_ticks', too small.\n";
        $minTicks = 2;
    }

    if ( $maxTicks < 5 * $minTicks )
    {

        #print STDERR "Chart::Base::_calcXTickInterval : Incorrect value for 'max_y_ticks', to small.\n";
        $maxTicks = 5 * $minTicks;
    }

    my $width = $max - $min;
    my @divisorList;

    for ( my $baseMul = 1 ; ; $baseMul *= 10 )
    {
      TRY: foreach my $tryMul ( 1, 2, 5 )
        {

            # Calc a fresh, smaller tick interval.
            my $divisor = $baseMul * $tryMul;

            # Count the number of ticks.
            my ( $tickCount, $pMin, $pMax ) = $self->_countTicks( $min, $max, 1 / $divisor );

            #print STDERR "Chart::Base::_calcXTickInterval : tickCount = $tickCount, maxTicks = $maxTicks\n";
            # Look a the number of ticks.
            if ( $maxTicks < $tickCount )
            {

                # If it is to high, Backtrack.
                $divisor = pop @divisorList;

                # just for security:
                if ( !defined($divisor) || $divisor == 0 ) { $divisor = 1; }
                ( $tickCount, $pMin, $pMax ) = $self->_countTicks( $min, $max, 1 / $divisor );

#print STDERR "\nChart::Base : Caution: Tick limit of $maxTicks exceeded. Backing of to an interval of ".1/$divisor." which plots $tickCount ticks\n";
                return ( 1 / $divisor, $tickCount, $pMin, $pMax );
            }
            elsif ( $minTicks > $tickCount )
            {

                # If it is too low, try again.
                next TRY;
            }
            else
            {

                # Store the divisor for possible later backtracking.
                push @divisorList, $divisor;

                # if the min or max is fixed, check they will fit in the interval.
                next TRY if ( $minF && ( int( $min * $divisor ) != ( $min * $divisor ) ) );
                next TRY if ( $maxF && ( int( $max * $divisor ) != ( $max * $divisor ) ) );

                # If everything passes the tests, return.
                return ( 1 / $divisor, $tickCount, $pMin, $pMax );
            }
        }
    }

    croak "can't happen!";
}

## @fn private int _countTicks($min,$max,$interval)
#
# @brief
# Works out how many ticks would be displayed at that interval
#
# @param $min Minimal value
# @param $max Maximal value
# @param $interval value
# @return ($tickCount, $minR, $maxR)
#
# @details
#
# e.g min=2, max=5, interval=1, result is 4 ticks.\n
# written by David Pottage of Tao Group.\n
# $minR = $self->_round2Tick( $min, $interval, -1);\n
# $maxR = $self->_round2Tick( $max, $interval, 1);\n
# $tickCount = ( $maxR/$interval ) - ( $minR/$interval ) +1;
sub _countTicks
{
    my $self = shift;
    my ( $min, $max, $interval ) = @_;

    my $minR = $self->_round2Tick( $min, $interval, -1 );
    my $maxR = $self->_round2Tick( $max, $interval, 1 );

    my $tickCount = ( $maxR / $interval ) - ( $minR / $interval ) + 1;

    return ( $tickCount, $minR, $maxR );
}

## @fn private int _round2Tick($input, $interval, $roundUP)
# Rounds up or down to the next tick of interval size.
#
# $roundUP can be +1 or -1 to indicate if rounding should be up or down.\n
# written by David Pottage of Tao Group.
#
# @param $input
# @param $interval
# @param $roundUP
# @return retN*interval
sub _round2Tick
{
    my $self = shift;
    my ( $input, $interval, $roundUP ) = @_;
    return $input if $interval == 0;
    die unless 1 == $roundUP * $roundUP;

    my $intN  = int( $input / $interval );
    my $fracN = ( $input / $interval ) - $intN;

    my $retN =
      ( ( 0 == $fracN ) || ( ( $roundUP * $fracN ) < 0 ) )
      ? $intN
      : $intN + $roundUP;

    return $retN * $interval;
}

## @fn private array _sepFP($num)
# @brief
# Seperates a number into it's base 10 floating point exponent & mantisa.
# @details
# written by David Pottage of Tao Group.
#
# @param $num Floating point number
# @return ( exponent, mantissa)
sub _sepFP
{
    my $self = shift;
    my ($num) = @_;
    return ( 0, 0 ) if $num == 0;

    my $sign = ( $num > 0 ) ? 1 : -1;
    $num *= $sign;

    my $exponent = int( log($num) / log(10) );
    my $mantisa = $sign * ( $num / ( 10**$exponent ) );

    return ( $exponent, $mantisa );
}

## @fn private array _find_y_range()
# Find minimum and maximum value of y data sets.
#
# @return ( min, max, flag_all_integers )
sub _find_y_range
{
    my $self = shift;
    my $data = $self->{'dataref'};

    my $max               = undef;
    my $min               = undef;
    my $flag_all_integers = 1;       # assume true

    for my $dataset ( @$data[ 1 .. $#$data ] )
    {
        for my $datum (@$dataset)
        {
            if ( defined $datum )
            {

                #croak "Missing data (dataset)";
                if ($flag_all_integers)
                {

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

    # 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], $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
{
    my $self = shift;
    my $side = shift;
    my $font = $self->{'label_font'};
    my ( $label, $h, $w, $x, $y, $color );

    # get the label
    if ( $side eq 'left' )
    {
        $label = $self->{'y_label'};
        $color = $self->_color_role_to_index('y_label');
    }
    elsif ( $side eq 'right' )
    {
        $label = $self->{'y_label2'};
        $color = $self->_color_role_to_index('y_label2');
    }

    # 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
    if ( $side eq 'left' )
    {
        $x = $self->{'curr_x_min'} + $self->{'text_space'};
    }
    elsif ( $side eq 'right' )
    {
        $x = $self->{'curr_x_max'} - $self->{'text_space'} - $h;
    }
    $y = ( $self->{'curr_y_max'} - $self->{'curr_y_min'} ) / 2 + $self->{'curr_y_min'} + ( length($label) * $w ) / 2;

    # write it
    $self->{'gd_obj'}->stringUp( $font, $x, $y, $label, $color );

    # mark the space written to as used
    if ( $side eq 'left' )
    {
        $self->{'curr_x_min'} += $h + 2 * $self->{'text_space'};
    }
    elsif ( $side eq 'right' )
    {
        $self->{'curr_x_max'} -= $h + 2 * $self->{'text_space'};
    }

    # now return
    return 1;
}

## @fn private int _draw_ticks()
# draw the ticks and tick labels
# @return status
sub _draw_ticks
{
    my $self = shift;

    #if the user wants an xy_plot, calculate the x-ticks too
    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->_draw_x_number_ticks;
    }
    else
    {    # draw the x ticks with strings
        $self->_draw_x_ticks;
    }

    # now the y ticks
    $self->_draw_y_ticks( $self->{'y_axes'} );

    # then return
    return 1;
}

## @fn private int _draw_x_number_ticks()
# draw the ticks and tick labels
# @return status
sub _draw_x_number_ticks
{
    my $self      = shift;
    my $data      = $self->{'dataref'};
    my $font      = $self->{'tick_label_font'};
    my $textcolor = $self->_color_role_to_index('text');
    my $misccolor = $self->_color_role_to_index('misc');
    my ( $h, $w, $x1, $y1, $y2, $x2, $delta, $width, $label );
    my @labels = @{ $self->{'x_tick_labels'} };

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

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

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

    #store actual borders, for a possible later repair
    $self->{'temp_x_min'} = $self->{'curr_x_min'};
    $self->{'temp_x_max'} = $self->{'curr_x_max'};
    $self->{'temp_y_max'} = $self->{'curr_y_max'};
    $self->{'temp_y_min'} = $self->{'curr_y_min'};

    #get the right x-value and width
    #The one and only way to get the RIGHT x value and the width
    if ( $self->{'y_axes'} =~ /^right$/i )
    {
        $x1 = $self->{'curr_x_min'};
        $width =
          $self->{'curr_x_max'} -
          $x1 -
          ( $w * $self->{'y_tick_label_length'} ) -
          3 * $self->{'text_space'} -
          $self->{'tick_len'};
    }
    elsif ( $self->{'y_axes'} =~ /^both$/i )
    {
        $x1 = $self->{'curr_x_min'} + ( $w * $self->{'y_tick_label_length'} ) + 3 * $self->{'text_space'} + $self->{'tick_len'};
        $width =
          $self->{'curr_x_max'} -
          $x1 -
          ( $w * $self->{'y_tick_label_length'} ) -
          ( 3 * $self->{'text_space'} ) -
          $self->{'tick_len'};
    }
    else
    {
        $x1 = $self->{'curr_x_min'} + ( $w * $self->{'y_tick_label_length'} ) + 3 * $self->{'text_space'} + $self->{'tick_len'};
        $width = $self->{'curr_x_max'} - $x1;
    }

    #get the delta value
    $delta = $width / ( $self->{'x_number_ticks'} - 1 );

    #draw the labels
    $y2 = $y1;

    if ( $self->{'x_ticks'} =~ /^normal/i )
    {    #just normal ticks
            #get the point for updating later
        $y1 = $self->{'curr_y_max'} - 2 * $self->{'text_space'} - $h - $self->{'tick_len'};

        #get the start point
        $y2 = $y1 + $self->{'tick_len'} + $self->{'text_space'};

        if ( $self->{'xlabels'} )
        {
            unless ( $self->{'xrange'} )
            {
                croak "Base.pm: xrange must be specified with xlabels!\n";
            }
            my $xmin   = $self->{'xrange'}[0];
            my $xmax   = $self->{'xrange'}[1];
            my @labels = @{ $self->{'xlabels'}[0] };
            my @vals   = @{ $self->{'xlabels'}[1] };
            my $delta  = $width / ( $xmax - $xmin );

            for ( 0 .. $#labels )
            {
                my $label = $labels[$_];
                my $val   = $vals[$_];
                $x2 = $x1 + ( $delta * ( $val - $xmin ) ) - ( 0.5 * $w * length($label) );
                $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );

                #print "write x-label '".$label."' at ($x2,$y2)\n";
            }
        }
        else
        {
            my $last_x = 'undefined';
            for ( 0 .. $#labels )
            {
                $label = $self->{f_x_tick}->( $self->{'x_tick_labels'}[$_] );
                $x2 = $x1 + ( $delta * $_ ) - ( 0.5 * $w * length($label) );
                if (   $last_x eq 'undefined'
                    or $last_x < $x2 )
                {
                    $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );
                    $last_x = $x2 + ( $w * length($label) );
                }

                #print "last_x = $last_x, write string '".$label."' at ($x2,$y2) to '$_'\n";
            }
        }
    }

    elsif ( $self->{'x_ticks'} =~ /^staggered/i )
    {    #staggered ticks
            #get the point for updating later
        $y1 = $self->{'curr_y_max'} - 3 * $self->{'text_space'} - 2 * $h - $self->{'tick_len'};

        if ( $self->{'xlabels'} )
        {
            unless ( $self->{'xrange'} )
            {
                croak "Base.pm: xrange must be specified with xlabels!\n";
            }
            my $xmin   = $self->{'xrange'}[0];
            my $xmax   = $self->{'xrange'}[1];
            my @labels = @{ $self->{'xlabels'}[0] };
            my @vals   = @{ $self->{'xlabels'}[1] };
            my $delta  = $width / ( $xmax - $xmin );

            for ( 0 .. $#labels )
            {
                my $label = $labels[$_];
                my $val   = $vals[$_];
                $x2 = $x1 + ( $delta * ( $val - $xmin ) ) - ( 0.5 * $w * length($label) );
                unless ( $_ % 2 )
                {
                    $y2 = $y1 + $self->{'text_space'} + $self->{'tick_len'};
                }
                else
                {
                    $y2 = $y1 + $h + 2 * $self->{'text_space'} + $self->{'tick_len'};
                }
                $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );

                #print "write x-label '".$label."' at ($x2,$y2)\n";
            }
        }
        else
        {
            for ( 0 .. $#labels )
            {
                $label = $self->{f_x_tick}->( $self->{'x_tick_labels'}[$_] );
                $x2 = $x1 + ( $delta * $_ ) - ( 0.5 * $w * length($label) );
                unless ( $_ % 2 )
                {
                    $y2 = $y1 + $self->{'text_space'} + $self->{'tick_len'};
                }
                else
                {
                    $y2 = $y1 + $h + 2 * $self->{'text_space'} + $self->{'tick_len'};
                }
                $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );
            }
        }
    }

    elsif ( $self->{'x_ticks'} =~ /^vertical/i )
    {    #vertical ticks
            #get the point for updating later
        $y1 = $self->{'curr_y_max'} - 2 * $self->{'text_space'} - $w * $self->{'x_tick_label_length'} - $self->{'tick_len'};

        if ( $self->{'xlabels'} )
        {
            unless ( $self->{'xrange'} )
            {
                croak "Base.pm: xrange must be specified with xlabels!\n";
            }
            my $xmin   = $self->{'xrange'}[0];
            my $xmax   = $self->{'xrange'}[1];
            my @labels = @{ $self->{'xlabels'}[0] };
            my @vals   = @{ $self->{'xlabels'}[1] };
            my $delta  = $width / ( $xmax - $xmin );

            for ( 0 .. $#labels )
            {
                my $label = $labels[$_];
                my $val   = $vals[$_];
                $y2 = $y1 + $self->{'tick_len'} + $w * length($label) + $self->{'text_space'};
                $x2 = $x1 + ( $delta * ( $val - $xmin ) ) - ( $h / 2 );
                $self->{'gd_obj'}->stringUp( $font, $x2, $y2, $label, $textcolor );

                #print "write x-label '".$label."' at ($x2,$y2)\n";
            }
        }
        else
        {

            for ( 0 .. $#labels )
            {
                $label = $self->{f_x_tick}->( $self->{'x_tick_labels'}[$_] );

                #get the start point
                $y2 = $y1 + $self->{'tick_len'} + $w * length($label) + $self->{'text_space'};
                $x2 = $x1 + ( $delta * $_ ) - ( $h / 2 );
                $self->{'gd_obj'}->stringUp( $font, $x2, $y2, $label, $textcolor );
            }
        }
    }

    else
    {
        croak "I don't understand the type of x-ticks you specified\n"
          . "x-ticks must be one of 'normal', 'staggered' or 'vertical' but not of '"
          . $self->{'x_ticks'} . "'.";
    }

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

    #draw the ticks
    $y1 = $self->{'curr_y_max'};
    $y2 = $self->{'curr_y_max'} + $self->{'tick_len'};

    #draw grid lines
    if ( $self->{'xlabels'} )
    {
        unless ( $self->{'xrange'} )
        {
            croak "Base.pm: xrange must be specified with xlabels!\n";
        }
        my $xmin  = $self->{'xrange'}[0];
        my $xmax  = $self->{'xrange'}[1];
        my @vals  = @{ $self->{'xlabels'}[1] };
        my $delta = $width / ( $xmax - $xmin );

        for ( 0 .. $#vals )
        {
            my $val = $vals[$_];
            $x2 = ($x1) + ( $delta * ( $val - $xmin ) );
            $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
            if (   $self->true( $self->{'grid_lines'} )
                or $self->true( $self->{'x_grid_lines'} ) )
            {
                $self->{'grid_data'}->{'x'}->[$_] = $x2;
            }
        }
    }
    else
    {
        for ( 0 .. $#labels )
        {
            $x2 = $x1 + ( $delta * $_ );
            $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
            if (   ( $self->true( $self->{'grid_lines'} ) )
                or ( $self->true( $self->{'x_grid_lines'} ) ) )
            {
                $self->{'grid_data'}->{'x'}->[$_] = $x2;
            }
        }
    }
    return 1;
}

## @fn private int _draw_x_ticks()
# draw the x-ticks and their labels
# @return status
sub _draw_x_ticks
{
    my $self      = shift;
    my $data      = $self->{'dataref'};
    my $font      = $self->{'tick_label_font'};
    my $textcolor = $self->_color_role_to_index('text');
    my $misccolor = $self->_color_role_to_index('misc');
    my $label;
    my ( $h,     $w );
    my ( $x1,    $x2, $y1, $y2 );
    my ( $width, $delta );
    my ($stag);

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

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

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

    # maybe, we need the actual x and y values later for drawing the x-ticks again
    # in the draw function in the lines modul. So copy them.
    $self->{'temp_x_min'} = $self->{'curr_x_min'};
    $self->{'temp_x_max'} = $self->{'curr_x_max'};
    $self->{'temp_y_min'} = $self->{'curr_y_min'};
    $self->{'temp_y_max'} = $self->{'curr_y_max'};

    # allow for the amount of space the y-ticks will push the
    # axes over to the right
    ## _draw_y_ticks allows 3 * text_space, not 1 * ;  this caused mismatch between
    ## the ticks (and grid lines) and the data.
    #   $x1 = $self->{'curr_x_min'} + ($w * $self->{'y_tick_label_length'})
    #          + $self->{'text_space'} + $self->{'tick_len'};
    ## And, what about the right-tick space??  Only affects Composite, I guess....

    #The one and only way to get the RIGHT x value and the width
    if ( $self->{'y_axes'} =~ /^right$/i )
    {
        $x1 = $self->{'curr_x_min'};
        $width =
          $self->{'curr_x_max'} -
          $x1 -
          ( $w * $self->{'y_tick_label_length'} ) -
          3 * $self->{'text_space'} -
          $self->{'tick_len'};
    }
    elsif ( $self->{'y_axes'} =~ /^both$/i )
    {
        $x1 = $self->{'curr_x_min'} + ( $w * $self->{'y_tick_label_length'} ) + 3 * $self->{'text_space'} + $self->{'tick_len'};
        $width =
          $self->{'curr_x_max'} -
          $x1 -
          ( $w * $self->{'y_tick_label_length'} ) -
          3 * $self->{'text_space'} -
          $self->{'tick_len'};
    }
    else
    {
        $x1 = $self->{'curr_x_min'} + ( $w * $self->{'y_tick_label_length'} ) + 3 * $self->{'text_space'} + $self->{'tick_len'};
        $width = $self->{'curr_x_max'} - $x1;
    }

    #the same for the y value, but not so tricky
    $y1 = $self->{'curr_y_max'} - $h - $self->{'text_space'};

    # get the delta value, figure out how to draw the labels
    $delta = $width / ( $self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1 );
    if ( !defined( $self->{'skip_x_ticks'} ) )
    {
        $self->{'skip_x_ticks'} = 1;
    }
    elsif ( $self->{'skip_x_ticks'} == 0 )
    {
        $self->{'skip_x_ticks'} = 1;
    }
    if ( $delta <= ( $self->{'x_tick_label_length'} * $w ) / $self->{'skip_x_ticks'} )
    {
        if ( $self->{'x_ticks'} =~ /^normal$/i )
        {
            $self->{'x_ticks'} = 'staggered';
        }
    }

    # now draw the labels
    if ( $self->{'x_ticks'} =~ /^normal$/i )
    {    # normal ticks
        if ( $self->{'skip_x_ticks'} > 1 )
        {    # draw only every nth tick and label
            for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_x_ticks'} ) )
            {
                if ( defined( $data->[0][ $_ * $self->{'skip_x_ticks'} ] ) )
                {
                    $label = $self->{f_x_tick}->( $data->[0][ $_ * $self->{'skip_x_ticks'} ] );
                    $x2 = $x1 + ( $delta / 2 ) + ( $delta * ( $_ * $self->{'skip_x_ticks'} ) ) - ( $w * length($label) ) / 2;
                    $self->{'gd_obj'}->string( $font, $x2, $y1, $label, $textcolor );
                }
            }
        }
        elsif ( $self->{'custom_x_ticks'} )
        {    # draw only the ticks they wanted
            for ( @{ $self->{'custom_x_ticks'} } )
            {
                if ( defined($_) )
                {
                    $label = $self->{f_x_tick}->( $data->[0][$_] );
                    $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ ) - ( $w * length($label) ) / 2;
                    $self->{'gd_obj'}->string( $font, $x2, $y1, $label, $textcolor );
                }
            }
        }
        else
        {
            for ( 0 .. $self->{'num_datapoints'} - 1 )
            {
                if ( defined($_) )
                {
                    $label = $self->{f_x_tick}->( $data->[0][$_] );
                    $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ ) - ( $w * length($label) ) / 2;
                    $self->{'gd_obj'}->string( $font, $x2, $y1, $label, $textcolor );
                }
            }
        }
    }

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

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

    # now plot the ticks
    $y1 = $self->{'curr_y_max'};
    $y2 = $self->{'curr_y_max'} - $self->{'tick_len'};
    if ( $self->{'skip_x_ticks'} > 1 )
    {
        for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_x_ticks'} ) )
        {
            $x2 = $x1 + ( $delta / 2 ) + ( $delta * ( $_ * $self->{'skip_x_ticks'} ) );
            $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
            if (   $self->true( $self->{'grid_lines'} )
                or $self->true( $self->{'x_grid_lines'} ) )
            {
                $self->{'grid_data'}->{'x'}->[$_] = $x2;
            }
        }
    }
    elsif ( $self->{'custom_x_ticks'} )
    {
        for ( @{ $self->{'custom_x_ticks'} } )
        {
            $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ );
            $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
            if (   $self->true( $self->{'grid_lines'} )
                or $self->true( $self->{'x_grid_lines'} ) )
            {
                $self->{'grid_data'}->{'x'}->[$_] = $x2;
            }
        }
    }
    else
    {
        for ( 0 .. $self->{'num_datapoints'} - 1 )
        {
            $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ );
            $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
            if (   $self->true( $self->{'grid_lines'} )
                or $self->true( $self->{'x_grid_lines'} ) )
            {
                $self->{'grid_data'}->{'x'}->[$_] = $x2;
            }
        }
    }

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

## @fn private int _draw_y_ticks()
#  draw the y-ticks and their labels
# @return status
sub _draw_y_ticks
{
    my $self      = shift;
    my $side      = shift || 'left';
    my $data      = $self->{'dataref'};
    my $font      = $self->{'tick_label_font'};
    my $textcolor = $self->_color_role_to_index('text');
    my $misccolor = $self->_color_role_to_index('misc');
    my @labels    = @{ $self->{'y_tick_labels'} };
    my ( $w, $h );
    my ( $x1, $x2, $y1, $y2 );
    my ( $height, $delta, $label );
    my ( $s, $f );

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

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

    # find out how big the font is
    ( $w, $h ) = ( $font->width, $font->height );

    # figure out which ticks not to draw
    if ( $self->{'min_val'} >= 0 )
    {
        $s = 1;
        $f = $#labels;
    }
    elsif ( $self->{'max_val'} <= 0 )
    {
        $s = 0;
        $f = $#labels;    # -1 entfernt
    }
    else
    {
        $s = 0;
        $f = $#labels;
    }

    # now draw them
    if ( $side eq 'right' )
    {                     # put 'em on the right side of the chart
                          # get the base x-y values, and the delta value
        $x1 =
          $self->{'curr_x_max'} - $self->{'tick_len'} - ( 3 * $self->{'text_space'} ) - ( $w * $self->{'y_tick_label_length'} );
        $y1     = $self->{'curr_y_max'};
        $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
        $self->{'y_ticks'} = 2 if $self->{'y_ticks'} < 2;
        $delta = $height / ( $self->{'y_ticks'} - 1 );

        # update the curr_x_max value
        $self->{'curr_x_max'} = $x1;

        # now draw the ticks
        $x2 = $x1 + $self->{'tick_len'};
        for ( $s .. $f )
        {
            $y2 = $y1 - ( $delta * $_ );
            $self->{'gd_obj'}->line( $x1, $y2, $x2, $y2, $misccolor );
            if (   $self->true( $self->{'grid_lines'} )
                or $self->true( $self->{'y2_grid_lines'} ) )
            {
                $self->{'grid_data'}->{'y2'}->[$_] = $y2;
            }
        }

        # update the current x-min value
        $x1 += $self->{'tick_len'} + ( 2 * $self->{'text_space'} );
        $y1 -= $h / 2;

        # now draw the labels
        for ( 0 .. $#labels )
        {
            $y2 = $y1 - ( $delta * $_ );
            $self->{'gd_obj'}->string( $font, $x1, $y2, $self->{'y_tick_labels'}[$_], $textcolor );
        }
    }
    elsif ( $side eq 'both' )
    {    # put the ticks on the both sides
        ## left side first

        # get the base x-y values
        $x1 = $self->{'curr_x_min'} + $self->{'text_space'};
        $y1 = $self->{'curr_y_max'} - $h / 2;

        # now draw the labels
        $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
        $delta = $height / ( $self->{'y_ticks'} - 1 );
        for ( 0 .. $#labels )
        {
            $label = $self->{'y_tick_labels'}[$_];
            $y2    = $y1 - ( $delta * $_ );
            $x2    = $x1 + ( $w * $self->{'y_tick_label_length'} ) - ( $w * length($label) );
            $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );
        }

        # and update the current x-min value
        $self->{'curr_x_min'} += ( 3 * $self->{'text_space'} ) + ( $w * $self->{'y_tick_label_length'} );

        # now draw the ticks (skipping the one at zero);
        $x1 = $self->{'curr_x_min'};
        $x2 = $self->{'curr_x_min'} + $self->{'tick_len'};
        $y1 += $h / 2;
        for ( $s .. $f )
        {
            $y2 = $y1 - ( $delta * $_ );
            $self->{'gd_obj'}->line( $x1, $y2, $x2, $y2, $misccolor );
            if (   $self->true( $self->{grid_lines} )
                or $self->true( $self->{'y_grid_lines'} ) )
            {
                $self->{'grid_data'}->{'y'}->[$_] = $y2;
            }
        }

        # update the current x-min value
        $self->{'curr_x_min'} += $self->{'tick_len'};

        ## now the right side
        # get the base x-y values, and the delta value
        $x1 =
          $self->{'curr_x_max'} - $self->{'tick_len'} - ( 3 * $self->{'text_space'} ) - ( $w * $self->{'y_tick_label_length'} );
        $y1     = $self->{'curr_y_max'};
        $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
        $delta  = $height / ( $self->{'y_ticks'} - 1 );

        # update the curr_x_max value
        $self->{'curr_x_max'} = $x1;

        # now draw the ticks (skipping the one at zero);
        $x2 = $x1 + $self->{'tick_len'};

        for ( $s .. $f )
        {
            $y2 = $y1 - ( $delta * $_ );
            $self->{'gd_obj'}->line( $x1, $y2, $x2, $y2, $misccolor );    # draw tick_line
            if (   $self->true( $self->{grid_lines} )
                or $self->true( $self->{'y2_grid_lines'} ) )
            {
                $self->{'grid_data'}->{'y2'}->[$_] = $y2;
            }
        }

        # update the current x-min value
        $x1 += $self->{'tick_len'} + ( 2 * $self->{'text_space'} );
        $y1 -= $h / 2;

        # now draw the labels
        for ( 0 .. $#labels )
        {
            $y2 = $y1 - ( $delta * $_ );
            $self->{'gd_obj'}->string( $font, $x1, $y2, $self->{'y_tick_labels'}[$_], $textcolor );
        }
    }
    else
    {    # just the left side
            # get the base x-y values
        $x1 = $self->{'curr_x_min'} + $self->{'text_space'};
        $y1 = $self->{'curr_y_max'} - $h / 2;

        # now draw the labels
        $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
        $self->{'y_ticks'} = 2 if $self->{'y_ticks'} < 2;
        $delta = $height / ( $self->{'y_ticks'} - 1 );
        for ( 0 .. $#labels )
        {
            $label = $self->{'y_tick_labels'}[$_];
            $y2    = $y1 - ( $delta * $_ );
            $x2    = $x1 + ( $w * $self->{'y_tick_label_length'} ) - ( $w * length($label) );
            $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );
        }

        # and update the current x-min value
        $self->{'curr_x_min'} += ( 3 * $self->{'text_space'} ) + ( $w * $self->{'y_tick_label_length'} );

        # now draw the ticks
        $x1 = $self->{'curr_x_min'};
        $x2 = $self->{'curr_x_min'} + $self->{'tick_len'};
        $y1 += $h / 2;
        for ( $s .. $f )
        {
            $y2 = $y1 - ( $delta * $_ );
            $self->{'gd_obj'}->line( $x1, $y2, $x2, $y2, $misccolor );
            if (   $self->true( $self->{'grid_lines'} )
                or $self->true( $self->{'y_grid_lines'} ) )
            {
                $self->{'grid_data'}->{'y'}->[$_] = $y2;
            }
        }

        # update the current x-min value
        $self->{'curr_x_min'} += $self->{'tick_len'};
    }

    # and return
    return 1;
}

## @fn private int _grey_background()
#  put a grey background on the plot of the data itself
# @return status
sub _grey_background
{
    my $self = shift;

    # draw it
    $self->{'gd_obj'}
      ->filledRectangle( $self->{'curr_x_min'}, $self->{'curr_y_min'}, $self->{'curr_x_max'}, $self->{'curr_y_max'},
        $self->_color_role_to_index('grey_background') );

    # now return
    return 1;
}

## @fn private int _draw_grid_lines()
# draw grid_lines
# @return status
sub _draw_grid_lines
{
    my $self = shift;
    $self->_draw_x_grid_lines();
    $self->_draw_y_grid_lines();
    $self->_draw_y2_grid_lines();
    return 1;
}

## @fn private int _draw_x_grid_lines()
# draw grid_lines for x
# @return status
sub _draw_x_grid_lines
{
    my $self      = shift;
    my $grid_role = shift || 'x_grid_lines';
    my $gridcolor = $self->_color_role_to_index($grid_role);
    my ( $x, $y, $i );

    foreach $x ( @{ $self->{grid_data}->{'x'} } )
    {
        if ( defined $x )
        {
            $self->{gd_obj}->line( ( $x, $self->{'curr_y_min'} + 1 ), $x, ( $self->{'curr_y_max'} - 1 ), $gridcolor );
        }
    }
    return 1;
}

## @fn private int _draw_y_grid_lines()
# draw grid_lines for y
# @return status
sub _draw_y_grid_lines
{
    my $self      = shift;
    my $grid_role = shift || 'y_grid_lines';
    my $gridcolor = $self->_color_role_to_index($grid_role);
    my ( $x, $y, $i );

    #Look if I'm an HorizontalBars object
    if ( $self->isa('Chart::HorizontalBars') )
    {
        for ( $i = 0 ; $i < ( $#{ $self->{grid_data}->{'y'} } ) + 1 ; $i++ )
        {
            $y = $self->{grid_data}->{'y'}->[$i];
            $self->{gd_obj}->line( ( $self->{'curr_x_min'} + 1 ), $y, ( $self->{'curr_x_max'} - 1 ), $y, $gridcolor );
        }
    }
    else
    {

        # loop for y values is a little different. This is to discard the first
        # and last values we were given - the top/bottom of the chart area.
        for ( $i = 1 ; $i < ( $#{ $self->{grid_data}->{'y'} } ) + 1 ; $i++ )
        {    ###
            $y = $self->{grid_data}->{'y'}->[$i];
            $self->{gd_obj}->line( ( $self->{'curr_x_min'} + 1 ), $y, ( $self->{'curr_x_max'} - 1 ), $y, $gridcolor );
        }
    }
    return 1;
}

## @fn private int _draw_y2_grid_lines()
# draw grid_lines for y
# @return status
sub _draw_y2_grid_lines
{
    my $self      = shift;
    my $grid_role = shift || 'y2_grid_lines';
    my $gridcolor = $self->_color_role_to_index($grid_role);
    my ( $x, $y, $i );

    #Look if I'm an HorizontalBars object
    if ( $self->isa('Chart::HorizontalBars') )
    {
        for ( $i = 0 ; $i < ( $#{ $self->{grid_data}->{'y'} } ) + 1 ; $i++ )
        {
            $y = $self->{grid_data}->{'y'}->[$i];
            $self->{gd_obj}->line( ( $self->{'curr_x_min'} + 1 ), $y, ( $self->{'curr_x_max'} - 1 ), $y, $gridcolor );
        }
    }
    else
    {

        # loop for y2 values is a little different. This is to discard the first
        # and last values we were given - the top/bottom of the chart area.
        for ( $i = 1 ; $i < $#{ $self->{grid_data}->{'y2'} } ; $i++ )
        {
            $y = $self->{grid_data}->{'y2'}->[$i];
            $self->{gd_obj}->line( ( $self->{'curr_x_min'} + 1 ), $y, ( $self->{'curr_x_max'} - 1 ), $y, $gridcolor );
        }
    }
    return 1;
}

## @fn private int _prepare_brush($color,$type,$role)
# prepare brush
#
# @details
#  set the gdBrush object to tick GD into drawing fat lines & points
#  of interesting shapes
#  Needed by "Lines", "Points" and "LinesPoints"
#  All hacked up by Richard Dice <rdice@pobox.com> Sunday 16 May 1999
#
# @param $color
# @param $type    'line','point'
# @param $role
#
# @return status
sub _prepare_brush
{
    my $self  = shift;
    my $color = shift;
    my $type  = shift;
    my $role  = shift || 'default';

    my $brushStyle = $self->{'brushStyle'};
    if ( defined $role )
    {
        my (@brushStyles) = $self->_brushStyles_of_roles($role);
        $brushStyle = $brushStyles[0];
    }

    #print STDERR "role=$role\n";

    # decide what $type should be in the event that a param isn't
    # passed -- this is necessary to preserve backward compatibility
    # with apps that use this module prior to putting _prepare_brush
    # in with Base.pm
    if ( !defined($type) ) { $type = 'point'; }

    if (   ( !length($type) )
        || ( !grep { $type eq $_ } ( 'line', 'point' ) ) )
    {
        $brushStyle = $self->{'brushStyle'};
        $type       = 'line' if ref $self eq 'Chart::Lines';
        $type       = 'point' if ref $self eq 'Chart::Points';
    }

    my ( $radius, @rgb, $brush, $white, $newcolor );

    # get the rgb values for the desired color
    @rgb = $self->{'gd_obj'}->rgb($color);

    # get the appropriate brush size
    if ( $type eq 'line' )
    {
        $radius = $self->{'brush_size'} / 2;
    }
    elsif ( $type eq 'point' )
    {
        $radius = $self->{'pt_size'} / 2;
    }

    # create the new image
    $brush = GD::Image->new( $radius * 2, $radius * 2 );

    # get the colors, make the background transparent
    $white = $brush->colorAllocate( 255, 255, 255 );
    $newcolor = $brush->colorAllocate(@rgb);
    $brush->transparent($white);

    # draw the circle
    if ( $type eq 'line' )
    {
        $brush->arc( $radius - 1, $radius - 1, $radius, $radius, 0, 360, $newcolor );
        $brush->fill( $radius - 1, $radius - 1, $newcolor );

        # RLD
        #
        # Does $brush->fill really have to be here?  Dunno... this
        # seems to be a relic from earlier code
        #
        # Note that 'line's don't benefit from a $brushStyle... yet.
        # It shouldn't be too tough to hack this in by taking advantage
        # of GD's gdStyled facility

    }

    if ( $type eq 'point' )
    {
        $brushStyle = $self->{'brushStyle'}
          unless grep { $brushStyle eq $_ } (
            'FilledCircle',  'circle',             'donut',  'OpenCircle',
            'triangle',      'upsidedownTriangle', 'square', 'hollowSquare',
            'OpenRectangle', 'fatPlus',            'Star',   'OpenStar',
            'FilledDiamond', 'OpenDiamond'
          );

        my ( $xc, $yc ) = ( $radius, $radius );

        if ( grep { $brushStyle eq $_ } ( 'default', 'circle', 'donut', 'OpenCircle', 'FilledCircle' ) )
        {
            $brush->arc( $xc, $yc, $radius, $radius, 0, 360, $newcolor );
            $brush->fill( $xc, $yc, $newcolor );

            # draw a white (and therefore transparent) circle in the middle
            # of the existing circle to make the "donut", if appropriate

            if ( $brushStyle eq 'donut' || $brushStyle eq 'OpenCircle' )
            {
                $brush->arc( $xc, $yc, int( $radius / 2 ), int( $radius / 2 ), 0, 360, $white );
                $brush->fill( $xc, $yc, $white );
            }
        }

        if ( grep { $brushStyle eq $_ } ( 'triangle', 'upsidedownTriangle' ) )
        {
            my $poly = new GD::Polygon;
            my $sign = ( $brushStyle eq 'triangle' ) ? 1 : (-1);
            my $z    = int( 0.8 * $radius );                       # scaling factor

            # co-ords are chosen to make an equilateral triangle

            $poly->addPt( $xc, $yc - ( $z * $sign ) );
            $poly->addPt( $xc + int( ( sqrt(3) * $z ) / 2 ), $yc + ( int( $z / 2 ) * $sign ) );
            $poly->addPt( $xc - int( ( sqrt(3) * $z ) / 2 ), $yc + ( int( $z / 2 ) * $sign ) );

            $brush->filledPolygon( $poly, $newcolor );
        }

        if ( $brushStyle eq 'fatPlus' )
        {
            my $poly = new GD::Polygon;

            my $z = int( 0.3 * $radius );

            $poly->addPt( $xc + $z,     $yc + $z );
            $poly->addPt( $xc + 2 * $z, $yc + $z );
            $poly->addPt( $xc + 2 * $z, $yc - $z );

            $poly->addPt( $xc + $z, $yc - $z );
            $poly->addPt( $xc + $z, $yc - 2 * $z );
            $poly->addPt( $xc - $z, $yc - 2 * $z );

            $poly->addPt( $xc - $z,     $yc - $z );
            $poly->addPt( $xc - 2 * $z, $yc - $z );
            $poly->addPt( $xc - 2 * $z, $yc + $z );

            $poly->addPt( $xc - $z, $yc + $z );
            $poly->addPt( $xc - $z, $yc + 2 * $z );
            $poly->addPt( $xc + $z, $yc + 2 * $z );
            $brush->filledPolygon( $poly, $newcolor );
        }

        if ( $brushStyle eq 'Star' || $brushStyle eq 'OpenStar' )
        {
            my $poly = new GD::Polygon;

            my $z  = int($radius);
            my $sz = int( $z / 3 * 1.75 );    # small z

            my $x1 = int( $xc + $z );
            my $y1 = int($yc);
            my ( $x2, $y2 );

            my $xyRatio = $self->_xyRatio();

            $poly->addPt( $x1, $y1 );

            $x2 = $xc + int( $sz * 0.5 );
            $y2 = $yc - int( $sz * 0.5 );
            $poly->addPt( $x2, $y2 );

            $x2 = $xc;
            $y2 = $yc - $z;
            $poly->addPt( $x2, $y2 );

            $x2 = $xc - int( $sz * 0.5 );
            $y2 = $yc - int( $sz * 0.5 );
            $poly->addPt( $x2, $y2 );

            $x2 = $xc - $z;
            $y2 = $yc;
            $poly->addPt( $x2, $y2 );

            $x2 = $xc - int( $sz * 0.5 );
            $y2 = $yc + int( $sz * 0.5 );
            $poly->addPt( $x2, $y2 );

            $x2 = $xc;
            $y2 = $yc + $z;
            $poly->addPt( $x2, $y2 );

            $x2 = $xc + int( $sz * 0.5 );
            $y2 = $yc + int( $sz * 0.5 );
            $poly->addPt( $x2, $y2 );
            if ( $brushStyle eq 'OpenStar' )
            {
                $brush->polygon( $poly, $newcolor );
            }
            else
            {
                $brush->filledPolygon( $poly, $newcolor );
            }
        }

        if ( grep { $brushStyle eq $_ } ( 'square', 'hollowSquare', 'OpenRectangle' ) )
        {
            my $z = int( 0.5 * $radius );

            $brush->filledRectangle( $xc - $z, $yc - $z, $xc + $z, $yc + $z, $newcolor );

            if ( $brushStyle eq 'hollowSquare' || $brushStyle eq 'OpenRectangle' )
            {
                $z = int( $z / 2 );

                $brush->filledRectangle( $xc - $z, $yc - $z, $xc + $z, $yc + $z, $white );
            }
        }

        if ( grep { $brushStyle eq $_ } ( 'FilledDiamond', 'OpenDiamond' ) )
        {
            my $z = int( 0.75 * $radius );

            $brush->line( $xc + $z, $yc,      $xc,      $yc + $z, $newcolor );
            $brush->line( $xc,      $yc + $z, $xc - $z, $yc,      $newcolor );
            $brush->line( $xc - $z, $yc,      $xc,      $yc - $z, $newcolor );
            $brush->line( $xc,      $yc - $z, $xc + $z, $yc,      $newcolor );

            if ( $brushStyle eq 'FilledDiamond' )
            {

                # and fill it
                $brush->fill( $radius - 1, $radius - 1, $newcolor );
            }
        }

    }

    # set the new image as the main object's brush
    return $brush;
}

## @fn private int _default_f_tick
# default tick conversion function
# This function is pointed to be $self->{f_x_tick} resp. $self->{f_y_tick}
# if the user does not provide another function
#
# @return status
sub _default_f_tick
{
    my $label = shift;

    return $label;
}

## @fn private float _xyRatio
# Get ratio width_x/width_y
#
# @return ratio width_x and width_y
sub _xyRatio
{
    my $self = shift;

    my $width_x = $self->{'curr_x_max'} - $self->{'curr_x_min'} + 1;
    my $width_y = $self->{'curr_y_max'} - $self->{'curr_y_min'} + 1;

    my $ratio = $width_x / $width_y;

    return $ratio;
}

## @fn private float _xPixelInReal
# Get witdh of one Pixel in real coordinates in x-direction
#
#
# @return width(interval) of reality in x direction
#
sub _xPixelInReal
{
    my $self = shift;

    my $width_x = $self->{'curr_x_max'} - $self->{'curr_x_min'} + 1;
    my ( $min, $max ) = $self->_find_x_range();
    my $xRealWidth = $max - $min;
    my $ratio      = $xRealWidth / $width_x;

    return $ratio;
}

## @fn private float _yPixelInReal
# Get witdh of one Pixel in real coordinates in y-direction
#
#
# @return width(interval) of reality in y direction
#
sub _yPixelInReal
{
    my $self = shift;

    my $width_y = $self->{'curr_y_max'} - $self->{'curr_y_min'} + 1;
    my ( $min, $max, $flag_all_integers ) = $self->_find_y_range();
    my $yRealWidth = $max - $min;
    my $ratio      = $yRealWidth / $width_y;

    return $ratio;
}

## be a good module and return positive
1;

