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