#==================================================================== # Chart::Split # # written by Chart-Group # # maintained by the Chart Group # Chart@wettzell.ifag.de # #--------------------------------------------------------------------- # History: #---------- # $RCSfile: Split.pm,v $ $Revision: 1.2 $ $Date: 2003/02/14 14:25:30 $ # $Author: dassing $ # $Log: Split.pm,v $ # Revision 1.2 2003/02/14 14:25:30 dassing # First setup to cvs # #==================================================================== package Chart::Split; use Chart::Base '2.4.1'; use GD; use Carp; use strict; @Chart::Split::ISA = qw(Chart::Base); $Chart::Split::VERSION = '2.4.1'; #>>>>>>>>>>>>>>>>>>>>>>>>>># # public methods go here # #<<<<<<<<<<<<<<<<<<<<<<<<<<# #>>>>>>>>>>>>>>>>>>>>>>>>>>># # private methods go here # #<<<<<<<<<<<<<<<<<<<<<<<<<<<# #draw the ticks sub _draw_x_number_ticks { my $self = shift; my $data = $self->{'dataref'}; my $font = $self->{'tick_label_font'}; my $textcolor = $self->_color_role_to_index('text'); my $misccolor = $self->_color_role_to_index('misc'); my $num_points = $self->{'num_datapoints'}; my ($h, $w, $width, $step, $start, $interval, $label, $stag, @labels); my ($x_start, $y_start, $y, $x, $lines, $delta, $ticks); my $x_label_len = 1; my $y_label_len = 1; my $x_max = -0x80000000; $self->{'grid_data'}-> {'x'} = []; # find the width $width = $self->{'curr_x_max'} - $self->{'curr_x_min'}; $width = 1 if $width == 0; # make sure we got a real font unless ((ref $font) eq 'GD::Font') { croak "The tick label font you specified isn\'t a GD Font object"; } # find out how big the font is ($w, $h) = ($font->width, $font->height); unless (defined $self->{'start'} && defined $self->{'interval'}) { croak "I need two values from you to draw a split chart: start and interval!"; } else { $interval = $self->{'interval'}; $start = $self->{'start'}; $ticks = $self->{'interval_ticks'}-1; $label = $start; } #look after devision by zero! if ( $ticks ==0 ) { $ticks=1; } #calculate the step between the ticks $step = $interval/$ticks; for (0..$ticks) { push @labels, $self->{f_x_tick}-> (sprintf ("%.".$self->{'precision'}."f", $label)); $label += $step; } #find the biggest x value foreach (@{$data->[0]}) { if ($_ > $x_max) { $x_max = $_; } } #find the length of the x and y labels foreach (@labels) { if (length($_) > $x_label_len) { $x_label_len = length($_); } } #find the amount of lines $lines = int((($x_max-$start) / $interval)+0.99999999999); $lines = 1 if $lines == 0; #find the length, of the label. $y_label_len = length($lines); #get the starting point and the width if ($lines > 1) { #if there are y-ticks if ($self->{'y_axes'} =~ /^right$/i) { $x_start = $self->{'curr_x_min'} ; $width = $self->{'curr_x_max'}-$x_start - $self->{'text_space'}*2- $y_label_len*$w-$self->{'tick_len'}; } elsif ($self->{'y_axes'} =~ /^both$/i) { $x_start = $self->{'curr_x_min'} + ($w * $y_label_len) +2*$self->{'text_space'} + $self->{'tick_len'}; $width = $self->{'curr_x_max'} - $x_start- ($w * $y_label_len) - 2 * $self->{'text_space'} - $self->{'tick_len'}; } else { $x_start = $self->{'curr_x_min'} + ($w * $y_label_len) + 3 * $self->{'text_space'} ; $width = $self->{'curr_x_max'} - $x_start; } } else { #if there are no y-axes $x_start = $self->{'curr_x_min'}; $width = $self->{'curr_x_max'} - $x_start; } #and the y_start value $y_start = $self->{'curr_y_max'} - $h - $self->{'text_space'}; #get the delta value $delta = $width / ($ticks); if ( ! defined($self->{'skip_x_ticks'}) ) { $self->{'skip_x_ticks'} = 1; } #draw the labels if ($self->{'x_ticks'} =~ /^normal$/i ) { if ($self->{'skip_x_ticks'} > 1) { #draw a normal tick every nth label for ( 0..$#labels-1) { if ( defined ($labels[$_*$self->{'skip_x_ticks'}]) ) { $x = $x_start + $delta*($_*$self->{'skip_x_ticks'}) - ($w * length($labels[$_*$self->{'skip_x_ticks'}]))/2; $self->{'gd_obj'}->string($font, $x, $y_start,$labels[$_*$self->{'skip_x_ticks'}], $textcolor); } } } elsif($self->{'custom_x_ticks'}) { #draw only the normal ticks they wanted foreach (@{$self->{'custom_x_ticks'}}) { if ( defined $labels[$_] ) { $x = $x_start + $delta*$_ - ($w * length($labels[$_]))/2; $self->{'gd_obj'}->string($font, $x, $y_start, $labels[$_], $textcolor); } } } else { for (0..$#labels) { #draw all ticks normal if ( defined $labels[$_] ) { $x = $x_start + $delta*($_) - ($w * length($labels[$_]))/2; $self->{'gd_obj'}->string($font, $x, $y_start,$labels[$_], $textcolor); } } } } elsif ($self->{'x_ticks'} =~ /^staggered$/i ) { $stag = 0; if ($self->{'skip_x_ticks'} > 1 ) { #draw a staggered tick every nth label for ( 0..$#labels-1) { if ( defined ($labels[$_*$self->{'skip_x_ticks'}])) { $x = $x_start + $delta*($_*$self->{'skip_x_ticks'}) - ($w * length($labels[$_*$self->{'skip_x_ticks'}]))/2; if ($stag % 2 == 0) { $y_start -= $self->{'text_space'} + $h; } $self->{'gd_obj'}->string($font, $x, $y_start,$labels[$_*$self->{'skip_x_ticks'}], $textcolor); if ($stag % 2 == 0) { $y_start += $self->{'text_space'} + $h; } $stag++; } } } elsif ($self->{'custom_x_ticks'}) { # draw only the wanted ticks staggered foreach (sort (@{$self->{'custom_x_ticks'}})) { if ( defined $labels[$_]) { $x = $x_start + $delta*$_ - ($w*(length($labels[$_])))/2; if ($stag % 2 == 0) { $y_start -= $self->{'text_space'} + $h; } $self->{'gd_obj'}->string($font, $x, $y_start,$labels[$_], $textcolor); if ($stag % 2 == 0) { $y_start += $self->{'text_space'} + $h; } $stag++; } } } else { # draw all ticks staggered for (0..$#labels) { if ( defined $labels[$_] ) { $x = $x_start + $delta*$_ -($w*(length($labels[$_])))/2; if ($stag % 2 == 0) { $y_start -= $self->{'text_space'} + $h; } $self->{'gd_obj'}->string($font, $x, $y_start,$labels[$_], $textcolor); if ($stag % 2 == 0) { $y_start += $self->{'text_space'} + $h; } $stag++; } } } } elsif ( $self->{'x_ticks'} =~ /^vertical$/i ) { $y_start = $self->{'curr_y_max'} - $self->{'text_space'}; if ( $self->{'skip_x_ticks'} > 1) { #draw every nth tick vertical for (0..$#labels) { if (defined $_) { $x = $x_start + $delta*($_ * $self->{'skip_x_ticks'}) - $h/2; $y = $y_start - ($x_label_len- length($labels[$_*$self->{'skip_x_ticks'}]))*$w; $self->{'gd_obj'}->stringUp($font, $x, $y,$labels[$_*$self->{'skip_x_ticks'}], $textcolor); } } } elsif ( $self->{'custom_x_ticks'} ) { foreach ( @{$self->{'custom_x_ticks'}}) { #draw the ticks they want vertical if (defined $labels[$_]) { $x = $x_start + $delta*$_ - $h/2; $y = $y_start - ($x_label_len- length($labels[$_]))*$w; $self->{'gd_obj'}->stringUp($font, $x, $y,$labels[$_], $textcolor); } } } else { # draw all ticks vertical for ( 0..$#labels) { if ( defined $labels[$_]) { $x = $x_start + $delta*$_ - $h/2; $y = $y_start - ($x_label_len- length($labels[$_]))*$w; $self->{'gd_obj'}->stringUp($font, $x, $y,$labels[$_], $textcolor); } } } } #update the borders if ($self->{'interval_ticks'} > 0) { if ($self->{'x_ticks'} =~ /^normal$/i ) { $self->{'curr_y_max'} -= $h + $self->{'text_space'}*2; } elsif ($self->{'x_ticks'} =~ /^staggered$/i ) { $self->{'curr_y_max'} -= 2*$h + 3*$self->{'text_space'}; } elsif ($self->{'x_ticks'} =~ /^vertical$/i ) { $self->{'curr_y_max'} -= $w * $x_label_len + $self->{'text_space'} * 2; } } #draw the ticks $y_start = $self->{'curr_y_max'}; $y = $y_start - $self->{'tick_len'}; if ($self->{'skip_x_ticks'} > 1) { for ( 0..int(($#labels )/$self->{'skip_x_ticks'})) { $x = $x_start + $delta*($_*$self->{'skip_x_ticks'}) ; $self->{'gd_obj'}->line($x, $y_start, $x, $y, $misccolor); if ( $self->{'grid_lines'} =~ /^true$/i or $self->{'x_grid_lines'} =~ /^true$/i) { $self->{'grid_data'} ->{'x'}->[$_] = $x; } } } elsif ($self->{'custom_x_ticks'}) { foreach (@{$self->{'custom_x_ticks'}}) { if ($_ <= $ticks) { $x = $x_start + $delta*$_; $self->{'gd_obj'}->line($x, $y_start, $x, $y, $misccolor); if ( $self->{'grid_lines'} =~ /^true$/i or $self->{'x_grid_lines'} =~ /^true$/i) { $self->{'grid_data'} ->{'x'}->[$_] = $x; } } } } else { for (0..$#labels) { $x = $x_start + $_*$delta; $self->{'gd_obj'}->line($x, $y_start, $x, $y, $misccolor); if ( $self->{'grid_lines'} =~ /^true$/i or $self->{'x_grid_lines'} =~ /^true$/i) { $self->{'grid_data'} ->{'x'}->[$_] = $x; } } } #another update of the borders $self->{'curr_y_max'} -= $self->{'tick_len'} if $self->{'interval_ticks'} > 0; #finally return return; } # override the funktion implemented in base sub _draw_x_ticks { my $self = shift; #Use always the _draw_x_tick funktion because we always do a xy_plot!!! $self->_draw_x_number_ticks(); #and return return 1; } sub _draw_y_ticks { my $self = shift; my $side = shift || 'left'; my $data = $self->{'dataref'}; my $font = $self->{'tick_label_font'}; my $textcolor = $self->_color_role_to_index('text'); my $misccolor = $self->_color_role_to_index('misc'); my @labels = @{$self->{'y_tick_labels'}}; my $num_points = $self->{'num_datapoints'}; my ($w, $h); my ($x_start, $x, $y_start, $y, $start, $interval); my ($height, $delta, $label, $lines,$label_len); my ($s, $f); my $x_max = -0x80000000; $self->{grid_data}->{'y'} = []; $self->{grid_data}->{'y2'} = []; # find the height $height = $self->{'curr_y_max'} - $self->{'curr_y_min'}; # make sure we got a real font unless ((ref $font) eq 'GD::Font') { croak "The tick label font you specified isn\'t a GD Font object"; } # find out how big the font is ($w, $h) = ($font->width, $font->height); #get the base variables $interval = $self->{'interval'}; $start = $self->{'start'}; #find the biggest x value foreach (@{$data->[0]}) { if ($_ > $x_max) { $x_max = $_; } } #calculate the number of lines and the length $lines = int((($x_max-$start)/ $interval)+0.99999999999); $lines = 1 if $lines == 0; $label_len = length($lines); #get the space between two lines $delta = $height / $lines; #now draw them if ($lines > 1) { if ( $side =~ /^right$/i ) { #get the starting point $x_start = $self->{'curr_x_max'}; $y_start = $self->{'curr_y_min'}; #draw the labels for $label (0..$lines-1) { $x = $x_start - $self->{'text_space'} - $label_len*$w; $y = $y_start + $label*$delta + $delta/2 -$h/2; $self->{'gd_obj'}->string($font, $x, $y, $label, $textcolor); } #draw the ticks for $label ( 0..$lines) { $x = $x_start - $self->{'text_space'} *2 - $label_len*$w - $self->{'tick_len'}; $y = $y_start + $label*$delta; $self->{'gd_obj'} ->line( $x_start-$self->{'text_space'}, $y, $x, $y, $misccolor); #add data for grid_lines push @{$self->{grid_data} ->{'y'}}, $y; } #update the borders $self->{'curr_x_max'} = $x_start - $self->{'text_space'}*2- $label_len*$w-$self->{'tick_len'}; } elsif ( $side =~ /^both$/i) { #get the starting point $x_start = $self->{'curr_x_min'}; $y_start = $self->{'curr_y_min'}; #first the left side #draw the labels for $label (0..$lines-1) { $x = $self->{'curr_x_min'}+$self->{'text_space'}*2; $y = $y_start+ $label*$delta + $delta/2 -$h/2; $self->{'gd_obj'}->string($font, $x, $y, $self->{'f_y_tick'}->($label), $textcolor); } #draw the ticks for $label (0..$lines) { $x = $x_start + $self->{'text_space'}*2+ $label_len*$w+$self->{'tick_len'}; $y = $y_start+ $label*$delta ; $self->{'gd_obj'}->line( $x_start+$self->{'text_space'}, $y, $x, $y, $misccolor); } #then the right side #get the starting point $x_start = $self->{'curr_x_max'}; $y_start = $self->{'curr_y_min'}; #draw the labels for $label (0..$lines-1) { $x = $x_start - $self->{'text_space'} - $label_len*$w; $y = $y_start + $label*$delta + $delta/2 -$h/2; $self->{'gd_obj'}->string($font, $x, $y, $self->{'f_y_tick'}->($label), $textcolor); } #draw the ticks for $label ( 0..$lines) { $x = $x_start - $self->{'text_space'} *2 - $label_len*$w - $self->{'tick_len'}; $y = $y_start + $label*$delta; $self->{'gd_obj'} ->line( $x_start-$self->{'text_space'}, $y, $x, $y, $misccolor); #add data for grid_lines push @{$self->{grid_data} ->{'y'}}, $y; } #update the borders $self->{'curr_x_min'} += $self->{'text_space'}*2 + $label_len*$w+$self->{'tick_len'}; $self->{'curr_x_max'} = $x_start -$self->{'text_space'}*2 - $label_len*$w-$self->{'tick_len'}; } else { #get the starting point $x_start = $self->{'curr_x_min'}; $y_start = $self->{'curr_y_min'}; #draw the labels for $label (0..$lines-1) { $x = $self->{'curr_x_min'}+$self->{'text_space'}*2; $y = $y_start+ $label*$delta + $delta/2 -$h/2; $self->{'gd_obj'}->string($font, $x, $y, $self->{'f_y_tick'}->($label), $textcolor); } #draw the ticks for $label (0..$lines) { $x = $x_start + $label_len*$w+$self->{'tick_len'}+$self->{'text_space'}*3; $y = $y_start+ $label*$delta ; $self->{'gd_obj'}->line( $x_start+$self->{'text_space'}, $y, $x, $y, $misccolor); #this is also where we have to draw the grid_lines push @{$self->{grid_data}->{'y'}} , $y; } #update the borders $self->{'curr_x_min'} = $x_start + $self->{'text_space'}*3+ $label_len*$w; } } #finally return return 1; } #plot the data sub _draw_data { my $self = shift; my $data = $self->{'dataref'}; my $misccolor = $self->_color_role_to_index('misc'); my $num_points = $self->{'num_datapoints'}; $num_points = 1 if $num_points == 0; my $num_sets = $self->{'num_datasets'}; $num_sets = 1 if $num_sets == 0; my ($lines, $split, $width, $height, $delta_lines, $delta_sets, $map, $last_line ); my ($akt_line, $akt_set, $akt_point, $color, $x_start, $y_start, $x, $y); my ($x_last, $y_last, $delta_point, $brush, $mod, $x_interval, $start); my $i =0; my $interval = ($self->{'max_val'} - $self->{'min_val'}); $interval = 1 if $interval == 0; my $x_max = -0x80000000; # find the height and the width $width = $self->{'curr_x_max'} - $self->{'curr_x_min'}; $width = 1 if $width == 0; $height = $self->{'curr_y_max'} - $self->{'curr_y_min'}; $height = 1 if $height == 0; # init the imagemap data field if they asked for it if ($self->{'imagemap'} =~ /^true$/i) { $self->{'imagemap_data'} = []; } #get the base values $x_interval = $self->{'interval'}; $x_interval = 1 if $x_interval == 0; $start = $self->{'start'}; #find the biggest x value foreach (@{$data->[0]}) { if ($_ > $x_max) { $x_max = $_; } } #calculate the number of lines $lines = int((($x_max-$start)/ $x_interval)+0.99999999999); $lines = 1 if $lines == 0; #find delta_lines for the space between the lines #and delta_sets for the space of the datasets of one line #and the delta_point for the space between the datapoints $delta_lines = $height / $lines; $delta_sets = $delta_lines / $num_sets; $delta_point = $width / ($x_interval); #find $map, for the y values $map = $delta_sets / $interval; #find the mod and the y_start value #correct the start value, if scale is set! Otherwise the plot is to high or to low! #The corecction, isn't perfect, but it does a good job in most cases. if ($self->{'min_val'} >= 0) { $mod = $self->{'min_val'}; if ($self->{'scale'} > 1) { $y_start = $self->{'curr_y_min'} +($interval*$map/2) *($self->{'scale'}-1); } else { $y_start = $self->{'curr_y_min'} } } elsif ($self->{'max_val'} <= 0) { $mod = $self->{'min_val'}; if ($self->{'scale'} > 1) { $y_start = $self->{'curr_y_min'} +($interval*$map/2) *($self->{'scale'}-1); } else { $y_start = $self->{'curr_y_min'}; } } else { $y_start = $self->{'curr_y_min'}+ ($map * $self->{'min_val'}); $mod = 0; } #The upper right corner is the point, where we start $x_start = $self->{'curr_x_min'}; #draw the lines for $akt_set (0..$num_sets-1) { for $akt_point (0..$self->{'num_datapoints'}-1) { #get the color for this dataset $color = $self->_color_role_to_index('dataset'.$akt_set); $brush = $self->_prepare_brush ($color, 'line'); $self->{'gd_obj'}->setBrush ($brush); #start with the first point at line number zero $last_line =0; for $akt_line ($last_line..$lines-1) { #update the last line. That makes it a little bit faster. $last_line = $akt_line; #Don't try to draw, if there is no data if (defined $data->[0][$akt_point]) { if ( $data->[0][$akt_point] <= (($akt_line+1)*$x_interval +$start) && $data->[0][$akt_point] >= $akt_line*$x_interval + $start) { #the current point $x = $x_start + ($data->[0][$akt_point] - ($akt_line *$x_interval)- ($start))* $delta_point; $y = $y_start + $akt_line*$delta_lines + $akt_set*$delta_sets + $delta_sets -($data->[1+$akt_set][$akt_point]-$mod) * $map *$self->{'scale'}; #draw the line $self->{'gd_obj'}->line($x_last, $y_last, $x, $y, gdBrushed)if $akt_point!=0; #calculate the start point for the next line #first if the next point is in the same line if ( defined ($data->[0][$akt_point+1]) && $data->[0][$akt_point+1] <= (($akt_line+1)*$x_interval +$start) && $data->[0][$akt_point+1] > $akt_line*$x_interval + $start ){ $x_last = $x; $y_last = $y; } #second, if the next point is not in the same line else { $x_last = $self->{'curr_x_min'}; $y_last = $y_start + ($akt_line+1)*$delta_lines + $akt_set*$delta_sets + $delta_sets -($data->[1+$akt_set][$akt_point]-$mod) * $map *$self->{'scale'}; } # store the imagemap data if they asked for it if ($self->{'imagemap'} =~ /^true$/i) { $self->{'imagemap_data'}->[$akt_set][$akt_point-1] = [ $x_last, $y_last ]; $self->{'imagemap_data'}->[$akt_set][$akt_point] = [ $x, $y ]; } } else { #Go to the next line. Maybe the current point is in that line! next; } } else { if ($self->{'imagemap'} =~ /^true$/i) { $self->{'imagemap_data'}->[$akt_set][$akt_point-1] = [ undef(), undef() ]; $self->{'imagemap_data'}->[$akt_set][$akt_point] = [ undef(), undef() ]; } } } } } $y_start = $self->{'curr_y_min'}; #draw some nice little lines for $akt_set (0..$num_sets-1) { for $akt_line (0..$lines-1) { #draw a line between the sets at the left side of the chart $self->{'gd_obj'}->line($x_start, $y_start+ $akt_line*$delta_lines + $akt_set*$delta_sets, $x_start+$self->{'tick_len'}, $y_start+ $akt_line*$delta_lines + $akt_set*$delta_sets, $misccolor); #draw a line between the sets at the right side of the chart $self->{'gd_obj'}->line($self->{'curr_x_max'}, $y_start + $akt_line*$delta_lines + $akt_set*$delta_sets, $self->{'curr_x_max'}-$self->{'tick_len'}, $y_start + $akt_line*$delta_lines + $akt_set*$delta_sets, $misccolor); } } #Box it off $self->{'gd_obj'}->rectangle ($self->{'curr_x_min'}, $self->{'curr_y_min'}, $self->{'curr_x_max'}, $self->{'curr_y_max'}, $misccolor); #finally retrun return; } #be a good modul and return 1 1;