Blob Blame History Raw
#==========================================================================
#			   Copyright (c) 1995-1998 Martien Verbruggen
#--------------------------------------------------------------------------
#
#	Name:
#		GIFgraph::pie.pm
#
# $Id: pie.pm,v 1.1.1.1 2002/02/26 10:16:37 oetiker Exp $
#
#==========================================================================

package GIFgraph::pie;

use strict qw(vars refs subs);

use GIFgraph;
use GIFgraph::legend;
use GIFgraph::utils qw(:all);
use GIFgraph::colour qw(:colours :lists);

@GIFgraph::pie::ISA = qw( GIFgraph::legend GIFgraph );

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 GIFgraph::pie::initialise
 
	#   pie_height => _round(0.1*${'gifx'}),
 
	# 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->init_graph($self->{graph});
		$self->plot_legend($self->{graph});
		$self->setup_coords();
		$self->draw_text($self->{graph});
		$self->draw_pie($self->{graph});
		$self->draw_data($data, $self->{graph});

		return $self->{graph}->gif;
	}
 
	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 GIFgraph
 
	# 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->{gify}) );
 
		$self->set_value_font(GD::gdTinyFont);
		$self->set_label_font(GD::gdSmallFont);
	}

	# inherit checkdata from GIFgraph
 
	# Setup the coordinate system and colours, calculate the
	# relative axis coordinates in respect to the gif 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->{gify} - $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->{gifx} - $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 Gif size too small" 
			if ( ($s->{bottom} - $s->{top}) <= 0 );
		die "Horizontal Gif size too small"
			if ( ($s->{right} - $s->{left}) <= 0 );
	}
 
	# inherit open_graph from GIFgraph
 
	# 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->{gify} - $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. Colours index from 1 not 0.
			my $dc = $s->set_clr( $g, $s->pick_data_clr($i+1) );

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

			# Horrible kludge by AF # $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));
			}
		}

		# More horrible kludge by AF
		$pb = $s->{start_angle};
		for $i ( 0..$s->{numpoints} ) 
		{
			my $pa = $pb;
			$pb += 360*$data->[1][$i]/$total;
			my ($xe, $ye) = 
				cartesian(
					3 * $s->{w}/8, ($pa+$pb)/2,
					$s->{xc}, $s->{yc}, $s->{h}/$s->{w}
				);
			$s->put_label($g, $xe, $ye, $$data[0][$i]);
		}
	} #GIFgraph::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))
			{
				# both in front
				# don't do anything
			}
			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) = @_;

		$x -= length($label) * $s->{vfw}/2;
		$y -= $s->{vfw}/2;
		$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)
		);
	}

} # End of package GIFgraph::pie
 
1;