#==========================================================================
#              Copyright (c) 1999 Dmitry Ovsyanko
#--------------------------------------------------------------------------
#
#   Name:
#       PNGgraph::pie.pm
#
#==========================================================================

package PNGgraph::pie;

use strict qw(vars refs subs);

use PNGgraph;
use PNGgraph::utils qw(:all);
use PNGgraph::colour qw(:colours :lists);

@PNGgraph::pie::ISA = qw( PNGgraph );

my $ANGLE_OFFSET = 90;

my %Defaults = (

    # Set the height of the pie.
    # Because of the dependency of this on runtime information, this
    # is being set in PNGgraph::pie::initialise

    #   pie_height => _round(0.1*${'pngx'}),

    # Do you want a 3D pie?

    '3d'         => 1,

    # The angle at which to start the first data set
    # 0 is at the front/bottom

    start_angle => 0,
);

{
    # PUBLIC methods, documented in pod
    sub plot($) # (\@data)
    {
        my $self = shift;
        my $data = shift;

        $self->check_data($data);
        $self->setup_coords();
        $self->init_graph($self->{graph});
        $self->draw_text($self->{graph});
        $self->draw_pie($self->{graph});
        $self->draw_data($data, $self->{graph});
        $self->draw_labels($data, $self->{graph});

        return $self->{graph}->png;
    }

    sub set_label_font($) # (fontname)
    {
        my $self = shift;

        $self->{lf} = shift;
        $self->set(
            lfw => $self->{lf}->width,
            lfh => $self->{lf}->height,
        );
    }

    sub set_value_font($) # (fontname)
    {
        my $self = shift;

        $self->{vf} = shift;
        $self->set(
            vfw => $self->{vf}->width,
            vfh => $self->{vf}->height,
        );
    }

    # Inherit defaults() from PNGgraph

    # PRIVATE
    # called on construction by new.
    sub initialise()
    {
        my $self = shift;

        $self->SUPER::initialise();

        my $key;
        foreach $key (keys %Defaults)
        {
            $self->set( $key => $Defaults{$key} );
        }

        $self->set( pie_height => _round(0.1 * $self->{pngy}) );

        $self->set_value_font(GD::gdTinyFont);
        $self->set_label_font(GD::gdSmallFont);
    }

    # inherit checkdata from PNGgraph

    # Setup the coordinate system and colours, calculate the
    # relative axis coordinates in respect to the png size.

    sub setup_coords()
    {
        my $s = shift;

        # Make sure we're not reserving space we don't need.
        $s->set(tfh => 0)           unless ( $s->{title} );
        $s->set(lfh => 0)           unless ( $s->{label} );
        $s->set('3d' => 0)          if     ( $s->{pie_height} <= 0 );
        $s->set(pie_height => 0)    unless ( $s->{'3d'} );

        # Calculate the bounding box for the pie, and
        # some width, height, and centre parameters
        $s->{bottom} =
            $s->{pngy} - $s->{pie_height} - $s->{b_margin} -
            ( $s->{lfh} ? $s->{lfh} + $s->{text_space} : 0 );

        $s->{top} =
            $s->{t_margin} + ( $s->{tfh} ? $s->{tfh} + $s->{text_space} : 0 );

        $s->{left} = $s->{l_margin};

        $s->{right} = $s->{pngx} - $s->{r_margin};

        ( $s->{w}, $s->{h} ) =
            ( $s->{right}-$s->{left}, $s->{bottom}-$s->{top} );

        ( $s->{xc}, $s->{yc} ) =
            ( ($s->{right}+$s->{left})/2, ($s->{bottom}+$s->{top})/2 );

        die "Vertical Png size too small"
            if ( ($s->{bottom} - $s->{top}) <= 0 );
        die "Horizontal Png size too small"
            if ( ($s->{right} - $s->{left}) <= 0 );

        # set up the data colour list if it doesn't exist yet.
        $s->set(
            dclrs => [qw( lred lgreen lblue lyellow lpurple cyan lorange )]
        ) unless ( exists $s->{dclrs} );
    }

    # inherit open_graph from PNGgraph

    # Put the text on the canvas.
    sub draw_text($) # (GD::Image)
    {
        my $s = shift;
        my $g = shift;

        if ( $s->{tfh} )
        {
            my $tx = $s->{xc} - length($s->{title}) * $s->{tfw}/2;
            $g->string($s->{tf}, $tx, $s->{t_margin}, $s->{title}, $s->{tci});
        }

        if ( $s->{lfh} )
        {
            my $tx = $s->{xc} - length($s->{label}) * $s->{lfw}/2;
            my $ty = $s->{pngy} - $s->{b_margin} - $s->{lfh};
            $g->string($s->{lf}, $tx, $ty, $s->{label}, $s->{lci});
        }
    }

    # draw the pie, without the data slices

    sub draw_pie($) # (GD::Image)
    {
        my $s = shift;
        my $g = shift;

        my $left = $s->{xc} - $s->{w}/2;

        $g->arc(
            $s->{xc}, $s->{yc},
            $s->{w}, $s->{h},
            0, 360, $s->{acci}
        );

        $g->arc(
            $s->{xc}, $s->{yc} + $s->{pie_height},
            $s->{w}, $s->{h},
            0, 180, $s->{acci}
        ) if ( $s->{'3d'} );

        $g->line(
            $left, $s->{yc},
            $left, $s->{yc} + $s->{pie_height},
            $s->{acci}
        );

        $g->line(
            $left + $s->{w}, $s->{yc},
            $left + $s->{w}, $s->{yc} + $s->{pie_height},
            $s->{acci}
        );
    }

    # Draw the data slices

    sub draw_data($$) # (\@data, GD::Image)
    {
        my $s = shift;
        my $data = shift;
        my $g = shift;

        my $total = 0;
        my $j = 1;                      # for now, only one pie..

        my $i;
        for $i ( 0 .. $s->{numpoints} )
        {
            $total += $data->[$j][$i];
        }
        die "no Total" unless $total;

        my $ac = $s->{acci};            # Accent colour
        my $pb = $s->{start_angle};

        my $val = 0;

        for $i ( 0..$s->{numpoints} )
        {
            # Set the data colour
            my $dc = $s->set_clr_uniq( $g, $s->pick_data_clr($i) );

            # Set the angles of the pie slice
            my $pa = $pb;
            $pb += 360 * $data->[1][$i]/$total;

            # Calculate the end points of the lines at the boundaries of
            # the pie slice
            my ($xe, $ye) =
                cartesian(
                    $s->{w}/2, $pa,
                    $s->{xc}, $s->{yc}, $s->{h}/$s->{w}
                );

            $g->line($s->{xc}, $s->{yc}, $xe, $ye, $ac);

            # Draw the lines on the front of the pie
            $g->line($xe, $ye, $xe, $ye + $s->{pie_height}, $ac)
                if ( in_front($pa) && $s->{'3d'} );

            # Make an estimate of a point in the middle of the pie slice
            # And fill it
            ($xe, $ye) =
                cartesian(
                    3 * $s->{w}/8, ($pa+$pb)/2,
                    $s->{xc}, $s->{yc}, $s->{h}/$s->{w}
                );

            $g->fillToBorder($xe, $ye, $ac, $dc);

            # $s->put_label($g, $xe, $ye, $data->[0][$i]);

            # If it's 3d, colour the front ones as well
            if ( $s->{'3d'} )
            {
                my ($xe, $ye) = $s->_get_pie_front_coords($pa, $pb);

                $g->fillToBorder($xe, $ye + $s->{pie_height}/2, $ac, $dc)
                    if (defined($xe) && defined($ye));
            }
        }
    } #PNGgraph::pie::draw_data


    # Draw the data labels

sub draw_labels($$) # (\@data, GD::Image)
    {
        my $s = shift;
        my $data = shift;
        my $g = shift;

        my $total = 0;
        my $j = 1;                      # for now, only one pie..

        my $i;
        for $i ( 0 .. $s->{numpoints} )
        {
            $total += $data->[$j][$i];
        }
        die "no Total" unless $total;

        my $ac = $s->{acci};            # Accent colour
        my $pb = $s->{start_angle};

        my $val = 0;

        for $i ( 0..$s->{numpoints} )
        {
            # Set the data colour
            my $dc = $s->set_clr_uniq( $g, $s->pick_data_clr($i) );

            # Set the angles of the pie slice
            my $pa = $pb;
            $pb += 360 * $data->[1][$i]/$total;

            # Calculate the label coords
            my ($xe, $ye) =
                cartesian(
                    4 * $s->{w}/11, ($pa+$pb)/2,
                    $s->{xc}, $s->{yc}, $s->{h}/$s->{w}
                );

            $s->put_label($g, $xe, $ye, $data->[0][$i]);

        }
    }; #PNGgraph::pie::draw_data

    sub _get_pie_front_coords($$) # (angle 1, angle 2)
    {
        my $s = shift;
        my $pa = level_angle(shift);
        my $pb = level_angle(shift);

        if (in_front($pa))
        {
            if (in_front($pb))
            {
	       if ($pa>$pb)
		    {
		     $pa = 0 - 1;
		     $pb = 0 - 2;
		    }
                # both in front
            }
            else
            {
                # start in front, end in back
                $pb = $ANGLE_OFFSET;
            }
        }
        else
        {
            if (in_front($pb))
            {
                # start in back, end in front
                $pa = $ANGLE_OFFSET - 180;
            }
            else
            {
                # both in back
                return;
            }
        }

        my ($x, $y) =
            cartesian(
                $s->{w}/2, ($pa+$pb)/2,
                $s->{xc}, $s->{yc}, $s->{h}/$s->{w}
            );

        return ($x, $y);
    }

    # return true if this angle is on the front of the pie

    sub in_front($) # (angle)
    {
        my $a = level_angle( shift );
        ( $a > ($ANGLE_OFFSET - 180) && $a < $ANGLE_OFFSET ) ? 1 : 0;
    }

    # return a value for angle between -180 and 180

    sub level_angle($) # (angle)
    {
        my $a = shift;
        return level_angle($a-360) if ( $a > 180 );
        return level_angle($a+360) if ( $a <= -180 );
        return $a;
    }

    # put the label on the pie

    sub put_label($) # (GD:Image)
    {
        my $s = shift;
        my $g = shift;

        my ($x, $y, $label) = @_;
	
	my $h = $s->{vfw};
	my $w = length($label) * $s->{vfw};

        $x -= $h/2;
        $y -= $w/2;
	my $black = $g -> colorAllocate(0,0,0);
	my $white = $g -> colorAllocate(255,255,255);
	$g->filledRectangle($x-4, $y-4, $x+$w+4, $y+$h+4, $black);
	$g->filledRectangle($x-3, $y-3, $x+$w+3, $y+$h+3, $white);
        $g->string($s->{vf}, $x, $y, $label, $s->{alci});
    }

    # return x, y coordinates from input
    # radius, angle, center x and y and a scaling factor (height/width)
    #
    # $ANGLE_OFFSET is used to define where 0 is meant to be
    sub cartesian($$$$$)
    {
        my ($r, $phi, $xi, $yi, $cr) = @_;
        my $PI=4*atan2(1, 1);

        return (
            $xi + $r * cos($PI * ($phi + $ANGLE_OFFSET)/180),
            $yi + $cr * $r * sin($PI * ($phi + $ANGLE_OFFSET)/180)
        );
    }

    sub pick_data_clr($) # (number)
    {
        my $s = shift;
        return _rgb( $s->{dclrs}[ $_[0] % (1 + $#{$s->{dclrs}}) ] );
    }

} # End of package PNGgraph::pie

1;
