# The LearningOnline Network with CAPA # Dynamic plot # # $Id: lonplot.pm,v 1.8 2001/12/19 19:22:52 matthew Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA 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 LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # # 12/15/01 Matthew # 12/18 Matthew package Apache::lonplot; use strict; use Apache::response; use Apache::lonxml; use Digest::MD5 qw(md5 md5_hex md5_base64); sub BEGIN { &Apache::lonxml::register('Apache::lonplot',('plot')); } ## ## Tests used in checking the validitity of input ## my $int_test = sub {$_[0]=~/^\d+$/}; my $real_test = sub {$_[0]=~/^[+-]?\d*\.?\d*$/}; my $color_test = sub {$_[0]=~/^x[\da-f]{6}$/}; my $onoff_test = sub {$_[0]=~/^(on|off)$/}; my $key_pos_test = sub {$_[0]=~/^(top|bottom|right|left|outside|below)+$/}; my $sml_test = sub {$_[0]=~/^(small|medium|large)$/}; my $linestyle_test = sub {$_[0]=~/^(lines|linespoints|dots|points|steps)$/}; my $words_test = sub {$_[0]=~/^((\w+\b*)+$/}; ## ## Default values for attributes of elements ## my %plot_defaults = ( height => {default => 200, test => $int_test }, width => {default => 200, test => $int_test }, bgcolor => {default => 'xffffff', test => $color_test}, fgcolor => {default => 'x000000', test => $color_test}, transparent => {default => 'off', test => $onoff_test}, grid => {default => 'off', test => $onoff_test}, border => {default => 'on', test => $onoff_test}, font => {default => 'medium', test => $sml_test } ); my %key_defaults = ( title => { default => '', test => $words_test }, box => { default => 'off', test => $onoff_test }, pos => { default => 'top right', test => $key_pos_test} ); my %label_defaults = ( xpos => {default => 0, test => $real_test }, ypos => {default => 0, test => $real_test }, justify => {default => 'left', test => sub {$_[0]=~/^(left|right|center)$/}} ); my %axis_defaults = ( color => {default => 'x000000', test => $color_test}, # thickness => {default => 1, test => $int_test }, xmin => {default => -10.0, test => $real_test }, xmax => {default => 10.0, test => $real_test }, ymin => {default => -10.0, test => $real_test }, ymax => {default => 10.0, test => $real_test } ); my %curve_defaults = ( color => {default => 'x000000', test => $color_test }, name => {default => 'x000000', test => sub {$_[0]=~/^[\w ]*$/} }, linestyle => {default => 'lines', test => $linestyle_test } ); ## ## End of defaults ## my (%plot,%key,%axis,$title,$xlabel,$ylabel,@labels,@curves); sub start_plot { %plot = ''; %key=''; %axis=''; $title=''; $xlabel=''; $ylabel=''; @labels = ''; @curves=''; # my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result=''; &Apache::lonxml::register('Apache::plot', ('title','xlabel','ylabel','key','axis','label','curve')); push (@Apache::lonxml::namespace,'plot'); ## Always evaluate the insides of the tags my $inside = &Apache::lonxml::get_all_text("/plot",$$parser[-1]); $inside=&Apache::run::evaluate($inside,$safeeval,$$parstack[-1]); &Apache::lonxml::newparser($parser,\$inside); ##------------------------------------------------------- &get_attributes(\%plot,\%plot_defaults,$parstack,$safeeval,'plot'); if ($target eq 'web') { } return ''; } sub end_plot { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; pop @Apache::lonxml::namespace; &Apache::lonxml::deregister('Apache::lonplot', ('title','xlabel','ylabel','key','axis','label','curve')); my $result = ''; if ($target eq 'web') { ## Determine filename -- Need to use the 'id' thingy that Gerd ## mentioned. my $tmpdir = '/home/httpd/perl/tmp/'; my $filename = $tmpdir.$ENV{'user.name'}.'_'.$ENV{'user.domain'}. '_plot.data'; my $usersees=md5_base64($filename.'_'.$ENV{'REMOTE_ADDR'}); ## Write the plot description to the file my $fh=&Apache::File->new('/home/httpd/perl/tmp/'.$realname); &write_gnuplot_file($fh); ## return image tag for the plot $result = '{'text'} = &Apache::lonxml::get_all_text("/label",$$parser[-1]); push(@labels,\%label); if ($target eq 'web') { # This routine should never return anything. } return $result; } sub end_label { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result = ''; if ($target eq 'web') { # This routine should never return anything. } return $result; } ##------------------------------------------------------------------- curve sub start_curve { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result=''; my %curve; &get_attributes($curve,\%curve_defaults,$parstack,$safeeval,$tagstack); push (@curves,$curve); &Apache::lonxml::register('Apache::lonplot',('function','data')); push (@Apache::lonxml::namespace,'curve'); if ($target eq 'web') { # This routine should never return anything. } return $result; } sub end_curve { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result = ''; pop @Apache::lonxml::namespace; &Apache::lonxml::deregister('Apache::lonplot',('function','data')); if ($target eq 'web') { # This routine should never return anything. } return $result; } ##------------------------------------------------------------ curve function sub start_function { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result=''; if (exists($curves[-1]->{'data'}) { &Apache::lonxml::warning('Use of precludes use of . The will be omitted in favor of the declaration.'); delete($curves[-1]->{'data'}); } $curves[-1]->{'function'} = &Apache::lonxml::get_all_text("/function",$$parser[-1]); if ($target eq 'web') { # This routine should never return anything. } return $result; } sub end_function { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result = ''; if ($target eq 'web') { # This routine should never return anything. } return $result; } ##------------------------------------------------------------ curve data sub start_data { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result=''; if (exists($curves[-1]->{'function'})) { &Apache::lonxml::warning('Use of precludes use of . The will be omitted in favor of the declaration.'); delete($curves[-1]->{'function'}); } my $datatext = &Apache::lonxml::get_all_text("/data",$$parser[-1]); $datatext =~ s/(\s+$|^\s+)//g; $datatext =~ s/\s+/ /g; if ($datatext !~ /^(([+-]?\d*\.?\d*)[, ]?)+$/) { &Apache::lonxml::warning('Malformed data: '.$datatext); $datatext = ''; } # Need to do some error checking on the @data array - # make sure it's all numbers and make sure each array # is of the same length. my @data = split /[, ]/,$datatext; push( @{$curves[-1]->{'data'}},\@data; if ($target eq 'web') { # This routine should never return anything. } return $result; } sub end_data { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result = ''; if ($target eq 'web') { # This routine should never return anything. } return $result; } ##------------------------------------------------------------------- axis sub start_axis { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result=''; &get_attributes(\%axis,\%label_defaults,$parstack,$safeeval,$tagstack); if ($target eq 'web') { # This routine should never return anything. } return $result; } sub end_axis { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; my $result = ''; if ($target eq 'web') { # This routine should never return anything. } return $result; } ##------------------------------------------------------------------- misc sub get_attributes{ %values = %{shift}; %defaults = %{shift}; $parstack = shift; $safeeval = shift; $tag = shift; my $attr; foreach $attr (keys %defaults) { $values{$attr} = &Apache::lonxml::get_param($attr,$parstack,$safeeval); if ($values{$attr} eq '' | !defined($values{$attr})) { $values{$attr} = $defaults{$attr}; next; } my $test = $defaults{$attr}->{'test'}; if (! &$test($values{$attr})) { &Apache::lonxml::warning ($tag.':'.$attr.': Bad value.'.'Replacing your value with : ' .$defaults{$attr} ); $values{$attr} = $defaults{$attr}; } return ; } sub write_gnuplot_file { my $fh = shift; my $gnuplot_input = ''; # Collect all the colors my @Colors; push @Colors, $plot{'bgcolor'}; push @Colors, $plot{'fgcolor'}; push @Colors, $axis{'color'}; push @Colors, $axis{'color'}; foreach $curve (@Curves) { push @Colors, ($curve{'color'} ne '' ? $curve{'color'} : $plot{'fgcolor'} ); } # set term $gnuplot_input .= 'set term gif '; $gnuplot_input .= 'transparent ' if ($plot{'transparent'} eq 'on'); $gnuplot_input .= $plot{'font'} . ' '; $gnuplot_input .= 'size ' . $plot{'width'} . ' '; $gnuplot_input .= $plot{'height'} . ' '; $gnuplot_input .= "@Colors\n"; # grid $gnuplot_input .= ($plot->{'grid'} eq 'on' ? 'set grid\n' : '' ); # border $gnuplot_input .= ($plot->{'border'} eq 'on'? 'set border\n' : 'set noborder\n' ); # title, xlabel, ylabel { $gnuplot_input .<<"ENDLABELS"; set title $title->{'text'} set xlabel $xlabel->{'text'} set ylabel $ylabel->{'text'} set xrange $axis->{'xmin'}:$axis->{'xmax'} set yrange $axis->{'ymin'}:$axis->{'ymax'} ENDLABELS } # Key if (defined($key{'pos'})) { $gnuplot_input .= 'set key '.$key->{'pos'}.' '; $gnuplot_input .= ($key->{'box'} eq 'on' ? 'box ' : 'nobox '); if ($key->{'title'} ne '') { $gnuplot_input .= 'title "'$key->{'title'}.'"\n'; } else { $gnuplot_input .= '\n'; } } else { $gnuplot_input .= 'set nokey\n'; } # axis $gnuplot_input .= 'set xrange ['.$axis{'xmin'}.':'.$axis{'xmin'}.']\n'; $gnuplot_input .= 'set yrange ['.$axis{'ymin'}.':'.$axis{'ymin'}.']\n'; # labels foreach $label (@labels) { $gnuplot_input .= 'set label "'.$label->{'text'}.'" at '. $label->{'x'}.','.$label->{'y'}.'\n'; } # curves $gnuplot_input .= 'plot '; my $datatext = ''; foreach $curve (@curves) { if (exists($curve->{'function'})) { $gnuplot_input.= $curve->{'function'}.' with '.$curve->{'linestyle'}; } elsif (exists($curve->{'data'})) { $gnuplot_input.= '\'-\' with '.$curve->{'linestyle'}; my @Data = @{$curve->{'data'}}; for ($i =0; $i<=$#Data; $i++) { foreach $dataset (@Data) { $datatext .= $dataset[$i] . ' '; } $datatext .='\n'; } $datatext .='\n'; } } $gnuplot_input .= $datatext; print $fh $gnuplot_input; } 1; __END__