## ## The cuttlefish Visualization Tool. ## Copyright (C) 2006 The Regents of the University of California. ## ## This program is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License version 2 as published ## by the Free Software Foundation. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## ## written by Bradley Huffaker ## ## documention by Joshua Polterock ## Bradley Huffaker ## Marina Fomenkova ## ## # Histogram # This module draws a histogram which contains all the data seen across # all the frames. # package Histogram; use GD; use Canvas; use Time::Local; use Carp; use Util; require Exporter; @ISA = qw( Exporter ); @EXPORT = qw(); @EXPORT_OK = qw(); use strict; my $default_font = gdSmallFont; sub new { my ($this, $linenum) = @_; my $histogram = bless { "type" => "Histogram", "linenum" => $linenum, "x" => 0, "y" => 0, "width" => 240, "height" => 120, "title" => "Global:", "font" => $default_font, "x-axis" => "", "y-axis" => "", "sum-min" => undef, "sum-max" => undef, "time-min" => undef, "time-max" => undef, "background-color" => [0,0,0], "foreground-color" => [255,255,255], "frames" => [] }, $this; return $histogram; } sub check { my ($this) = @_; my @missing; foreach my $key (qw(x y width height)) { unless (defined $this->{$key}) { push @missing, $key; } } if ($#missing > 0) { return "undefined ",join(",", @missing); } return; } sub setValue { my ($this, $key, $value) = @_; if ($key eq "x-axis" || $key eq "y-axis" || $key eq "title") { $this->{$key} = $value; } elsif ($key eq "font") { return $this->setFont($key,$value); } elsif ($key =~ /-color$/) { return $this->setColor($key, $value); } else { return $this->_setValue("setValueNumber", [$key], [$value]); } return; } sub setFont { my ($this, $key, $value) = @_; my $font = String2Font($value); unless (defined $font) { return "Histogram::setValue $font is not a valid font"; } $this->{$key} = $font; return; } sub setColor { my ($this, $key, $value) = @_; my @values = split /\s+/, $value; my $error_msg; my $error_found; foreach my $v (0..2) { if ($v =~ /^\d+$/) { $error_msg .= "$v " } else { $error_msg .= "\"$v\" "; $error_found = 1; } } if (defined $error_found) { return "Histogram::setColor requires three integers, found $error_msg"; } $this->{$key} = \@values; return; } sub _setValue { my ($this, $func, $keys, $values) = @_; my $num_keys = @$keys; foreach my $i (0..($num_keys-1)) { unless (defined $values->[$i] && $values->[$i] =~ /^-?\d+(\.\d+)?$/) { return "Histogram::$func(".join(",",@$keys).") " ."illegal value for $keys->[$i]:$values->[$i]"; } $this->{$keys->[$i]} = $values->[$i]; } return; } sub setFrames { my ($this, @orginals) = @_; my @frames; my @index2master_index; foreach my $index (0..$#orginals) { my $frame = $orginals[$index]; if (defined $frame->{"master"}) { push @frames, $frame; } $index2master_index[$index] = $#frames; } $this->{"frames"} = \@frames; $this->{"index2master_index"} = \@index2master_index; my $sum_min = $this->{"sum-min"}; my $sum_max = $this->{"sum-max"}; my $time_min = $this->{"time-min"}; my $time_max = $this->{"time-max"}; foreach my $frame (@orginals) { my $sum = $frame->{"sum"}; if (!defined $sum_min) { $sum_min = $sum_max = $sum; } elsif ($sum_min > $sum) { $sum_min = $sum; } elsif ($sum_max < $sum) { $sum_max = $sum; } my $time_front = $frame->{"time"}; my $time_back = $frame->{"time"} + $frame->{"size"}; if (!defined $time_min) { $time_min = $time_front; $time_max = $time_back; } if ($time_min > $time_front) { $time_min = $time_front; } if ($time_max < $time_back) { $time_max = $time_back; } } $this->{"sum-min"} = $sum_min; $this->{"sum-max"} = $sum_max; $this->{"time-min"} = $time_min; $this->{"time-max"} = $time_max; } ############################################################################ # Check Images and Values ########################################################################### sub draw { my ($this, $canvas, $index_orginal) = @_; my $index = $this->{"index2master_index"}[$index_orginal]; my $image = $canvas->{IMAGE}; my $width = $this->{width}; my $height = $this->{height}; my $title = $this->{title}; my $x_axis = $this->{"x-axis"}; my $y_axis = $this->{"y-axis"}; my $font = $this->{font}; my $graph_x = $this->{x}; my $graph_y = $this->{y}; my @frames = @{$this->{frames}}; my $num_frames = @frames; my $max = $this->{"sum-max"}; $max = 0.000000001 if ($max == 0); ####################################################### # shift for title, axis ####################################################### my ($unit, $width, @x_key_values) = $this->CreateTimeKeys($width); unless (defined $x_axis) { $x_axis = $unit; } else { if ($x_axis =~ /[^\s]/) { $x_axis .= " ($unit)"; } else { $x_axis = $unit; } } my $title_y = $graph_y; if ($title =~ /[^\s]/) { my $size = 1.5*$font->height; $graph_y += $size; $height -= $size; } my $x_axis_y = $graph_y + $height - $font->height; $width -= $font->width; if ($x_axis =~ /[^\s]/) { $height -= 2*$font->height; } my $y_axis_x = $graph_x; if ($y_axis =~ /[^\s]/) { $width -= $font->height; $graph_x += $font->height; } my $title_x = $graph_x; ####################################################### # shift for ticks ####################################################### my ($sum_len, @y_key_values) = $this->CreateSumKeys($height); $graph_x += $sum_len; $width -= $sum_len; ####################################################### # Draw data ####################################################### my $deactive = $canvas->getColor(100,100,255); my $active = $canvas->getColor(210,210,255); # my $active_sky = $canvas->getColor(100,50,50); my $active_sky = $canvas->getColor(100,50,50); my $num_frames = @frames; my $w = ($width/$num_frames); $w = int($w); my $time_max = $this->{"time-max"}; my $time_min = $this->{"time-min"}; my $time_width = $time_max - $time_min; my @x0; foreach my $i (0..$#frames) { my $time = $frames[$i]{"time"}; $x0[$i] = $width*(($time-$time_min)/$time_width) + $graph_x; } my @selected; foreach my $i (0..$#frames) { my $x0 = $x0[$i]; my $x1; if ($i == $#frames) { $x1 = $width + $graph_x; } else { $x1 = $x0[$i+1]; } my $sum = $frames[$i]{"sum"}; my $h = $height*($sum/$max); $h = int($h); my $y1 = $graph_y + $height; my $y0= $y1 - $h; my $color = $deactive; if ($i == $index) { $color = $active; } my @values = ($x0, $y0, $x1, $y1, $color); if ($i == $index) { @selected = @values; } else { $image->filledRectangle(@values); } } my ($x0,$y0,$x1, $y1) = @selected; my $y_sky0 = $graph_y; my $y_sky1 = $y0 -15; #my $y_sky0 = $graph_y + $height; #my $y_sky1 = $y_sky0 + $font->height; if ($y_sky1 > $y_sky0) { $image->filledRectangle($x0,$y_sky0,$x1,$y_sky1,$active_sky); } $image->filledRectangle(@selected); ####################################################### # Draw axises ####################################################### my $foreground = $canvas->getColor(@{$this->{"foreground-color"}}); my $background = $canvas->getColor(@{$this->{"background-color"}}); # Draw the shared 0 my $x = $graph_x-$font->width; my $y = $graph_y+$height; $image->string($font, $x, $y, "0", $foreground); foreach my $index (0..$#y_key_values) { my $key_val = $y_key_values[$index]; my ($key, $val) = @$key_val; my $x = $graph_x - $font->width*length($key); my $y = $graph_y + $height - $height*($val/$max); my $y_string -= $font->height/2; if ($val != 0) { $image->string($font, $x, $y-$font->height/2, $key, $foreground); my $x = $graph_x; my $xx = $x + $font->width; $image->line($x, $y, $xx, $y, $foreground); } } foreach my $index (0..$#x_key_values) { my ($key, $val) = @{$x_key_values[$index]}; if ($val != 0) { my $x = $graph_x + $width*($val/$time_width); my $x_string = $x; $x_string -= $font->width*(length($key)/2); my $y = $graph_y + $height; $image->string($font, $x_string, $y, $key, $foreground); my $yy = $y - $font->width; $image->line($x, $yy, $x, $y, $foreground); } } ####################################################### # Draw titles ####################################################### if ($title =~ /[^\s]/) { my $x = $title_x;# + ($width - $font->width*length($title))/2; my $y = $title_y; $image->string($font, $x, $y, $title,$foreground); } if ($x_axis =~ /[^\s]/) { my $x = $graph_x + ($width - $font->width*length($x_axis))/2; $image->string($font, $x, $x_axis_y, $x_axis,$foreground); } if ($y_axis =~ /[^\s]/) { my $x = $y_axis_x; my $y = $graph_y + ($height + $font->width*length($y_axis))/2; $y = int($y); $image->stringUp($font, $x, $y, $y_axis,$foreground); } } sub CreateTimeKeys { my ($this, $width) = @_; my $max = $this->{"time-max"}; my $min = $this->{"time-min"}; my $font = $this->{"font"}; my $hash = { "min" => $min, "max" => $max, "width" => $width, "font_size" => $font->width, "side_by_side" => 1, "value2unit" => { 1 => "sec", 60 => "min", 60*60 => "hour", 24*60*60 => "day", 7*24*60*60 => "week", 30*24*60*60 => "month", 365*24*60*60 => "year" } }; my ($unit, $label_length, @key_values) = Range2Units($hash); my $last_key = $key_values[$#key_values][0]; $width -= $font->width*length($last_key); return ($unit, $width, @key_values); } sub CreateSumKeys { my ($this, $height) = @_; my $font = $this->{font}; my $max = $this->{"sum-max"}; my $hash = { "min" => 0, "max" => $max, "width" => $height, "font_size" => $font->height, "compress" => 1 }; my ($unit, $label_length, @key_values) = Range2Units($hash); return ($label_length, @key_values); }