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