[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 5570 - (download) (as text) (annotate)
Sun Oct 28 16:33:36 2007 UTC (12 years, 1 month ago) by gage
File size: 17371 byte(s)
Added explicit  loadMacros("MathObject.pl")  to
handle webwork questions which don't use math objects
for any other purpose other than graphs.

 CVS: ----------------------------------------------------------------------

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9