summaryrefslogtreecommitdiff
path: root/lib/GD/Graph/pie.pm
diff options
context:
space:
mode:
authorAndreas Brachold <vdr07@deltab.de>2007-08-13 18:41:27 +0000
committerAndreas Brachold <vdr07@deltab.de>2007-08-13 18:41:27 +0000
commitbcbf441e09fb502cf64924ff2530fa144bdf52c5 (patch)
treef377707a2dac078db8cd0c7d7abfe69ac1006d71 /lib/GD/Graph/pie.pm
downloadxxv-bcbf441e09fb502cf64924ff2530fa144bdf52c5.tar.gz
xxv-bcbf441e09fb502cf64924ff2530fa144bdf52c5.tar.bz2
* Move files to trunk
Diffstat (limited to 'lib/GD/Graph/pie.pm')
-rw-r--r--lib/GD/Graph/pie.pm446
1 files changed, 446 insertions, 0 deletions
diff --git a/lib/GD/Graph/pie.pm b/lib/GD/Graph/pie.pm
new file mode 100644
index 0000000..a945ba0
--- /dev/null
+++ b/lib/GD/Graph/pie.pm
@@ -0,0 +1,446 @@
+#==========================================================================
+# Copyright (c) 1995-1998 Martien Verbruggen
+#--------------------------------------------------------------------------
+#
+# Name:
+# GD::Graph::pie.pm
+#
+# $Id: pie.pm,v 1.20 2003/02/10 22:12:41 mgjv Exp $
+#
+#==========================================================================
+
+package GD::Graph::pie;
+
+($GD::Graph::pie::VERSION) = '$Revision: 1.20 $' =~ /\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::pie::ISA = qw( GD::Graph );
+
+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 GD::Graph::pie::initialise
+
+ # pie_height => _round(0.1*${'width'}),
+ pie_height => undef,
+
+ # 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,
+
+ # Angle below which a label on a pie slice is suppressed.
+ suppress_angle => 0, # CONTRIB idea ryan <xomina@bitstream.net>
+
+ # and some public attributes without defaults
+ label => undef,
+
+ # This misnamed attribute is used for pie 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( pie_height => _round(0.1 * $self->{height}) );
+ $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_pie() 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;
+
+ # Make sure we're not reserving space we don't need.
+ $self->{'3d'} = 0 if $self->{pie_height} <= 0;
+ $self->set(pie_height => 0) unless $self->{'3d'};
+
+ 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 pie, and
+ # some width, height, and centre parameters
+ $self->{bottom} =
+ $self->{height} - $self->{pie_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 pie, without the data slices
+sub draw_pie
+{
+ my $self = shift;
+
+ my $left = $self->{xc} - $self->{w}/2;
+
+ $self->{graph}->arc(
+ $self->{xc}, $self->{yc},
+ $self->{w}, $self->{h},
+ 0, 360, $self->{acci}
+ );
+
+ $self->{graph}->arc(
+ $self->{xc}, $self->{yc} + $self->{pie_height},
+ $self->{w}, $self->{h},
+ 0, 180, $self->{acci}
+ ) if ( $self->{'3d'} );
+
+ $self->{graph}->line(
+ $left, $self->{yc},
+ $left, $self->{yc} + $self->{pie_height},
+ $self->{acci}
+ );
+
+ $self->{graph}->line(
+ $left + $self->{w}, $self->{yc},
+ $left + $self->{w}, $self->{yc} + $self->{pie_height},
+ $self->{acci}
+ );
+
+ return $self;
+}
+
+# Draw the data slices
+
+sub draw_data
+{
+ my $self = shift;
+
+ my $total = 0;
+ my @values = $self->{_data}->y_values(1); # for now, only one pie..
+ for (@values)
+ {
+ $total += $_
+ }
+
+ return $self->_set_error("Pie data total is <= 0")
+ unless $total > 0;
+
+ my $ac = $self->{acci}; # Accent colour
+ my $pb = $self->{start_angle};
+
+ for (my $i = 0; $i < @values; $i++)
+ {
+ # Set the data colour
+ my $dc = $self->set_clr_uniq($self->pick_data_clr($i + 1));
+
+ # Set the angles of the pie slice
+ # Angle 0 faces down, positive angles are clockwise
+ # from there.
+ # ---
+ # / \
+ # | |
+ # \ | /
+ # ---
+ # 0
+ # $pa/$pb include the start_angle (so if start_angle
+ # is 90, there will be no pa/pb < 90.
+ my $pa = $pb;
+ $pb += my $slice_angle = 360 * $values[$i]/$total;
+
+ # Calculate the end points of the lines at the boundaries of
+ # the pie slice
+ my ($xe, $ye) = cartesian(
+ $self->{w}/2, $pa,
+ $self->{xc}, $self->{yc}, $self->{h}/$self->{w}
+ );
+
+ $self->{graph}->line($self->{xc}, $self->{yc}, $xe, $ye, $ac);
+
+ # Draw the lines on the front of the pie
+ $self->{graph}->line($xe, $ye, $xe, $ye + $self->{pie_height}, $ac)
+ if in_front($pa) && $self->{'3d'};
+
+ # Make an estimate of a point in the middle of the pie slice
+ # And fill it
+ ($xe, $ye) = cartesian(
+ 3 * $self->{w}/8, ($pa+$pb)/2,
+ $self->{xc}, $self->{yc}, $self->{h}/$self->{w}
+ );
+
+ $self->{graph}->fillToBorder($xe, $ye, $ac, $dc);
+
+ # If it's 3d, colour the front ones as well
+ #
+ # if one slice is very large (>180 deg) then we will need to
+ # fill it twice. sbonds.
+ #
+ # Independently noted and fixed by Jeremy Wadsack, in a slightly
+ # different way.
+ if ($self->{'3d'})
+ {
+ foreach my $fill ($self->_get_pie_front_coords($pa, $pb))
+ {
+ $self->{graph}->fillToBorder(
+ $fill->[0], $fill->[1] + $self->{pie_height}/2,
+ $ac, $dc);
+ }
+ }
+ }
+
+ # CONTRIB Jeremy Wadsack
+ #
+ # Large text, sticking out over the pie edge, could cause 3D pies to
+ # fill improperly: Drawing the text for a given slice before the
+ # next slice was drawn and filled could make the slice boundary
+ # disappear, causing the fill colour to flow out. With this
+ # implementation, all the text is on top of the pie.
+
+ $pb = $self->{start_angle};
+ for (my $i = 0; $i < @values; $i++)
+ {
+ next unless $values[$i];
+
+ my $pa = $pb;
+ $pb += my $slice_angle = 360 * $values[$i]/$total;
+
+ next if $slice_angle <= $self->{suppress_angle};
+
+ my ($xe, $ye) =
+ cartesian(
+ 3 * $self->{w}/8, ($pa+$pb)/2,
+ $self->{xc}, $self->{yc}, $self->{h}/$self->{w}
+ );
+
+ $self->put_slice_label($xe, $ye, $self->{_data}->get_x($i));
+ }
+
+ return $self;
+
+} #GD::Graph::pie::draw_data
+
+sub _get_pie_front_coords # (angle 1, angle 2)
+{
+ my $self = shift;
+ my $pa = level_angle(shift);
+ my $pb = level_angle(shift);
+ my @fills = ();
+
+ if (in_front($pa))
+ {
+ if (in_front($pb))
+ {
+ # both in front
+ # don't do anything
+ # Ah, but if this wraps all the way around the back
+ # then both pieces of the front need to be filled.
+ # sbonds.
+ if ($pa > $pb )
+ {
+ # This takes care of the left bit on the front
+ # Since we know exactly where we are, and in which
+ # direction this works, we can just get the coordinates
+ # for $pa.
+ my ($x, $y) = cartesian(
+ $self->{w}/2, $pa,
+ $self->{xc}, $self->{yc}, $self->{h}/$self->{w}
+ );
+
+ # and move one pixel to the left, but only if we don't
+ # fall out of the pie!.
+ push @fills, [$x - 1, $y]
+ if $x - 1 > $self->{xc} - $self->{w}/2;
+
+ # Reset $pa to the right edge of the front arc, to do
+ # the right bit on the front.
+ $pa = level_angle(-$ANGLE_OFFSET);
+ }
+ }
+ 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(
+ $self->{w}/2, ($pa + $pb)/2,
+ $self->{xc}, $self->{yc}, $self->{h}/$self->{w}
+ );
+
+ push @fills, [$x, $y];
+
+ return @fills;
+}
+
+# return true if this angle is on the front of the pie
+# XXX UGLY! We need to leave a slight room for error because of rounding
+# problems
+sub in_front
+{
+ my $a = level_angle(shift);
+ return
+ $a > ($ANGLE_OFFSET - 180 + 0.00000001) &&
+ $a < $ANGLE_OFFSET - 0.000000001;
+}
+
+# XXX Ugh! I need to fix this. See the GD::Text module for better ways
+# of doing this.
+# 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 slice label on the pie
+sub put_slice_label
+{
+ my $self = shift;
+ my ($x, $y, $label) = @_;
+
+ return unless defined $label;
+
+ $self->{gdta_value}->set_text($label);
+ $self->{gdta_value}->draw($x, $y);
+}
+
+# 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";