forked from openwebwork/pg
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathFun.pm
427 lines (329 loc) · 11.1 KB
/
Fun.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# Fun.pm
# methods:
# new Fun($rule,$graphRef)
# If $rule is a subroutine then a function object is created,
# with default data. If the graphRef is present the function is
# installed into the
# graph and the domain is reset to the graphRef's domain.
# If the $rule is another function object then a copy of that function is
# made with all of its data and it is installed in the graphRef if that is present.
# In this case the domain of the function is not affected by the domain of the graphRef.
# initial data
# @domain = ($tstart, $tstop) the domain of the function -- initially (-1,1)
# steps the number of steps in drawing -- initially 20
# color the pen color to draw with -- initially 'red'
# weight the width of the pen in pixels -- initially 2
# rule reference to a subroutine
# which calculates the function -- initially null
# graph reference to the enclosing graph -- initially $ref or else null
# What will the domain of new Fun($rule, $graphRef) be?
# It will be the same as $rule if $rule is actually another function object
# ELSE the same as the domain of $graphRef if that is present
# ELSE the interval (-1,1)
# public access methods:
# domain
# steps
# color
# rule
# weight
;
=head1 NAME
Fun
=head1 SYNPOSIS
use Carp;
use GD;
use WWPlot;
use Fun;
$fn = new Fun( rule_reference);
$fn = new Fun( rule_reference , graph_reference);
$fn = new Fun ( x_rule_ref, y_rule_ref );
$fn = new Fun ( x_rule_ref, y_rule_ref, graph_ref );
=head1 DESCRIPTION
This module defines a parametric or non-parametric function object. The function object is designed to
be inserted into a graph object defined by WWPlot.
The following functions are provided:
=head2 new (non-parametric version)
=over 4
=item $fn = new Fun( rule_reference);
rule_reference is a reference to a subroutine which accepts a numerical value and returns a numerical value.
The Fun object will draw the graph associated with this subroutine.
For example: $rule = sub { my $x= shift; $x**2}; will produce a plot of the x squared.
The new method returns a reference to the function object.
=item $fn = new Fun( rule_reference , graph_reference);
The function is also placed into the printing queue of the graph object pointed to by graph_reference and the
domain of the function object is set to the domain of the graph.
=back
=head2 new (parametric version)
=over 4
=item $fn = new Fun ( x_rule_ref, y_rule_ref );
A parametric function object is created where the subroutines refered to by x_rule_ref and y_rule_ref define
the x and y outputs in terms of the input t.
=item $fn = new Fun ( x_rule_ref, y_rule_ref, graph_ref );
This variant inserts the parametric function object into the graph object referred to by graph_ref. The domain
of the function object is not adjusted. The domain's default value is (-1, 1).
=back
=head2 Properites
All of the properties are set using the construction $new_value = $fn->property($new_value)
and read using $current_value = $fn->property()
=over 4
=item tstart, tstop, steps
The domain of the function is (tstart, tstop). steps is the number of subintervals
used in graphing the function.
=item color
The color used to draw the function is specified by a word such as 'orange' or 'yellow'.
C<$fn->color('blue')> sets the drawing color to blue. The RGB values for the color are defined in the graph
object in which the function is drawn. If the color, e.g. 'mauve', is not defined by the graph object
then the function is drawn using the color 'default_color' which is always defined (and usually black).
=item x_rule
A reference to the subroutine used to calculate the x value of the graph. This is set to the identity function (x = t )
when using the function object in non-parametric mode.
=item y_rule
A reference to the subroutine used to calculate the y value of the graph.
=item weight
The width in pixels of the pen used to draw the graph. The pen is square.
=back
=head2 Actions which affect more than one property.
=over 4
=item rule
This defines a non-parametric function.
$fn->rule(sub {my $x =shift; $x**2;} )
is equivalent to
$fn->x_rule(sub {my $x = shift; $x;});
$fn->y_rule(sub {my $x = shift; $x**2;);
$fn->rule() returns the reference to the y_rule.
=item domain
$array_ref = $fn->domain(-1,1) sets tstart to -1 and tstop to 1 and
returns a reference to an array containing this pair of numbers.
=item draw
$fn->draw($graph_ref) draws the function in the graph object pointed to by $graph_ref. If one of
the points bounding a subinterval is undefined then that segment is not drawn. This usually does the "right thing" for
functions which have simple singularities.
The graph object must
respond to the methods below. The draw call is mainly for internal use by the graph object. Most users will not
call it directly.
=over 4
=item $graph_ref->{colors}
a hash containing the defined colors
=item $graph_ref ->im
a GD image object
=item $graph_ref->lineTo(x,y, color_number)
draw line to the point (x,y) from the current position using the specified color. To obtain the color number
use a construction such as C<$color_number = $graph_ref->{colors}{'blue'};>
=item $graph_ref->lineTo(x,y,gdBrushed)
draw line to the point (x,y) using the pattern set by SetBrushed (see GD documentation)
=item $graph_ref->moveTo(x,y)
set the current position to (x,y)
=back
=back
=cut
BEGIN {
be_strict(); # an alias for use strict. This means that all global variable must contain main:: as a prefix.
}
package Fun;
#use "WWPlot.pm";
#Because of the way problem modules are loaded 'use' is disabled.
@Fun::ISA = qw(WWPlot);
# import gdBrushed from GD. It unclear why, but a good many global methods haven't been imported.
sub gdBrushed {
&GD::gdBrushed();
}
my $GRAPH_REFERENCE = "WWPlot";
my $FUNCTION_REFERENCE = "Fun";
my %fields =(
tstart => -1, # (tstart,$tstop) constitutes the domain
tstop => 1,
steps => 50,
color => 'blue',
x_rule => \&identity,
y_rule => \&identity,
weight => 2, # line thickness
);
sub new {
my $class = shift;
# my ($rule,$graphRef) = @_;
my $self = {
# _permitted => \%fields,
%fields,
};
bless $self, $class;
$self -> _initialize(@_);
return $self;
}
sub identity { # the identity function
shift;
}
sub rule { # non-parametric functions are defined using rule; use x_rule and y_rule to define parametric functions
my $self = shift;
my $rule = shift;
my $out;
if ( defined($rule) ){
$self->x_rule (\&identity);
$self->y_rule($rule);
$out = $self->y_rule;
} else {
$out = $self->y_rule
}
$out;
}
sub _initialize {
my $self = shift;
my ($xrule,$yrule, $rule,$graphRef);
my @input = @_;
if (ref($input[$#input]) eq $GRAPH_REFERENCE ) {
$graphRef = pop @input; # get the last argument if it refers to a graph.
$graphRef->fn($self); # Install this function in the graph.
}
if ( @input == 1 ) { # only one argument left -- this is a non parametric function
$rule = $input[0];
if ( ref($rule) eq $FUNCTION_REFERENCE ) { # clone another function
my $k;
foreach $k (keys %fields) {
$self->{$k} = $rule->{$k};
}
} else {
$self->rule($rule);
if (ref($graphRef) eq $GRAPH_REFERENCE) { # use graph to initialize domain
$self->domain($graphRef->xmin,$graphRef->xmax);
}
}
} elsif (@input == 2 ) { # two arguments -- parametric functions
$self->x_rule($input[0]);
$self->y_rule($input[1]);
} else {
die "Fun.pm:_initialize: Can't call function with more than two arguments";
}
}
sub draw {
my $self = shift; # this function
my $g = shift; # the graph containing the function.
my $color; # get color scheme from graph
if ( defined( $g->{'colors'}{$self->color} ) ) {
$color = $g->{'colors'}{$self->color};
} else {
$color = $g->{'colors'}{'default_color'}; # what you do if the color isn't there
}
my $brush = new GD::Image($self->weight,$self->weight);
my $brush_color = $brush->colorAllocate($g->im->rgb($color)); # transfer color
$g->im->setBrush($brush);
my $stepsize = ( $self->tstop - $self->tstart )/$self->steps;
my ($t,$x,$i,$y);
my $x_prev = undef;
my $y_prev = undef;
foreach $i (0..$self->steps) {
$t=$stepsize*$i + $self->tstart;
$x=&{$self->x_rule}( $t );;
$y=&{$self->y_rule}( $t );
# Points where the function were not defined were not being handled
# gracefully. They would come as blank y values, which would ultimately
# trigger errors downstream unless y was undefined.
if(defined($y) and $y eq "") { $y = undef; }
if (defined($x) && defined($x_prev) && defined($y) && defined($y_prev) ) {
$g->lineTo($x, $y, gdBrushed);
} else {
$g->moveTo($x, $y) if defined($x) && defined($y);
}
$x_prev = $x;
$y_prev = $y;
}
}
sub domain {
my $self =shift;
my $tstart = shift;
my $tstop = shift;
if (defined($tstart) && defined($tstop) ) {
$self->tstart($tstart);
$self->tstop($tstop);
}
[$self->tstart,$self->tstop];
}
##########################
# Access methods
##########################
sub tstart {
my $self = shift;
my $type = ref($self) || die "$self is not an object";
unless (exists $self->{tstart} ) {
die "Can't find tstart field in object of class $type";
}
if (@_) {
return $self->{tstart} = shift;
} else {
return $self->{tstart}
}
}
sub tstop {
my $self = shift;
my $type = ref($self) || die "$self is not an object";
unless (exists $self->{tstop} ) {
die "Can't find tstop field in object of class $type";
}
if (@_) {
return $self->{tstop} = shift;
} else {
return $self->{tstop}
}
}
sub steps {
my $self = shift;
my $type = ref($self) || die "$self is not an object";
unless (exists $self->{steps} ) {
die "Can't find steps field in object of class $type";
}
if (@_) {
return $self->{steps} = shift;
} else {
return $self->{steps}
}
}
sub color {
my $self = shift;
my $type = ref($self) || die "$self is not an object";
unless (exists $self->{color} ) {
die "Can't find color field in object of class $type";
}
if (@_) {
return $self->{color} = shift;
} else {
return $self->{color}
}
}
sub x_rule {
my $self = shift;
my $type = ref($self) || die "$self is not an object";
unless (exists $self->{x_rule} ) {
die "Can't find x_rule field in object of class $type";
}
if (@_) {
return $self->{x_rule} = shift;
} else {
return $self->{x_rule}
}
}
sub y_rule {
my $self = shift;
my $type = ref($self) || die "$self is not an object";
unless (exists $self->{y_rule} ) {
die "Can't find y_rule field in object of class $type";
}
if (@_) {
return $self->{y_rule} = shift;
} else {
return $self->{y_rule}
}
}
sub weight {
my $self = shift;
my $type = ref($self) || die "$self is not an object";
unless (exists $self->{weight} ) {
die "Can't find weight field in object of class $type";
}
if (@_) {
return $self->{weight} = shift;
} else {
return $self->{weight}
}
}
sub DESTROY {
# doing nothing about destruction, hope that isn't dangerous
}
1;