Parent Directory
|
Revision Log
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 |