#==================================================================== # Chart::ErrorBars # # written by Chart-Group # # maintained by the Chart Group # Chart@wettzell.ifag.de # #--------------------------------------------------------------------- # History: #---------- # $RCSfile: ErrorBars.pm,v $ $Revision: 1.2 $ $Date: 2003/02/14 13:32:48 $ # $Author: dassing $ # $Log: ErrorBars.pm,v $ # Revision 1.2 2003/02/14 13:32:48 dassing # First setup # #==================================================================== package Chart::ErrorBars; use Chart::Base '2.4.1'; use GD; use Carp; use strict; @Chart::ErrorBars::ISA = qw(Chart::Base); $Chart::ErrorBars::VERSION = '2.4.1'; #>>>>>>>>>>>>>>>>>>>>>>>>>># # public methods go here # #<<<<<<<<<<<<<<<<<<<<<<<<<<# #>>>>>>>>>>>>>>>>>>>>>>>>>>># # private methods go here # #<<<<<<<<<<<<<<<<<<<<<<<<<<<# ## finally get around to plotting the data sub _draw_data { my $self = shift; my $data = $self->{'dataref'}; my $misccolor = $self->_color_role_to_index('misc'); my ($x1, $x2, $x3, $y1, $y2, $y3, $mod, $y_error_up, $y_error_down); my ($width, $height, $delta, $map, $delta_num, $zero_offset, $flag); my ($i, $j, $color, $brush); my $dataset =0; my $diff; # init the imagemap data field if they want it if ($self->{'imagemap'} =~ /^true$/i) { $self->{'imagemap_data'} = []; } # find the delta value between data points, as well # as the mapping constant $width = $self->{'curr_x_max'} - $self->{'curr_x_min'}; $height = $self->{'curr_y_max'} - $self->{'curr_y_min'}; $delta = $width / ( $self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1); $diff = $self->{'max_val'} - $self->{'min_val'}; $diff = 1 if $diff == 0; $map = $height / $diff; #for a xy-plot, use this delta and maybe an offset for the zero-axes if ($self->{'xy_plot'} =~ /^true$/i ) { $diff = $self->{'x_max_val'} - $self->{'x_min_val'}; $diff = 1 if $diff == 0; $delta_num = $width / $diff; if ($self->{'x_min_val'} <= 0 && $self->{'x_max_val'} >= 0) { $zero_offset = abs($self->{'x_min_val'}) * abs($delta_num); } elsif ($self->{'x_min_val'} > 0 || $self->{'x_max_val'} < 0) { $zero_offset = -$self->{'x_min_val'} * $delta_num; } else { $zero_offset = 0; } } # get the base x-y values if ($self->{'xy_plot'} =~ /^false$/i ) { $x1 = $self->{'curr_x_min'} + ($delta / 2); } else { $x1 = $self->{'curr_x_min'}; } if ($self->{'min_val'} >= 0) { $y1 = $self->{'curr_y_max'}; $mod = $self->{'min_val'}; } elsif ($self->{'max_val'} <= 0) { $y1 = $self->{'curr_y_min'}; $mod = $self->{'max_val'}; } else { $y1 = $self->{'curr_y_min'} + ($map * $self->{'max_val'}); $mod = 0; $self->{'gd_obj'}->line ($self->{'curr_x_min'}, $y1, $self->{'curr_x_max'}, $y1, $misccolor); } # first of all box it off $self->{'gd_obj'}->rectangle ($self->{'curr_x_min'}, $self->{'curr_y_min'}, $self->{'curr_x_max'}, $self->{'curr_y_max'}, $misccolor); # draw the points for $i (1..$self->{'num_datasets'}) { if ($self->{'same_error'} =~ /^false$/i) { # get the color for this dataset, and set the brush $color = $self->_color_role_to_index('dataset'.($dataset)); # draw every point for this dataset $dataset++ if (($i-1)%3 == 0); for $j (0..$self->{'num_datapoints'}) { #get the brush for points $brush = $self->_prepare_brush ($color, 'point'); $self->{'gd_obj'}->setBrush ($brush); # only draw if the current set is really a dataset and no errorset if ( ($i-1)%3 == 0) { # don't try to draw anything if there's no data if (defined ($data->[$i][$j]) ) { if ($self->{'xy_plot'} =~ /^true$/i ) { $x2 = $x1 + $delta_num * $data->[0][$j] + $zero_offset+1; $x3 = $x2 ; } else { $x2 = $x1 + ($delta * $j)+1; $x3 = $x2; } $y2 = $y1 - (($data->[$i][$j] - $mod) * $map); $y3 = $y2; $y_error_up = $y2-abs($data->[$i+1][$j]) *$map; $y_error_down= $y2+abs($data->[$i+2][$j]) *$map; # draw the point only if it is within the chart borders if ($data->[$i][$j] <= $self->{'max_val'} && $data->[$i][$j] >= $self->{'min_val'}) { $self->{'gd_obj'}->line($x2, $y2, $x3, $y3, gdBrushed); $flag = 'true'; } #reset the brush for lines $brush = $self->_prepare_brush ($color, 'line'); $self->{'gd_obj'}->setBrush ($brush); #draw the error bars if ($flag =~ /^true$/i) { # the upper lines $self->{'gd_obj'}->line($x2, $y2, $x3, $y_error_up, gdBrushed); $self->{'gd_obj'}->line($x2-3, $y_error_up, $x3+3, $y_error_up, gdBrushed); # the down lines $self->{'gd_obj'}->line($x2, $y2, $x3, $y_error_down, gdBrushed); $self->{'gd_obj'}->line($x2-3, $y_error_down, $x3+3, $y_error_down, gdBrushed); $flag = 'false'; } # store the imagemap data if they asked for it if ($self->{'imagemap'} =~ /^true$/i) { $self->{'imagemap_data'}->[$i][$j] = [ $x2, $y2 ]; } } } } } else { # get the color for this dataset, and set the brush $color = $self->_color_role_to_index('dataset'.($dataset)); # draw every point for this dataset $dataset++ if (($i-1)%2 == 0); for $j (0..$self->{'num_datapoints'}) { #get the brush for points $brush = $self->_prepare_brush ($color, 'point'); $self->{'gd_obj'}->setBrush ($brush); # only draw if the current set is really a dataset and no errorset if ( ($i-1)%2 == 0) { # don't try to draw anything if there's no data if (defined ($data->[$i][$j]) ) { if ($self->{'xy_plot'} =~ /^true$/i ) { $x2 = $x1 + $delta_num * $data->[0][$j] + $zero_offset; $x3 = $x2 ; } else { $x2 = $x1 + ($delta * $j); $x3 = $x2; } $y2 = $y1 - (($data->[$i][$j] - $mod) * $map); $y3 = $y2; $y_error_up = $y2-abs($data->[$i+1][$j]) *$map; $y_error_down= $y2+abs($data->[$i+1][$j]) *$map; # draw the point only if it is within the chart borders if ($data->[$i][$j] <= $self->{'max_val'} && $data->[$i][$j] >= $self->{'min_val'}) { $self->{'gd_obj'}->line($x2, $y2, $x3, $y3, gdBrushed); $flag = 'true'; } #reset the brush for lines $brush = $self->_prepare_brush ($color, 'line'); $self->{'gd_obj'}->setBrush ($brush); #draw the error bars if ($flag =~ /^true$/i) { # the upper lines $self->{'gd_obj'}->line($x2, $y2, $x3, $y_error_up, gdBrushed); $self->{'gd_obj'}->line($x2-3, $y_error_up, $x3+3, $y_error_up, gdBrushed); # the down lines $self->{'gd_obj'}->line($x2, $y2, $x3, $y_error_down, gdBrushed); $self->{'gd_obj'}->line($x2-3, $y_error_down, $x3+3, $y_error_down, gdBrushed); $flag = 'false'; } # store the imagemap data if they asked for it if ($self->{'imagemap'} =~ /^true$/i) { $self->{'imagemap_data'}->[$i][$j] = [ $x2, $y2 ]; } } } } } } return 1; } ## set the gdBrush object to trick GD into drawing fat lines sub _prepare_brush { my $self = shift; my $color = shift; my $type = shift; my ($radius, @rgb, $brush, $white, $newcolor); # get the rgb values for the desired color @rgb = $self->{'gd_obj'}->rgb($color); # get the appropriate brush size if ($type eq 'line') { $radius = $self->{'brush_size'}/2; } elsif ($type eq 'point') { $radius = $self->{'pt_size'}/2; } # create the new image $brush = GD::Image->new ($radius*2, $radius*2); # get the colors, make the background transparent $white = $brush->colorAllocate (255,255,255); $newcolor = $brush->colorAllocate (@rgb); $brush->transparent ($white); # draw the circle $brush->arc ($radius-1, $radius-1, $radius, $radius, 0, 360, $newcolor); # fill it if we're using lines $brush->fill ($radius-1, $radius-1, $newcolor); # set the new image as the main object's brush return $brush; } ## let them know what all the pretty colors mean sub _draw_legend { my $self = shift; my ($length, $step, $temp, $post_length); my $j = 0; # check to see if legend type is none.. if ($self->{'legend'} =~ /^none$/) { return 1; } #just for later checking and warning if ($#{$self->{'legend_labels'}} >= 0) { $post_length = scalar(@{$self->{'legend_labels'}}); } #look if every second or eyery third dataset is a set for data if ( $self->{'same_error'} =~ /^false$/i) { $step = 3; } else { $step = 2; } # init a field to store the length of the longest legend label unless ($self->{'max_legend_label'}) { $self->{'max_legend_label'} = 0; } # fill in the legend labels, find the longest one for (my $i = 1; $i < $self->{'num_datasets'}; $i += $step) { my $label = $j+1; unless ($self->{'legend_labels'}[$j]) { $self->{'legend_labels'}[$j] = "Dataset $label"; } $length = length($self->{'legend_labels'}[$j]); if ($length > $self->{'max_legend_label'}) { $self->{'max_legend_label'} = $length; } $j++; } #we just have to label the datasets in the legend #we'll reset it, to draw the sets $temp = $self->{'num_datasets'}; $self->{'num_datasets'} = $j; # check to see if they have as many labels as datasets, # warn them if not if ( ($post_length > 0) && ($post_length != $j) ) { carp "The number of legend labels and datasets doesn\'t match"; } # different legend types if ($self->{'legend'} eq 'bottom') { $self->_draw_bottom_legend; } elsif ($self->{'legend'} eq 'right') { $self->_draw_right_legend; } elsif ($self->{'legend'} eq 'left') { $self->_draw_left_legend; } elsif ($self->{'legend'} eq 'top') { $self->_draw_top_legend; } else { carp "I can't put a legend there (at ".$self->{'legend'}.")\n"; } #reset the number of dataset to make sure that everything goes right $self->{'num_datasets'} = $temp; # and return return 1; } #find the range of the x scale, don't forget the errors! sub _find_y_range { my $self = shift; my $data = $self->{'dataref'}; my $max = undef; my $min = undef; if ($self->{'same_error'} =~ /^false$/i) { for my $i (1..$self->{'num_datasets'}) { if ( ($i-1)%3 == 0) { for my $j (0..$self->{'num_datapoints'}) { if (defined ($data->[$i][$j]) && defined($data->[$i+1][$j]) && defined($data->[$i+2][$j]) ){ if (defined $max){ if ( ($data->[$i][$j] + abs($data->[$i+1][$j])) > $max ) { $max = $data->[$i][$j] + abs($data->[$i+1][$j]); } if ( ($data->[$i][$j] - abs($data->[$i+2][$j])) < $min ) { $min = $data->[$i][$j] - abs($data->[$i+2][$j]); } } else {$min = $max = $data->[$i][$j];} } } } }return ($min, $max); } else { for my $i (1..$self->{'num_datasets'}) { if ( ($i-1)%2 == 0) { for my $j (0..$self->{'num_datapoints'}) { if (defined ($data->[$i][$j]) && defined($data->[$i+1][$j]) ){ if (defined $max){ if ( ($data->[$i][$j] + $data->[$i+1][$j]) > $max ) { $max = $data->[$i][$j] + $data->[$i+1][$j]; } if ( ($data->[$i][$j] - $data->[$i+1][$j]) < $min ) { $min = $data->[$i][$j] - $data->[$i+1][$j]; } } else {$min = $max = $data->[$i][$j];} } } } } return ($min, $max); } } ## be a good module and return 1 1;