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

View of /branches/gage_dev/pg/macros/PGgraphmacros.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6867 - (download) (as text) (annotate)
Sat Jun 18 03:59:31 2011 UTC (23 months ago) by gage
File size: 18216 byte(s)
committing changes made at and just before the sage31 work days


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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9