#! perl
#
# example of using PGPLOT and CGI
#
# This script does a linear regression fit of data entered via a form.
# Fit parameters and a plot are displayed.
#
# HTML NOTE: ± -> +-
#
# you need to modify location of $WWDIR for your own setup.
#
# Author: C. Lane lane@duphy4.physics.drexel.edu
#
use CGI;
use PGPLOT;
$DEBUG = 0; # if set, writes plot to file and keeps datafile
$CRINOID::Reuse = 1;
$WWDIR = 'sys$scratch:'; # world-writable directory
$q = new CGI;
%SYM = ('+'=>2, 'o'=>4, 'x'=>5, '*'=>12) unless defined(%SYM);
$N = $N2 = 0;
$XLOG = $YLOG = 0;
undef(@X);
undef(@Y);
undef(@X2);
undef(@Y2);
$XLAB = 'X';
$YLAB = 'Y';
$TITLE = 'Linear Regression Plot';
$OLEG = $PLEG = $XLEG = $SLEG = '';
#
# if we have an ID, then form was processed and we're making a plot
#
if ($q->param('ID') ne '') {
do_plot($q);
} else {
do_form($q);
}
sub do_plot
{
my $q = shift;
my ($xmax, $xmin, $ymax, $ymin, $xlo, $xhi, $ylo, $yhi);
my (@u, @v);
my $legy = -2;
my $legx = 0.1;
my $legj = 0.0;
ReadParams($q->param('ID'),1); # read & delete file
print "Content-type: image/gif\n\n";
PGPLOT::pgbeg(0,$DEBUG? 'linear.gif/GIF' : '-/GIF',1,1);
PGPLOT::pgpap(450./85.,1.);
PGPLOT::pgscr(0,1.,1.,1.);
PGPLOT::pgscr(1,0.,0.,0.);
($xmax,$xmin) = maxmin(@X2);
($ymax,$ymin) = maxmin(@Y2);
if ($xmax <= $xmin) {$xmax = $xmin + 0.1; $xmin -= 0.1;}
if ($ymax <= $ymin) {$ymax = $ymin + 0.1; $ymin -= 0.1;}
PGPLOT::pgrnge($xmin,$xmax,$xlo,$xhi);
PGPLOT::pgrnge($ymin,$ymax,$ylo,$yhi);
PGPLOT::pgenv($xlo,$xhi,$ylo,$yhi,0,0 + ($XLOG? 10:0)+($YLOG? 20:0));
PGPLOT::pglab($XLAB,$YLAB,$TITLE);
PGPLOT::pgpnts($N2,\@X2,\@Y2,\@S2,$N2);
my ($m, $b) = regress($N2,\@X2,\@Y2);
if (defined($m)) {
@u = ($xlo, $xhi);
@v = ($m*$xlo+$b, $m*$xhi+$b);
PGPLOT::pgline(2,\@u,\@v);
$legx = 0.5 if $m < 0;
if (!$XLOG && !$YLOG) {
PGPLOT::pgmtxt('T',$legy--, $legx, $legj, sprintf('Fit: \\fiy\\fn = (%5.3g) \\fix\\fn + (%5.3g)',$m,$b));
} elsif ($XLOG && !$YLOG) {
PGPLOT::pgmtxt('T',$legy--, $legx, $legj, sprintf('Fit: \\fiy\\fn = (%5.3g) log(\\fix\\fn) + (%5.3g)',$m,$b));
} elsif (!$XLOG && $YLOG) {
PGPLOT::pgmtxt('T',$legy--, $legx, $legj, sprintf('Fit: \\fiy\\fn = (%5.3g) (%5.3g)\\u\\fix\\fn\\d',10**$b,10**$m));
} else {
PGPLOT::pgmtxt('T',$legy--, $legx, $legj, sprintf('Fit: \\fiy\\fn = (%5.3g) \\fix\\fn\\u (%5.3g)\\d',10**$b,$m));
}
}
PGPLOT::pgmtxt('T',$legy--,$legx,$legj,"\\m4 $OLEG") if $OLEG ne '';
PGPLOT::pgmtxt('T',$legy--,$legx,$legj,"\\m2 $PLEG") if $PLEG ne '';
PGPLOT::pgmtxt('T',$legy--,$legx,$legj,"\\m5 $XLEG") if $XLEG ne '';
PGPLOT::pgmtxt('T',$legy--,$legx,$legj,"\\m12 $SLEG") if $SLEG ne '';
PGPLOT::pgend;
}
sub process_data
{
my $q = shift;
my ($j, $n, $tx);
$j = $N = $N2 = 0;
if ($q->param) {
$TITLE = $q->param('title');
$XLAB = $q->param('xlabel');
$YLAB = $q->param('ylabel');
$XLOG = $q->param('xscale') eq 'log';
$YLOG = $q->param('yscale') eq 'log';
$OLEG = $q->param('oleg');
$PLEG = $q->param('pleg');
$XLEG = $q->param('xleg');
$SLEG = $q->param('sleg');
while (defined($q->param("X$j"))) {
if ($q->param("delete_$j") || $q->param("X$j") eq '' || $q->param("Y$j") eq '') {
$j++;
next;
}
$EM[$N] = '';
$tx = $q->param("X$j");
if (validate_number($tx)>0) {
$X[$N] = sprintf("%g",$tx);
if ($XLOG && $tx <= 0) {
$EM[$N] = 'Need X>0 for log scale ';
}
} else {
$EM[$N] = 'Error parsing X';
}
$tx = $q->param("Y$j");
if (validate_number($tx)>0) {
$Y[$N] = sprintf("%g",$tx);
if ($YLOG && $tx <= 0) {
$EM[$N] .= 'Need Y>0 for log scale ';
}
} else {
$EM[$N] .= 'Error parsing Y ';
}
$S[$N] = $q->param("S$j");
if ($EM[$N] eq '') {
$X2[$N2] = $XLOG ? log10($X[$N]) : $X[$N];
$Y2[$N2] = $YLOG ? log10($Y[$N]) : $Y[$N];
$S2[$N2] = $SYM{$S[$N]};
$N2++;
}
$j++;
$N++;
}
}
return $N;
}
sub do_form
{
my $q = shift;
process_data($q); # process any input data first
print $q->header;
print $q->start_html("Linear Regression Utility");
print "
Linear Regression Utility
\n";
print $q->startform;
print "\n";
print "Plot title: | ",$q->textfield(-name=>'title', -override=>1, -default=>$TITLE, -size=>20, -maxlength=>64),"\n";
print " |
X-axis label: | ",$q->textfield(-name=>'xlabel', -override=>1, -default=>$XLAB, -size=>20, -maxlength=>64)," | ",
$q->radio_group(-name=>'xscale', -values=>['linear', 'log'], -default=>'linear', -columns=>2),"\n";
print " |
Y-axis label: | ",$q->textfield(-name=>'ylabel', -override=>1, -default=>$YLAB, -size=>20, -maxlength=>64)," | ",
$q->radio_group(-name=>'yscale', -values=>['linear', 'log'], -default=>'linear', -columns=>2),"\n";
print ' |
o legend: | ',$q->textfield(-name=>'oleg', -override=>1, -default=>$OLEG, -size=>20, -maxlength=>64);
print ' |
+ legend: | ',$q->textfield(-name=>'pleg', -override=>1, -default=>$PLEG, -size=>20, -maxlength=>64);
print ' |
x legend: | ',$q->textfield(-name=>'xleg', -override=>1, -default=>$XLEG, -size=>20, -maxlength=>64);
print ' |
* legend: | ',$q->textfield(-name=>'sleg', -override=>1, -default=>$SLEG, -size=>20, -maxlength=>64);
print " |
\n";
print "\n";
print "Pt# | X | Y | Symbol\n";
for ($j = 0; $j < $N; $j++) {
if ($EM[$j] eq '') {
print $q->hidden(-name=>"X$j", -override=>1, -value=>$X[$j]);
print $q->hidden(-name=>"Y$j", -override=>1, -value=>$Y[$j]);
print $q->hidden(-name=>"S$j", -override=>1, -value=>$S[$j]);
print ' |
',$j+1,' | ',$X[$j],' | ',$Y[$j],' | ',$S[$j];
print ' | ';
} else {
print ' |
',$j+1;
print ' | ',$q->textfield(-name=>"X$j",-default=>$X[$j],-override=>1,-size=>10,-maxlength=>10);
print ' | ',$q->textfield(-name=>"Y$j",-default=>$Y[$j],-override=>1,-size=>10,-maxlength=>10);
print ' | ',$q->radio_group(-name=>"S$j", -values=>['o', '+', 'x', '*'], -default=>'o'),"\n";
print ' | ',$EM[$j];
}
print ' | ',$q->checkbox(-name=>"delete_$j",-override=>1,-label=>"Delete?");
}
for ($j = $N; $j < $N+4; $j++) {
print " |
",$j+1," | ";
print $q->textfield(-name=>"X$j",-default=>'',-override=>1,-size=>10,-maxlength=>10);
print " | ";
print $q->textfield(-name=>"Y$j",-default=>'',-override=>1,-size=>10,-maxlength=>10);
print ' | ',$q->radio_group(-name=>"S$j", -values=>['o', '+', 'x', '*'], -default=>'o'),"\n";
print " | | ";
}
print ' |
',$q->submit(-name=>'Update');
print ' | ',$q->reset;
print " |
\n";
print $q->endform;
if ($N2 > 1) {
my ($m,$b) = regress($N2,\@X2,\@Y2);
print "
\nLinear Regression:
\n";
if (defined($m)) {
print '',$YLOG? 'log(y)':'y','= m ',$XLOG? 'log(x)':'x',' + b';
print ' |
Slope (m): | ',sprintf("%5.3g\n",$m);
print ' |
Intercept (b): | ',sprintf("%5.3g\n",$b);
print " |
\n";
} else {
print "Error! Infinite slope\n";
}
}
if ($N2 > 2) {
print "
\n";
$ID = SaveParams($q);
print '
',"\n";
}
print $q->end_html;
}
sub ReadParams {
my $ID = shift; # ID of saved data
my $dflag = shift; # deletion flag
local *SAVEFILE;
return unless defined($ID);
return unless open(SAVEFILE, "<$WWDIR$ID");
my $q2 = new CGI SAVEFILE;
process_data($q2);
close SAVEFILE;
unlink "$WWDIR$ID" if !$DEBUG && defined($dflag) && $dflag;
return $ID;
}
sub SaveParams {
my $q = shift;
local *SAVEFILE;
srand unless defined($DidSrand);
$DidSrand = 1;
my $ID = sprintf('LINEAR_%08.8x',rand(0xFFFFFF)); # generate random ID
if (open(SAVEFILE,">$WWDIR$ID")) {
$q->save(SAVEFILE);
close(SAVEFILE);
} else {
print "Error Saving CGI state!\n";
return undef;
}
return $ID;
}
#
# checks that text number is valid format and within limits
#
sub validate_number {
my ($v, $ll, $ul) = @_;
my ($v2,$ret);
$v2 = $v;
$v =~ s/\s*//g;
return -1 if (length($v) == 0);
$ret = 1;
if ($v =~ /^[+-]?\d*(\.\d*)?([Ee][+-]?\d+)?$/) {
if ((defined($ll) && $v < $ll) || (defined($ul) && $v > $ul)) {
$ret = 0;
}
} else {
$ret = -1;
}
return $ret;
}
#
# do linear-regression, assumes all data points have same error
#
sub regress {
my $n = shift;
my $x = shift; # ref
my $y = shift; # ref
my $Sum1 = $n;
my $SumX = 0.;
my $SumY = 0.;
my $SumXX = 0.;
my $SumXY = 0.;
my $SumYY = 0.;
my ($j, $tx, $ty);
for ($j = 0; $j < $n; $j++) {
$tx = $x->[$j];
$ty = $y->[$j];
$SumX += $tx;
$SumY += $ty;
$SumXX += $tx*$tx;
$SumXY += $tx*$ty;
$SumYY += $ty*$ty;
}
my $D = $Sum1*$SumXX - $SumX*$SumX;
my $b = ($SumY * $SumXX - $SumXY * $SumX);
my $m = ($SumXY*$Sum1 - $SumY*$SumX);
return undef if abs($D) < 1.e-10;
return ($m/$D, $b/$D);
}
sub log10 {
$ILOG10 = 1./log(10.) unless defined($ILOG10);
return log(shift)*$ILOG10;
}
sub maxmin {
my (@v) = @_;
my ($max, $min);
$max = $min = $v[0];
foreach (@v) {
$max = $max < $_ ? $_ : $max;
$min = $min > $_ ? $_ : $min;
}
return ($max,$min);
}