#!/usr/bin/perl -w use strict; use Tk; use Tk::Balloon; use Tk::NumEntry; use Tk::ROText; use Tk::Dialog; use Statistics::Distributions; use Statistics::Descriptive; &gui; sub gui { my $mw = MainWindow->new(-title=>'Competition Plotter'); my $topfr = $mw->Frame->pack; my $botfr = $mw->Frame->pack(-fill=>'both', -expand=>1 ); my $inputfr = $topfr->Frame(-relief=>'groove', -borderwidth=>2, )->pack(-side=>'left', -anchor=>'nw', -fill=>'both', ); my $wordfile_label = $inputfr->Label(-text=>'Data File', )->grid(-row=>0,-column=> 0,-sticky=>'e'); my $wordfile_lab_bal = $topfr->Balloon(-state=>'balloon', -background=>'white'); my $wordfilehelp = "A tab-delimited file of competition values. First column should be data labels (e.g. mouse number). Competition pairs should be located in adjacent columns."; $wordfile_lab_bal->attach($wordfile_label, -balloonmsg => $wordfilehelp, ); my $wordfile; my $wordfile_e = $inputfr->Entry(-state=>'normal', -textvariable=> \$wordfile, -width=>21, )->grid(-row=>0, -column=> 1, -sticky=>'w', -columnspan=>2, ); my %data; my $ymax; my $ymin = 0; $inputfr->Label(-text=>'Y-axis min', )->grid(-row=>1,-column=>0,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$ymin, -width=>7, )->grid(-row=>1,-column=>1,-sticky=>'w'); $inputfr->Label(-text=>'Y-axis max', )->grid(-row=>1,-column=>2,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$ymax, -width=>7, )->grid(-row=>1,-column=>3,-sticky=>'w'); my $ytick = 5; $inputfr->Label(-text=>'Y-axis ticks', )->grid(-row=>2,-column=>0,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$ytick, -width=>7, )->grid(-row=>2,-column=>1,-sticky=>'w'); my $ptsize = 5; $inputfr->Label(-text=>'Point size', )->grid(-row=>2,-column=>2,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$ptsize, -width=>7, )->grid(-row=>2,-column=>3,-sticky=>'w'); my $tickin = 5; $inputfr->Label(-text=>'Tick length Inside', )->grid(-row=>3,-column=>0,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$tickin, -width=>7, )->grid(-row=>3,-column=>1,-sticky=>'w'); my $tickout = 0; $inputfr->Label(-text=>'Tick Length Outside', )->grid(-row=>3,-column=>2,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$tickout, -width=>7, )->grid(-row=>3,-column=>3,-sticky=>'w'); my $width = 480; $inputfr->Label(-text=>'Plot width', )->grid(-row=>4,-column=>0,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$width, -width=>7, )->grid(-row=>4,-column=>1,-sticky=>'w'); my $height = 300; $inputfr->Label(-text=>'Plot height', )->grid(-row=>4,-column=>2,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$height, -width=>7, )->grid(-row=>4,-column=>3,-sticky=>'w'); my $pair = 50; $inputfr->Label(-text=>'Pair spacing', )->grid(-row=>5,-column=>0,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$pair, -width=>7, )->grid(-row=>5,-column=>1,-sticky=>'w'); my $set = 100; $inputfr->Label(-text=>'Set spacing', )->grid(-row=>5,-column=>2,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$set, -width=>7, )->grid(-row=>5,-column=>3,-sticky=>'w'); my $hbuf = 40; $inputfr->Label(-text=>'Horiz buffer', )->grid(-row=>6,-column=>0,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$hbuf, -width=>7, )->grid(-row=>6,-column=>1,-sticky=>'w'); my $vbuf = 30; $inputfr->Label(-text=>'Vert buffer', )->grid(-row=>6,-column=>2,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$vbuf, -width=>7, )->grid(-row=>6,-column=>3,-sticky=>'w'); my $databuf = 50; $inputfr->Label(-text=>'First data set buffer', )->grid(-row=>7,-column=>0,-sticky=>'e'); $inputfr->NumEntry(-minvalue => 0, -textvariable => \$databuf, -width=>7, )->grid(-row=>7,-column=>1,-sticky=>'w'); my $labels = 0; $inputfr->Label(-text=>'Data labels', )->grid(-row=>7,-column=>2,-sticky=>'e'); $inputfr->Checkbutton(-variable=>\$labels, )->grid(-row=>7,-column=>3,-sticky=>'w'); $inputfr->Button(-text=>"Exit", -command=> sub { exit }, )->grid(-row=>8, -column=>3, -pady=>5, ); my $plotfr = $topfr->Frame(-relief=>'groove', -borderwidth=>2, )->pack(-side=>'right', -anchor=>'nw', -fill=>'both', ); my $plot = $plotfr->Canvas(-background=>'white', -height=>300, -width=>480, )->pack(); $inputfr->Button(-text=>"Reset", -command=> [\&reset, \$plotfr, \$plot, \%data, \$ymin, \$ymax, \$ytick, \$ptsize, \$width, \$height, \$pair, \$set, \$hbuf, \$vbuf, \$databuf, \$labels, \$tickin, \$tickout ], )->grid(-row=>8, -column=>0, -pady=>5, ); $inputfr->Button(-text=>"Save Plot", -command=> [\&saveplot, \$mw, \$plot], )->grid(-row=>8, -column=>2, -pady=>5, ); $inputfr->Button(-text=>"Update plot", -command=> [\&main, \$plotfr, \$plot, \%data, \$ymin, \$ymax, \$ytick, \$ptsize, \$width, \$height, \$pair, \$set, \$hbuf, \$vbuf, \$databuf, \$labels, \$tickin, \$tickout ], )->grid(-row=>8, -column=>1, -pady=>5, ); my $textfr = $botfr->Frame( )->pack(-fill=>'both', -expand=>1, ); my $textwin = $textfr->Scrolled('ROText', -height=>14, -scrollbars=>'osoe', -wrap=>'word', )->pack(-fill=>'both', -expand=>1, ); $inputfr->Button(-text=>'Load', -command=> [ \&load, \$wordfile_e , \$mw, \%data, \$ymax, \$plotfr, \$plot, \$width, \$height, \$textwin ], -borderwidth=>1, -padx=> 0, -pady=> 0, )->grid(-row=>0 ,-column=>3 ,-sticky=>'w', -padx=>3, -pady=>5, ); $inputfr->Button(text=>"Calculate Stats", -command=> [ \&stats, \$mw, \%data, \$textwin ], )->grid(-row=>9, -column=>0, -pady=>5, ); $inputfr->Button(text=>"Save Stats", -command=> [ \&savestat, \$mw, \$textwin ], )->grid(-row=>9, -column=>1, -pady=>5, ); MainLoop; } sub stats { my ($mwref, $dataref, $textwinref) = @_; $$textwinref->delete('1.0','end'); $$mwref->update; my %data = %{$dataref}; my @xcat; my %pairdiff; my %pairname; foreach my $col (keys %data) { next if ($col % 2 == 1); my %temphash = %{$data{$col}}; my $count = 1; foreach my $row (keys %temphash) { if ($row == 0) { $xcat[$col] = $temphash{$row}; next; } my $diff = $data{$col-1}{$row} - $data{$col}{$row}; my $colpair = $col-1 . "_" . $col; $pairname{$colpair} = $data{$col-1}{0} . ' vs ' . $data{$col}{0}; $pairdiff{$colpair}{$count} = $diff; $count++; } } foreach my $colpair (keys %pairdiff) { my %temphash = %{$pairdiff{$colpair}}; my $stat = Statistics::Descriptive::Sparse->new(); foreach my $count (sort keys %temphash) { $stat->add_data($temphash{$count}); } my $mean = $stat->mean; my $stdev = $stat->standard_deviation; my $count = $stat->count; my $dof = $count - 1; my $t = ($mean / ($stdev/sqrt($count)) ); my $tprob1 = Statistics::Distributions::tprob($dof,$t); my $tprob2 = $tprob1 * 2; my $results = "$pairname{$colpair} Paired t-test (2-tailed)\n\tMean Difference\t$mean\n\tDF\t$dof\n\tt-value\t$t\n\tp-value\t$tprob2\n\n"; &printlog($results,$textwinref); } } sub savestat { my $mwref = shift; my $mw = $$mwref; my $logwinref = shift; # fix portability my $outfile = $mw->getSaveFile(-title=>'Save Results As'); $outfile .= '.txt' if $outfile !~ /\.txt$/i; open(OUTFILE,">$outfile") or die "Can't open output file $outfile\n"; print OUTFILE $$logwinref->get("1.0","end"); close OUTFILE; $mw->messageBox(-title => 'Finished', -message => 'Results Saved', -type => 'OK'); } sub printlog { my $line = shift; my $logwinref = shift; my $logwin = $$logwinref; $logwin->insert('end',$line); $logwin->update(); } sub main { my ($plotfrref, $plotref, $dataref, $yminref, $ymaxref, $ytickref, $ptsizeref, $widthref, $heightref, $pairref, $setref, $hbufref, $vbufref, $databufref, $labelsref, $tickinref, $tickoutref) = @_; my %data = %{$dataref}; my $ymin = $$yminref; my $ymax = $$ymaxref; my $ytick = $$ytickref; my $ptsize = $$ptsizeref; my $width = $$widthref; my $height = $$heightref; my $pair = $$pairref; my $set = $$setref; my $hbuf = $$hbufref; my $vbuf = $$vbufref; my $databuf = $$databufref; my $labels = $$labelsref; my $tickin = $$tickinref; my $tickout = $$tickoutref; # clear canvas $$plotref->destroy if Tk::Exists($$plotref); $$plotref = $$plotfrref->Canvas(-width=>$width, -height=>$height, -background=>'white', )->pack(); # draw axes $$plotref->createLine( $hbuf,$vbuf, $hbuf,$height-$vbuf, $width-$hbuf,$height-$vbuf); # draw y ticks for (my $c = 0; $c <= $ytick; $c++) { my $xstart = $hbuf - $tickout; my $xstop = $hbuf + $tickin; my $y = ((($height - ($vbuf*2)) / $ytick) * $c) + $vbuf; $$plotref->createLine($xstart,$y, $xstop,$y); my $yval = $ymax - ((($ymax - $ymin)/($ytick))*$c); if ($ymax / $ytick >= 1) { $yval = int($yval+0.5); } else { $yval = sprintf("%.1f",$yval); } my $offset = length($yval) * 3 + 5; $$plotref->createText($xstart-$offset,$y, -text=>"$yval"); } # plot data my @xcat; foreach my $col (sort keys %data) { my %temphash = %{$data{$col}}; my $xval = ((($col) / 2 ) * ($pair + $set)) -$set + $hbuf + $databuf; my $lastx = ((($col - 2) / 2 ) * ($pair + $set)) + $hbuf + $databuf; foreach my $row (sort keys %temphash) { # print "$row\t$col\t$temphash{$row}\n"; if ($row == 0) { $xcat[$col] = $temphash{$row}; next; } next if ($col % 2 == 1); my $datapoint = $temphash{$row}; my $yval = $vbuf + ($ymax-$datapoint) / ($ymax-$ymin) * ($height - (2*$vbuf) ); $$plotref->createOval($xval-$ptsize/2,$yval-$ptsize/2, $xval+$ptsize/2, $yval+$ptsize/2, -fill=>'black'); $$plotref->createText($xval + ($ptsize/2) + 12,$yval, -text=>"$datapoint") if $labels; # connect with line my $lasty = $vbuf + ($ymax-$data{$col-1}{$row}) / ($ymax-$ymin) * ($height - (2*$vbuf) ); $$plotref->createLine($xval,$yval, $lastx,$lasty); $$plotref->createOval($lastx-$ptsize/2,$lasty-$ptsize/2, $lastx+$ptsize/2,$lasty+$ptsize/2, -fill=>'white'); my $textoffset = length($data{$col-1}{$row}) * 5; $$plotref->createText($lastx - ($ptsize/2) - $textoffset,$lasty, -text=>"$data{$col-1}{$row}") if $labels; } if ($col % 2 == 0) { $$plotref->createText($xval,$height-$vbuf/2, -text=>"$xcat[$col]"); } else { my $labelx = ((($col - 1) / 2 ) * ($pair + $set)) + $hbuf + $databuf; $$plotref->createText($labelx,$height-$vbuf/2, -text=>"$xcat[$col]"); } } } sub load { my $entryblankref = shift; my $entryblank = $$entryblankref; my $mwref = shift; my $mw = $$mwref; my $dataref = shift; my %data = %{$dataref}; my $ymaxref = shift; my $plotfrref = shift; my $plotref = shift; my $widthref = shift; my $heightref = shift; my $width = $$widthref; my $height = $$heightref; my $textwinref = shift; # cleanup $$plotref->destroy if Tk::Exists($$plotref); $$plotref = $$plotfrref->Canvas(-width=>$width, -height=>$height, -background=>'white', )->pack(); $$textwinref->delete('1.0','end'); $$mwref->update; undef(%data); # main my $file; if ($^O =~ /mswin/i) { my $opentypes = [ "{All files} * ", "{PCL files} {.pcl} ", "{Text files} {.txt} ", ]; $file = $mw->getOpenFile(-filetypes=>$opentypes); } else { $file = $mw->getOpenFile(-title=>'Load File'); } if (defined $file and $file ne '') { $entryblank->delete(0, 'end'); $entryblank->insert(0, $file); $entryblank->xview('end'); } open(INFILE,$file) or die "Couldn't open input file $file\n"; my $linecount = 0; while() { chomp; my @line = split(/\t/); for (my $c = 1; $c <= $#line; $c++) { $data{$c}{$linecount} = $line[$c]; } $linecount++; } %{$dataref} = %data; my $max = 0; foreach my $col (keys %data) { my %temphash = %{$data{$col}}; foreach my $row (keys %temphash) { # print "$row\t$col\t$temphash{$row}\n"; next if $row == 0; $max = $temphash{$row} if $max < $temphash{$row}; } } $$ymaxref = $max + (0.05 * $max); if ($$ymaxref >= 15) { my $dialog = $mw->Dialog(-title => "Warning", -text => 'Data contains high values, indicating it may not be transformed to log scale. OK to proceed?', -buttons => [qw/Yes No/], -default_button => 'No', ); my $answer = $dialog->Show(); if ($answer eq 'No') { undef $file; undef %data; $entryblank->delete(0, 'end'); $$ymaxref = undef; return; } } } sub reset { my ($plotfrref, $plotref, $dataref, $yminref, $ymaxref, $ytickref, $ptsizeref, $widthref, $heightref, $pairref, $setref, $hbufref, $vbufref, $databufref, $labelsref, $tickinref, $tickoutref) = @_; my %data = %{$dataref}; $$yminref = 0; # my $ymax = $$ymaxref; $$ytickref = 5; $$ptsizeref = 5; $$widthref = 480; $$heightref = 300; $$pairref = 50; $$setref = 100; $$hbufref = 40; $$vbufref = 30; $$databufref = 50; $$labelsref = 0; $$tickinref = 5; $$tickoutref = 0; my $max = 0; foreach my $col (keys %data) { my %temphash = %{$data{$col}}; foreach my $row (keys %temphash) { # print "$row\t$col\t$temphash{$row}\n"; next if $row == 0; $max = $temphash{$row} if $max < $temphash{$row}; } } $$ymaxref = $max + (0.05 * $max); } sub saveplot { my $mwref = shift; my $mw = $$mwref; my $plotref = shift; my $plot = $$plotref; my $file = $mw->getSaveFile(); $file .= '.ps' if $file !~ /\.ps$/i; print $file; return if $file eq '.ps'; $plot->postscript(-file=>"$file"); $mw->messageBox(-title => 'Finished', -message => 'Plot Saved', -type => 'OK'); }