[system] / trunk / pg / macros / PGgraphmacros.pl Repository:
ViewVC logotype

View of /trunk/pg/macros/PGgraphmacros.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2215 - (download) (as text) (annotate)
Mon May 24 21:24:41 2004 UTC (15 years, 9 months ago) by jj
File size: 17074 byte(s)
Allow default size of on the fly graphics to be set in global.conf, and make pop-ups of those graphs have windows closer to the size of the graph.  Also, pop-ups always make a new window - important for problems which make you compare two or more popped up graphs.

    1 
    2 
    3 =head1 NAME
    4 
    5   PGgraphmacros -- in courseScripts directory
    6 
    7 =head1 SYNPOSIS
    8 
    9 
   10 #   use Fun;
   11 #   use Label;
   12 #   use Circle;
   13 #   use WWPlot;
   14 
   15 =head1 DESCRIPTION
   16 
   17 This collection of macros provides easy access to the facilities provided by the graph
   18 module WWPlot and the modules for objects which can be drawn on a graph: functions (Fun.pm)
   19 labels (Label.pm) and images.  The only image implemented currently are open and closed circles
   20 (Circle) which can be used to mark graphs of functions defined on open and closed intervals.
   21 
   22 These macros provide an easy ability to graph simple functions.  More complicated projects
   23 may require direct access to the underlying modules.  If these complicated projects are common
   24 then it may be desirable to create additional macros.  (See numericalmacros.pl for one example.)
   25 
   26 
   27 =cut
   28 
   29 =head2 Other constructs
   30 
   31 See F<PGbasicmacros> for definitions of C<image> and C<caption>
   32 
   33 =cut
   34 
   35 
   36 #my $User = $main::studentLogin;
   37 #my $psvn = $main::psvnNumber; #$main::in{'probSetKey'};  #in{'probSetNumber'}; #$main::probSetNumber;
   38 #my $setNumber     = $main::setNumber;
   39 #my $probNum       = $main::probNum;
   40 
   41 #########################################################
   42 # this initializes a graph object
   43 #########################################################
   44 # graphObject = init_graph(xmin,ymin,xmax,ymax,options)
   45 # options include  'grid' =>[8,8] or
   46 #          'ticks'=>[8,8] and/or
   47 #                  'axes'
   48 #########################################################
   49 
   50 my %images_created = ();  # this keeps track of the base names of the images created during this session.
   51                      #  We tack on
   52                      # $imageNum  = ++$images_created{$imageName} to keep from overwriting files
   53                      # when we don't want to.
   54 
   55 
   56 
   57 
   58 =head2 init_graph
   59 
   60 =pod
   61 
   62     $graphObject = init_graph(xmin,ymin,xmax,ymax,'ticks'=>[4,4],'axes'=>[0,0])
   63     options are
   64       'grid' =>[8,8] or
   65       # there are 8 evenly spaced lines intersecting the horizontal axis
   66       'ticks'=>[8,8] and/or
   67       # there are 8 ticks on the horizontal axis, 8 on the vertical
   68       'axes' => [0,0]
   69       # axes pass through the point (0,0) in real coordinates
   70       'size' => [200,200]
   71       # dimensions of the graph in pixels.
   72       'pixels' =>[200,200]  # synonym for size
   73 
   74 Creates a graph object with the default size 200 by 200 pixels.
   75 If you want axes or grids you need to specify them in options. But the default values can be selected for you.
   76 
   77 
   78 =cut
   79 BEGIN {
   80   be_strict();
   81 }
   82 sub _PGgraphmacros_init {
   83 
   84 
   85 }
   86 #sub _PGgraphmacros_export {
   87 #
   88 # my @EXPORT = (
   89 #   '&init_graph', '&add_functions', '&plot_functions', '&open_circle',
   90 #   '&closed_circle', '&my_math_constants', '&string_to_sub',
   91 #    );
   92 #    @EXPORT;
   93 #}
   94 
   95 sub init_graph {
   96   my ($xmin,$ymin,$xmax,$ymax,%options) = @_;
   97   my @size;
   98   if ( defined($options{'size'}) ) {
   99     @size = @{$options{'size'}};
  100   } elsif ( defined($options{'pixels'}) ) {
  101     @size = @{$options{'pixels'}};
  102   }   else {
  103     my $defaultSize = $main::envir{onTheFlyImageSize} || 200;
  104     @size=($defaultSize,  $defaultSize);
  105   }
  106     my $graphRef = new WWPlot(@size);
  107   # select a  name for this graph based on the user, the psvn and the problem
  108   my $setName = $main::setNumber;
  109   # replace dots in set name to keep latex happy
  110   $setName =~ s/\./d0T/g;
  111   my $imageName = "$main::studentLogin-$main::problemSeed-set${setName}prob${main::probNum}";
  112   # $imageNum counts the number of graphs with this name which have been created since PGgraphmacros.pl was initiated.
  113   my $imageNum  = ++$main::images_created{$imageName};
  114   # this provides a unique name for the graph -- it does not include an extension.
  115   $graphRef->imageName("${imageName}image${imageNum}");
  116 
  117   $graphRef->xmin($xmin) if defined($xmin);
  118   $graphRef->xmax($xmax) if defined($xmax);
  119   $graphRef->ymin($ymin) if defined($ymin);
  120   $graphRef->ymax($ymax) if defined($ymax);
  121   my $x_delta = ($graphRef->xmax -  $graphRef->xmin)/8;
  122   my $y_delta = ($graphRef->ymax -  $graphRef->ymin)/8;
  123   if (defined($options{grid})) {   #   draw grid
  124       my $xdiv = ( ${$options{'grid'}}[0]) ? ${$options{'grid'}}[0] : 8; # number of ticks (8 is default)
  125       my $ydiv = ( ${$options{'grid'}}[1] )  ? ${$options{'grid'}}[1] : 8;
  126     my $x_delta = ($graphRef->xmax -  $graphRef->xmin)/$xdiv;
  127       my $y_delta = ($graphRef->ymax -  $graphRef->ymin)/$ydiv;
  128       my $i; my @x_values=(); my @y_values=();
  129       foreach $i (1..($xdiv-1) ) {
  130         push( @x_values, $i*$x_delta+$graphRef->{xmin});
  131       }
  132       foreach $i (1..($ydiv-1) ) {
  133         push( @y_values, $i*$y_delta+$graphRef->{ymin});
  134       }
  135     $graphRef->v_grid('gray',@x_values);
  136     $graphRef->h_grid('gray',@y_values);
  137     $graphRef->lb(new Label($x_delta,0,sprintf("%1.1f",$x_delta),'black','center','middle'));
  138     $graphRef->lb(new Label(0,$y_delta,sprintf("%1.1f",$y_delta),'black','center','middle'));
  139 
  140     $graphRef->lb(new Label($xmax,0,$xmax,'black','right'));
  141     $graphRef->lb(new Label($xmin,0,$xmin,'black','left'));
  142     $graphRef->lb(new Label(0,$ymax,$ymax,'black','top'));
  143     $graphRef->lb(new Label(0,$ymin,$ymin,'black','bottom','right'));
  144 
  145   } elsif ($options{ticks}) {   #   draw ticks -- grid over rides ticks
  146     my $xdiv = ${$options{ticks}}[0]? ${$options{ticks}}[0] : 8; # number of ticks (8 is default)
  147           my $ydiv = ${$options{ticks}}[1]? ${$options{ticks}}[1] : 8;
  148     my $x_delta = ($graphRef->xmax -  $graphRef->xmin)/$xdiv;
  149           my $y_delta = ($graphRef->ymax -  $graphRef->ymin)/$ydiv;
  150           my $i; my @x_values=(); my @y_values=();
  151       foreach $i (1..($xdiv-1) ) {
  152         push( @x_values, $i*$x_delta+$graphRef->{xmin});
  153       }
  154       foreach $i (1..($ydiv-1) ) {
  155         push( @y_values, $i*$y_delta+$graphRef->{ymin});
  156       }
  157     $graphRef->h_ticks(0,'black',@x_values);
  158     $graphRef->v_ticks(0,'black',@y_values);
  159     $graphRef->lb(new Label($x_delta,0,$x_delta,'black','right'));
  160     $graphRef->lb(new Label(0,$y_delta,$y_delta,'black','top'));
  161 
  162     $graphRef->lb(new Label($xmax,0,$xmax,'black','right'));
  163     $graphRef->lb(new Label($xmin,0,$xmin,'black','left'));
  164     $graphRef->lb(new Label(0,$ymax,$ymax,'black','top'));
  165     $graphRef->lb(new Label(0,$ymin,$ymin,'black','bottom','right'));
  166   }
  167 
  168   if ($options{axes}) {   #   draw axis
  169       my $ra_axes = $options{axes};
  170     $graphRef->h_axis($ra_axes->[1],'black');
  171     $graphRef->v_axis($ra_axes->[0],'black');
  172   }
  173 
  174 
  175   $graphRef;
  176 }
  177 
  178 sub init_graph_no_labels {
  179   my ($xmin,$ymin,$xmax,$ymax,%options) = @_;
  180   my @size;
  181   if ( defined($options{'size'}) ) {
  182     @size = @{$options{'size'}};
  183   } elsif ( defined($options{'pixels'}) ) {
  184     @size = @{$options{'pixels'}};
  185   }   else {
  186     my $defaultSize = $main::envir{onTheFlyImageSize} || 200;
  187     @size=($defaultSize,  $defaultSize);
  188   }
  189     my $graphRef = new WWPlot(@size);
  190   # select a  name for this graph based on the user, the psvn and the problem
  191   my $imageName = "$main::studentLogin-$main::psvnNumber-set${main::setNumber}prob${main::probNum}";
  192   # $imageNum counts the number of graphs with this name which have been created since PGgraphmacros.pl was initiated.
  193   my $imageNum  = ++$main::images_created{$imageName};
  194   # this provides a unique name for the graph -- it does not include an extension.
  195   $graphRef->imageName("${imageName}image${imageNum}");
  196 
  197   $graphRef->xmin($xmin) if defined($xmin);
  198   $graphRef->xmax($xmax) if defined($xmax);
  199   $graphRef->ymin($ymin) if defined($ymin);
  200   $graphRef->ymax($ymax) if defined($ymax);
  201   my $x_delta = ($graphRef->xmax -  $graphRef->xmin)/8;
  202   my $y_delta = ($graphRef->ymax -  $graphRef->ymin)/8;
  203   if (defined($options{grid})) {   #   draw grid
  204       my $xdiv = ( ${$options{'grid'}}[0]) ? ${$options{'grid'}}[0] : 8; # number of ticks (8 is default)
  205       my $ydiv = ( ${$options{'grid'}}[1] )  ? ${$options{'grid'}}[1] : 8;
  206     my $x_delta = ($graphRef->xmax -  $graphRef->xmin)/$xdiv;
  207       my $y_delta = ($graphRef->ymax -  $graphRef->ymin)/$ydiv;
  208       my $i; my @x_values=(); my @y_values=();
  209       foreach $i (1..($xdiv-1) ) {
  210         push( @x_values, $i*$x_delta+$graphRef->{xmin});
  211       }
  212       foreach $i (1..($ydiv-1) ) {
  213         push( @y_values, $i*$y_delta+$graphRef->{ymin});
  214       }
  215     $graphRef->v_grid('gray',@x_values);
  216     $graphRef->h_grid('gray',@y_values);
  217     #$graphRef->lb(new Label($x_delta,0,sprintf("%1.1f",$x_delta),'black','center','top'));
  218     #$graphRef->lb(new Label($x_delta,0,"|",'black','center','middle'));
  219     #$graphRef->lb(new Label(0,$y_delta,sprintf("%1.1f ",$y_delta),'black','right','middle'));
  220     #$graphRef->lb(new Label(0,$y_delta,"-",'black','center','middle'));
  221 
  222 
  223     $graphRef->lb(new Label($xmax,0,$xmax,'black','right'));
  224     $graphRef->lb(new Label($xmin,0,$xmin,'black','left'));
  225     $graphRef->lb(new Label(0,$ymax,$ymax,'black','top','right'));
  226     $graphRef->lb(new Label(0,$ymin,$ymin,'black','bottom','right'));
  227 
  228   } elsif ($options{ticks}) {   #   draw ticks -- grid over rides ticks
  229     my $xdiv = ${$options{ticks}}[0]? ${$options{ticks}}[0] : 8; # number of ticks (8 is default)
  230           my $ydiv = ${$options{ticks}}[1]? ${$options{ticks}}[1] : 8;
  231     my $x_delta = ($graphRef->xmax -  $graphRef->xmin)/$xdiv;
  232           my $y_delta = ($graphRef->ymax -  $graphRef->ymin)/$ydiv;
  233           my $i; my @x_values=(); my @y_values=();
  234       foreach $i (1..($xdiv-1) ) {
  235         push( @x_values, $i*$x_delta+$graphRef->{xmin});
  236       }
  237       foreach $i (1..($ydiv-1) ) {
  238         push( @y_values, $i*$y_delta+$graphRef->{ymin});
  239       }
  240     $graphRef->v_ticks(0,'black',@x_values);
  241     $graphRef->h_ticks(0,'black',@y_values);
  242     $graphRef->lb(new Label($x_delta,0,$x_delta,'black','right'));
  243     $graphRef->lb(new Label(0,$y_delta,$y_delta,'black','top'));
  244 
  245     $graphRef->lb(new Label($xmax,0,$xmax,'black','right'));
  246     $graphRef->lb(new Label($xmin,0,$xmin,'black','left'));
  247     $graphRef->lb(new Label(0,$ymax,$ymax,'black','top'));
  248     $graphRef->lb(new Label(0,$ymin,$ymin,'black','bottom','right'));
  249   }
  250 
  251   if ($options{axes}) {   #   draw axis
  252       my $ra_axes = $options{axes};
  253     $graphRef->h_axis($ra_axes->[1],'black');
  254     $graphRef->v_axis($ra_axes->[0],'black');
  255   }
  256 
  257 
  258   $graphRef;
  259 }
  260 
  261 
  262 
  263 =head2  plot_functions
  264 
  265 =pod
  266 
  267   Usage:  ($f1, $f2, $f3) = plot_functions($graph, $f1, $f2, $f3);
  268   Synonym: add_functions($graph,$f1,$f2,$f3);
  269 
  270 Where $f1 is a string of the form
  271 
  272   $f1 = qq! x^2 - 3*x + 45 for x in [0, 45) using color:red and weight:2!
  273 
  274 The phrase translates as: formula    B<for> variable B<in>  interval B<using>   option-list.
  275 The option-list contains pairs of the form attribute:value.
  276 The default for color is "default_color" which is usually black.
  277 The default for the weight (pixel width) of the pen is 2 pixels.
  278 
  279 The string_to_sub subroutine is used to translate the formula into a subroutine.
  280 
  281 The functions in the list are installed in the graph object $graph and will appear when the graph object is next drawn.
  282 
  283 =cut
  284 
  285 sub add_functions {
  286   &plot_functions;
  287 }
  288 
  289 sub plot_functions {
  290   my $graph = shift;
  291   my @function_list = @_;
  292   my $error = "";
  293   $error .= "The first argument to plot_functions must be a graph object" unless ref($graph) =~/WWPlot/;
  294   my $fn;
  295   my @functions=();
  296   foreach $fn (@function_list) {
  297 
  298       # model:   "2.5-x^2 for x in <-1,0> using color:red and weight:2"
  299     if ($fn =~ /^(.+)for\s*(\w+)\s*in\s*([\(\[\<])\s*([\d\.\-]+)\s*,\s*([\d\.\-]+)\s*([\)\]\>])\s*using\s*(.*)$/ )  {
  300       my ($rule,$var, $left_br, $left_end, $right_end, $right_br, $options)=  ($1, $2, $3, $4, $5, $6, $7);
  301 
  302       my %options = split( /\s*and\s*|\s*:\s*|\s*,\s*|\s*=\s*|\s+/,$options);
  303       my ($color, $weight);
  304       if ( defined($options{'color'})  ){
  305         $color = $options{'color'}; #set pen color
  306       } else {
  307         $color = 'default_color';
  308       }
  309       if ( defined($options{'weight'}) ) {
  310         $weight = $options{'weight'}; # set pen weight (width in pixels)
  311       } else {
  312         $weight =2;
  313       }
  314 
  315       my $subRef = string_to_sub($rule,$var);
  316       my $funRef = new Fun($subRef,$graph);
  317       $funRef->color($color);
  318       $funRef->weight($weight);
  319       $funRef->domain($left_end , $right_end);
  320       push(@functions,$funRef);
  321         # place open (1,3) or closed (1,3) circle at the endpoints or do nothing <1,3>
  322         if ($left_br eq '[' ) {
  323           $graph->stamps(closed_circle($left_end,&$subRef($left_end),$color) );
  324         } elsif ($left_br eq '(' ) {
  325           $graph->stamps(open_circle($left_end, &$subRef($left_end), $color) );
  326         }
  327         if ($right_br eq ']' ) {
  328           $graph->stamps(closed_circle($right_end,&$subRef($right_end),$color) );
  329         } elsif ($right_br eq ')' ) {
  330           $graph->stamps(open_circle($right_end, &$subRef($right_end), $color) );
  331         }
  332 
  333     } else {
  334       $error .= "Error in parsing: $fn $main::BR";
  335     }
  336 
  337   }
  338   die ("Error in plot_functions: \n\t $error ") if $error;
  339   @functions;   # return function references unless there is an error.
  340 }
  341 
  342 =head2 insertGraph
  343 
  344   $filePath = insertGraph(graphObject);
  345       returns a path to the file containing the graph image.
  346 
  347 B<Note:> Because insertGraph involves writing to the disk, it is actually defined in dangerousMacros.pl.
  348 
  349 insertGraph(graphObject) writes a image file to the C<html/tmp/gif> directory of the current course.
  350 The file name is obtained from the graphObject.  Warnings are issued if errors occur while writing to
  351 the file.
  352 
  353 The permissions and ownership of the file are controlled by C<$main::tmp_file_permission>
  354 and C<$main::numericalGroupID>.
  355 
  356 B<Returns:>   A string containing the full path to the temporary file containing the  image.
  357 
  358 
  359 
  360 InsertGraph draws the object $graph, stores it in "${tempDirectory}gif/$imageName.gif (or .png)" where
  361 the $imageName is obtained from the graph object.  ConvertPath and surePathToTmpFile are used to insure
  362 that the correct directory separators are used for the platform and that the necessary directories
  363 are created if they are not already present.  The directory address to the file is the result.
  364 
  365 The most common use of C,insertGraph> is
  366 
  367   TEXT(image(insertGraph($graph)) );
  368 
  369 where C<image> takes care of creating the proper URL for accessing the graph and for creating the HTML code to display the image.
  370 
  371 Another common usage is:
  372 
  373   TEXT(htmlLink( alias(insertGraph($graph), "picture" ) ) );
  374 
  375 which inserts the URL pointing to the picture.
  376 alias converts the directory address to a URL when serving HTML pages and insures that
  377 an eps file is generated when creating TeX code for downloading. (Image, automatically applies alias to its input
  378 in order to obtain the URL.)
  379 
  380 See the documentation in F<dangerousMacros.pl> for the latest details.
  381 
  382 =cut
  383 
  384 =head2  'Circle' lables
  385 
  386   Usage: $circle_object = open_circle( $x_position, $y_position, $color );
  387           $circle_object2 = closed_circle( $x_position, $y_position, $color );
  388 
  389 Creates a small open (resp. filled in or closed) circle for use as a stamp in marking graphs.
  390 For example
  391 
  392   $graph -> stamps($circle_object2); # puts a filled dot at $x_position, $y_position
  393 
  394 =cut
  395 
  396 #########################################################
  397 sub open_circle {
  398     my ($cx,$cy,$color) = @_;
  399   new Circle ($cx, $cy, 4,$color,'nearwhite');
  400 }
  401 
  402 sub closed_circle {
  403     my ($cx,$cy, $color) = @_;
  404     $color = 'black' unless defined $color;
  405   new Circle ($cx, $cy, 4,$color, $color);
  406 }
  407 
  408 
  409 =head2 Auxiliary macros
  410 
  411 =head3  string_to_sub and my_math_constants
  412 
  413 
  414 These are internal macros which govern the interpretation of equations.
  415 
  416 
  417   Usage: $string = my_math_constants($string)
  418          $subroutine_reference = my_string_to_sub($string)
  419 
  420 C<my_math_constants>
  421 interprets pi, e  as mathematical constants 3.1415926... and 2.71828... respectively. (Case is important).
  422 The power operator ^ is replaced by ** to conform with perl constructs
  423 
  424 C<string_to_sub>
  425 converts a string defining a single perl arithmetic expression with independent variable $XVAR into a subroutine.
  426 The string is first filtered through C<my_math_macros>. The resulting subroutine
  427 takes a single real number as input and produces a single output value.
  428 
  429 =cut
  430 
  431 sub my_math_constants {
  432   my($in) = @_;
  433   $in =~s/\bpi\b/(4*atan2(1,1))/g;
  434   $in =~s/\be\b/(exp(1))/g;
  435   $in =~s/\^/**/g;
  436   $in;
  437 }
  438 
  439 sub string_to_sub {
  440   my $str_in = shift;
  441   my $var    = shift;
  442   my $out = undef;
  443   if ( defined(&check_syntax)  ) {
  444     #prepare the correct answer and check it's syntax
  445       my $rh_correct_ans = new AnswerHash;
  446     $rh_correct_ans->input($str_in);
  447     $rh_correct_ans = check_syntax($rh_correct_ans);
  448     warn  $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag};
  449     $rh_correct_ans->clear_error();
  450     $rh_correct_ans = function_from_string2($rh_correct_ans, ra_vars => ['x'], store_in =>'rf_correct_ans');
  451     my $correct_eqn_sub = $rh_correct_ans->{rf_correct_ans};
  452     warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag};
  453     $out = sub{ scalar( &$correct_eqn_sub(@_) ) };  #ignore the error messages from the function.
  454 
  455   } else {
  456     my $in =$str_in;
  457 
  458     $in =~ s/\b$var\b/\$XVAR/g;
  459     $in = &my_math_constants($in);
  460     my ($subRef, $PG_eval_errors,$PG_full_error_report) = PG_restricted_eval( " sub { my \$XVAR = shift; my \$out = $in; \$out; } ");
  461     if ($PG_eval_errors) {
  462       die " ERROR while defining a function from the string:\n\n$main::BR $main::BR $str_in $main::BR $main::BR\n\n  $PG_eval_errors"
  463     } else {
  464       $out = $subRef;
  465     }
  466 
  467   }
  468   $out;
  469 }
  470 
  471 
  472 
  473 
  474 
  475 
  476 
  477 
  478 
  479 #########################################################
  480 
  481 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9