GD-Graph3d-0.63/0040755000175000010010000000000007574203640012214 5ustar kilroyNoneGD-Graph3d-0.63/Changes0100644000175000010010000000424107553335371013511 0ustar kilroyNoneRevision history for Perl extension GD-Graph3d. See individual files for file-specific changes. 0.61 16.Oct.2002 - Added support for h_bars (or rotate_graph) (Chia-liang Kao) - Added a new cynlider.pm bar style (Chia-liang Kao) 0.60 Dec 07 2001 - Fixed another bug with x_tick_number rendering - off by one tick. Sent patch to MVERB for same bug in GD::Graph::axestype.pm 0.59 Dec 05 2001 - Fixed bug with x_tick_number 0.58 Dec 05 2001 - Added visual tests and note in Makefile.PL - Built test suite in t/ 0.57 Nov 08 2001 - Rebuilt with h2xs 1.20 with options -v 0.57 -X -n GD::Graph3d 0.56 Aug 10 2001 - Added lengend support to pie charts 0.55 Oct 09 2000 - Allowed box_clr without box axis 0.54 Sep 05 2000 - Fixed a bug in MANIFEST 0.53 Sep 04 2000 - Added ability to render box_clr without box_axis - Added a small segment to 'one point' line graphs 0.52 Aug 29 2000 - Reorganized distribution to pacify CPAN. No change to code. 0.51 Aug 23 2000 - Fixed bug in shading with cycle_clrs on - Fixed bug in shading on lines of positive or negative slope 0.50 Aug 19-21 2000 - Sent patch to MVERB for pie.pm -- 100% didn't color edge and length and width were one pixel too wide - Changed rendering code so that lines have consistent width Sent patch of lines.pm to MVERB with similar fix - Added shading to the bar and line charts and to the axes 0.42 July 07 2000 - Attempt to make CPAN index it better 0.41 April 24 2000 - Fixed axes rendering to maintain right side - Fixed frame rendering so 'box' and 'no box' make more sense - Set default color for text on pie chart slices to black 0.40 Apr 18 2000 Updated for compatibility with GD::Graph 1.30. 0.34 Mar 10 2000 - Fixed bar bottom rendering bug in bars3d module. 0.33 Feb 21 2000 - Fixed axes rendering bug in bars3d module. 0.32 Feb 11 2000 - Fixed rendering bug in bars3d module. 0.31 Feb 07 2000 - Fixed bug in version data. 0.30 Jan 19 2000 - Converted to GD::Graph subclasses. - First CPAN release 0.20 Oct 15 2000 - Modified all files to support GD with PNG output. 0.10 Sep 15 2000 - Original version. Based on GIFgraph. GD-Graph3d-0.63/lib/0040755000175000010010000000000007574203640012762 5ustar kilroyNoneGD-Graph3d-0.63/lib/GD/0040755000175000010010000000000007574203640013254 5ustar kilroyNoneGD-Graph3d-0.63/lib/GD/Graph/0040755000175000010010000000000007574203640014315 5ustar kilroyNoneGD-Graph3d-0.63/lib/GD/Graph/axestype3d.pm0100644000175000010010000005544107574202464016755 0ustar kilroyNone#========================================================================== # Module: GD::Graph::axestype3d # # Copyright (C) 1999,2001 Wadsack-Allen. All Rights Reserved. # # Based on axestype.pm,v 1.21 2000/04/15 08:59:36 mgjv # Copyright (c) 1995-1998 Martien Verbruggen # #-------------------------------------------------------------------------- # Date Modification Author # ------------------------------------------------------------------------- # 1999SEP18 Created 3D axestype base class (this JW # module) changes noted in comments. # 1999OCT15 Fixed to include all GIFgraph functions JW # necessary for PNG support. # 2000JAN19 Converted to GD::Graph sublcass JW # 2000FEB21 Fixed bug in y-labels' height JW # 2000APR18 Updated for compatibility with GD::Graph 1.30 JW # 2000AUG21 Added 3d shading JW # 2000SEP04 Allowed box_clr without box axis JW # 06Dec2001 Fixed bug in rendering of x tick when x_tick_number is set JW #========================================================================== # TODO # * Modify to use true 3-d extrusions at any theta and phi #========================================================================== package GD::Graph::axestype3d; use strict; use GD::Graph; use GD::Graph::axestype; use GD::Graph::utils qw(:all); use GD::Graph::colour qw(:colours); use Carp; @GD::Graph::axestype3d::ISA = qw(GD::Graph::axestype); $GD::Graph::axestype3d::VERSION = '0.63'; # Commented inheritance from GD::Graph::axestype unless otherwise noted. use constant PI => 4 * atan2(1,1); my %Defaults = ( depth_3d => 20, '3d_shading' => 1, # the rest are inherited ); # Inherit _has_default # Can't inherit initialise, because %Defaults is referenced file- # specific, not class specific. sub initialise { my $self = shift; my $rc = $self->SUPER::initialise(); while( my($key, $val) = each %Defaults ) { $self->{$key} = $val } # end while return $rc; } # end initialise # PUBLIC # Inherit plot # Inherit set # Inherit setup_text # Inherit set_x_label_font # Inherit set_y_label_font # Inherit set_x_axis_font # Inherit set_y_axis_font # Inherit set_legend # Inherit set_legend_font # ---------------------------------------------------------- # Sub: init_graph # # Args: (None) # # Description: # Override GD::Graph::init_graph to add 3d shading colors, # if requested # # [From GD::Graph] # Initialise the graph output canvas, setting colours (and # getting back index numbers for them) setting the graph to # transparent, and interlaced, putting a logo (if defined) # on there. # ---------------------------------------------------------- # Date Modification Author # ---------------------------------------------------------- # 20Aug2000 Added to support 3d graph extensions JW # ---------------------------------------------------------- sub init_graph { my $self = shift; # Sets up the canvas and color palette $self->SUPER::init_graph( @_ ); # Now create highlights and showdows for each color # in the palette if( $self->{'3d_shading'} ) { $self->{'3d_highlights'} = []; $self->{'3d_shadows'} = []; $self->{'3d_highlights'}[$self->{bgci}] = $self->set_clr( $self->_brighten( _rgb($self->{bgclr}) ) ); $self->{'3d_shadows'}[$self->{bgci}] = $self->set_clr( $self->_darken( _rgb($self->{bgclr}) ) ); $self->{'3d_highlights'}[$self->{fgci}] = $self->set_clr( $self->_brighten( _rgb($self->{fgclr}) ) ); $self->{'3d_shadows'}[$self->{fgci}] = $self->set_clr( $self->_darken( _rgb($self->{fgclr}) ) ); $self->{'3d_highlights'}[$self->{tci}] = $self->set_clr( $self->_brighten( _rgb($self->{textclr}) ) ); $self->{'3d_shadows'}[$self->{tci}] = $self->set_clr( $self->_darken( _rgb($self->{textclr}) ) ); $self->{'3d_highlights'}[$self->{lci}] = $self->set_clr( $self->_brighten( _rgb($self->{labelclr}) ) ); $self->{'3d_shadows'}[$self->{lci}] = $self->set_clr( $self->_darken( _rgb($self->{labelclr}) ) ); $self->{'3d_highlights'}[$self->{alci}] = $self->set_clr( $self->_brighten( _rgb($self->{axislabelclr}) ) ); $self->{'3d_shadows'}[$self->{alci}] = $self->set_clr( $self->_darken( _rgb($self->{axislabelclr}) ) ); $self->{'3d_highlights'}[$self->{acci}] = $self->set_clr( $self->_brighten( _rgb($self->{accentclr}) ) ); $self->{'3d_shadows'}[$self->{acci}] = $self->set_clr( $self->_darken( _rgb($self->{accentclr}) ) ); $self->{'3d_highlights'}[$self->{valuesci}] = $self->set_clr( $self->_brighten( _rgb($self->{valuesclr}) ) ); $self->{'3d_shadows'}[$self->{valuesci}] = $self->set_clr( $self->_darken( _rgb($self->{valuesclr}) ) ); $self->{'3d_highlights'}[$self->{legendci}] = $self->set_clr( $self->_brighten( _rgb($self->{legendclr}) ) ); $self->{'3d_shadows'}[$self->{legendci}] = $self->set_clr( $self->_darken( _rgb($self->{legendclr}) ) ); if( $self->{boxclr} ) { $self->{'3d_highlights'}[$self->{boxci}] = $self->set_clr( $self->_brighten( _rgb($self->{boxclr}) ) ); $self->{'3d_shadows'}[$self->{boxci}] = $self->set_clr( $self->_darken( _rgb($self->{boxclr}) ) ); } # end if } # end if return $self; } # end init_graph # PRIVATE # ---------------------------------------------------------- # Sub: _brighten # # Args: $r, $g, $b # $r, $g, $b The Red, Green, and Blue components of a color # # Description: Brightens the color by adding white # ---------------------------------------------------------- # Date Modification Author # ---------------------------------------------------------- # 21AUG2000 Created to build 3d highlights table JW # ---------------------------------------------------------- sub _brighten { my $self = shift; my( $r, $g, $b ) = @_; my $p = ($r + $g + $b) / 70; $p = 3 if $p < 3; my $f = _max( $r / $p, _max( $g / $p, $b / $p ) ); $r = _min( 255, int( $r + $f ) ); $g = _min( 255, int( $g + $f ) ); $b = _min( 255, int( $b + $f ) ); return( $r, $g, $b ); } # end _brighten # ---------------------------------------------------------- # Sub: _darken # # Args: $r, $g, $b # $r, $g, $b The Red, Green, and Blue components of a color # # Description: Darkens the color by adding black # ---------------------------------------------------------- # Date Modification Author # ---------------------------------------------------------- # 21AUG2000 Created to build 3d shadows table JW # ---------------------------------------------------------- sub _darken { my $self = shift; my( $r, $g, $b ) = @_; my $p = ($r + $g + $b) / 70; $p = 3 if $p < 3; my $f = _max( $r / $p, _max( $g / $p, $b / $p) ); $r = _max( 0, int( $r - $f ) ); $g = _max( 0, int( $g - $f ) ); $b = _max( 0, int( $b - $f ) ); return( $r, $g, $b ); } # end _darken # inherit check_data from GD::Graph # [JAW] Setup boundaries as parent, the adjust for 3d extrusion sub _setup_boundaries { my $self = shift; $self->SUPER::_setup_boundaries(); # adjust for top of 3-d extrusion $self->{top} += $self->{depth_3d}; return $self->_set_error('Vertical size too small') if $self->{bottom} <= $self->{top}; # adjust for right of 3-d extrusion $self->{right} -= $self->{depth_3d}; return $self->_set_error('Horizontal size too small') if $self->{right} <= $self->{left}; return $self; } # end _setup_boundaries # [JAW] Determine 3d-extrusion depth, then call parent sub setup_coords { my $self = shift; # Calculate the 3d-depth of the graph # Note this sets a minimum depth of ~20 pixels # if (!defined $self->{x_tick_number}) { my $depth = _max( $self->{bar_depth}, $self->{line_depth} ); if( $self->{overwrite} == 1 ) { $depth *= $self->{_data}->num_sets(); } # end if $self->{depth_3d} = _max( $depth, $self->{depth_3d} ); # } # end if $self->SUPER::setup_coords(); return $self; } # end setup_coords # Inherit create_y_labels # Inherit get_x_axis_label_height # Inherit create_x_labels # inherit open_graph from GD::Graph # Inherit draw_text # [JAW] Draws entire bounding cube for 3-d extrusion sub draw_axes { my $s = shift; my $g = $s->{graph}; my ($l, $r, $b, $t) = ( $s->{left}, $s->{right}, $s->{bottom}, $s->{top} ); my $depth = $s->{depth_3d}; if ( $s->{box_axis} ) { # -- Draw a bounding box if( $s->{boxci} ) { # -- Fill the box with color # Back box $g->filledRectangle($l+$depth+1, $t-$depth+1, $r+$depth-1, $b-$depth-1, $s->{boxci}); # Left side my $poly = new GD::Polygon; $poly->addPt( $l, $t ); $poly->addPt( $l + $depth, $t - $depth ); $poly->addPt( $l + $depth, $b - $depth ); $poly->addPt( $l, $b ); if( $s->{'3d_shading'} ) { $g->filledPolygon( $poly, $s->{'3d_shadows'}[$s->{boxci}] ); } else { $g->filledPolygon( $poly, $s->{boxci} ); } # end if # Bottom $poly = new GD::Polygon; $poly->addPt( $l, $b ); $poly->addPt( $l + $depth, $b - $depth ); $poly->addPt( $r + $depth, $b - $depth ); $poly->addPt( $r, $b ); if( $s->{'3d_shading'} ) { $g->filledPolygon( $poly, $s->{'3d_highlights'}[$s->{boxci}] ); } else { $g->filledPolygon( $poly, $s->{boxci} ); } # end if } # end if # -- Draw the box frame # Back box $g->rectangle($l+$depth, $t-$depth, $r+$depth, $b-$depth, $s->{fgci}); # Connecting frame $g->line($l, $t, $l + $depth, $t - $depth, $s->{fgci}); $g->line($r, $t, $r + $depth, $t - $depth, $s->{fgci}); $g->line($l, $b, $l + $depth, $b - $depth, $s->{fgci}); $g->line($r, $b, $r + $depth, $b - $depth, $s->{fgci}); # Front box $g->rectangle($l, $t, $r, $b, $s->{fgci}); } else { if( $s->{boxci} ) { # -- Fill the background box with color # Back box $g->filledRectangle($l+$depth+1, $t-$depth+1, $r+$depth-1, $b-$depth-1, $s->{boxci}); # Left side my $poly = new GD::Polygon; $poly->addPt( $l, $t ); $poly->addPt( $l + $depth, $t - $depth ); $poly->addPt( $l + $depth, $b - $depth ); $poly->addPt( $l, $b ); if( $s->{'3d_shading'} ) { $g->filledPolygon( $poly, $s->{'3d_shadows'}[$s->{boxci}] ); } else { $g->filledPolygon( $poly, $s->{boxci} ); } # end if # Bottom $poly = new GD::Polygon; $poly->addPt( $l, $b ); $poly->addPt( $l + $depth, $b - $depth ); $poly->addPt( $r + $depth, $b - $depth ); $poly->addPt( $r, $b ); if( $s->{'3d_shading'} ) { $g->filledPolygon( $poly, $s->{'3d_highlights'}[$s->{boxci}] ); } else { $g->filledPolygon( $poly, $s->{boxci} ); } # end if } # end if # -- Draw the frame only for back & sides # Back box $g->rectangle($l + $depth, $t - $depth, $r + $depth, $b - $depth, $s->{fgci}); # Y axis my $poly = new GD::Polygon; $poly->addPt( $l, $t ); $poly->addPt( $l, $b ); $poly->addPt( $l + $depth, $b - $depth ); $poly->addPt( $l + $depth, $t - $depth ); $g->polygon( $poly, $s->{fgci} ); # X axis if( !$s->{zero_axis_only} ) { $poly = new GD::Polygon; $poly->addPt( $l, $b ); $poly->addPt( $r, $b ); $poly->addPt( $r + $depth, $b - $depth ); $poly->addPt( $l + $depth, $b - $depth ); $g->polygon( $poly, $s->{fgci} ); } # end if # Second Y axis if( $s->{two_axes} ){ $poly = new GD::Polygon; $poly->addPt( $r, $b ); $poly->addPt( $r, $t ); $poly->addPt( $r + $depth, $t - $depth ); $poly->addPt( $r + $depth, $b - $depth ); $g->polygon( $poly, $s->{fgci} ); } # end if } # end if # Zero axis if ($s->{zero_axis} or $s->{zero_axis_only}) { my ($x, $y) = $s->val_to_pixel(0, 0, 1); my $poly = new GD::Polygon; $poly->addPt( $l, $y ); $poly->addPt( $r, $y ); $poly->addPt( $r + $depth, $y - $depth ); $poly->addPt( $l + $depth, $y - $depth); $g->polygon( $poly, $s->{fgci} ); } # end if } # end draw_axes # [JAW] Draws ticks and values for y axes in 3d extrusion # Modified from MVERB source sub draw_y_ticks { my $self = shift; for my $t (0 .. $self->{y_tick_number}) { for my $a (1 .. ($self->{two_axes} + 1)) { my $value = $self->{y_values}[$a][$t]; my $label = $self->{y_labels}[$a][$t]; my ($x, $y) = $self->val_to_pixel(0, $value, $a); $x = ($a == 1) ? $self->{left} : $self->{right}; # CONTRIB Jeremy Wadsack # Draw on the back of the extrusion $x += $self->{depth_3d}; $y -= $self->{depth_3d}; if ($self->{y_long_ticks}) { $self->{graph}->line( $x, $y, $x + $self->{right} - $self->{left}, $y, $self->{fgci} ) unless ($a-1); # CONTRIB Jeremy Wadsack # Draw conector ticks $self->{graph}->line( $x - $self->{depth_3d}, $y + $self->{depth_3d}, $x, $y, $self->{fgci} ) unless ($a-1); } else { $self->{graph}->line( $x, $y, $x + (3 - 2 * $a) * $self->{y_tick_length}, $y, $self->{fgci} ); # CONTRIB Jeremy Wadsack # Draw conector ticks $self->{graph}->line( $x - $self->{depth_3d}, $y + $self->{depth_3d}, $x - $self->{depth_3d} + (3 - 2 * $a) * $self->{y_tick_length}, $y + $self->{depth_3d} - (3 - 2 * $a) * $self->{y_tick_length}, $self->{fgci} ); } next if $t % ($self->{y_label_skip}) || ! $self->{y_plot_values}; $self->{gdta_y_axis}->set_text($label); $self->{gdta_y_axis}->set_align('center', $a == 1 ? 'right' : 'left'); $x -= (3 - 2 * $a) * $self->{axis_space}; # CONTRIB Jeremy Wadsack # Subtract 3-d extrusion width from left axis label # (it was added for ticks) $x -= (2 - $a) * $self->{depth_3d}; # CONTRIB Jeremy Wadsack # Add 3-d extrusion height to label # (it was subtracted for ticks) $y += $self->{depth_3d}; $self->{gdta_y_axis}->draw($x, $y); } # end foreach } # end foreach return $self; } # end draw_y_ticks # [JAW] Darws ticks and values for x axes wih 3d extrusion # Modified from MVERB source sub draw_x_ticks { my $self = shift; for (my $i = 0; $i < $self->{_data}->num_points; $i++) { my ($x, $y) = $self->val_to_pixel($i + 1, 0, 1); $y = $self->{bottom} unless $self->{zero_axis_only}; # CONTRIB Damon Brodie for x_tick_offset next if (!$self->{x_all_ticks} and ($i - $self->{x_tick_offset}) % $self->{x_label_skip} and $i != $self->{_data}->num_points - 1 ); # CONTRIB Jeremy Wadsack # Draw on the back of the extrusion $x += $self->{depth_3d}; $y -= $self->{depth_3d}; if ($self->{x_ticks}) { if ($self->{x_long_ticks}) { # CONTRIB Jeremy Wadsack # Move up by 3d depth $self->{graph}->line( $x, $self->{bottom} - $self->{depth_3d}, $x, $self->{top} - $self->{depth_3d}, $self->{fgci}); # CONTRIB Jeremy Wadsack # Draw conector ticks $self->{graph}->line( $x - $self->{depth_3d}, $y + $self->{depth_3d}, $x, $y, $self->{fgci} ); } else { $self->{graph}->line( $x, $y, $x, $y - $self->{x_tick_length}, $self->{fgci} ); # CONTRIB Jeremy Wadsack # Draw conector ticks $self->{graph}->line( $x - $self->{depth_3d}, $y + $self->{depth_3d}, $x - $self->{depth_3d} + $self->{x_tick_length}, $y + $self->{depth_3d} - $self->{x_tick_length}, $self->{fgci} ); } } # CONTRIB Damon Brodie for x_tick_offset next if ($i - $self->{x_tick_offset}) % ($self->{x_label_skip}) and $i != $self->{_data}->num_points - 1; $self->{gdta_x_axis}->set_text($self->{_data}->get_x($i)); # CONTRIB Jeremy Wadsack # Subtract 3-d extrusion width from left label # Add 3-d extrusion height to left label # (they were changed for ticks) $x -= $self->{depth_3d}; $y += $self->{depth_3d}; my $yt = $y + $self->{axis_space}; if ($self->{x_labels_vertical}) { $self->{gdta_x_axis}->set_align('center', 'right'); $self->{gdta_x_axis}->draw($x, $yt, PI/2); } else { $self->{gdta_x_axis}->set_align('top', 'center'); $self->{gdta_x_axis}->draw($x, $yt); } } # end for return $self; } # end draw_x_ticks # CONTRIB Scott Prahl # Assume x array contains equally spaced x-values # and generate an appropriate axis # #### # 'True' numerical X axis addition # From: Gary Deschaines # # These modification to draw_x_ticks_number pass x-tick values to the # val_to_pixel subroutine instead of x-tick indices when ture[sic] numerical # x-axis mode is detected. Also, x_tick_offset and x_label_skip are # processed differently when true numerical x-axis mode is detected to # allow labeled major x-tick marks and un-labeled minor x-tick marks. # # For example: # # x_tick_number => 14, # x_ticks => 1, # x_long_ticks => 1, # x_tick_length => -4, # x_min_value => 100, # x_max_value => 800, # x_tick_offset => 2, # x_label_skip => 2, # # # ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ # | | | | | | | | | | | | | # 1 -| | | | | | | | | | | | | # | | | | | | | | | | | | | # 0 _|_________|____|____|____|____|____|____|____|____|____|____|_________| # | | | | | | | | | | | # 200 300 400 500 600 700 #### # [JAW] Added commented items for 3d rendering # Based on MVERB source sub draw_x_ticks_number { my $self = shift; for my $i (0 .. $self->{x_tick_number}) { my ($value, $x, $y); if (defined($self->{x_min_value}) && defined($self->{x_max_value})) { next if ($i - $self->{x_tick_offset}) < 0; next if ($i + $self->{x_tick_offset}) > $self->{x_tick_number}; $value = $self->{x_values}[$i]; ($x, $y) = $self->val_to_pixel($value, 0, 1); } else { $value = ($self->{_data}->num_points - 1) * ($self->{x_values}[$i] - $self->{true_x_min}) / ($self->{true_x_max} - $self->{true_x_min}); ($x, $y) = $self->val_to_pixel($value + 1, 0, 1); } $y = $self->{bottom} unless $self->{zero_axis_only}; # Draw on the back of the extrusion $x += $self->{depth_3d}; $y -= $self->{depth_3d}; if ($self->{x_ticks}) { if ($self->{x_long_ticks}) { # XXX This mod needs to be done everywhere ticks are # drawn if ( $self->{x_tick_length} >= 0 ) { # Move up by 3d depth $self->{graph}->line( $x, $self->{bottom} - $self->{depth_3d}, $x, $self->{top} - $self->{depth_3d}, $self->{fgci}); } else { $self->{graph}->line( $x, $self->{bottom} - $self->{x_tick_length}, $x, $self->{top}, $self->{fgci}); } # CONTRIB Jeremy Wadsack # Draw conector ticks $self->{graph}->line( $x - $self->{depth_3d}, $y + $self->{depth_3d}, $x, $y, $self->{fgci} ); } else { $self->{graph}->line($x, $y, $x, $y - $self->{x_tick_length}, $self->{fgci} ); # CONTRIB Jeremy Wadsack # Draw conector ticks $self->{graph}->line( $x - $self->{depth_3d}, $y + $self->{depth_3d}, $x, - $self->{depth_3d} + $self->{tick_length}, $y, + $self->{depth_3d} - $self->{tick_length}, $self->{fgci} ); } # end if -- x_long_ticks } # end if -- x_ticks # If we have to skip labels, we'll do it here. # Make sure to always draw the last one. next if $i % $self->{x_label_skip} && $i != $self->{x_tick_number}; $self->{gdta_x_axis}->set_text($self->{x_labels}[$i]); # CONTRIB Jeremy Wadsack # Subtract 3-d extrusion width from left label # Add 3-d extrusion height to left label # (they were changed for ticks) $x -= $self->{depth_3d}; $y += $self->{depth_3d}; if ($self->{x_labels_vertical}) { $self->{gdta_x_axis}->set_align('center', 'right'); my $yt = $y + $self->{text_space}/2; $self->{gdta_x_axis}->draw($x, $yt, PI/2); } else { $self->{gdta_x_axis}->set_align('top', 'center'); my $yt = $y + $self->{text_space}/2; $self->{gdta_x_axis}->draw($x, $yt); } # end if } # end for return $self; } # end draw_x_tick_number # Inherit draw_ticks # Inherit draw_data # Inherit draw_data_set # Inherit set_max_min # Inherit get_max_y # Inherit get_min_y # Inherit get_max_min_y_all # Inherit _get_bottom # Inherit val_to_pixel # Inherit setup_legend # [JW] Override draw_legend and reverse the drawing order # if cumulate is enabled so legend matches data on chart sub draw_legend { my $self = shift; return unless defined $self->{legend}; my $xl = $self->{lg_xs} + $self->{legend_spacing}; my $y = $self->{lg_ys} + $self->{legend_spacing} - 1; # If there's a frame, offset by the size and margin $xl += $self->{legend_frame_margin} + $self->{legend_frame_size} if $self->{legend_frame_size}; $y += $self->{legend_frame_margin} + $self->{legend_frame_size} if $self->{legend_frame_size}; my $i = 0; my $row = 1; my $x = $xl; # start position of current element my @legends = @{$self->{legend}}; my $i_step = 1; # If we are working in cumulate mode, then reverse the drawing order if( $self->{cumulate} ) { @legends = reverse @legends; $i = scalar(@legends); $i = $self->{_data}->num_sets if $self->{_data}->num_sets < $i; $i++; $i_step = -1; } # end if foreach my $legend (@legends) { $i += $i_step; # Legend for Pie goes over first set, and all points # Works in either direction last if $i > $self->{_data}->num_sets; last if $i < 1; my $xe = $x; # position within an element next unless defined($legend) && $legend ne ""; $self->draw_legend_marker($i, $xe, $y); $xe += $self->{legend_marker_width} + $self->{legend_spacing}; my $ys = int($y + $self->{lg_el_height}/2 - $self->{lgfh}/2); $self->{gdta_legend}->set_text($legend); $self->{gdta_legend}->draw($xe, $ys); $x += $self->{lg_el_width}; if (++$row > $self->{lg_cols}) { $row = 1; $y += $self->{lg_el_height}; $x = $xl; } } # If there's a frame, draw it now if( $self->{legend_frame_size} ) { $x = $self->{lg_xs} + $self->{legend_spacing}; $y = $self->{lg_ys} + $self->{legend_spacing} - 1; for $i ( 0 .. $self->{legend_frame_size} - 1 ) { $self->{graph}->rectangle( $x + $i, $y + $i, $x + $self->{lg_x_size} + 2 * $self->{legend_frame_margin} - $i - 1, $y + $self->{lg_y_size} + 2 * $self->{legend_frame_margin} - $i - 1, $self->{acci}, ); } # end for } # end if } # Inherit draw_legend_marker 1; GD-Graph3d-0.63/lib/GD/Graph/bars3d.pm0100644000175000010010000002342607574203327016037 0ustar kilroyNone#========================================================================== # Module: GD::Graph::bars3d # # Copyright (C) 1999,2001 Wadsack-Allen. All Rights Reserved. # # Based on GD::Graph::bars.pm,v 1.16 2000/03/18 10:58:39 mgjv # Copyright (c) 1995-1998 Martien Verbruggen # #-------------------------------------------------------------------------- # Date Modification Author # ------------------------------------------------------------------------- # 1999SEP18 Created 3D bar chart class (this module) JAW # 1999SEP19 Rewrote to include a single bar-drawing JAW # function and process all bars in series # 1999SEP19 Implemented support for overwrite 2 style JAW # 1999SEP19 Fixed a bug in color cycler (colors were off by 1) JAW # 2000JAN19 Converted to GD::Graph class JAW # 2000MAR10 Fixed bug where bars ran off bottom of chart JAW # 2000APR18 Modified to be compatible with GD::Graph 1.30 JAW # 2000APR24 Fixed a lot of rendering bugs and added shading JAW # 2000AUG21 Added 3d shading JAW # 2000AUG24 Fixed shading on cycle_clrs option JAW # 06Dec2002 Fixed on-bar rendering with bars.pm draw_values JW #========================================================================== package GD::Graph::bars3d; use strict; use GD::Graph::axestype3d; use GD::Graph::bars; use GD::Graph::utils qw(:all); use GD::Graph::colour qw(:colours); @GD::Graph::bars3d::ISA = qw(GD::Graph::axestype3d); $GD::Graph::bars3d::VERSION = '0.63'; use constant PI => 4 * atan2(1,1); my %Defaults = ( # Spacing between the bars bar_spacing => 0, # The 3-d extrusion depth of the bars bar_depth => 10, ); sub initialise { my $self = shift; my $rc = $self->SUPER::initialise(); $self->set(correct_width => 1); while( my($key, $val) = each %Defaults ) { $self->{$key} = $val } # end while return $rc; } # end initialise sub set { my $s = shift; my %args = @_; $s->{_set_error} = 0; for (keys %args) { /^bar_depth$/ and do { $s->{bar_depth} = $args{$_}; delete $args{$_}; next; }; } return $s->SUPER::set(%args); } # CONTRIB Jeremy Wadsack # This is a complete overhaul of the original GD::Graph::bars # design, because all versions (overwrite = 0, 1, 2) # require that the bars be drawn in a loop of point over sets sub draw_data { my $self = shift; my $g = $self->{graph}; my $bar_s = _round($self->{bar_spacing}/2); my $zero = $self->{zeropoint}; my $i; my @iterate = (0 .. $self->{_data}->num_points()); for $i ($self->{rotate_chart} ? reverse(@iterate) : @iterate) { my ($xp, $t); my $overwrite = 0; $overwrite = $self->{overwrite} if defined $self->{overwrite}; my $j; my @iterate = (1 .. $self->{_data}->num_sets()); for $j (($self->{rotate_chart} && $self->{cumulate} == 0) ? reverse(@iterate) : @iterate) { my $value = $self->{_data}->get_y( $j, $i ); next unless defined $value; my $bottom = $self->_get_bottom($j, $i); $value = $self->{_data}->get_y_cumulative($j, $i) if ($self->{cumulate}); # Pick a data colour, calc shading colors too, if requested # cycle_clrs option sets the color based on the point, not the dataset. my @rgb; if( $self->{cycle_clrs} ) { @rgb = $self->pick_data_clr( $i + 1 ); } else { @rgb = $self->pick_data_clr( $j ); } # end if my $dsci = $self->set_clr( @rgb ); if( $self->{'3d_shading'} ) { $self->{'3d_highlights'}[$dsci] = $self->set_clr( $self->_brighten( @rgb ) ); $self->{'3d_shadows'}[$dsci] = $self->set_clr( $self->_darken( @rgb ) ); } # end if # contrib "Bremford, Mike" my $brci; if( $self->{cycle_clrs} > 1 ) { $brci = $self->set_clr($self->pick_data_clr($i + 1)); } else { $brci = $self->set_clr($self->pick_border_clr($j)); } # end if # get coordinates of top and center of bar ($xp, $t) = $self->val_to_pixel($i + 1, $value, $j); # calculate offsets of this bar my $x_offset = 0; my $y_offset = 0; if( $overwrite == 1 ) { $x_offset = $self->{bar_depth} * ($self->{_data}->num_sets() - $j); $y_offset = $self->{bar_depth} * ($self->{_data}->num_sets() - $j); } $t -= $y_offset; # calculate left and right of bar my ($l, $r); if ($self->{rotate_chart}) { $l = $bottom; ($r) = $self->val_to_pixel($i + 1, $value, $j); } if( (ref $self eq 'GD::Graph::mixed') || ($overwrite >= 1) ) { if ($self->{rotate_chart}) { $bottom = $t + $self->{x_step}/2 - $bar_s + $x_offset; $t = $t - $self->{x_step}/2 + $bar_s + $x_offset; } else { $l = $xp - $self->{x_step}/2 + $bar_s + $x_offset; $r = $xp + $self->{x_step}/2 - $bar_s + $x_offset; } } else { if ($self->{rotate_chart}) { warn "base is $t"; $bottom = $t - $self->{x_step}/2 + ($j) * $self->{x_step}/$self->{_data}->num_sets() + $bar_s + $x_offset; $t = $t - $self->{x_step}/2 + ($j-1) * $self->{x_step}/$self->{_data}->num_sets() - $bar_s + $x_offset; warn "top bottom is ($t, $bottom)"; } else { $l = $xp - $self->{x_step}/2 + ($j - 1) * $self->{x_step}/$self->{_data}->num_sets() + $bar_s + $x_offset; $r = $xp - $self->{x_step}/2 + $j * $self->{x_step}/$self->{_data}->num_sets() - $bar_s + $x_offset; } } if ($value >= 0) { # draw the positive bar $self->draw_bar( $g, $l, $t, $r, $bottom-$y_offset, $dsci, $brci, 0 ) } else { # draw the negative bar $self->draw_bar( $g, $l, $bottom-$y_offset, $r, $t, $dsci, $brci, -1 ) } # end if } # end for } # end for # redraw the 'zero' axis, front and right if( $self->{zero_axis} ) { $g->line( $self->{left}, $self->{zeropoint}, $self->{right}, $self->{zeropoint}, $self->{fgci} ); $g->line( $self->{right}, $self->{zeropoint}, $self->{right}+$self->{depth_3d}, $self->{zeropoint}-$self->{depth_3d}, $self->{fgci} ); } # end if # redraw the box face if ( $self->{box_axis} ) { # Axes box $g->rectangle($self->{left}, $self->{top}, $self->{right}, $self->{bottom}, $self->{fgci}); $g->line($self->{right}, $self->{top}, $self->{right} + $self->{depth_3d}, $self->{top} - $self->{depth_3d}, $self->{fgci}); $g->line($self->{right}, $self->{bottom}, $self->{right} + $self->{depth_3d}, $self->{bottom} - $self->{depth_3d}, $self->{fgci}); } # end if return $self; } # end draw_data # CONTRIB Jeremy Wadsack # This function draws a bar at the given # coordinates. This is called in all three # overwrite modes. sub draw_bar { my $self = shift; my $g = shift; my( $l, $t, $r, $b, $dsci, $brci, $neg ) = @_; # get depth of the bar my $depth = $self->{bar_depth}; # get the bar shadow depth and color my $bsd = $self->{shadow_depth}; my $bsci = $self->set_clr(_rgb($self->{shadowclr})); my( $xi ); # shadow if( $bsd > 0 ) { my $sb = $b - $depth; my $st = $t - $depth + $bsd; if( $neg != 0 ) { $st -= $bsd; if( $self->{zero_axis_only} ) { $sb += $bsd; } else { $sb = _min($b-$depth+$bsd, $self->{bottom}-$depth); } # end if } # end if # ** If this isn't the back bar, then no side shadow should be # drawn or else the top should be lowered by # ($bsd * dataset_num), it should be drawn on the back surface, # and a shadow should be drawn behind the front bar if the # bar is positive and the back is negative. $g->filledRectangle($l+$depth+$bsd, $st, $r+$depth+$bsd, $sb, $bsci); # Only draw bottom shadow if at the bottom and has bottom # axis. Always draw top shadow if( ($neg == 0) || ($sb >= $self->{bottom}-$depth) ) { my $poly = new GD::Polygon; $poly->addPt( $r, $b ); $poly->addPt( $r+$bsd, $b ); $poly->addPt( $r+$depth+$bsd, $b-$depth ); $poly->addPt( $r+$depth, $b-$depth ); $g->filledPolygon( $poly, $bsci ); } # end if } # end if # side my $poly = new GD::Polygon; $poly->addPt( $r, $t ); $poly->addPt( $r+$depth, $t-$depth ); $poly->addPt( $r+$depth, $b-$depth ); $poly->addPt( $r, $b ); if( $self->{'3d_shading'} ) { $g->filledPolygon( $poly, $self->{'3d_shadows'}[$dsci] ); } else { $g->filledPolygon( $poly, $dsci ); } # end if $g->polygon( $poly, $brci ); # top # -- only draw negative tops if the bar starts at zero if( ($neg == 0) || ($t <= $self->{zeropoint}) ) { $poly = new GD::Polygon; $poly->addPt( $l, $t ); $poly->addPt( $l+$depth, $t-$depth ); $poly->addPt( $r+$depth, $t-$depth ); $poly->addPt( $r, $t ); if( $self->{'3d_shading'} ) { $g->filledPolygon( $poly, $self->{'3d_highlights'}[$dsci] ); } else { $g->filledPolygon( $poly, $dsci ); } # end if $g->polygon( $poly, $brci ); } # end if # face $g->filledRectangle( $l, $t, $r, $b, $dsci ); $g->rectangle( $l, $t, $r, $b, $brci ); } # end draw_bar # [JAW] Overrides axestype's set_max_min. # Go through the parent's process then adjust the baseline to 0 for bar graphs. sub set_max_min { my $self = shift; $self->SUPER::set_max_min( @_ ); # This code is taken from Martien's axestype.pm for my $i (1..($self->{two_axes} ? 2 : 1)) { # If at the same side of the zero axis if( $self->{y_max}[$i] && $self->{y_min}[$i]/$self->{y_max}[$i] > 0 ) { $self->{y_min}[$i] > 0 ? $self->{y_min}[$i] = 0 : $self->{y_max}[$i] = 0 ; } # end if } # end for return $self; } # end set_max_min # [JW] Just use the one in GD::Graph::bars sub draw_values { return &GD::Graph::bars::draw_values( @_ ); } 1; GD-Graph3d-0.63/lib/GD/Graph/cylinder.pm0100644000175000010010000001031607574202471016463 0ustar kilroyNone# $File: //depot/RG/rg/lib/RG/lib/GD/Graph/cylinder.pm $ $Author: autrijus $ # $Revision: #3 $ $Change: 370 $ $DateTime: 2002/07/17 20:38:38 $ package GD::Graph::cylinder; use strict; use GD::Graph::axestype3d; use GD::Graph::utils qw(:all); use GD::Graph::colour qw(:colours); use base qw/GD::Graph::bars3d/; $GD::Graph::cylinder::VERSION = '0.63'; my %Defaults = ( # Spacing between the bars bar_spacing => 0, # The 3-d extrusion depth of the bars bar_depth => 10, ); sub initialise { my $self = shift; my $rc = $self->SUPER::initialise(); $self->set(correct_width => 1); while( my($key, $val) = each %Defaults ) { $self->{$key} = $val } # end while return $rc; } # end initialise sub draw_bar_h { my $self = shift; my $g = shift; my( $l, $t, $r, $b, $dsci, $brci, $neg ) = @_; my $fnord = $g->colorAllocate(0,0,0); my $depth = $self->{bar_depth}; my ($lighter, $darker) = ($dsci, $dsci); if ($self->{'3d_shading'}) { $lighter = $self->{'3d_highlights'}[$dsci]; $darker = $self->{'3d_shadows'}[$dsci]; } $g->line($l+$depth, $t+1, $r+$depth, $t+1, $dsci); $g->line($l+$depth, $b, $r+$depth, $b, $dsci); $g->arc($r+$depth, ($t+$b)/2, $depth*2, ($b-$t), 270, 90, $dsci); $g->arc($l+$depth, ($t+$b)/2, $depth*2, ($b-$t), 90, 270, $dsci); # find border my $foo = $l+$depth; --$foo until $foo == $l || $g->getPixel($foo, $t+($b-$t)/5) == $dsci; my $bar = $foo+1; ++$bar until $bar == $foo || $g->getPixel($bar, $t+($b-$t)/5) == $dsci; $g->line($foo, $t+($b-$t)/5, $bar, $t+($b-$t)/5, $dsci); $g->line($foo, $b-($b-$t)/5, $bar, $b-($b-$t)/5, $dsci); $g->fillToBorder($l+$depth, ($t+$b)/2, $dsci, $dsci); $g->arc($l+$depth, ($b+$t)/2, $depth*2, ($b-$t), 90, 270, $dsci); if ($foo < $bar + 3) { $g->fillToBorder(($l+$r)/2+$depth, $t+($b-$t)/5-1, $dsci, $lighter) unless $g->getPixel(($l+$r)/2+$depth, $t+($b-$t)/5-1) == $dsci; $g->fillToBorder(($l+$r)/2+$depth, $b-($b-$t)/5+1, $dsci, $darker) unless $g->getPixel(($l+$r)/2+$depth, $b-($b-$t)/5+1) == $dsci; $g->fillToBorder(($l+$r)/2, ($t+$b)/2, $dsci, $dsci); } $g->arc($l+$depth, ($b+$t)/2, $depth*2, ($b-$t), 90, 270, $brci); $g->arc($r+$depth, ($b+$t)/2, $depth*2, ($b-$t), 0, 360, $brci); $g->line($l+$depth, $t+1, $r+$depth, $t+1, $brci); $g->line($l+$depth, $b, $r+$depth, $b, $brci); $g->fillToBorder($r+$depth, ($b+$t)/2, $brci, $dsci); } sub draw_bar { my $self = shift; return $self->draw_bar_h(@_) if $self->{rotate_chart}; my $g = shift; my( $l, $t, $r, $b, $dsci, $brci, $neg ) = @_; my $fnord = $g->colorAllocate(0,0,0); my $depth = $self->{bar_depth}; my ($lighter, $darker) = ($dsci, $dsci); if ($self->{'3d_shading'}) { $lighter = $self->{'3d_highlights'}[$dsci]; $darker = $self->{'3d_shadows'}[$dsci]; } $g->line($l+1, $t-$depth, $l+1, $b-$depth, $dsci); $g->line($r, $t-$depth, $r, $b-$depth, $dsci); $g->arc(($l+$r)/2, $t-$depth, ($r-$l), $depth*2, 180, 360, $dsci); $g->arc(($l+$r)/2, $b-$depth, ($r-$l), $depth*2, 0, 180, $dsci); # find border my $foo = $b-$depth+1; ++$foo until $foo == $b || $g->getPixel($l+($r-$l)/5,$foo) == $dsci; my $bar = $foo-1; --$bar until $bar == $foo || $g->getPixel($l+($r-$l)/5,$bar) == $dsci; $g->line($l+($r-$l)/5, $bar, $l+($r-$l)/5, $foo, $dsci); $g->line($r-($r-$l)/5, $bar, $r-($r-$l)/5, $foo, $dsci); $g->fillToBorder(($l+$r)/2, $t-$depth, $dsci, $dsci); $g->arc(($l+$r)/2, $b-$depth, ($r-$l), $depth*2, 0, 180, $dsci); if ($foo > $bar + 3) { $g->fillToBorder($l+($r-$l)/5-1, ($foo+$bar)/2, $dsci, $lighter) unless $g->getPixel($l+($r-$l)/5-1, ($foo+$bar)/2) == $dsci; $g->fillToBorder($r-($r-$l)/5+1, ($foo+$bar)/2, $dsci, $darker) unless $g->getPixel($r-($r-$l)/5+1, ($foo+$bar)/2) == $dsci; $g->fillToBorder(($l+$r)/2, ($t+$b)/2, $dsci, $dsci); } $g->arc(($l+$r)/2, $b-$depth, ($r-$l), $depth*2, 0, 180, $brci); $g->arc(($l+$r)/2, $t-$depth, ($r-$l), $depth*2, 0, 360, $brci); $g->line($l+1, $t-$depth, $l+1, $b-$depth, $brci); $g->line($r, $t-$depth, $r, $b-$depth, $brci); $g->fillToBorder(($l+$r)/2, $t-$depth, $brci, $dsci); } 1; GD-Graph3d-0.63/lib/GD/Graph/cylinder3d.pm0100644000175000010010000000162707574202474016722 0ustar kilroyNone############################################################ # # Module: GD::Graph::cylinder3d # # Description: # This is merely a wrapper around GD::Graph::cylinder # to be used as an alias # # Created: 16 October 2002 by Jeremy Wadsack for Wadsack-Allen Digital Group # Copyright (C) 2002 Wadsack-Allen. All rights reserved. ############################################################ # Date Modification Author # ---------------------------------------------------------- # # ############################################################ package GD::Graph::cylinder3d; use strict; use GD; use GD::Graph; use GD::Graph::cylinder; use Carp; @GD::Graph::cylinder3d::ISA = qw( GD::Graph::cylinder ); $GD::Graph::cylinder3d::VERSION = '0.63'; # Inherit everything from GD::Graph::cylinder 1; GD-Graph3d-0.63/lib/GD/Graph/lines3d.pm0100644000175000010010000004103207574202477016220 0ustar kilroyNone#========================================================================== # Module: GD::Graph::lines3d # # Copyright (C) 1999,2001 Wadsack-Allen. All Rights Reserved. # # Based on GD::Graph::lines.pm,v 1.10 2000/04/15 mgjv # Copyright (c) 1995-1998 Martien Verbruggen # #-------------------------------------------------------------------------- # Date Modification Author # ------------------------------------------------------------------------- # 1999SEP18 Created 3D line chart class (this module) JAW # 1999SEP19 Finished overwrite 1 style JAW # 1999SEP19 Polygon'd linewidth rendering JAW # 2000SEP19 Converted to a GD::Graph class JAW # 2000APR18 Modified for compatibility with GD::Graph 1.30 JAW # 2000APR24 Fixed a lot of rendering bugs JAW # 2000AUG19 Changed render code so lines have consitent width JAW # 2000AUG21 Added 3d shading JAW # 2000AUG24 Fixed shading top/botttom vs. postive/negative slope JAW # 2000SEP04 For single point "lines" made a short segment JAW # 2000OCT09 Fixed bug in rendering of legend JAW #========================================================================== # TODO # ** The new mitred corners don't work well at data anomlies. Like # the set (0,0,1,0,0,0,1,0,1) Looks really wrong! # * Write a draw_data_set that draws the line so they appear to pass # through one another. This means drawing a border edge at each # intersection of the data lines so the points of pass-through show. # Probably want to draw all filled polygons, then run through the data # again finding intersections of line segments and drawing those edges. #========================================================================== package GD::Graph::lines3d; use strict; use GD; use GD::Graph::axestype3d; use Data::Dumper; @GD::Graph::lines3d::ISA = qw( GD::Graph::axestype3d ); $GD::Graph::lines3d::VERSION = '0.63'; my $PI = 4 * atan2(1, 1); my %Defaults = ( # The depth of the line in their extrusion line_depth => 10, ); sub initialise() { my $self = shift; my $rc = $self->SUPER::initialise(); while( my($key, $val) = each %Defaults ) { $self->{$key} = $val # *** [JAW] # Should we reset the depth_3d param based on the # line_depth, numsets and overwrite parameters, here? # } # end while return $rc; } # end initialize sub set { my $s = shift; my %args = @_; $s->{_set_error} = 0; for (keys %args) { /^line_depth$/ and do { $s->{line_depth} = $args{$_}; delete $args{$_}; next; }; } return $s->SUPER::set(%args); } # end set # PRIVATE # [JAW] Changed to draw_data intead of # draw_data_set to allow better control # of multiple set rendering sub draw_data { my $self = shift; my $d = $self->{_data}; my $g = $self->{graph}; $self->draw_data_overwrite( $g, $d ); # redraw the 'zero' axis, front and right if( $self->{zero_axis} ) { $g->line( $self->{left}, $self->{zeropoint}, $self->{right}, $self->{zeropoint}, $self->{fgci} ); $g->line( $self->{right}, $self->{zeropoint}, $self->{right} + $self->{depth_3d}, $self->{zeropoint} - $self->{depth_3d}, $self->{fgci} ); } # end if # redraw the box face if ( $self->{box_axis} ) { # Axes box $g->rectangle($self->{left}, $self->{top}, $self->{right}, $self->{bottom}, $self->{fgci}); $g->line($self->{right}, $self->{top}, $self->{right} + $self->{depth_3d}, $self->{top} - $self->{depth_3d}, $self->{fgci}); $g->line($self->{right}, $self->{bottom}, $self->{right} + $self->{depth_3d}, $self->{bottom} - $self->{depth_3d}, $self->{fgci}); } # end if return $self; } # end draw_data # Copied from MVERB source sub pick_line_type { my $self = shift; my $num = shift; ref $self->{line_types} ? $self->{line_types}[ $num % (1 + $#{$self->{line_types}}) - 1 ] : $num % 4 ? $num % 4 : 4 } # ---------------------------------------------------------- # Sub: draw_data_overwrite # # Args: $gd # $gd The GD object to draw on # # Description: Draws each line segment for each set. Runs # over sets, then points so that the appearance is better. # ---------------------------------------------------------- # Date Modification Author # ---------------------------------------------------------- # 19SEP1999 Added this for overwrite support. JW # 20AUG2000 Changed structure to use points 'objects' JW # ---------------------------------------------------------- sub draw_data_overwrite { my $self = shift; my $g = shift; my @points_cache; my $i; for $i (0 .. $self->{_data}->num_points()) { my $j; for $j (1 .. $self->{_data}->num_sets()) { my @values = $self->{_data}->y_values($j) or return $self->_set_error( "Impossible illegal data set: $j", $self->{_data}->error ); if( $self->{_data}->num_points() == 1 && $i == 1 ) { # Copy the first point to the "second" $values[$i] = $values[0]; } # end if next unless defined $values[$i]; # calculate offset of this line # *** Should offset be the max of line_depth # and depth_3d/numsets? [JAW] # my $offset = $self->{line_depth} * ($self->{_data}->num_sets() - $j); # Get the coordinates of the previous point, if this is the first # point make a point object and start over (i.e. next;) unless( $i ) { my( $xb, $yb ); if (defined($self->{x_min_value}) && defined($self->{x_max_value})) { ($xb, $yb) = $self->val_to_pixel( $self->{_data}->get_x($i), $values[$i], $j ); } else { ($xb, $yb) = $self->val_to_pixel( $i + 1, $values[$i], $j ); } # end if $xb += $offset; $yb -= $offset; $points_cache[$i][$j] = { coords => [$xb, $yb] }; next; } # end unless # Pick a data colour, calc shading colors too, if requested my( @rgb ) = $self->pick_data_clr( $j ); my $dsci = $self->set_clr( @rgb ); if( $self->{'3d_shading'} ) { $self->{'3d_highlights'}[$dsci] = $self->set_clr( $self->_brighten( @rgb ) ); $self->{'3d_shadows'}[$dsci] = $self->set_clr( $self->_darken( @rgb ) ); } # end if # Get the type my $type = $self->pick_line_type($j); # Get the coordinates of the this point unless( ref $points_cache[$i][$j] ) { my( $xe, $ye ); if( defined($self->{x_min_value}) && defined($self->{x_max_value}) ) { ( $xe, $ye ) = $self->val_to_pixel( $self->{_data}->get_x($i), $values[$i], $j ); } else { ( $xe, $ye ) = $self->val_to_pixel($i + 1, $values[$i], $j); } # end if $xe += $offset; $ye -= $offset; $points_cache[$i][$j] = { coords => [$xe, $ye] }; } # end if # Find the coordinates of the next point if( defined $values[$i + 1] ) { my( $xe, $ye ); if( defined($self->{x_min_value}) && defined($self->{x_max_value}) ) { ( $xe, $ye ) = $self->val_to_pixel( $self->{_data}->get_x($i + 1), $values[$i + 1], $j ); } else { ( $xe, $ye ) = $self->val_to_pixel($i + 2, $values[$i + 1], $j); } # end if $xe += $offset; $ye -= $offset; $points_cache[$i + 1][$j] = { coords => [$xe, $ye] }; } # end if if( $self->{_data}->num_points() == 1 && $i == 1 ) { # Nudge the x coords back- and forwards my $n = int(($self->{right} - $self->{left}) / 30); $n = 2 if $n < 2; $points_cache[$i][$j]{coords}[0] = $points_cache[$i - 1][$j]{coords}[0] + $n; $points_cache[$i - 1][$j]{coords}[0] -= $n; } # end if # Draw the line segment $self->draw_line( $points_cache[$i - 1][$j], $points_cache[$i][$j], $points_cache[$i + 1][$j], $type, $dsci ); # Draw the end cap if last segment if( $i >= $self->{_data}->num_points() - 1 ) { my $poly = new GD::Polygon; $poly->addPt( $points_cache[$i][$j]{face}[0], $points_cache[$i][$j]{face}[1] ); $poly->addPt( $points_cache[$i][$j]{face}[2], $points_cache[$i][$j]{face}[3] ); $poly->addPt( $points_cache[$i][$j]{face}[2] + $self->{line_depth}, $points_cache[$i][$j]{face}[3] - $self->{line_depth} ); $poly->addPt( $points_cache[$i][$j]{face}[0] + $self->{line_depth}, $points_cache[$i][$j]{face}[1] - $self->{line_depth} ); if( $self->{'3d_shading'} ) { $g->filledPolygon( $poly, $self->{'3d_shadows'}[$dsci] ); } else { $g->filledPolygon( $poly, $dsci ); } # end if $g->polygon( $poly, $self->{fgci} ); } # end if } # end for -- $self->{_data}->num_sets() } # end for -- $self->{_data}->num_points() } # end sub draw_data_overwrite # ---------------------------------------------------------- # Sub: draw_line # # Args: $prev, $this, $next, $type, $clr # $prev A hash ref for the prev point's object # $this A hash ref for this point's object # $next A hash ref for the next point's object # $type A predefined line type (2..4) = (dashed, dotted, dashed & dotted) # $clr The color (colour) index to use for the fill # # Point "Object" has these properties: # coords A 2 element array of the coordinates for the line # (this should be filled in before calling) # face An 4 element array of end points for the face # polygon. This will be populated by this method. # # Description: Draws a line segment in 3d extrusion that # connects the prev point the the this point. The next point # is used to calculate the mitre at the joint. # ---------------------------------------------------------- # Date Modification Author # ---------------------------------------------------------- # 18SEP1999 Modified MVERB source to work on data # point, not data set for better rendering JAW # 19SEP1999 Ploygon'd line rendering for better effect JAW # 19AUG2000 Made line width perpendicular JAW # 19AUG2000 Changed parameters to use %line_seg hash/obj JAW # 20AUG2000 Mitred joints of line segments JAW # ---------------------------------------------------------- sub draw_line { my $self = shift; my( $prev, $this, $next, $type, $clr ) = @_; my $xs = $prev->{coords}[0]; my $ys = $prev->{coords}[1]; my $xe = $this->{coords}[0]; my $ye = $this->{coords}[1]; my $lw = $self->{line_width}; my $lts = $self->{line_type_scale}; my $style = gdStyled; my @pattern = (); LINE: { ($type == 2) && do { # dashed for (1 .. $lts) { push @pattern, $clr } for (1 .. $lts) { push @pattern, gdTransparent } $self->{graph}->setStyle(@pattern); last LINE; }; ($type == 3) && do { # dotted, for (1 .. 2) { push @pattern, $clr } for (1 .. 2) { push @pattern, gdTransparent } $self->{graph}->setStyle(@pattern); last LINE; }; ($type == 4) && do { # dashed and dotted for (1 .. $lts) { push @pattern, $clr } for (1 .. 2) { push @pattern, gdTransparent } for (1 .. 2) { push @pattern, $clr } for (1 .. 2) { push @pattern, gdTransparent } $self->{graph}->setStyle(@pattern); last LINE; }; # default: solid $style = $clr; } # [JAW] Removed the dataset loop for better results. # Need the setstyle to reset $self->{graph}->setStyle(@pattern) if (@pattern); # # Find the x and y offsets for the edge of the front face # Do this by adjusting them perpendicularly from the line # half the line width in front and in back. # my( $lwyoff, $lwxoff ); if( $xe == $xs ) { $lwxoff = $lw / 2; $lwyoff = 0; } elsif( $ye == $ys ) { $lwxoff = 0; $lwyoff = $lw / 2; } else { my $ln = sqrt( ($ys-$ye)**2 + ($xe-$xs)**2 ); $lwyoff = ($xe-$xs) / $ln * $lw / 2; $lwxoff = ($ys-$ye) / $ln * $lw / 2; } # end if # For first line, figure beginning point unless( defined $prev->{face}[0] ) { $prev->{face} = []; $prev->{face}[0] = $xs - $lwxoff; $prev->{face}[1] = $ys - $lwyoff; $prev->{face}[2] = $xs + $lwxoff; $prev->{face}[3] = $ys + $lwyoff; } # end unless # Calc and store this point's face coords unless( defined $this->{face}[0] ) { $this->{face} = []; $this->{face}[0] = $xe - $lwxoff; $this->{face}[1] = $ye - $lwyoff; $this->{face}[2] = $xe + $lwxoff; $this->{face}[3] = $ye + $lwyoff; } # end if # Now find next point and nudge these coords to mitre if( ref $next->{coords} eq 'ARRAY' ) { my( $lwyo2, $lwxo2 ); my( $x2, $y2 ) = @{$next->{coords}}; if( $x2 == $xe ) { $lwxo2 = $lw / 2; $lwyo2 = 0; } elsif( $y2 == $ye ) { $lwxo2 = 0; $lwyo2 = $lw / 2; } else { my $ln2 = sqrt( ($ye-$y2)**2 + ($x2-$xe)**2 ); $lwyo2 = ($x2-$xe) / $ln2 * $lw / 2; $lwxo2 = ($ye-$y2) / $ln2 * $lw / 2; } # end if $next->{face} = []; $next->{face}[0] = $x2 - $lwxo2; $next->{face}[1] = $y2 - $lwyo2; $next->{face}[2] = $x2 + $lwxo2; $next->{face}[3] = $y2 + $lwyo2; # Now get the intersecting coordinates my $mt = ($ye - $ys)/($xe - $xs); my $mn = ($y2 - $ye)/($x2 - $xe); my $bt = $this->{face}[1] - $this->{face}[0] * $mt; my $bn = $next->{face}[1] - $next->{face}[0] * $mn; if( $mt != $mn ) { $this->{face}[0] = ($bn - $bt) / ($mt - $mn); } # end if $this->{face}[1] = $mt * $this->{face}[0] + $bt; $bt = $this->{face}[3] - $this->{face}[2] * $mt; $bn = $next->{face}[3] - $next->{face}[2] * $mn; if( $mt != $mn ) { $this->{face}[2] = ($bn - $bt) / ($mt - $mn); } # end if $this->{face}[3] = $mt * $this->{face}[2] + $bt; } # end if # Make the top/bottom polygon my $poly = new GD::Polygon; if( ($ys-$ye)/($xe-$xs) > 1 ) { $poly->addPt( $prev->{face}[2], $prev->{face}[3] ); $poly->addPt( $this->{face}[2], $this->{face}[3] ); $poly->addPt( $this->{face}[2] + $self->{line_depth}, $this->{face}[3] - $self->{line_depth} ); $poly->addPt( $prev->{face}[2] + $self->{line_depth}, $prev->{face}[3] - $self->{line_depth} ); if( $self->{'3d_shading'} && $style == $clr ) { if( ($ys-$ye)/($xe-$xs) > 0 ) { $self->{graph}->filledPolygon( $poly, $self->{'3d_shadows'}[$clr] ); } else { $self->{graph}->filledPolygon( $poly, $self->{'3d_highlights'}[$clr] ); } # end if } else { $self->{graph}->filledPolygon( $poly, $style ); } # end if } else { $poly->addPt( $prev->{face}[0], $prev->{face}[1] ); $poly->addPt( $this->{face}[0], $this->{face}[1] ); $poly->addPt( $this->{face}[0] + $self->{line_depth}, $this->{face}[1] - $self->{line_depth} ); $poly->addPt( $prev->{face}[0] + $self->{line_depth}, $prev->{face}[1] - $self->{line_depth} ); if( $self->{'3d_shading'} && $style == $clr ) { if( ($ys-$ye)/($xe-$xs) < 0 ) { $self->{graph}->filledPolygon( $poly, $self->{'3d_shadows'}[$clr] ); } else { $self->{graph}->filledPolygon( $poly, $self->{'3d_highlights'}[$clr] ); } # end if } else { $self->{graph}->filledPolygon( $poly, $style ); } # end if } # end if $self->{graph}->polygon( $poly, $self->{fgci} ); # *** This paints dashed and dotted patterns on the faces of # the polygons. They don't look very good though. Would it # be better to extrude the style as well as the lines? # Otherwise could also be improved by using gdTiled instead of # gdStyled and making the tile a transform of the line style # for each face. [JAW] # Make the face polygon $poly = new GD::Polygon; $poly->addPt( $prev->{face}[0], $prev->{face}[1] ); $poly->addPt( $this->{face}[0], $this->{face}[1] ); $poly->addPt( $this->{face}[2], $this->{face}[3] ); $poly->addPt( $prev->{face}[2], $prev->{face}[3] ); $self->{graph}->filledPolygon( $poly, $style ); $self->{graph}->polygon( $poly, $self->{fgci} ); } # end draw line # ---------------------------------------------------------- # Sub: draw_legend_marker # # Args: $dsn, $x, $y # $dsn The dataset number to draw the marker for # $x The x position of the marker # $y The y position of the marker # # Description: Draws the legend marker for the specified # dataset number at the given coordinates # ---------------------------------------------------------- # Date Modification Author # ---------------------------------------------------------- # 2000OCT06 Fixed rendering bugs JW # ---------------------------------------------------------- sub draw_legend_marker { my $self = shift; my ($n, $x, $y) = @_; my $ci = $self->set_clr($self->pick_data_clr($n)); my $type = $self->pick_line_type($n); $y += int($self->{lg_el_height}/2); # Joe Smith local($self->{line_width}) = 2; # Make these show up better $self->draw_line( { coords => [$x, $y] }, { coords => [$x + $self->{legend_marker_width}, $y] }, undef, $type, $ci ); } # end draw_legend_marker 1; GD-Graph3d-0.63/lib/GD/Graph/pie3d.pm0100644000175000010010000002003207574175643015664 0ustar kilroyNone############################################################ # # Module: GD::Graph::pie3d # # Description: # This is merely a wrapper around GD::Graph::pie that forces # the 3d option for pie charts. # # Created: 2000.Jan.19 by Jeremy Wadsack for Wadsack-Allen Digital Group # Copyright (C) 2000,2001 Wadsack-Allen. All rights reserved. ############################################################ # Date Modification Author # ---------------------------------------------------------- # 2000APR18 Modified to be compatible w/ GD::Graph 1.30 JW # 2000APR24 Set default slice label color to black JW # 2001Feb16 Added support for a legend JW ############################################################ package GD::Graph::pie3d; use strict; use GD; use GD::Graph; use GD::Graph::pie; use GD::Graph::utils qw(:all); use Carp; @GD::Graph::pie3d::ISA = qw( GD::Graph::pie ); $GD::Graph::pie3d::VERSION = '0.63'; my %Defaults = ( '3d' => 1, axislabelclr => 'black', # values on slices. black because default colors use dblue # Size of the legend markers legend_marker_height => 8, legend_marker_width => 12, legend_spacing => 4, legend_placement => 'BC', # '[BR][LCR]' lg_cols => undef, legend_frame_margin => 4, legend_frame_size => undef, ); # PRIVATE # Have to include because this is a different %Defaults hash sub _has_default { my $self = shift; my $attr = shift || return; exists $Defaults{$attr} || $self->SUPER::_has_default($attr); } sub initialise { my $self = shift; my $rc = $self->SUPER::initialise(); while( my($key, $val) = each %Defaults ) { $self->{$key} = $val; } # end while $self->set_legend_font(GD::gdTinyFont); return $rc; } # end initialise # Add lengend calc and draw code 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_legend(); $self->setup_coords() or return; $self->{b_margin} += 4 if $self->{label}; # Kludge for descenders $self->draw_text() or return; $self->draw_pie() or return; $self->draw_data() or return; $self->draw_legend(); return $self->{graph}; } # Added legend stuff sub setup_text { my $self = shift; my $rc = $self->SUPER::setup_text( @_ ); $self->{gdta_legend}->set(colour => $self->{legendci}); $self->{gdta_legend}->set_align('top', 'left'); $self->{lgfh} = $self->{gdta_legend}->get('height'); return $rc } # end setup_text # Inherit everything else from GD::Graph::pie # Legend Support. Added 16.Feb.2001 - JW/WADG sub set_legend # List of legend keys { my $self = shift; $self->{legend} = [@_]; } sub set_legend_font # (font name) { my $self = shift; $self->_set_font('gdta_legend', @_); } # # Legend # sub setup_legend { my $self = shift; return unless defined $self->{legend}; my $maxlen = 0; my $num = 0; # Save some variables $self->{r_margin_abs} = $self->{r_margin}; $self->{b_margin_abs} = $self->{b_margin}; foreach my $legend (@{$self->{legend}}) { if (defined($legend) and $legend ne "") { $self->{gdta_legend}->set_text($legend); my $len = $self->{gdta_legend}->get('width'); $maxlen = ($maxlen > $len) ? $maxlen : $len; $num++; } # Legend for Pie goes over first set, and all points last if $num >= $self->{_data}->num_points; } $self->{lg_num} = $num; # calculate the height and width of each element my $legend_height = _max($self->{lgfh}, $self->{legend_marker_height}); $self->{lg_el_width} = $maxlen + $self->{legend_marker_width} + 3 * $self->{legend_spacing}; $self->{lg_el_height} = $legend_height + 2 * $self->{legend_spacing}; my ($lg_pos, $lg_align) = split(//, $self->{legend_placement}); if ($lg_pos eq 'R') { # Always work in one column $self->{lg_cols} = 1; $self->{lg_rows} = $num; # Just for completeness, might use this in later versions $self->{lg_x_size} = $self->{lg_cols} * $self->{lg_el_width}; $self->{lg_y_size} = $self->{lg_rows} * $self->{lg_el_height}; # Adjust the right margin for the rest of the graph $self->{r_margin} += $self->{lg_x_size}; # Adjust for frame if defined if( $self->{legend_frame_size} ) { $self->{r_margin} += 2 * ($self->{legend_frame_margin} + $self->{legend_frame_size}); } # end if; # Set the x starting point $self->{lg_xs} = $self->{width} - $self->{r_margin}; # Set the y starting point, depending on alignment if ($lg_align eq 'T') { $self->{lg_ys} = $self->{t_margin}; } elsif ($lg_align eq 'B') { $self->{lg_ys} = $self->{height} - $self->{b_margin} - $self->{lg_y_size}; } else # default 'C' { my $height = $self->{height} - $self->{t_margin} - $self->{b_margin}; $self->{lg_ys} = int($self->{t_margin} + $height/2 - $self->{lg_y_size}/2) ; } } else # 'B' is the default { # What width can we use my $width = $self->{width} - $self->{l_margin} - $self->{r_margin}; (!defined($self->{lg_cols})) and $self->{lg_cols} = int($width/$self->{lg_el_width}); $self->{lg_cols} = _min($self->{lg_cols}, $num); $self->{lg_rows} = int($num / $self->{lg_cols}) + (($num % $self->{lg_cols}) ? 1 : 0); $self->{lg_x_size} = $self->{lg_cols} * $self->{lg_el_width}; $self->{lg_y_size} = $self->{lg_rows} * $self->{lg_el_height}; # Adjust the bottom margin for the rest of the graph $self->{b_margin} += $self->{lg_y_size}; # Adjust for frame if defined if( $self->{legend_frame_size} ) { $self->{b_margin} += 2 * ($self->{legend_frame_margin} + $self->{legend_frame_size}); } # end if; # Set the y starting point $self->{lg_ys} = $self->{height} - $self->{b_margin}; # Set the x starting point, depending on alignment if ($lg_align eq 'R') { $self->{lg_xs} = $self->{width} - $self->{r_margin} - $self->{lg_x_size}; } elsif ($lg_align eq 'L') { $self->{lg_xs} = $self->{l_margin}; } else # default 'C' { $self->{lg_xs} = int($self->{l_margin} + $width/2 - $self->{lg_x_size}/2); } } } sub draw_legend { my $self = shift; return unless defined $self->{legend}; my $xl = $self->{lg_xs} + $self->{legend_spacing}; my $y = $self->{lg_ys} + $self->{legend_spacing} - 1; # If there's a frame, offset by the size and margin $xl += $self->{legend_frame_margin} + $self->{legend_frame_size} if $self->{legend_frame_size}; $y += $self->{legend_frame_margin} + $self->{legend_frame_size} if $self->{legend_frame_size}; my $i = 0; my $row = 1; my $x = $xl; # start position of current element foreach my $legend (@{$self->{legend}}) { $i++; # Legend for Pie goes over first set, and all points last if $i > $self->{_data}->num_points; my $xe = $x; # position within an element next unless defined($legend) && $legend ne ""; $self->draw_legend_marker($i, $xe, $y); $xe += $self->{legend_marker_width} + $self->{legend_spacing}; my $ys = int($y + $self->{lg_el_height}/2 - $self->{lgfh}/2); $self->{gdta_legend}->set_text($legend); $self->{gdta_legend}->draw($xe, $ys); $x += $self->{lg_el_width}; if (++$row > $self->{lg_cols}) { $row = 1; $y += $self->{lg_el_height}; $x = $xl; } } # If there's a frame, draw it now if( $self->{legend_frame_size} ) { $x = $self->{lg_xs} + $self->{legend_spacing}; $y = $self->{lg_ys} + $self->{legend_spacing} - 1; for $i ( 0 .. $self->{legend_frame_size} - 1 ) { $self->{graph}->rectangle( $x + $i, $y + $i, $x + $self->{lg_x_size} + 2 * $self->{legend_frame_margin} - $i - 1, $y + $self->{lg_y_size} + 2 * $self->{legend_frame_margin} - $i - 1, $self->{acci}, ); } # end for } # end if } sub draw_legend_marker # data_set_number, x, y { my $s = shift; my $n = shift; my $x = shift; my $y = shift; my $g = $s->{graph}; my $ci = $s->set_clr($s->pick_data_clr($n)); $y += int($s->{lg_el_height}/2 - $s->{legend_marker_height}/2); $g->filledRectangle( $x, $y, $x + $s->{legend_marker_width}, $y + $s->{legend_marker_height}, $ci ); $g->rectangle( $x, $y, $x + $s->{legend_marker_width}, $y + $s->{legend_marker_height}, $s->{acci} ); } 1; GD-Graph3d-0.63/lib/GD/Graph3d.pm0100644000175000010010000001132307574202430015073 0ustar kilroyNone#========================================================================== # Module: GD::Graph3d # # Copyright (C) 2000 Wadsack-Allen. All Rights Reserved. # #-------------------------------------------------------------------------- # Date Modification Author # ------------------------------------------------------------------------- # 08Nov2001 Re-sourced to use standard module files and structure. # The package is now GD-Graph3d which us what people expect JW #========================================================================== package GD::Graph3d; $GD::Graph3d::VERSION = '0.63'; 1; =head1 NAME GD::Graph3D - Create 3D Graphs with GD and GD::Graph =head1 SYNOPSIS use GD::Graph::moduleName; my @data = ( ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], [ 1203, 3500, 3973, 2859, 3012, 3423, 1230] ); my $graph = new GD::Graph::moduleName( 400, 300 ); $graph->set( x_label => 'Day of the week', y_label => 'Number of hits', title => 'Daily Summary of Web Site', ); my $gd = $graph->plot( \@data ); Where I is one of C, C or C. =head1 DESCRIPTION This is the GD::Graph3d extensions module. It provides 3D graphs for the GD::Graph module by Martien Verbruggen, which in turn generates graph using Lincoln Stein's GD.pm. You use these modules just as you would any of the GD::Graph modules, except that they generate 3d-looking graphs. Each graph type is described below with only the options that are unique to the 3d version. The modules are based on their 2d versions (e.g. GD::Graph::bars3d works like GD::Graph::bars), and support all the options in those. Make sure to read the documentation on GD::Graph. =over 4 =item GD::Graph::pie3d This is merely a wrapper around GD::Graph::pie for consistency. It also sets 3d pie mode by default (which GD::Graph does as of version 1.22). All options are exactly as in GD::Graph::pie. =item GD::Graph::bars3d This works like GD::Graph::bars, but draws 3d bars. The following settings are new or changed in GD::Graph::bars3d. =over 4 =item bar_depth Sets the z-direction depth of the bars. This defaults to 10. If you have a large number of bars or a small chart width, you may want to change this. A visually good value for this is approximately width_of_chart / number_of_bars. =item overwrite In GD::Graph::bars, multiple series of bars are normally drawn side-by-side. You can set overwrite to 1 to tell it to draw each series behind the previous one. By setting overwrite to 2 you can have them drawn on top of each other, that is the series are stacked. =item shading By default this is set to '1' and will shade and highlight the bars (and axes). The light source is at top-left-center which scan well for most computer users. You can disable the shading of bars and axes by specifying a false value for this option. =back =item GD::Graph::lines3d This works like GD::Graph::lines, but draws 3d line. The following settings are new or changed in GD::Graph::line3d. =over 4 =item line_depth Sets the z-direction depth of the lines. This defaults to 10. If you have a large number of bars or a small chart width, you may want to change this. A visually good value for this is approximately width_of_chart / number_of_bars. =item shading By default this is set to '1' and will shade and highlight the line (and axes). The light source is at top-left-center which scan well for most computer users. You can disable the shading of lines and axes by specifiying a false value for this option. =back =back =head1 VERSION 0.63 (6 December 2002) =head1 INSTALLATION You will need to have the GD::Graph version 1.30 or later installed. You should also have Perl version 5.005 or 5.6 installed. To install, just do the normal: perl Makefile.PL make make install The documentation is in GD::Graph::Graph3d.pod. =head1 AUTHOR Jeremy Wadsack for Wadsack-Allen Digital Group. > Most of the modules are based on the GD::Graph modules by Martien Verbruggen. =head1 LATEST RELEASE The latest release is available from CPAN: http://www.cpan.org/. =head1 COPYRIGHT Copyright (c) 1999-2001 Wadsack-Allen. All rights reserved. Much of the original code is from GD::Graph: GIFgraph: Copyright (c) 1995-1999 Martien Verbruggen. Chart::PNGgraph: Copyright (c) 1999 Steve Bonds. GD::Graph: Copyright (c) 1999 Martien Verbruggen. This package is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut GD-Graph3d-0.63/Makefile.PL0100644000175000010010000000176207403766570014200 0ustar kilroyNoneuse ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( 'NAME' => 'GD::Graph3d', 'VERSION_FROM' => 'lib/GD/Graph3d.pm', # finds $VERSION ($] >= 5.005 ? ('ABSTRACT' => 'Creates 3D charts with GD::Graph and GD.', 'AUTHOR' => 'Jeremy Wadsack ', ) : () ), 'PREREQ_PM' => { 'GD' => '1.18', 'GD::Graph' => '1.30', 'GD::Text::Align' => undef, }, ); print < 15 } # The modules we're testing ok( eval "require GD::Graph::bars3d" ); ok( eval "require GD::Graph::lines3d" ); ok( eval "require GD::Graph::pie3d" ); ok( eval "require GD::Graph::cylinder" ); ok( eval "require GD::Graph::cylinder3d" ); $graph = new GD::Graph::bars3d(); ok( $graph ); @data = ( ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], [ 1203, 3500, 3973, 2859, 3012, 3423, 1230] ); ok( $graph->plot( \@data ) ); $graph = new GD::Graph::lines3d(); ok( $graph ); @data = ( ["Jan 1", "Jan 2", "Jan 3", "Jan 4", "Jan 5", "Jan 6", "Jan 7"], [ 120, 350, 397, 540, 110, 287, 287] ); ok( $graph->plot( \@data ) ); $graph = new GD::Graph::pie3d(); ok( $graph ); @data = ( [".com", ".net", ".gov", ".org", ".de", ".uk", "Other"], [ 37, 25, 9, 7, 11, 3, 8] ); ok( $graph->plot( \@data ) ); $graph = new GD::Graph::cylinder(); ok( $graph ); @data = ( [".com", ".net", ".gov", ".org", ".de", ".uk", "Other"], [ 37, 25, 9, 7, 11, 3, 8] ); ok( $graph->plot( \@data ) ); $graph = new GD::Graph::cylinder3d(); ok( $graph ); @data = ( [".com", ".net", ".gov", ".org", ".de", ".uk", "Other"], [ 37, 25, 9, 7, 11, 3, 8] ); ok( $graph->plot( \@data ) ); GD-Graph3d-0.63/t/bar.png0100644000175000010010000000610007372612126013722 0ustar kilroyNonePNG  IHDR,hg-PLTE**TgggFF??? tRNS@f IDATx=s9#vYO4ͨٻM|e덦j_.:5ӦQ^= !QCF RPa@!#!2NU!rvT*TU!gfoQ!?vC 9_qvO?mr> )K|a!e7RgSlPgTʧBL)em ^rWUC9\hyOFH}b𼍆? "/&!n!3B[7]vD 9 Y|bLo<**m=) ";ӅUI|mTG<. q9' t]x|w(]|l-~bjY;bvq"(<\i.lN2 puy90{otgNQ\b.eOH q=B[S{%oЩnoruՏp'=:Cۼ*f?Kr5y װ7R=]I3bq9b 0}ʃ.0ޥ1]+! ]*̅ s'pi?X n4 }.rRqUҼ#k.7Υ]Gw|. wQ/K³۽yK:p]v,֊XvpžH'oB.~*\^ȴ&Fl#%{yxB6S{9B4evΘ6c$R?_hQ|;HD8vl0~75ao,2;Tyf*4fEH+Vf'JAN5 UWSE {>15fyn"kQg!BPҞC"E"E$&r;!"iG$'ȱXtav"#$Pp+OvI9]IxymdA[UE~R-4D *V~E\G͛""YdL<'HKaw\0VFw?0jD.Z R Hf|?mL#\[9Q)BmѥړRH-%>^C]tK!ߝ6[ڲel${ArXrُY[bMܗ@Avs$} #uFEZr$k@(LoiFD< qx^ȭre e,f$5 YI2HjAR m/,ZQy ( M*ZQ)Br+E,f$5 YI; 6z\OXVM tDocŞf #2R =!@\kK43@A в l#E_wpalM!N:ddՊkR[N[/ATL(dWff]?9d|#ZK-R/H${ @"0+cuA<Һ(͎9oDj(/v:eQ LS2ȴ9 A<rG\%H3{ $AuV d&~CmEIϊ2 {x5Xgގ3Yκٲe˖i%Y Œfv #4;i46eTʯ( _kXe$Y4.YaIENDB`GD-Graph3d-0.63/t/line.png0100644000175000010010000001245407372612126014116 0ustar kilroyNonePNG  IHDR,hg*PLTE**TgggFFE]\tRNS@fIDATx];o8n7HD -FGACTc*}X,v&|}BSC)HKهR~RPD)HAL!י|rޔyIyߖ8L2A7efٻ9)fJuxyRz-Rń0sNgZzS@ H%SDn䛙"ʋ\MK’22&?gϤ͔LυqcZ*ާ[*Fw'/a"߯>;%,rb7$kT<@<*[SWSiDhad窎b0W8W5<<᧫X -TN#9Bsf&YúFvId,Q/юzꆂTZ']nd4!pC=Q|qI?DǩAUU>]M@Ew1UzS D )=8\PH  , ɍXGe)} *V *KyC-m 6?TAAP ׳8)13|WSbz OIiCkd4YbVZLj6 WuQ}UKh*TtV|+ *2zT0D7g`/m9r[Nl JR1qn/Y0)s0Aiܖ**vP# b!- ]ep#PPq TedL ME3Ԗ~"gruTηTg*?2TL-C$bO!ĞT|+:ObC*2Ps+Pycg`Ect-2[v\>_M#>m-*Vn%uIH+s r!$8|[nX '蟈 63忹7^_{FlB݋F(0A(:$jfpCڏ=zd-A}, L@@J@nY [`^!oDj[`.-sg=^)|9"6Pfb>+2֬9 |8IA A Rj2(ÕBX ٘R*1J 4/%F)O(;5J]-( _FV^,*b &Q^^ A嵶+" e2'KAh$l\.2JdBc{!XœmL{! -gBs{KX> 2賰Y^qA0 yyܑ9af@+^U ;6o! DZ>PóIB|[6wbAΟ&F"=j|@vvq PDIŃzVXC~}G4JAo+X'`do׫鍙 ]qޘaxfbIgB{=(am\ E$AFF 8lw7Dn6v N @‰V"vwA&I%%s$d{t_SIFm_K)hiH &{MoL>8"@пTy H bPY`*)H,ӥ@!]ݛRw| CD5*Q<7%+7fD7Yo A (SoLh@;hHě`  8ewiDT%- IkHO$or$]qTlS,'0@DLb]6H ABͼ Mv1I&y\ψI 9vHbmC $Xi@rLkh$^&P8fH&p3ɳ\le$^B[ĶT2q ϻ8]oD~D@h( uصY}NgA$}!WpO"F2@cr AA;;G|I8;ZDv!Ν#-";AtĤ cmGȎd1|mʏ(u{ۃmuy@KD{axD); vn~Sy&#.H+u_bCIRg+DGԎ,9:[)BZ8xޓ?Ze)<&lnݽCjuPou8t>d ΞngtBFww7fLH{-Gѣ4w"=H(ZuT$"IC"ovrtbRn[DގE>C>Cހl-$>+}^;\cv_\h-1Fx "{#HxD3DxDH/iu"LSG{"}ً_ "N܄!0ONlF$yZDEI+<~" 2`HS'y"! ׭W 2ÈH"-`؄INd |+;'"^*IM}@`ݶnݗ'YCuW"|k*&8=-vuD0Q.ID ?Eyڌ&AD̢,"n ôu fQkG$Yֶn݋Xy~DAAUݺUni'bT݇u7qQ-/d{xD̊58 .W"V-ooT'bWqo'~1X6n -Vm$s-Unuus"-X&NbH["XKWX[qO!˗ۙK9I!" [6N*P-DByF䱂ܣ=`9XnNy}3D{op~ELIΎ6R" DymB_e2IZZK#` !!]b;"C $p&2{nKd(ۭUKd(۝hw6a ס2i͝Eh"IZmfQtagFFn$My>4pTD{=RCDT%Ki- EU=KBoى AeË_Pv[aŤq SXZB$I<D|bjE3H]bG&m Gre ' ςX+{Ek{KPKtEY2'ɇ>{Չd s-%Yήz!c`2 6m{A]~C r-&vx$eXG2 O^跌)o{uueűHh{A QBK3_!'Q;9sM.h\GHAY> n$.fls"^$Ikz6$iAk Ȫ0KIj ڶ%JJ\OdukAsp ">E7y<&r'mY6H6ukY)}[dIbI8*y `tJ3ȕ,zT-PPn% SPb3ig)O&g5,6T.U)HMx)"'6=J\}1U3B$TļEVrKJ+߱Q}Ie *YsN2Qr3CAM$d\5J9"(YKKAɴ(|JI9+߭{R] Jr%3wVk?,3I\u)O_̒һXHI%wv:+q܎J=A4E%UR +s)({% ^TPb%!%<\\̃bSrJ$ (OJJҿkW2N*y>_ѓេJV&|-ws6-䒓A ^J@P|c5ƼӼs^{nז5{0uJ{g$<'tS}kN)(]b]J\r`%6\@)=6O󻤲x%K(IqAz%!ߖw6%dfImJ~e%6DC,Jا޷JPɒlz\&J'MP2b%ܡwJL-(s),n9#ۄ %=*]]`=I%䷠dq9%)yK))HJ|x]>/D%>5d+C#/^R4t$_(!g('+ .%W"xV(:`T`dB%8|jGĘV*Ć%ܰJȝ$x%^?N Jb஠?ݎ~SH%!Q$AzT9VJŹm$J%edJMuJd#w׃JD3qJ%r\w"%!v͋YbAH0ٵgJQI b%J,nP (ygǜ#qJ%bPr[%unӹUT<)q;:rJqYp?dmڄ@dyo>z%wwd\%%KyJ -J^9_kąf+[wL\ J %]`s%s%(1&*ȹk+aXsVXE/m]OJI%J|%DkPbsܽۗ5wݒvqJ|Θ{kpJُMk(%P_:p#y%K1aUs =]2*Y>P>JKXm.XB]dhMTr JbY%7GtPUؠ[P2͓Wa0P\6p'*y}CEϐ9.0Pw3*y"%Kf,/P|b%6Tl[@%>"6_o.L>(qǟ,Ę$qv.nG^8:+ϲJ~A&T Ξg[OȋERI.*jwI>L꩓ɦFIr^oo.NxeBWCVJ.y,ɘFÂ#fNgN&ʻ^#˘Og]ŭ#9.Kݑvw QkP.pd)( G XGh!+-lAc:#" tX_#'әI&s\:pD/ "r*"YD$$`{kA?x⓪p?ÑDrꆯTH@㴎~gt!בI!II#Ӹ;S3ƽ L܊{>e k)E$sdtXxGnbGxiGfsG \lJ8wʑ-Kɑ8a~)827#nݻ:HrS'";bGƹ#s*{wS~̫ ̲?uOY@dYTh#cSY{R^,&SsZ yfC_v ,юGeGr$y`􎘰jqss@b 7ޑȒ=صGXWpd1e%eUz^#KΘ7dxKđ]tfcH5,P_&u$6N,P=mѤaP8/oM k&RG|:# #&֑'ޔfź:ϗ$,&?=+s5ۍJ$%zs$mŷ<G.#k#Bhs*‘'t;DHrRG@*pkZAt#]~y*xfBG(7.DߩD8t# {;2*H$)[gy"'Uh^`%[/Wuׅ]Fo]:,kG\J|G] 5?*qG#̮zGPo=e/@`2:B/t鐒ۣ4jPٜ-0W#+6-ZEGpq+|*5y3+VG Ilo#PT",4:p$EFD,4:< ;€w$yj;GbJ__xbGO\|:G&ّؐtGq-Zҳ8w|2-i<"كtG W|G Ǟu 9rMGyw-hp$6AؽEkI'HoTpĸJ#1+>w\Ɏ8zFrhG؝HLtY7#(`F_9OKH6)S(zՎ0+vg#ĺeG 8iYp$V#63Rܞ$#ȝ!Pӎ,k:GБ[93"_bGlG~= 3Gb[9rqOӎ,PluG?:n~vHBu#W+vd{!q<‘u-H{PX#4nH#t0q%̯H41w#FpSoTZG%n$H8';):ؑe+ }A',%m^B;bwQ]IGܖ׻^/*'&GޑPp0{Ic#ƖdT|?r{R6~p$θf[W9b1P$#?;7lU9ngݘ:GnY},F ?#˞G jGȏ GuP!:Lue`Ě G #U<.@_ȝuwܑ8Yvh{?COTy?MN9bn#БpN8ן[E #!P|HGI*\.GBz 2GL<%:b2G\ךDrQ#<GGh_Ub3K8A<Gb[8B!"9zÍ3GVS爻'> iqJdHdrGHlƏJ -SXA m1ZfKERCm 5a$3N2նQl7hm:` 7{ŵ^խb;}j?Ɛ =4 =$)=zۀp )E)2 |dj3@Qy*9 Rdj#@(萭c& ˺~;}%C6IuȊJZb"+ R($| 0H~ru -Hʹ8\|# .$E]E__pC_JĂ0Ry^ZހķWOcO!cQSHt- u#LDe3+SHH;QtIRTfJ@ߗ'w*єsнk ! mm$6߭2 V.~ <GG8tF}#4IqW H}p1zb#& Wox-C' k$In(s < ? bI$xh!$ =!kmF_jB޳= lM+q y W G6@B/ `e# Հޖ;1$d`3 >xtT rP@Ӕ\7 PHL!~:XHQ^W/ dH" H,w pgZ~gW@\ pӚs@`?>pޞUG3 x$4B N`]&Ϧ#ؓ0+$FHA% $}}W^h, ;_niu"E {aI!4L7ipn/@@ ÷4c(@AS; .=!ܬ$A1yp&g$ m >d/ Y 5AQN~t[i- <$b4@DpfH 0v@. i5uə2X%t}&3OP *i}l @\I'űSRh1 t& H C Z"% Ş_$ .p0 |s@{Eq5vnA/ZMM&u]Z?Pþ+ qzYY8 n;," 'LWuC=>5G٪j-$r/r; ܐc.K,o6 )b j FW__1s ^rW9Y K3{89@ (P7Z ^=@Z> #KD \ȐOBzH^'/t@kuUtT2 ^y<NsP@Ll!:IlDU- zDg @B6IS0$ .tc8mO$m{b ȦM#`< !VAj[ K2 $eHIlfr"b @y-:mn+Ue ̀P91TW] H:k Ifw# % $e䅨k\  p/ĕZ+MI9 > 5~rcLEY@ ]@v$+'|Ph27U[$Ğ@y cl- (rJϔZ@y =5 FEV,Ds")@@>=2 9Hl =5<@LА&4| :mÓ&k ML@`w7{{D@ĻnnH$+-:p>!>0R[E$+T`jrq:&V?1 ==M2dȐ!Cl%;ֈ_tS^8EŅ}Sc@وxh>U>oBxzU# 8SM>orګ;J,x; tş5D^Pn-3ujsRbKW&Dn|= SeBQT))BP\&ND|"6@Hȏ4&-" |+$~YcW>:(Q@%4TZgG28 y opl~x@+” R!ygZ顱yTj(&% N<r,%UMed TZ`$z #7iDR*, 9h eUk #R)D hɭ$F$> HiJO<0P!( *d0TNoRR!A; IK_@T!+Z7Ia/ 0 !)@l؞ 90x+1]e!CB=Ȁ$𫬶$VOt ;" ^?A$@PHNc%{3"N MPE@-4; \#* l z # ހaPysg@F8WQ ,aaCaT´Cx ۈ ѯTp S G2' S00Xafrl"wCs\br@80ظT3Cu)@OP ~lVD, ! 1 2 Nte @\UvdNP @>(B@&T3g<ՆL1nx ?5U@2%MusNLc^Sݜqw%J3&@T Eq R% `db `z IDAT _ <(> xWR |) R@D1( ! @p.wP}T1 ~k%P<$'JB $)@BP%V.$ +C zbD) \Be9Cjd#b#+bnV2@[b>#3:BEs'3AslɁ @= '<y@e!@{>y.ky%>d@Ȏ&Brb@{vhrJ< B2 @pMFU$ϝI+aLZe%}@I$rqC@B {w@⎀@cD#}1쾀@i@BrT@^ۉՇlV ?A AТHV@ 0 =( Q|UPr(H+ @ʑ1y0Ac@BT+wVK 4 D#–I`Ds@  dO>F%@T1$T"[ Ԁ(1Y_y2)uVb[!["ˀiPG@ y< UwJdsHY<2 'bZ%H9Aup$+~Aٍ!Ј %H99@.G {=)#`@<> it5A+\(@bOUGD@`O  E>z@d{Dp3!oFF=n$辀0 U=; ¹AQ5 ItNPq3m4cH@|MP1y.w@I5ċ>(e HiT4 w @ٴ. ug@%fu<m}#@'W*@ = Dz@L Sr$ml81r8 iؼ "?HibHl&$%ev [ Hh1YyۀȋU^*19A E:pS*@(J++ a lsfH204Cj,`8x=tq1O+x擀SI]3}c.ghSx28 ZɕW7RevA?5 [x_)Dbx2]HHHv z*j\+ `Y-U dWniCxQ蟹V8Kt\d l6\b .ZӼ@@bo!V^T0H.$e \Xv 0т$tYRxɥ B?@E 7!}qOb9P>l$edW.jU9X} r6ʇMz6xLz5 ^$]X, 0y@3I HlwV.!f&6@BgC'!2 <;< ؠ h Ԉ I*B@׿5||.@&Pٔ1 %>- J3@l HCwg@b/^yH $Cfny H̦WătqwfIBbj@L3;HYȦICj>d=I$Vaej)-V/Rؖ$L2Whu b!JH\ bZ1I D\*B$!eʩ"ebʀ26C r@ A*@C;ACW@#e[y!BѰ/CրGBb3Ȁ$X- ?Aޓn?H\R춀t6$LP櫼#5`0<G Hj6Qak!m0 4cB'M G(w̞aНGum &P HjO xAMkɀ C>ⷅf &KԞ @ɨ4(3@!B'PHjO iLɨG'&![d% ړC< YLoOr'DYSI ܓCY3@|&8<ʀtSi3@$- :`@<"fAq*M:|$\+ͤD f\Ld$9*@ǩ4̭[+h#7LPi2 1T|!CS蓵[77xl;:@{>*I!x<mѨ΀@TGG67=T$@}g4dHO @ H x]@ Of=?+":=GD@nȳ챀Hkl tx4Va6z@d\,o-( Hqn GihgX{f@4xnFAAB H6AvOjن DvAA ~'AS  d 0f62RH "cݓtA6%H>VL '+ ԙ# u h H%RIF;2Rm܂U% S u ԞttֽAAuÔba / 8ڲ[NvOju|^E26[),@d t0Uf= wAA-apʓtA#`pAAuQ.527nڔ åAAs SDy H|aj39.AVN 1q6%Y d5I+餤lNu nXoԂlS͌dZj A?K.g#H`apBc ɷ;SdJ(x  m25R'&H =''HWY& Q#uZ  7QXh?kʮDX1AHz*+訮&BaϏo'HFQ]IJܚ / 1HiP~ܱ & ^sғ4Z:#^xd"ًb%(QV&x6T07o$$hdڂ(^܋ t(Aă@IzOoyB!)-AU 6 OXf'<\ HZ2C~5QuBnAZ $%!J: "HlTueN=AA "r@Er9Ҵ0GY2H Ewt!HiN 4SudVIٙ[dUhlQHnңjsU }-H!lA5x_тhaJg'XVAB[)UIA Sz4 =V GBv-AU 66L1U1q x%'B\44Ru촑v7LUuHgɾ WM>`i?T At;m( 0ՊF3A Fj(_I7AO."KDY i#ͤ;njE̡c W| Dc5 ׊1n1)iV4\zD+ rV;:W6L Do:)(ȂFWSd7^M `i]jvA rZΡ2Q>Atiך͏WhJ-HEAt|HX=6L Ajo"ODŪ0U>A|;A6 F 09b5Ae =$AJG'ЏۤUC+ ,kB)ul(ݺX5` 첆AAR::ؑ8QHң3Ae tTI:' + H~ʉBͲ?D4 'yp]KD!*L${z-A,kupD5QhH ǴA(zp+"8Z ̕k*ܯ$j0UC [ww!A*x*G_TVk*fd\ Bow Kqdf`Bu**F S$=d\/zlG(Y'PXzTDd݁Qu&dnK3q_ iOڈ az^@'Rl"Wgxu#$EV؛ bPE |k4,[؇ KU@ DS>+475B_=p'+v$~l :).HR#э l: R?B*]{?YA-0q\A>>Gr'Ht,NVDabC40$Hy+upp=n^ٝ Ɋ]"@HG3Ai"SIv{ԝҟ "U:M#d@=|Ajcɽ R>YhQAj޼ BG h]J| y R 8Ae'_7$a[ R1_p@DNFy*a6LibQ$  m)'"HZ{Vq#aJq!F5udE:!HXd)Ea3ж,NGsQɤJs|ۊ )2gdzpѷAPAʞЁEL-"mJ=n߀ X(U};A`9֚N?{vVp 7떻_ m 0oxIJ7A4@:݃ |`1/L r3#MAQ a5iUۃ 3!WZd7ZЛRm2KzB`Լõ?A * ޶}Wj(*$7{E[2.2AHz$ұ'A\VyJc$n{Q*3?B$ CKݪw4aFFdl LaJZeG˚R7asI4iÌ RQ%0uv~([C8{P`TǞ Ha/(fur@d\{L%1gYb4=}u4ߐ  dM/A S{4Z$HPqX uA-üXߣ gfQBAEAJsJ1%ȴ+A!"2UQ:zuETؐݎ)F; RcpF DdtHۍ U| RX -A NVT*3kƍTE؍ lEAtz2{-AZgˉB]52%bF 6qf_( >?Ã5'"Gƫj|XMM%-^/Q<(w$AHzPJ nRUD@YaJȝҎ֐dͧ 2kDԾDަ1ݬ\fČ:(A kωSDަ1ݬL^֏ RX{Kg\ɷ cLGTW?D?Cݤv`Z ي  Iw @p+D t_R >L  /'fҽ<; =NLU!T~5A4٫!IDATQ A:Y +O|ى E-UduF~aU4MeOF*1]A8zkH*OMъf7tDZ ~$[2 ^uND#H*-ec1_FH*,Z]\@8*Az-Y9_'4oOs]9Ad_M{C>Hg =6L}Hg-A:=05|&߾C݄C 2dȐ!Cp &|/ފN/o߆=Þ*{vM '4&c/ {=-2 /3M+IkSTVZ/s,d*+Y0/K_lOcIdb{:C 2dȐ!C 2dȐ!C %y2%wd2I$hM ĿϷ C5Qb4O\IENDB`GD-Graph3d-0.63/t/multiline.png0100644000175000010010000007157307403534266015203 0ustar kilroyNonePNG  IHDR ĐEPLTE**TgggFFFFFF##R>KtRNS@f IDATx]ق:Eo\RKhf KBUJ^#YmW!z rp|g񥭠 |qP wSm]1oHw\=?+tVJ@^n!v{(8y_ K-pf֍\[ 0Cݟoհ|Q*jDB'uQm} iij1Ѕ_@~n0A0Kab_&;a 04)6] o釥PVkid) @jdMK#}K#KClwFvVIl!,1LR]:i徥dJU8.S'0 Vo#7Fpد(-H.4a#4a#m@Nbq}vq(c#Ida\H#"R}`~Pě0Dc# 9QYN 4r煥 G)bΣ*rt;D VB0-A_zgrnƎ` =5OEicQ.c]s4/ˠ(. '}屢FDia"HIP0&l;3GxPH S\O`#4] c᎝c#y{X4Y.ZqapFX .z$@ [I+h,2Zg(fV 9 Nxgujs}?P=QNB-Do!u7h#z漑MՖT>d#_@طW!tfq[o!H\\\\t~zyf._[17&gx_;NܽZn3}lZJw{N@-0g7%ihGX95Nݖ/ݟ? â~"e}`D }R?,;5Y [j`nTRRqcc C B\ ݰKcXNj:FF qD9X! 3wӨ`)6crN0o!H i"){B3qz2wEGgaU)qY@PT/@3>#srFj03uibmȊٱ:fbFv+ǿΓz?+!1S&sPΆy1Bab?*Ŝep$Q$vOPr\aa)f.,]X*qidqGrI?>FG^bkpD61ݍ|H1-Ԫ/EȘu(s>b.+K#\j_8}.-ǜg;^Z;6%8?b˕;I-&Zڗ="1W)^׌9CȞw|pGVS#jJJ T-F%c+nePLRŘ1&`;mSaH8+F T!mBiVq5]A#3H,62NkJ=5R\k"G 4P-RV\a^٤Zj8l#Jګ-8`3G.o׉¨F.G o`Ӻ. N]tEtM]N#:a#uV^-ЬƆ?RS]r[o6[l-{[YKȲ' 2VYKm!݉@970}VhfwިqclA\A>u7:@Ukl˘Iv2@ܸޥ8B8HcF }*3Hp;iO/ցkgQM (e0FIJEg&'cd$D;5LH=uC^ơ##  V6:FZ;":5gi fui@Nb<@1Zd {͝-2" q96G`&\hq92{8fW&]cvq0|hzy:Z:KjM8Q{$'V:\oČWp;/K"A~ AYi Fή /#$3w\B%1.@kLmhX LG x':ePt@%QБd}Yԋ:)D AH(# #4S"ϧ,c;NYwwR]lat>LމG>iaC<܁Lq9Ӕ |K:H+p?0" U MC  4 K`3oA 1g$~ uIPsD U@NPb'rMGFX6rPl%&F:?HY<J3XLIzfLQFHpV=JBô `Ofє 3 F_BoO!O{zag,"}hi\nq3l-렛xj)&h7S@s"6p',SrQ6OtU sFX>?hs~ \2}g1x94 ZP! 6HO>vc-ua:%F"[DB˃gP4,_>N;XAڋ(9stˬ~:<89(`*[2bW3bqŇ:$|Gw Щ!9a<4*Ǭ9g̎7 N@GL{ -py)3p'OMR:*>Pɘ&8t$ xQy2mM79s?#irEc=G^ `iGx,i~`CThٿi.WՋ[۲FזC[9b5?M3/1y^B>큿 %kKjg*qvJݕ=Q.4Ρȡt]fI?lOE!"OE!"OE*iEX-fj.O+ \S该.袿ӝ^޿*]\xw٥|d{lb>%F|[ܝcb`G Tgf\{06mNކޮ\n8CotbLBG7Āv U{WE2$M,n4 G*ڛvn1(32 #-*|M bR \!!gcH_s-ă)#b~W'v]щyH?Sv('cIe#` /blS3;:Pɿ6o::KFadx*AaIW:-#>Z~+nI mFd"-&FW<5 =eOk}9ujm0Nʅ2 hLr4-3( O?xGN󹚡8=]NxP 4Nb.:DIҐy=:Ίk[ZDs2 "hÜ1G9:t`)]݄1s>O F֋H1(4ʨQ3FC-1BHDC`{}|03I99ϳT #h)|F ȈgU#Tadqg߁HnO$&Fp GNƅ{?v*¤=JCNNc{׸3QsgQ98h#aY/,F/vCvniұ:~f93eyT"lab@t)N'G=W0‡G a<P2j|]Kz(8pbb!#Aٞγ8jQ߁|{a$*a ^)/jÄk(~Atss|$Ȍ|e)pF|8lm#!/,ѣ!N1pAH814sװ͘;:]gTx](N04 #t·xeD BHY O~XzG>0#43L 8b3 2#C5ǻi~W3Lv-v; Z֟?N(64ḃ)>?{܀a|"cD-ZQNOlrFZ|do<H:=OX$w}=22#`ABV/#9ӳ+xfpGN:|CdxoP0z4S#9+H&eD2M&"DF>wr>*"3).H#pg^~L 7Uzޛkcޑ7b0>$g $N!7@b#́n6#>#>cQhy0# lCG2| T;5dg`|gzKIEec #qx=$Y&'hZ n8PJ]%-0|p= #/#8bErFpL%,C~ aDAbk9*vA5(x/&`"DW (T3g \V>-h7Fa/z VKC(8]mf>fqxPMH_<9 <1QXȟ?Ț.{JϏZ+PcYͦUo:QTޅsr2'4j*9J5߷yoM|/yo*L0]ju^ruѯf^ղ=N$) ^wy5gM/Ʃqj`wWY)[.袋.袋.袋.袋.袋. E]tE]tE]tїӞ8R^.8^.8^.8^ {яKKQ%`Qz7}җI/Ԋ$.8~D_!P&WKtF@z9+86w% ;Xv\;5Jk/ŽW.Q;}Z/*NǢEcVo@oɧQg "`إpô5)wKlWL>$MھR YAg_p#j<yUyげk/LCX8g}$|,.LYoIMڳ߄7W]U c]PG,HҗU/uA ɜّÌndQ g|j'4v^cWoyi>/ɪ|^Y|P~7f -8qg٧ ;S_!G"]'Sހ3_mt`?JGMr]7рn7~Ś^.zM;Φxm`Oz~oP1 Ko){k y+^d@zDya̽VA@/Q713:Bf _HybE:jyZnT69+=tVa)>YVM>##MSzE#=v]Vf[ҍ U~}VgāO(qkuzG'gi.:^iD_<y0iiCgg[noejo @IZFLEh}v1opR v@]^: jA*3ge{ST2 "8a9 ~ ! #Z,̳DU$8@;d6 Tk1WF 9@c5fDx>g\ t3L* HxSp)z^DOw&*^o9AYHcN/n%8 n @xHRe-s0k;ɢj22.`C5@DA^]y6!{TY xI5 FD^q⩩7Ӽ0r:5+\Jo^9 u?)w$+jH/2&36 ߄uCL $ϗ2O"aՈ0UVCLՉ cIKfJ@Ԫ@B P8U-!H$Q-Ԭ0NF4KR>E8 &y48B.(YQ^6:$bAȬ8sfBdJ Hlouz 6KqV +d$}M`]>qiӔ? G~5"2~KYiU{3bAWֆ[晧Qb5dRU :V)LZRafSjӈ2 "ʸOi6Io2g*<3cIjY= #6İ;/!eN4n@tszuF ;Y$$F }H%ИlH pA7Lu-:i2( ۮNTЦ͔H0!fQ@#Ikvb<7kGII Hgĸ ZtZ@ L6CaH,I!ydV5HԃQ5LhBɬV|FVZ}T?=;_~ql3$M7P/IcGz՗8a4,4um}%$ 46}h\G\$'!BDQj--8 qر< FHAm_4:G=R#2+ǔ4Zh"BY Vz#0~lo  6Ĉ0A.b6BL`FG<8C- :ڑ70S-bBdt9qgG{8QC!+` GH%nd IDAT0}#y4K( ʄT7OP^u؈[D-dĘn/, &I2 n;K4jD@tLďJ9s1[ ,WHBx2 HγJքLθ˱ 7bQIůMK߀EN̸+QMbֈ/"Ta G5`f #'H Ml`W@5IDe cocrLu{K-@ -Ahvd e19H7Ͱ <[eM< !{%(N;[T'ێeUpˤ;,܂t>5nOܥyZUX^GXzpk@)X5"|⦈\<?~LVМYSv̦[ߤ W@} y5d) `iҧw0Qd|XC&&kE9 z=on?>2΂!@6])FR1ˠME /Z`=YKfź']'#hC]qOeR='+#eb鏄Y n&8CNr3 QN5A Yؙ<'ŐUFj {I}6S bDS&ֶ~*YK<<6vB;$ϲ%G@;`@ǁF㨲f 5LY>V@b$xiN!m%@ldn?I'mA?Ee4-$ W:;H,۹rM/ dT!niB dNxһIAEǦ $-KN:Wh!>*74> l%CxϠFM 7au2R|V^Ϭ#]y0ӿ$'/nOW?7`z@rgcX䎸Aԉoqi|*)[r^ мۀWAo;"xt,U?tݼ`#X@!M[p _b ͠6FN23e?7 IY J_4:&FcAVh\6d>ą w@IkVi~'03V5/]SE8bEI,WҸ-cL_2l.'u!al?@_0lLE{aKӋ=li-!:yز律ӹV^tN6}9l~/Cte{C.@-߫J_Y5.:k <)1:iJ<.@sctʰy* V1zltgyC0x<ESzX#4Uƞ4ZU?"RQ<`ⱐӜтk ZQԃJ`W ПG)\קѕbEtMxi᱇՛^}s]!Z =mr]5?XI`b_8Rig;[/YIʖ,4M~#EU) L+򤊔 1aH0_M|DjP\L3W7d!ZnoHs|A j7'^?aw[G3 CN)3.x4P1?7r '/h(Ю "1njiƒp}|*pBUhSAiska܀ڬE_&н-x@m[G- *R49RѲip Q8X~bAj$  <~d`AJxnltB*80X/^F|J2m1#njֳH4_XVPDaMIq" FH{`$]7: CmNЫ{bRK~`~RmVZi£Wkb@ ԝ"1fgUd _Jќho*=u7- 5,`H?Yŷً8䓑eL&@4(1V} C Hhw00<YgǪe@Tʸ(:Wk : dC>t2 *`@2Oeܪ A2K 2KneeȜZIX\@Зx׺V4g"FkUTʼnh&X_'0@ }qN\#d{@?@x{*nO:u$^Mo6 )7\a@MH?s@st&1ސ1;Hr;[WP@fd#0GG,P$0(r1PIW&m 0Ļ$x"i^T!@6&V@M@ K^jt.ÚD/x`ٹdV@=%@} 5ds[;0k &.< nf&;mIXT0:1 c1# }{g, 5WgكdedDF$Y˰ Z8r " ޻8$k% @z]H a1 :cvx _d✢lѼ$@Hk^;S\|\DW~){{! YCC GrD4:@FItqbvMH E;H\L.2V]B,jnec!sJngHZ!|e&K 6Edd%{~WjȑVUU!vrg* Dxà9DRڡd N9LCMf _WCK4 (6 4d=} YO{ം̲E>Y#.gȨ`Hb_&gHp )ʵ5 @F@uErVB[|Bdכ:3gP Vq5d(˾VHͦΧ^B ԰5}' 7f) ȴmu7 Y^*2 \]){VI˺R p}5'ͦdkӁO9?îxPtdJɰWȵj"9˦Rxۻ0,2HWdL7LB+6r,BS1gH2k̸'6`R8N`s9x~"ţ9ey@hǿ~] O欑9\&[ع%Щ) f[e^p@܂Qo=R5@R -?,C'tr@0?&E- `c|7f"D\Yq]xY$@n`v= (O\&O' qu7 h!6-ѴE1 l"O,+RF8"*ղGQhaNu4Q!e5و7ڀ2l/3**b]tNy?TtzȜ @d*kȔ&6vw3/˵BxbLS!@ֲ:D% :'܊PT(Q*"D\H~!6$lb?iϸcGF~5T2Ѵк[";+{&+k<sg jADgSkdh'Z%4 B4=ՍKu gj`;% b %="Rr#)0 q: ,נŔnU4.XEX2zq"a/_Y{0 Hg2܋$Ua($IIC4&$yY0  aSgե$mL#.ibϯQ I}єUЭ7 j~CbR@BS:V qi{q! R.t:H ʵuJ~cʢ~) $ڐ&Ob$5'ϩ`Xո( %$@k1\ҮښdLyO8 ,jˤ㜊 3OzŠ&נ7q;t.ӫ%3_ p8Q@473M$9Ӣ!sz N0PDdL ; U7HZYqHJK%@ `ncZX#FU=+B=nF==d 6I 18o@ : ̭AM>  R܆!.J!Dgoan "l9ibV rp-ŋ7/_"nnƬ<$8'’$*K3,V妏tG6 s?4D< {Ĉ&~2pjɩf[?+#l+yOC#x|reiS G>e )t)Lt(ʇF"ɀ4zkyBl@&xvorh\dR$,3pRƄFqRD-:@ݺYvb>hUd례 /F:ZO젇YA[FI3̀|8@&ӱ ƃtaDkEHla5C ftO#.ņGT|,H 2,!d~,oem$13!a}F L4eL@S.' _Z`5!q\X qD7}XV,D|ux3@D,8bbJO `DkN6}D/!').࿜#<:-@+|R#l_:vWJ2 +Lq3a:MJ%Ԥ.#=8eV#un a-}IN"`E 㰍4 X81%$ؐdP0 rD&8ͦde-cBb6v9ANmiƅ8/юDZIj.HqMz@hUVITy":;@ Ȁ0iMd0OlϸG3Kt+&鰀TҵY{꛸Kv ,#X@Ó ٔ7Q&pnSE H'\W, ؁ n}Iyt:$YogSI'cB_,iEYs k8sh<XDݘ$޽ 30˹1L[6 ANt6 {{Sܻ O@$գ€fɃr 1LӳS} LW#d(ƣw IDAT. rvWcTj@XD8F1WMJ7ScQem ǕDO"?!f@R}:;:fQ ed/#%e83ܘ.]]YFD <~+t[qSt>HwGȺV aJ`gZ XBwHBdnih?3؞L{ 2(G>Z <yU ^&@7yͩP" xh7ۜs@,'F_HxT7p^)KA#J;M:dMX=);5>I5q͡Jq@H.PVE^CǫtA(mX>\  }+~l:E/DlA: Q p\Ҷ^\ 0u~~\ښ:E/@?.mCE~\ڪaŵrыЏK[%-u\2V,k\ ] 2] 2] 2] 2] 2] 2}nkצ=bvv xꢋFjPE}Za% usE_H?.mAE~\v~"qi۵~._ǥm:HÏw_D?.m !]muK._ǥm}Uͼx/EҶM}x/E> N~"qikl?~Տ~">i 3NmXAD.>e()Y=eA~#?.mм?<-\tjtSS|߽ЂREIZz'g v]D_ޫvuRȔ-K ^x{=a4\|e;J/ߩm?wdD7C$fT\㊗qykK~q gǺc-o^hM箃l-h\[6ȗ ׼WQ*\CִwWc_n3tTΧ,FnGAbK=RUvrR.*}e!ﱛz{ʐQjVt$4čOM=vة:<.CQ*-a|*|C~.흐|7B^UxZgk>/^JU _a٩8>uʅ'܂QDHP滖z,zKhrMhYMTm)gUv?ԘZXN% 7 dU=x|p敡 ^:5+4fݲ~4amB"5}K\\Ϋ{7ڦDRT=+WNWiΏg%\=hK%\p!nG`,Q.ݢnVB@iZwKiLlk{kո2)`ԃ4E@hj͏ipR=T~hT&(VЯ`e!fĺD_Y9kc,Y Aū[%7֐Cw{,En~)M jW2)c!ja:gqh yIAz?>eɨPmnvbzx-cɴ^jzW _M` Ԛw0I?U5&.4TUByGT)%4y_fzzܽkk>r~*M{z'E;D_BA3YU#AlmT(ơylϾ~H}#.㖙ECr1kKΨQu}hSl QZNQ9XzV]+ԎM?O7*b!};dMA;vu;`sՙz`+,~J*z}g"2ii3 QC;YtOM=Dk5L#Z!uUPUE=Tl'ߢ*W Kd#C2MV&/ލ>6tX(W 2[y7EeWl@&nf2Zkrt5n^AWRؽ՘kO[ -=/y ĹsOށX?h ͈ԧu~PALw[b{H/QQ[M0l2T%X5Ԋ@ 8')׫S&NڭH\n勘z$95Ձ5k:z~~`ckQ^*7h;)߶zp+RT4lBHCA iѐZ E$R)0y>5-s#4ft at1Tm*d{v`ٺIfݎw2AGLr+Mĥda̯Q`ʣ TH 12tZ h sܷieU8Qbȥ܆ UZ1zp [07NVdfvHyc m'eg$MdC$s75dYdJȜ F27(0߯sM=vP^ki޲+T75ۘM1ΧgQc?=xXDAHHUy!s`CA`Ž!IA$WI.֐,dFDtnghL䤫[ ev3ա6seVěfb'< 1 ;^)|+rYyDA܄*䶂`ʋ놂ҿP S$cȄ @LHDÄHnm'MH";KN-I̍YxUA Vl\[A178΀4سS}T}Z\C97!RldZAS(`m 7F+{"u-^A BXA|Y,|,a.k RjJb3\xVAwG5TAFh:칗c? 3ߦ$VSotHA~^GHxɀtdoP=Kh] قR8hEc j'(ib~+㕾1z_ B'+x/Hܧ;4)ǫ(yX/ {Pj( 5ɹCT $!VV ȄM6u<'/ Կ2) (=97nO(HaUW;>SY`]A%ЈsUCh$qLY*E٩ BEAL;%XՅBCN,<] A+z8CnKA?VNH& Ybn"r=RAfcy\j@0d "eF9 x+Kܷ̓r >3 bØҀԂ<9GAcV# G΃od, Y>_>p<_CCIv= B[Ѝz$2=95P] HS:gBr #c+H1aI3M_ Հ_ʕ,{H_C GC}d35 )@JwPk¶L5a "Jԣ!AeLXkF jU' \]AN RzXO[CtQߍ:l&Q,Ov"5zNA!LLz[:b0%!s+J8~7|,5lNXٯ.LUn(rܹm9!(gGW aMt<:vuB;,c$KMnǃ*!I 䆋ƄG^?Zt{6 'J7^8IA!l[Vۈp<{Y‹}iB𤀃 nl,LT1qTZxӢV? q ' 2V B&xz;*HHgCu=$naiUD{7o̖ǃtqY41L 7'Fcth-ŎdHPKCdT @ ZWK&Es7?'b_b:c%w+Fu$ BQ57fQg&S9?g]A 'KF16,a0_cq #8_ή$Q9*H@+oiUdංcL R0 mMȗ)ފxH.?Q6VLq77d780fcwr7tR]_ed9+!f'kܧ ,g@ƒx;*!;k?~Ԃ4iC~NnEVMn>Vaas!mmyU' 1 myo)H兩T48WBqʃ$ -cGwuy3o:VCwyʷwϊ @rSҷDK>`C\|l)|9Y|6X[UsL !+x(m\K<)BfbT\U4LYV|Ђl[mRHr n3q8Jû`zfb]UA XzšPK6eT׵Zm-Mnݰ=Bn@4MӴB澈pPn >mHo$%;br8l݁\1󞚔h}s'|Gطyɜ R) !np |7N)HJ(eQBP܎sv`W,҄j!حf()uXԶ5RAx+s3N~8)=_7 L uYnm d ev>K IS ^i/ \l+}]7Uo)HV& DR\Alb*lȻX+>ɮ bL QA*'˕ RbPU?$J YoV $gjO2*rbja?bVW)o]02 jB< τĶ(|/)ͅSVzIYЙYQ"ݿ4^L"J\DuȢ ܔ;*]-yBr n~MWĬwdX*HE1zeJbq4M!B .aQyV}zY, [=2)}48ÀR8ba$^ޗTM7x)m<A&Nb͢zs:W*rR-$<(ꍨ3= afzY]j^GAU~$TBLAY R59uy E2PnPLۧ K5mDJ R B|WA3ZR-V[jbۙr & TS\+H{I2q; In zk]l(]͵r:+ A].} )=\\;(r -vk۴7%nkwP(ѝ i9t;)"6f}e' (N V\sO 7DJiyWb :}df9#]~+ 4=hB^@Av/G˅ ]M2'qr!lz-钂XXSR J3o/ \r+MHqCCHz#v>!.V[= Ax38c_L 'vj>7E=PAl պ]U 4$ >eX9*|Q6 Na $A4S+IDAT 8.0|y{z) aM SPzX6%2OC31yZzB v} IU4Ĝs(W&qwH\l R ]拆, )=2Sl,7oEΫ+\.Pr\,s̥)c;23WP5!F)NIA*^\IbI 5 q՗ bplHr˸OA9f<HRô+09M+wEiXlu.BǮo1|ǪԣZzt.XHu-ܿ'nۄRԜdt-5XيXrTq A/zcU\A4$RCgMALL㬵z{ApUe= xXv0wUbp b $۵Sg-}9 Mt?)1ĒLtB ZB.aIukWcMAyP}ßY]7נcAYq2cS^Ab+SJG]c@Q JRK!op 2 mUBTCTCp/RgC)pk 2PXNA*Hry4;b$sic{#mHTIYDuy \,92&b%Bdy IH B%fyX>ETQB=H|\A&Y&,*OVA s cq?%ޣ awRK&n{7RgyQ,(beOuK@Kg!HzW_ҡ)g@NJ>OFABA%q7ea -P6΀Uj\ iL#K=ĶTB$msH rclլ*e*ySl<~1 4%mS  2y I1h '2⍿d&0}K sMm=>]ÙА,8a4xJ kF*qɨf`"Vpn QfB`PeZ練?ǎscssVpeH_ # )zFޛ.+1=5)Bx&)޴nsOػla!Xt H'VULLA(= ,l;|,V"?GGԣ kK;W1 @ Vd(|A;,ԃLU@asaGن"o IP}HaXe@6[)mkݡp$Jg--[60)V3-*ܙ{W1! kRa"%@F=t¤a5@ }UOU:}?CRif_J|[e{ܕQODd+cR0Rgj=$b f t,XwvgBa=GI6'Kx(ȚzW֌xڲGmƂ'~ 8I UۅӮΥz9bt4BЍ`+J抖૶RAb ʹ}?CXq#*pϓP*Z|/κzP2$w _#Jӳ3`͍?QrvmhsTJD&m@^LAjfBl:#d8/"0gT֒%3jV{&$&loPA'Ad:zvx: PGT*HHwm lؔe_AĮc“* C']@HLTI 4 c I$ Ŧ8t| Z 7Pg Rϰf~ ܱwJImB `7@Z) *3c]A;* :n{X-biͧ|D!LA,il#TʻrڿRWBud (9 s.Gȟh[S:hVH{4H )uk٢$jOe[oD0$m0Xg~Rwfw P4j;~ڃZ ȋ(HeHر$;JA4* Re&p +wܣ d cgV:OAr7wѯ~e PFX9 //F?|q:[Ǿ~8d-V^"3rFa4 b*{,Zl-F:/AEI,Wtj)O Ÿ஖A[+5YoVD_Q9b&SmuV|.ӜjM?ZH4X !A%ߧ vUyTUnUtlfى&s߫QYmک+7ڸ޿דIƕc$u? E0z(MR*&EM Vi-*yK~ʨlTlvQ=TXo_A@琅y~*HStM3IAl۪A&3Cʷ )(YGi,S3 "?\ҮqzgU>z#ÉS VeQrʷ#30k=Ŀ={ᯭޏ^ O\ŽL1aOQMW= NAvedfw7&x}V@T(HͽԐg9GDޜԎ4>7K'*vWj*mi'w)(o*> j9F'R-@5iUzfG<(@Ź H])e5 9]EW_Qz٦!=>DoO89z[ {-nUWARfuuL~@r>-ZZi!ޯ ,hbƿj+HqG}\iYIq>Yki9yZWRH=D|=w5t6AJR4c=r[0Ugs!Q{ug{_!!9z(_ya^Wh>/ 8$q:?BVGV.رrQID{G|ƍuӉ ør/6ҶrE_G5A.:E}MV]袯DA1]n6^A.s{zX.袋.袋"チ ]6.rZjw|yssjX4#Uel7䤶3ncŦP[?E-֗.l8:)Zlk-LIBkyZ-DKyVc-WϚ ân5-t58M3C=R ^ 6kݠqi6ؾM,eJwZ0r}Q*rc-/n~RתZj߭GNm"?V-":-ճ2S-[|nKp%2(gtVAT[+UKbRimzTDlY]7-zC;es+RG `G 1' ;;{|2* nY*`fuO &CC 1& [|ftd4VB< [4VR)J/ֹK5* -v2TF-LC1T5?ח1k'jT^7O4TX>`TWUQY)bU ~ T|MǝgJuB PVy2:g s]h(rݪR*DךIg.Qa{ 4_v>ji CvI͍1'R?D1k-O2 KiAr4(S]񑤣kLi|^1γP-[c]yVb^,> ZHNϙwϪOLvΫbuqR`n;DE}1EZ[J:%RݺcJŝ136UxR ڢ4`*l@r/* V :Xjn/*\T^RU^ոR*uWJT췌5%7d1 ņT΀SP*zPxUӳʂke]b ~ia/EK/+NaS0OE<%A* r mS`*ճ ͼpUmK-\Wf`Z@ 53SeU s\%V_z2TP G44AX qկTb."FGe8 &C)=dԟ=6%<4&Aң I+$2r2[lA4y͔&`,00F(\[Z.wo/Ц &3nW;@~ fWĖ]E` *\n:P'dƪsb>GOMGO̅v >;|G+6w@Du re0 X6Zr';ⵖCvƀ(2C tG af0/g)w''''!lvj" ׯr3Џ R]; +Q^;[?G6. Qwʄg"@(N @f^^RDue\[ ' dL?}`q lxưy` =a+{ f0 xK1i]h -: i] v{Pa7 - ȋ2' -`LwK0kJy,g6u rmw `d, 0E(Wb1I G/J|8}$C @w=ĆdPfZ$q9HDzA"wΩ `F*-V]:'Ccˑd0+B>%`88s4l07(Zq&`l6! x Hc}]ľ.fXKb]c8.o0_`KqK 8r-E)Xșxx_C(^2?&e\b@lD` cc(DT%nY XV!HP(LQCHA@i,ojր Ҹ'Ճ~{di$<&Q1rO^4JkJOdP2Smo)2ˀdQPʛ,a  ^5ē]+ eOKS*dyEC Ts5ihbO 2Dt? ;O?^Rk bKWU\aR {y T0כT>A^-*  tAۙmSw41: K0qdn .:PW V5@TTJ0NmT =bUr#rayOmze/<0VIrP<_Yq`3l]BrJeԡ:R1]kP] RmOWB  铹67@ڃ4#aK#G\QQJ u6YELPi̩DsUg/S^[a, 9|(O&9^4ҧz^t*t3#T"_ HŏimngR(F ()3ث!1o]Qi t s·Q,@"Xwb]Wuhqn+QpcDmHt uI^9( Zf_aM<2O:q҂ Et&= 0|#{%$%o }P(S}"=`(FttϗA0$O ס 9Η 4auaqNFRž m&"xwi 9'Zߓ?hVzCmޱ"H N{$rt/"P|&1DjiO@68yau7@NwtS&R]9;I7>'W]SNDH5uD5)B|wP1ϻ #2FT8Vu]DӑH%JM]0yj"ID-IKyW"\%1Y?u"RA7pl} &s>NrɴP~.?ҝ(QD2Q+y KUXTEDɽT,R4YxKD^hn]5O~TEX2|cUrJZ.QY Y %۲]]j1jWYS%Z\+ٮ)r-ڔlo,QJfFɶ.Z ruI/V{Kb00dɴ*QK> UF ,hN8].82 GUwnTTg>]_쌷n]dUGvt`њFIUʼ?ڂGfQGgf} ?}Q uo%Uݿ5׽ov? K.}2{vSVVVI4}{am~q[[ralՃ-/з-lT~\oRVm۷M.{d\m>o(1Ʌ.ӵd>w>o&p48_80,À#$-p&1J԰WCkȖoz5c;s{cAأ5_>9Qs?#֘JёsՋ D!0îAI O(]19Ck~=ղ<*`yr ҩ2lkGeA5TYU+Us--cVq~ QyFUjTZ|V˻B}lEL sA+ b dRfJ!aHRel1pb+V5P*1sd9ܓoT*ߒ7|o%2v/8 wBH%3JWǫN|,㰎G#߬ArkzRQkJHh'=5UQqJOF 0ZGOJL9=Y4I:WʣY9lt龱 ]Ɯ: ήlW(:[f5!sK^wcH3 ɄiX`ADYf!҅xS4Ybe DmCRTB0B!&Epyi `?8E//)lvXdxW G {ye8nѦp\/'@JRn("|U"jI ]A]4W2ajhvB**bR:kLc;.b9=Ç1dv׵y L2(~Q8*6'㯉Zs0ֺvu9Us(&[4+Ġ. 5y.Blf|.&6@>sa6J8M6DxsHH]TOX jE?x?jYM46">32\M^C@&Jk0yg|梬Zζ 0~mTR, Và ܖ?{l;F^0 qhpEd35p "[#t9u{)^T{Wg 6s?(梭h@"} t% i{1 Q  ( JyM=;!؉ͱ:?bYL'  jWYV)KW݉O}ё 5U&@5f1\ #GbŊ,(rD !o3:'oޙi v:642A i4ȤbđIAI)t[$Dy#ǏQ_8aNqD\T̊ W!@NAK"AG@Ȱn zS0K y&drXrԃQ$%XL1#}DQJR0 Wp8bTlh4t48~"(T(d#6"7',Hb4G#! B0Oc ot@d}(E^ CWhk`$'%!]&oXǂ B?lZD(2-q0BŰ;5Hxuα{-=1~A\ N又\ǦqA,GF Bƀwgrtb@  0q 3 H*!$)7DfG:?>Hڏ!HJP Hc@T79h1P;#vEwl>$ H]C9A t= g2&(\{Jd R+AdDїE2 7JRZ^R<Ht~fX!A DP5X anl:#ɇq脀)5HQ4! Q)m&n.lly iA6h|뫁Е ߵ@D Q:t^D QT Ȇ:dҀ C v]@CRk+$HIh͢A"Lol} ȷ BI7h(6Ȕn{-ud0jjD@ٝND(*+L `Z~l"DYۋG !A.b ˛CsQBqAUSDj(N! ʋ]ۙG DR  pyGX~bh*a 㳱Ce bnj Jr^ʼ~7aDiBAli#TΙ0JNg H"ƀ炃(*+# (8;I*^d` 0d`h<ǼC}x ;=uʼ.EQGĽd&} uv),&CgE O  ^pmŬpѯb /~ b7+PMzW 9TGNjIQC܅ۙ$w4=6xAb4P| q&}24HLI8$@!A!h2HCP !9w=N/fe;;tH q"O┌FCbbxM"QzTZ5C]U;Xp7< @lQ@%-2luG|/2l1ϔ5ԙfHDhKoIJ,» tB8GvDO]#Hdx(lU%;(odž6 }{^,Z'Rve,aKoTrR{+2%Ol4BI?vF2_}=:@$#tkT"K'&y]nvq!&9ӝ<.:# LQM;y"6(^i8'#bXJbbmɿ\sD2AA3i0!$!=>&zIXLU^&9N.3ӈ5$xy6 Z>j%8ONSDi*rw}T\UzaD_mtW"*d>&&ҳIlkD(n#M{kukIY$xv׽^?U<^)" {لH^*/salf:.۽Q,}&^JGeQկ1I{*F'&$IEim#Ңg&b2s/,{9z&">߲nCfO/"pr؋DىXLLL8yI{!0 ҖBeqwF"Ӱ{4=$HD1>b9ޭd&R3u>==X<$M2 "֍tKõI>p L\Z؟'XDu/7x/Y)~xL{9xKND LZr;HPT *6Y0&U~xPg *O0:~ C5 f~ۿUD -DklA"O`RX#WOc`El8C<׵xXEkHT5ݸD)CC\"]IwAEp Rpyp9(BzI{'Hc"B4H9gVlȗDR{𱡷4 Z|}(4^J, fm BDyMQ"ew5wdёJ"iTv2Dzˊ7O2b{{'I\b;b%jSd*˰{4wx4wO]UL?QZ"uIENDB`GD-Graph3d-0.63/t/pie100-1.32.png0100644000175000010010000000536507372612130014544 0ustar kilroyNonePNG  IHDR,hgPLTEZKtRNS@f IDATx] Պ0WH[LM4gowfhPVUA/7k4%|O3i8 K3RFHO؍)7#lD4: D4CoInO*L<ˏVIz) jg(bՄ adF ٍ:N?~8NH,pR-ۜ;]'T9|jHvll {.'#WɨIxzPkμ'fd2g.. lYĠ!X4&@=1g0i(&CI-W!Y<6`<8\c^x3w'߷Y z TaŤz:Qom&ExS/sڪm{ڶߴ :gGm9LZ{b6@{ 8TC>J_ɮ 鎬%8k<9}:P5.\^wXEm[zJji(&aBE10iҘn.*}pȗ߸Mޚ AqQrfp܍Rp$'ty W*,{dJťZ6 {ECkFwG9^NךӁ iQW߻{m[|S[@z]+PxRϠ'&6蠸F )` hH@S3<:嵂/3޾?ǀt@P3\>%qмAb 0pM0B\RD42$x@+PiH`啄D@*iHݬd"@0KCHqJC$U'T^=et83Ȁ# I+ckT^ kRXz,)α2:S*kB/02& M4ZdPG 6q(Χ9|7ZQ=zWF`/Ș,5{_PۗnQcU Džg98}~%=pOu%E}`}wL"2܎]^-A]Ly;\lc)|+3_%فz…b.ҺD>Wdv-$':D#Th~o06(vGBk pǝ)%/pc&8zx$,Cy~'baz5,0搪I5il!>Rt)BRB8yiR[ kH oH 堸^`f]^s p< w|"r) 8Sp[Ry> I#EM"^!@E$u* 'H8;^l{DTBPs"`ďID)}k|@|QD(=c*$Rlb$n˫sT޵!PM(I~-L%I2dx0@g#A$I^;obcYXh" :̈k…s~7?zxTvCfLՈ,U|^-vϪ;EZ22˸2:z v\B}Vj 3 ?k#堈!Q_p… ·_ɇ?4tqcicI/$Q)7ؠ]L\k p-8 /WJYk`A|:"2rH!L1.WϞ73J̿ƀ2 cB,Ԫ#ߟgCLS őNIqNGT%W|JBCqrdEId!$a%ϻ*1B'91B'uH)\FTqI :$#\I dFh3)!IƓϝau $=+wU]r!+9fs?jn]B'&l$hBoWG8.!1q ) ,!b1uгZx r,DžWz^6& LY>)',!o1.22]K|)%s6so3pɜЃ|C /. rDtTH.S +xټJVv(t'ػ4vjٔAk;Iq ĜEɁ4po4u7aްp<&> +loc!<I~I[IN8! ڶR̳ܖJCIKYH[Cv+RhhfAg)O^Da[<+'r!d"ٞ!YΖ< GH CluB 4b1<;)qzb,SwbG"=,5֓&hcn|ѥ WE:RINF+IJ'$qp+N9I?$~lDž  ӛ^(IENDB`GD-Graph3d-0.63/t/pie100-1.33.png0100644000175000010010000000572607403760772014561 0ustar kilroyNonePNG  IHDR,hgPLTEZKtRNS@f uIDATx] *5[R` ~4HcӂIp84 "4Uuiz!f-3JV8(kzP 2EƗ3dp%͸+d B9 <%d9b"h`ǽ*/ AjV묦T3oō,.9`qT&WZB@ejZ58CFgwiۃ+1OH"<-Z+zcHnpՃYw~Sk@k5^WQ5{gL7# R ޻bᑰ@/"gә`: V{c<e@H456g儻?6w8U |@}r\*?煪; # lZ-iޓ*^̉*BCT"%ܘ}i*7)/C"Xh|_$׽]ga^Űh-y6NcLʓRHږW׾ {Frff QSE9len8!1wp%*`1pEx~> -`i3<'ޣSwZwu);G6Ʒ)]mܴv3H"G;6sȷS5+U ~SD48]*vVx|tZ'PL}pxwKͬz>mbpru5 N:كRjZQs@M&'qe$ojbaHrc`KI#AHf+ItF!}imU'%W`?h MNL22IQN%aX=dHgQG!Izo_#A$_]=;W3z.%-/֐$l4=FL[)KlJog1eyNYdR ,uj 4|oLb`QXi YfSYkaIΘnCذ9ִ7At,kJK۽:^$b}ѤmaHg ihJreO|לV0( ;-p\1$5 |2 lX-GCF}}a%ч0_]K̛=Jqqګsf- \l]7UmT š גwxZxzxl' <+ypFXGU݄4-4}m9An*eC!1@KONҐ0oHjK,Cd K eV˒` CKI.Ut2F>dͥȡ'Rbt4s'j'u ITy0'v>`_yԪ+Z!(9ժxgHd$c%V&Bo) B8Vu3WqP%%<9RR.ߋI:@%Ek 3xTr&M b`L69Г{1)MjVJ7f t<^YKݺAf; C[ZOHV &|X#~BrWHn =aLN^Bʯ[bo~RIdo#d:9 l5k~-$2󫐒M' 6{2-$;Śd.T,~o4SKɚgeh6Y߳da~U"w]UB[`QMIJ {(Yݗ=̡41uuqMP󨯗=爛OuB3#$nz\gx;cg@xrfq=pR"8=tdL/SpeLRJZ&ν')pΥ! nqlNJ[G۬aC*?\IENDB`GD-Graph3d-0.63/t/stackbar.png0100644000175000010010000000663207372612134014761 0ustar kilroyNonePNG  IHDR,n%f6PLTE**TgggFF???FFFFv+UtRNS@f IDATx]ҫ(mrNewQhvow84qJAG;z^)yN5L~1ǘ_|bLԟ/~&yDMɒŏ[ևɼ~ sY-Ee Un ɿ_lQ&'dq&(k 69~OVN&|6Y~~M%M(L&eT D wAUo]9b("doai2Ѻ.o,;dD=zDkL`!&I3eLs hvL|&G6YW1*!.erg/G -h"4g˟l"ى&*$KWMfG辻KJRw,Q0UmX/݁,.9ό5uvlN4zL:L'[+:цtrTdlpDnNf|ڂGpDrHA&HrF;;N$ω4dMr; FHbЉ6.Dp҄IONNtv2t"h %| ;XEX':ne+Qf݁Ay?q y);mjVNND>r@'@i'9{OZ݉w1-@'N \A'%е/KbNNj|xF8η{{#Y 8 .rQwo/N\'.'>8ʼnLPPqINNLe᜜sr*m;ٛ'$It8<'g 2 p"fqmD5p0rNźNUdH'bOD`'8|w"(DkVD'DG(~eqYnYr-ЛќVƿߍ1H "TP.ke*KN\1" gU3%2K? =N"" G䏀/{QR=ey L^ܙ_(0&>D(N"VoIH>H"$RxA ~D!ʨD.ZQDR#P+VDqF)3X!"ʼ;*(B"$B"$B"$x1#""`ʑD ^ktC$q$=V!ck{N:&G)wWϹM(@(D8O~.)B2ʼn%!)\Y% |OmowE1k*!Lle~S{ᢩsZx#Z_vXp~h~v\A%K(PG2j TTA!=dA]V;AnYrL_ye)oq䔥>H/K%~$2ZDDҼSW # (JY6A:p(H9D 5M].(Hg t‚o7 OCQAp9zym((HąbAA\($Hą2P?(Hg(! /RJj f -z%NY!pJ6Y)+Nz,O% vJ "|$,A;Db{)Ȕ1*_I LNx_AbW[EYN`pB qMuSRr LNxSAw GTᄷd~ r: o(WA3 ' uN`pK NĨ*^&p TeAA?Jur% 2 OAWApA׋#W ҍ  0^>%& A.=%) 9AAN SEAzD Iu!t"ȶ$OAd!ȥ{ + JA9\$ fNA)K NS_y JAia@pp9NIA(/* GP JA(Q V(T8D!FPpJHx9 N "uBA)!GPpʀ rQ'$ҫGr  $uBA): ů_(\y }G^SG o>#zNw^mgt9+G :J K>eAAA 5Ri{/[ r$WQvY:ĠC IENDB`GD-Graph3d-0.63/t/visual-test.pl0100755000175000010010000003100407574201760015273 0ustar kilroyNone#!/usr/bin/perl use strict; # For simplified test responses use Test; # The modules we're testing use GD::Graph::bars3d; use GD::Graph::lines3d; use GD::Graph::pie3d; use GD::Graph::lines; use GD::Graph::bars; # For version number use GD::Graph; # To find where the matching files are use FindBin; use File::Spec; use File::Basename; # To allow GD to compare the images use GD qw(:DEFAULT :cmp); # To allow users to compare the images use ExtUtils::MakeMaker qw( prompt ); # How many test do we have? use vars qw( $test_count $export_format ); BEGIN { $|=1; $test_count = 10; plan test => $test_count; } # If '-save' is given on the command-line then don't delete the comparison images my $STORE = grep /^-?-save$/i, @ARGV; # Get user response whether to run visual test # (Would be nice to localize (localise? :-> ) this.... print STDERR "The following tests may require you to visually compare two sets of images. In order to do that the test will need to save an image to disk and have you look at them with software that supports reading the images. (Usually a web browser will do.) If the test all pass an internal comparison, you will not need to visually compare them. Each test that passes internal comparison will give an 'ok' message. If you do not want to do any visual comparison this then you may skip these test. "; my $res = prompt( "\nDo you want to perform the visual tests?", 'y' ); print "\n"; if( $res =~ /^n/i ) { my $skip_message = "Skipped by user request."; for( 1 .. $test_count ) { skip( $skip_message, $_ ); } # end if exit 0; } # end if #--------------------------------------------------# # Basic 3d bar graph # #--------------------------------------------------# my $graph = new GD::Graph::bars3d(); $export_format = $graph->export_format; my @data = ( ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], [ 1203, 3500, 3973, 2859, 3012, 3423, 1230] ); $graph->set( x_label => 'Day of the week', y_label => 'Number of hits', title => 'Daily Summary of Web Site', box_axis => 1, boxclr => '#FFFFCC', ); ok( compare( $graph->plot( \@data ), 'bar.png' ) ); #--------------------------------------------------# # Large 3d bar graph with 3 datasets # #--------------------------------------------------# $graph = new GD::Graph::bars3d( 800, 400 ); @data = ( [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49], [3333,3573,3051,961,4318,1742,2889,1418,1683,3974,1140,4291,4496,1137,3942,1489,4084,2306,4397,3586,468,1202,428,3747,2467,866,1992,4043,4157,1914,4446,1620,1093,1491,1612,961,3983,2783,1844,3731,715,1187,3632,1935,4431,1012,2054,2374,3550,1811,608], [2691,2306,1545,1373,1903,2658,1163,2020,1206,1544,2264,654,2331,764,682,668,884,2734,643,2029,744,1099,934,1074,2311,1807,1320,1355,2690,2364,376,1369,2086,769,2140,1307,1954,1848,2782,2173,1720,2490,2194,1868,2104,1002,1680,1628,1841,2071,2668], [528,918,1472,996,1030,455,969,971,669,627,131,620,1272,597,779,745,410,1198,151,500,1320,1391,591,1316,846,1395,820,1451,934,87,1155,630,1435,487,338,460,236,410,1348,587,483,117,852,292,1417,1030,672,984,1073,361,923], ); $graph->set( overwrite => 1, x_label => 'Den', y_label => 'Pocet pristupu', title => 'Pristupy k fakultnimu informacnimu systemu', y_max_value => 4500, y_tick_number => 18, y_label_skip => 2, x_label_skip => 1, x_all_ticks => 1, x_labels_vertical => 1, box_axis => 0, y_long_ticks => 1, ); ok( compare( $graph->plot( \@data ), 'multibar.png' ) ); #--------------------------------------------------# # Stacked 3d bar graph with 3 datasets # #--------------------------------------------------# $graph = new GD::Graph::bars3d(); @data = ( ["1".."7"], [ 37, 25, 9, 10, 1, 30, 34], [ 12, 25, 56, 23, 51, 12, 8], [ 42, 25, 18, 32, 8, 13, 20], ); $graph->set( cumulate => 1, x_label => 'Number', y_label => 'Usage', title => 'Total usage', box_axis => 0, y_long_ticks => 1, ); ok( compare( $graph->plot( \@data ), 'stackbar.png' ) ); #--------------------------------------------------# # 3d bars with x-tick-number set # #--------------------------------------------------# $graph = new GD::Graph::bars(); @data = ( [ 0 .. 12 ], [ 14,16,19,20,23,25,22,23,22,23,25,27,28], ); $graph->set( title => 'Temperature', x_label => "Time", y_label => "Temperature C", long_ticks=>1, y_max_value=> 30, y_min_value => 0, y_tick_number => 6, # x_tick_number => 12, y_label_skip => 1, x_label_skip => 2, x_max_value=> 12, bar_spacing=> 4, accent_threshold=> 400, ); ok( compare( $graph->plot( \@data ), 'bar-ticks.png' ) ); #--------------------------------------------------# # Basic 3d line graph # #--------------------------------------------------# $graph = new GD::Graph::lines3d(); @data = ( ["Jan 1", "Jan 2", "Jan 3", "Jan 4", "Jan 5", "Jan 6", "Jan 7"], [ 120, 350, 397, 540, 110, 287, 287] ); $graph->set( x_label => 'Date', y_label => 'Number of hits', title => 'Web Site Traffic', line_width => 15, box_axis => 1, boxclr => '#FFFFCC', ); ok( compare( $graph->plot( \@data ), 'line.png' ) ); #--------------------------------------------------# # Large 3d line graph with 5 datasets & legend # #--------------------------------------------------# $graph = new GD::Graph::lines3d( 800, 400 ); @data = ( [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49], [320,2447,1832,0,2411,1666,196,2072,833,2302,554,980,973,740,1218,1430,1124,79,763,1545,645,612,139,1537,506,2345,8,1492,950,1502,1188,839,2321,1725,2352,829,1288,1919,363,109,1021,1445,2401,2209,43,2114,250,35,810,187,906], [715,2170,645,1835,1511,618,2031,1134,1871,1338,1070,1631,372,1516,1164,2388,1983,1964,1441,1775,2620,992,1966,1754,1602,2170,800,2341,201,2617,1726,1013,749,2217,216,579,1997,400,1482,278,180,872,677,1118,1497,945,1575,228,454,851,1313], [2820,2546,2750,2692,2537,2903,2599,2618,2960,2997,2882,2943,2847,2965,2798,2571,2564,2502,2713,2586,2909,2859,2586,2503,2708,2992,2805,2897,2744,2906,2607,2574,2852,2932,2566,2662,2774,2976,2819,2759,2575,2992,2513,2551,2721,2694,2659,2503,2636,2786,2844], [1082,1295,1267,1101,1017,1188,1051,1219,1141,1045,1145,1053,1011,1166,1200,1216,1297,1016,1299,1209,1280,1071,1063,1113,1276,1080,1045,1089,1145,1292,1250,1110,1205,1087,1058,1137,1191,1268,1092,1180,1181,1151,1108,1141,1053,1215,1046,1086,1165,1229,1128], [795,1263,497,2711,2269,2922,2080,2363,525,811,2671,2147,664,990,1795,1936,1265,2550,338,1618,819,890,704,3010,345,2904,1319,2913,414,935,2479,2910,1296,814,2973,2996,1442,2854,1487,1638,3036,1127,227,2358,1821,1868,754,1424,2923,361,1478], ); $graph->set( overwrite => 1, x_label => 'Den', y_label => 'Pocet pristupu', title => 'Pristupy k fakultnimu informacnimu systemu', y_max_value => 4500, y_tick_number => 18, y_label_skip => 2, x_label_skip => 1, x_all_ticks => 1, x_labels_vertical => 1, box_axis => 0, y_long_ticks => 1, legend_placement => 'RD', legend_spacing => 10, ); $graph->set_legend( 'Subset A', 'Subset B', 'Subset C', 'Subset D', 'Subset E',); ok( compare( $graph->plot( \@data ), 'multiline.png' ) ); #--------------------------------------------------# # 3d Lines with x-tick-number set # #--------------------------------------------------# $graph = new GD::Graph::lines3d(); @data = ( [ 0 .. 24 ], [ 14,16,19,20,23,25,22,23,22,23,25,27,28,30,25,23,20,19,17,14,15,16,14,13,11], ); $graph->set( title => 'Temperature', x_label => "Time", y_label => "Temperature C", long_ticks=>1, y_max_value=> 30, y_min_value => 0, y_tick_number => 6, # x_tick_number => 24, y_label_skip => 1, x_label_skip => 2, x_max_value=> 24, bar_spacing=> 4, accent_threshold=> 400, ); ok( compare( $graph->plot( \@data ), 'line-ticks.png' ) ); #--------------------------------------------------# # Basic 3d pie graph # #--------------------------------------------------# # GD::Graph makes different images in versions 1.32 and 1.33 (and will in 1.34) my $GD_Graph_VERSION = $GD::Graph::VERSION; if( ($GD_Graph_VERSION != 1.32) && ($GD_Graph_VERSION != 1.33) ) { warn "The version of GD::Graph that you have, $GD_Graph_VERSION, has not been tested. It may produce images that are different than those provided. If the images meet your satisfaction, respond, 'y' when asked if they are the same.\n"; $GD_Graph_VERSION = 1.32; } # end if $graph = new GD::Graph::pie3d(); @data = ( [".com", ".net", ".gov", ".org", ".de", ".uk", "Other"], [ 37, 25, 9, 7, 11, 3, 8] ); $graph->set( title => 'Geography of Web Site', ); ok( compare( $graph->plot( \@data ), "pie-$GD_Graph_VERSION.png" ) ); #--------------------------------------------------# # Basic 3d pie graph with one data point # #--------------------------------------------------# $graph = new GD::Graph::pie3d(); @data = ( [ '(Unknown)' ], [ 100 ] ); $graph->set( title => 'Organisational Use', ); ok( compare( $graph->plot( \@data ), "pie100-$GD_Graph_VERSION.png" ) ); #--------------------------------------------------# # Stacked bar chart with legend # #--------------------------------------------------# $graph = new GD::Graph::bars3d(); @data = ( ["1".."7"], [ 37, 25, 9, 10, 1, 30, 34], [ 12, 25, 56, 23, 51, 12, 8], [ 42, 25, 18, 32, 8, 13, 20], ); $graph->set( cumulate => 1, x_label => 'Number', y_label => 'Usage', title => 'Total usage', box_axis => 0, y_long_ticks => 1, legend_placement => 'RC', ); $graph->set_legend( 'Red', 'Green', 'Blue' ); ok( compare( $graph->plot( \@data ), 'stackbar-legend.png' ) ); exit 0; ############################################################################## # END OF TESTS # ############################################################################## # # A convenience function to build a graph and compare it with # a graphics file already done # # Returns true on success, false on failure. # If the failure is in building the graph then returns undef. # If the failure is in the comparison of images, returns 0. # sub compare { my( $graph, $file ) = @_; # Normalize the filename into the test directory ($file) = fileparse( $file ); $file = File::Spec->catfile( $FindBin::RealBin, $file ); # Open the file it should look like if( open( FILE, $file ) ) { my $gd = GD::Image->newFromPng( \*FILE ) || die "Error loading $file: $!\n"; close FILE; # See if GD compares them (bypass user part if possible) return 1 unless ($graph->compare( $gd ) & GD_CMP_IMAGE); } else { warn "Cannot open $file: $!\n"; } # end if # The images differ! # So write the image to a file and ask the user if they compare my $f2 = _write( $graph, $file ); if( defined $f2 ) { my $r = prompt( "Do the images '$file' and '$f2' look substantially similar?", 'n' ); print "\n"; # Now remove the file, unless in STORE mode unlink $f2 unless $STORE; return ( $r =~ /^y/i ) ? 1 : 0; return 0; } else { warn "Could not save file $f2: $!\n"; return 0; # Failure! } # end if } # end compare # # Writes a graphic to a file in whatever format the current engine supports # sub _write { # GLOBAL: $export_format my( $g, $f ) = @_; # Get the base filename, insert -t and format and put in t/ folder ($f) = fileparse( $f, '\..*' ); $f = File::Spec->catfile( $FindBin::RealBin, $f ); $f = "$f-t.$export_format"; # Write out in whatever format GD prefers open( FILE, ">$f" ) || return undef; binmode FILE; print FILE $g->$export_format; close FILE; # Give back the filename return $f; } # end _write