[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 3577 - (download) (as text) (annotate)
Fri Aug 26 04:19:09 2005 UTC (14 years, 6 months ago) by jj
File size: 17190 byte(s)
When making on-the-fly image names, replace periods in the student login
name to avoid problems with pdflatex.  Changed the replacement in set
names to use the same new trick (the old way had an infintesimal chance
of collision).  The new scheme is first double all instances of "Q".  Then
replace periods with "-Q-".

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9