File Coverage

blib/lib/Math/Project3D/Plot.pm
Criterion Covered Total %
statement 114 116 98.2
branch 23 30 76.6
condition 4 9 44.4
subroutine 18 19 94.7
pod 5 5 100.0
total 164 179 91.6


line stmt bran cond sub pod time code
1            
2             # See the POD documentation at the end of this
3             # document for detailed copyright information.
4             # (c) 2002-2006 Steffen Mueller, all rights reserved.
5            
6             package Math::Project3D::Plot;
7            
8 1     1   22472 use 5.006;
  1         3  
  1         32  
9 1     1   5 use strict;
  1         2  
  1         32  
10 1     1   10 use warnings;
  1         2  
  1         23  
11            
12 1     1   6 use Carp;
  1         2  
  1         94  
13            
14 1     1   825 use Math::Project3D;
  1         38322  
  1         40  
15 1     1   1484 use Imager;
  1         51008  
  1         8  
16            
17 1     1   66 use vars qw/$VERSION/;
  1         1  
  1         1190  
18             $VERSION = '1.02';
19            
20            
21             # Constructor class and object method new
22             #
23             # Creates a new Math::Project3D::Plot instance and returns it.
24             # Takes a list of object attributes as arguments.
25            
26             sub new {
27 1     1 1 5304 my $proto = shift;
28 1   33     9 my $class = ref $proto || $proto;
29            
30 1         5 my %args = @_;
31            
32             # check for require attributes
33 1         6 my $missing = _require_attributes(\%args, 'image', 'projection');
34            
35 1 50       7 croak "Required attribute $missing missing."
36             if $missing;
37            
38             # We might croak a lot.
39 1     0   5 my $croaker = sub { croak "Attribute '$_[0]' is bad." };
  0         0  
40            
41 1         3 my $self = {};
42            
43             # valid image and projection?
44 1 50       4 ref $args{image} or $croaker->('image');
45 1         3 $self->{image} = $args{image};
46            
47 1 50       5 ref $args{projection} eq 'Math::Project3D' or $croaker->('projection');
48 1         3 $self->{proj} = $args{projection};
49            
50             # defaults
51 1         7 $self = {
52             %$self,
53             scale => 10,
54             origin_x => $self->{image}->getwidth() / 2,
55             origin_y => $self->{image}->getheight() / 2,
56             };
57            
58 1         41 my @valid_args = qw(
59             origin_x origin_y
60             scale
61             );
62            
63             # Take all valid args from the user input and
64             # put them into our object.
65 1         3 foreach my $arg (@valid_args) {
66 3 100       20 $self->{$arg} = $args{$arg} if exists $args{$arg};
67             }
68            
69 1         3 bless $self => $class;
70            
71             # get min/max logical x/y coordinates
72 1         5 ( $self->{min_x}, $self->{min_y} ) = $self->_g_l(0, 0);
73 1         7 ( $self->{max_x}, $self->{max_y} ) = $self->_g_l(
74             $self->{image}->getwidth(),
75             $self->{image}->getheight(),
76             );
77            
78 1         11 return $self;
79             }
80            
81            
82             # Method plot
83             # Takes argument pairs: color => Imager color
84             # and params => array ref of params
85             # projects the point associated with the parameters.
86             # Plots the point.
87             # Returns the graphical coordinates of the point that
88             # was plotted.
89            
90             sub plot {
91 33     33 1 554 my $self = shift;
92 33         68 my %args = @_;
93            
94 33 50       74 ref $args{params} eq 'ARRAY' or
95             croak "Invalid parameters passed to plot().";
96            
97 33         42 my ($coeff1, $coeff2, $distance) = $self->{proj}->project(@{$args{params}});
  33         129  
98 33         8862 my ($g_x, $g_y) = $self->_l_g($coeff1, $coeff2);
99            
100 33         114 $self->{image}->setpixel(color=>$args{color}, x=>$g_x, y=>$g_y);
101            
102 33         798 return $g_x, $g_y;
103             }
104            
105            
106             # Method plot_list
107             # Takes argument pairs: color => Imager color,
108             # params => array ref of array ref of params
109             # and type => 'line' or 'points'
110             # Projects the points associated with the parameters.
111             # Plots the either points or the line connecting them.
112             # Returns 1.
113            
114             sub plot_list {
115 5     5 1 971 my $self = shift;
116 5         19 my %args = @_;
117            
118 5 50       15 ref $args{params} eq 'ARRAY' or
119             croak "Invalid parameters passed to plot_list().";
120            
121             # Get type, default to points
122 5         8 my $type = $args{type};
123 5   50     13 $type ||= 'points';
124            
125             # Do some calulation on the points.
126 5         9 my $matrix = $self->{proj}->project_list( @{ $args{params} } );
  5         18  
127            
128             # Cache
129 5         7872 my ($prev_g_x, $prev_g_y);
130            
131             # For every point...
132 5         9 for ( my $row = 1; $row <= @{$args{params}}; $row++ ) {
  31         263  
133            
134             # Get its coordinates
135 26         56 my ($g_x, $g_y) = $self->_l_g(
136             $matrix->element($row,1),
137             $matrix->element($row,2)
138             );
139            
140             # Plot line or points?
141 26 100       55 if ( $type eq 'line' ) {
142            
143 16 100       62 $self->{image}->line(
144             color => $args{color},
145             x1 => $prev_g_x, y1 => $prev_g_y,
146             x2 => $g_x, y2 => $g_y,
147             ) if defined $prev_g_x;
148            
149 16         659 ($prev_g_x, $prev_g_y) = ($g_x, $g_y);
150            
151             } else {
152 10         29 $self->{image}->setpixel(color=>$args{color}, x=>$g_x, y=>$g_y);
153             }
154             }
155            
156 5         35 return 1;
157             }
158            
159            
160             # Method plot_range
161             # Takes argument pairs: color => Imager color,
162             # params => array ref of array ref of ranges
163             # and type => 'line' or 'points'
164             # Projects the points associated with the parameter ranges.
165             # Plots the either points or the line connecting them.
166             # Returns 1.
167            
168             sub plot_range {
169 3     3 1 1884 my $self = shift;
170 3         15 my %args = @_;
171            
172 3 50       16 ref $args{params} eq 'ARRAY' or
173             croak "Invalid parameters passed to plot_range().";
174            
175             # Get type, default to points
176 3         7 my $type = $args{type};
177 3   50     9 $type ||= 'points';
178            
179             # Cache
180 3         5 my ($prev_g_x, $prev_g_y);
181            
182             # This will hold the callback routine
183 0         0 my $callback;
184            
185             # Use different callbacks for different drawing types
186 3 100       16 if ($type eq 'line') {
    100          
187             $callback = sub {
188             # Get its coordinates
189 121     121   31052 my ($g_x, $g_y) = $self->_l_g( @_[0,1] );
190            
191             # Draw the line
192 121 100       499 $self->{image}->line(
193             color => $args{color},
194             x1 => $prev_g_x, y1 => $prev_g_y,
195             x2 => $g_x, y2 => $g_y,
196             ) if defined $prev_g_x;
197            
198             # cache
199 121         6293 ($prev_g_x, $prev_g_y) = ($g_x, $g_y);
200 1         7 };
201             } elsif ($type eq 'multiline') {
202             $callback = sub {
203 121     121   36970 my $newline = $_[3]; # Did we start a new line?
204            
205             # Get its coordinates
206 121         295 my ($g_x, $g_y) = $self->_l_g( @_[0,1] );
207            
208             # Draw the line if not a new line:
209 121 100       581 $self->{image}->line(
210             color => $args{color},
211             x1 => $prev_g_x, y1 => $prev_g_y,
212             x2 => $g_x, y2 => $g_y,
213             ) if defined $prev_g_x;
214            
215             # cache
216 121         6209 ($prev_g_x, $prev_g_y) = ($g_x, $g_y);
217 121 100       940 ($prev_g_x, $prev_g_y) = (undef, undef) if $newline;
218 1         7 };
219             } else {
220             $callback = sub {
221             # Get its coordinates
222 121     121   37784 my ($g_x, $g_y) = $self->_l_g( @_[0,1] );
223            
224             # draw the point
225 121         439 $self->{image}->setpixel(color=>$args{color}, x=>$g_x, y=>$g_y);
226 1         8 };
227             }
228            
229             # Start the projection
230 3         19 $self->{proj}->project_range_callback(
231             $callback,
232 3         25 @{ $args{params} },
233             );
234            
235 3         71 return 1;
236             }
237            
238            
239             # Private method _require_attributes
240             #
241             # Arguments must be a list of attribute names (strings).
242             # Tests for the existance of those attributes.
243             # Returns the missing attribute on failure, undef on success.
244            
245             sub _require_attributes {
246 1     1   1 my $self = shift;
247 1   50     8 exists $self->{$_} or return $_ foreach @_;
248 1         2 return undef;
249             }
250            
251            
252             # Private method _l_g (logical to graphical)
253             # Takes an x/y pair of logical coordinates as
254             # argument and returns the corresponding graphical
255             # coordinates.
256            
257             sub _l_g {
258 422     422   848 my $self = shift;
259 422         467 my $x = shift;
260 422         402 my $y = shift;
261            
262             # A logical unit is a graphical one displaced by the origin
263             # and multiplied with the appropriate scaling factor.
264            
265 422         813 $x = $self->{origin_x} + $x * $self->{scale};
266            
267 422         648 $y = $self->{origin_y} - $y * $self->{scale};
268            
269 422         782 return $x, $y;
270             }
271            
272            
273             # Private method _g_l (graphical to logical)
274             # Takes an x/y pair of graphical coordinates as
275             # argument and returns the corresponding
276             # logical coordinates.
277            
278             sub _g_l {
279 2     2   21 my $self = shift;
280 2         4 my $x = shift;
281 2         2 my $y = shift;
282            
283             # A graphical unit is a logical one displaced by the origin
284             # and divided by the appropriate scaling factor.
285            
286 2         10 $x = ( $x - $self->{origin_x} ) / $self->{scale};
287            
288 2         4 $y = ( $y - $self->{origin_y} ) / $self->{scale};
289            
290 2         12 return $x, $y;
291             }
292            
293            
294             # Method plot_axis
295             #
296             # The plot_axis method draws an axis into the image. "Axis" used
297             # as in "a line that goes through the origin". Required arguments:
298             # color => Imager color to use (see Imager::Color manpage)
299             # vector => Array ref containing three vector components.
300             # (only the direction matters as the vector will
301             # be normalized by plot_axis.)
302             # length => Desired axis length.
303            
304             sub plot_axis {
305 3     3 1 1706 my $self = shift;
306 3         13 my %args = @_;
307            
308 3 50       13 ref $args{vector} eq 'ARRAY' or
309             croak "Invalid vector passed to plot_axis().";
310            
311             # Save original function
312 3         13 my $old_function = $self->{proj}->get_function();
313            
314             # Directional vector of the axis
315 3         13 my @vector = @{ $args{vector} };
  3         9  
316            
317             # Create new function along the axis' directional vector
318             # using only one parameter t that will be determined
319             # below
320 3         22 $self->{proj}->new_function(
321             't', "$vector[0]*\$t", "$vector[1]*\$t", "$vector[2]*\$t",
322             );
323            
324             # Calculate the length of the unit vector
325 3         542 my $vector_length = sqrt( $vector[0]**2 + $vector[1]**2 + $vector[2]**2 );
326            
327             # Calculate $t, the number of units needed to get
328             # a line of the correct length.
329 3         18 my $t = $args{length} / ( 2 * $vector_length );
330            
331             # Use the plot_list method to display the axis.
332 3         16 $self->plot_list(
333             color => $args{color},
334             type => 'line',
335             params => [
336             [-$t], # We calculated for $t for length/2, hence
337             [$t], # we may now draw from -$t to +$t
338             ],
339             );
340            
341             # Restore original function
342 3         17 $self->{proj}->set_function($old_function);
343            
344 3         49 return 1;
345             }
346            
347            
348             1;
349            
350             __END__