#========================================================================== # Copyright (c) 1995-1998 Martien Verbruggen # Copyright (c) 2000 Alan Jackson # Based on the pie.pm module by Martien Verbruggen #-------------------------------------------------------------------------- # # Name: # GD::Graph::rose.pm # # #========================================================================== package GD::Graph::rose; $GD::Graph::rose::VERSION = '$Revision: 1.00 $' =~ /\s([\d.]+)/; use strict; use constant PI => 4 * atan2(1,1); use GD; use GD::Graph; use GD::Graph::utils qw(:all); use GD::Graph::colour qw(:colours :lists); use GD::Text::Align; use Carp; @GD::Graph::rose::ISA = qw( GD::Graph ); my $ANGLE_OFFSET = -90; my %Defaults = ( # The angle at which to start the first data set # 0 is at the front/bottom start_angle => 0, # Angle below which a label on a rose slice is suppressed. suppress_angle => 0, # CONTRIB idea ryan # and some public attributes without defaults label => undef, # This misnamed attribute is used for rose marker colours axislabelclr => 'black', ); # PRIVATE sub _has_default { my $self = shift; my $attr = shift || return; exists $Defaults{$attr} || $self->SUPER::_has_default($attr); } sub initialise { my $self = shift; $self->SUPER::initialise(); while (my($key, $val) = each %Defaults) { $self->{$key} = $val } $self->set_value_font(gdTinyFont); $self->set_label_font(gdSmallFont); } # PUBLIC methods, documented in pod sub plot { my $self = shift; my $data = shift; $self->check_data($data) or return; $self->init_graph() or return; $self->setup_text() or return; $self->setup_coords() or return; $self->draw_text() or return; $self->draw_data() or return; return $self->{graph}; } sub set_label_font # (fontname) { my $self = shift; $self->_set_font('gdta_label', @_) or return; $self->{gdta_label}->set_align('bottom', 'center'); } sub set_value_font # (fontname) { my $self = shift; $self->_set_font('gdta_value', @_) or return; $self->{gdta_value}->set_align('center', 'center'); } # Inherit defaults() from GD::Graph # inherit checkdata from GD::Graph # Setup the coordinate system and colours, calculate the # relative axis coordinates in respect to the canvas size. sub setup_coords() { my $self = shift; my $tfh = $self->{title} ? $self->{gdta_title}->get('height') : 0; my $lfh = $self->{label} ? $self->{gdta_label}->get('height') : 0; # Calculate the bounding box for the rose, and # some width, height, and centre parameters $self->{bottom} = $self->{height} - $self->{b_margin} - ( $lfh ? $lfh + $self->{text_space} : 0 ); $self->{top} = $self->{t_margin} + ( $tfh ? $tfh + $self->{text_space} : 0 ); return $self->_set_error('Vertical size too small') if $self->{bottom} - $self->{top} <= 0; $self->{left} = $self->{l_margin}; $self->{right} = $self->{width} - $self->{r_margin}; return $self->_set_error('Horizontal size too small') if $self->{right} - $self->{left} <= 0; $self->{w} = $self->{right} - $self->{left}; $self->{h} = $self->{bottom} - $self->{top}; $self->{xc} = ($self->{right} + $self->{left})/2; $self->{yc} = ($self->{bottom} + $self->{top})/2; return $self; } # inherit open_graph from GD::Graph # Setup the parameters for the text elements sub setup_text { my $self = shift; if ( $self->{title} ) { #print "'$s->{title}' at ($s->{xc},$s->{t_margin})\n"; $self->{gdta_title}->set(colour => $self->{tci}); $self->{gdta_title}->set_text($self->{title}); } if ( $self->{label} ) { $self->{gdta_label}->set(colour => $self->{lci}); $self->{gdta_label}->set_text($self->{label}); } $self->{gdta_value}->set(colour => $self->{alci}); return $self; } # Put the text on the canvas. sub draw_text { my $self = shift; $self->{gdta_title}->draw($self->{xc}, $self->{t_margin}) if $self->{title}; $self->{gdta_label}->draw($self->{xc}, $self->{height} - $self->{b_margin}) if $self->{label}; return $self; } # Draw the data petals sub draw_data { my $self = shift; for (my $j=1;$j<=$#{$self->{_data}};$j++) { my $total = 0; my $max = 0; my @values = $self->{_data}->y_values($j); # for now, only one rose.. for (@values) { $total += $_ ; $max = $max > $_ ? $max : $_; } return $self->_set_error("Rose data total is <= 0") unless $total > 0; my $factor = 0.5*($self->{bottom} - $self->{top})/$max; # scaling factor my $ac = $self->{acci}; # Accent colour my $pb = 0; # start angle # Set the data colour my $dc = $self->set_clr_uniq($self->pick_data_clr($j)); for (my $i = 0; $i < @values; $i++) { # Set the angles of the rose slice # Angle 0 faces up, positive angles are clockwise # from there. # 0 # --- # / | \ # | | # \ / # --- my $pa = $pb; $pb += 360/@values; # Calculate the end points of the lines at the boundaries of # the rose petal my ($xe, $ye) = cartesian( $values[$i]*$factor, $pa, $self->{xc}, $self->{yc}, $self->{h}/$self->{w} ); $self->{graph}->line($self->{xc}, $self->{yc}, $xe, $ye, $ac); my ($xf, $yf) = cartesian( $values[$i]*$factor, $pb, $self->{xc}, $self->{yc}, $self->{h}/$self->{w} ); $self->{graph}->line($xe, $ye, $xf, $yf, $ac); $self->{graph}->line($self->{xc}, $self->{yc}, $xf, $yf, $ac); # Make an estimate of a point in the middle of the rose petal # And fill it ($xe, $ye) = cartesian( $values[$i]*$factor/2, ($pa+$pb)/2, $self->{xc}, $self->{yc}, $self->{h}/$self->{w} ); $self->{graph}->fillToBorder($xe, $ye, $ac, $dc); # Horrible kludge. If the top petal is longer than the petal # underneath, the fill will be incomplete. So pick a point just # inside the far extent of the petal and fill. ($xe, $ye) = cartesian( $values[$i]*$factor-2, ($pa+$pb)/2, $self->{xc}, $self->{yc}, $self->{h}/$self->{w} ); $self->{graph}->fillToBorder($xe, $ye, $ac, $dc); } } return $self; } #GD::Graph::rose::draw_data # 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) = @_; return ( $xi + $r * cos(PI * ($phi + $ANGLE_OFFSET)/180), $yi + $cr * $r * sin(PI * ($phi + $ANGLE_OFFSET)/180) ) } "Just another true value";