File Coverage

blib/lib/Chart/Direction.pm
Criterion Covered Total %
statement 377 527 71.5
branch 117 202 57.9
condition 16 45 35.6
subroutine 15 17 88.2
pod n/a
total 525 791 66.4


line stmt bran cond sub pod time code
1             #====================================================================
2             # Chart::Direction
3             #
4             # written by Chart-Group
5             #
6             # maintained by the Chart Group
7             # Chart@wettzell.ifag.de
8             #
9             #---------------------------------------------------------------------
10             # History:
11             #----------
12             # $RCSfile: Direction.pm,v $ $Revision: 1.2 $ $Date: 2003/02/14 13:30:42 $
13             # $Author: dassing $
14             # $Log: Direction.pm,v $
15             # Revision 1.2 2003/02/14 13:30:42 dassing
16             # Circumvent division of zeros
17             #
18             #====================================================================
19              
20             package Chart::Direction;
21              
22 4     4   142 use Chart::Base 2.3;
  4         110  
  4         72  
23 4     4   84 use GD;
  4         39  
  4         74  
24 4     4   79 use Carp;
  4         35  
  4         66  
25 4     4   96 use strict;
  4         38  
  4         60  
26 4     4   180 use POSIX;
  4         42  
  4         92  
27              
28             @Chart::Direction::ISA = qw(Chart::Base);
29             $Chart::Direction::VERSION = '2.3';
30              
31             #>>>>>>>>>>>>>>>>>>>>>>>>>>#
32             # public methods go here #
33             #<<<<<<<<<<<<<<<<<<<<<<<<<<#
34              
35              
36              
37             #>>>>>>>>>>>>>>>>>>>>>>>>>>>#
38             # private methods go here #
39             #<<<<<<<<<<<<<<<<<<<<<<<<<<<#
40              
41             #we don't need a legend for this type.
42             #sub _draw_legend {
43              
44             # return 1;
45             #}
46              
47             # we use the find_y_scale methode to det the labels of the circles and the amount of them
48             sub _find_y_scale
49             {
50 4     4   41 my $self = shift;
51              
52             # Predeclare vars.
53 4         36 my ($d_min, $d_max); # Dataset min & max.
54 4         36 my ($p_min, $p_max); # Plot min & max.
55 4         72 my ($tickInterval, $tickCount, $skip);
56 4         36 my @tickLabels; # List of labels for each tick.
57 4         38 my $maxtickLabelLen = 0; # The length of the longest tick label.
58              
59             # Find the datatset minimum and maximum.
60 4         63 ($d_min, $d_max) = $self->_find_y_range();
61              
62             # Force the inclusion of zero if the user has requested it.
63 4 100       58 if( $self->{'include_zero'} =~ m!^true$!i )
64             {
65 2 50       24 if( ($d_min * $d_max) > 0 ) # If both are non zero and of the same sign.
66             {
67 2 50       35 if( $d_min > 0 ) # If the whole scale is positive.
68             {
69 2         42 $d_min = 0;
70             }
71             else # The scale is entirely negative.
72             {
73 0         0 $d_max = 0;
74             }
75             }
76             }
77              
78             # Allow the dataset range to be overidden by the user.
79             # f_min/max are booleans which indicate that the min & max should not be modified.
80 4         44 my $f_min = defined $self->{'min_val'};
81 4 50       47 $d_min = $self->{'min_val'} if $f_min;
82              
83 4         41 my $f_max = defined $self->{'max_val'};
84 4 50       45 $d_max = $self->{'max_val'} if $f_max;
85              
86             # Assert against the min is larger than the max.
87 4 50       46 if( $d_min > $d_max )
88             {
89 0         0 croak "The the specified 'min_val' & 'max_val' values are reversed (min > max: $d_min>$d_max)";
90             }
91              
92             # Calculate the width of the dataset. (posibly modified by the user)
93 4         1815 my $d_width = $d_max - $d_min;
94              
95             # If the width of the range is zero, forcibly widen it
96             # (to avoid division by zero errors elsewhere in the code).
97 4 50       100 if( 0 == $d_width )
98             {
99 0         0 $d_min--;
100 0         0 $d_max++;
101 0         0 $d_width = 2;
102             }
103              
104             # Descale the range by converting the dataset width into
105             # a floating point exponent & mantisa pair.
106 4         103              my( $rangeExponent, $rangeMantisa ) = $self->_sepFP( $d_width );
107 4         137 my $rangeMuliplier = 10 ** $rangeExponent;
108              
109             # Find what tick
110             # to use & how many ticks to plot,
111             # round the plot min & max to suatable round numbers.
112 4         75 ($tickInterval, $tickCount, $p_min, $p_max)
113             = $self->_calcTickInterval($d_min/$rangeMuliplier, $d_max/$rangeMuliplier,
114             $f_min, $f_max,
115             $self->{'min_circles'}+1, $self->{'max_circles'}+1);
116             # Restore the tickInterval etc to the correct scale
117 4         40 $_ *= $rangeMuliplier foreach($tickInterval, $p_min, $p_max);
  4         49  
118              
119             #get teh precision for the labels
120 4         44 my $precision = $self->{'precision'};
121              
122             # Now sort out an array of tick labels.
123             for( my $labelNum = $p_min; $labelNum<=$p_max; $labelNum+=$tickInterval )
124             {
125 28         809 my $labelText;
126              
127 28 50       371 if( defined $self->{f_y_tick} )
128             {
129             # Is _default_f_tick function used?
130 28 50       568                         if ( $self->{f_y_tick} == \&Chart::Base::_default_f_tick ) {
131 28         320 $labelText = sprintf("%.".$precision."f", $labelNum);
132 0         0                         } else { print \&_default_f_tick;
133 0         0 $labelText = $self->{f_y_tick}->($labelNum);
134                                     }
135             }
136             else
137             {
138 0         0 $labelText = sprintf("%.".$precision."f", $labelNum);
139             }
140 28         266 push @tickLabels, $labelText;
141 28 100       893 $maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
142 4         38 }
143              
144             # Store the calculated data.
145 4         47 $self->{'min_val'} = $p_min;
146 4         93 $self->{'max_val'} = $p_max;
147 4         40 $self->{'y_ticks'} = $tickCount;
148 4         57 $self->{'y_tick_labels'} = \@tickLabels;
149 4         42 $self->{'y_tick_label_length'} = $maxtickLabelLen;
150              
151             # and return.
152 4         54 return 1;
153             }
154              
155             # Calculates the tick in normalised units.
156             sub _calcTickInterval
157 4     4   40 {       my $self = shift;
158             my(
159 4         66 $min, $max, # The dataset min & max.
160             $minF, $maxF, # Indicates if those min/max are fixed.
161             $minTicks, $maxTicks, # The minimum & maximum number of ticks.
162             ) = @_;
163              
164             # Verify the supplied 'min_y_ticks' & 'max_y_ticks' are sensible.
165 4 50       48 if( $minTicks < 2 )
166             {
167 0         0 print STDERR "Chart::Base : Incorrect value for 'min_circles', too small.\n";
168 0         0 $minTicks = 2;
169             }
170              
171 4 50       48 if( $maxTicks < 5*$minTicks )
172             {
173 0         0 print STDERR "Chart::Base : Incorrect value for 'max_circles', too small.\n";
174 0         0 $maxTicks = 5*$minTicks;
175             }
176              
177 4         43 my $width = $max - $min;
178 4         36 my @divisorList;
179              
180             for( my $baseMul = 1; ; $baseMul *= 10 )
181             {
182 4         43 TRY: foreach my $tryMul (1, 2, 5)
183             {
184             # Calc a fresh, smaller tick interval.
185 4         36 my $divisor = $baseMul * $tryMul;
186              
187             # Count the number of ticks.
188 4         80 my ($tickCount, $pMin, $pMax) = $self->_countTicks($min, $max, 1/$divisor);
189              
190             # Look a the number of ticks.
191 4 50       90 if( $maxTicks < $tickCount )
    50          
192             {
193             # If it is to high, Backtrack.
194 0         0 $divisor = pop @divisorList;
195             # just for security:
196 0 0 0     0                                 if ( !defined($divisor) || $divisor == 0 ) { $divisor = 1; }
  0         0  
197 0         0 ($tickCount, $pMin, $pMax) = $self->_countTicks($min, $max, 1/$divisor);
198 0         0 print "\nChart::Base : Caution: Tick limit of $maxTicks exceeded. Backing of to an interval of ".1/$divisor." which plots $tickCount ticks\n";
199 0         0 return(1/$divisor, $tickCount, $pMin, $pMax);
200             }
201             elsif( $minTicks > $tickCount )
202             {
203             # If it is to low, try again.
204 0         0 next TRY;
205             }
206             else
207             {
208             # Store the divisor for possible later backtracking.
209 4         40 push @divisorList, $divisor;
210              
211             # if the min or max is fixed, check they will fit in the interval.
212 4 50 33     65 next TRY if( $minF && ( int ($min*$divisor) != ($min*$divisor) ) );
213 4 50 33     80 next TRY if( $maxF && ( int ($max*$divisor) != ($max*$divisor) ) );
214              
215             # If everything passes the tests, return.
216 4         58 return(1/$divisor, $tickCount, $pMin, $pMax)
217             }
218             }
219 4         37 }
220 0         0 die "can't happen!";
221             }
222              
223             #this is where we draw the circles and the axes
224             sub _draw_y_ticks {
225 4     4   72   my $self = shift;
226 4         44   my $data = $self->{'dataref'};
227 4         53   my $misccolor = $self->_color_role_to_index('misc');
228 4         60   my $textcolor = $self->_color_role_to_index('text');
229 4         89   my $background = $self->_color_role_to_index('background');
230 4         39   my @labels = @{$self->{'y_tick_labels'}};
  4         101  
231 4         41   my ($width, $height, $centerX, $centerY, $diameter);
232 4         42   my ($pi, $font, $fontW, $fontH, $labelX, $labelY, $label_offset);
233 4         38   my ($dia_delta, $dia, $x, $y, @label_degrees, $arc, $angle_interval);
234              
235             # set up initial constant values
236 4         41   $pi = 3.14159265358979323846;
237 4         561   $font = $self->{'legend_font'};
238 4         75   $fontW = $self->{'legend_font'}->width;
239 4         56   $fontH = $self->{'legend_font'}->height;
240 4         40   $angle_interval = $self->{'angle_interval'};
241              
242 4 50       63   if ($self->{'grey_background'} =~ /^true$/i) {
243 0         0       $background = $self->_color_role_to_index('grey_background');
244               }
245             # init the imagemap data field if they wanted it
246 4 50       52   if ($self->{'imagemap'} =~ /^true$/i) {
247 0         0     $self->{'imagemap_data'} = [];
248               }
249              
250             # find width and height
251 4         46   $width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
252 4         42   $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
253              
254             # find center point, from which the pie will be drawn around
255 4         52   $centerX = int($width/2 + $self->{'curr_x_min'});
256 4         48   $centerY = int($height/2 + $self->{'curr_y_min'});
257              
258             # always draw a circle, which means the diameter will be the smaller
259             # of the width and height. let enougth space for the label.
260 4 50       51   if ($width < $height) {
261 0         0    $diameter = $width -110;
262               }
263               else {
264 4         39     $diameter = $height -80 ;
265               }
266              
267             #the difference between the diameter of two following circles;
268 4         268   $dia_delta = ceil($diameter / ($self->{'y_ticks'}-1));
269              
270             #store the calculated data
271 4         742   $self->{'centerX'} = $centerX;
272 4         102   $self->{'centerY'} = $centerY;
273 4         44   $self->{'diameter'} = $diameter;
274              
275             #draw the axes and its labels
276             # set up an array of labels for the axes
277 4 50 33     168   if ($angle_interval == 0) {
    50 33        
    50 66        
    100 33        
    50 66        
    100 33        
    50 0        
    0          
278 0         0      @label_degrees = ( );
279               }
280               elsif ($angle_interval <= 5 && $angle_interval > 0) {
281 0         0      @label_degrees = qw(180 175 170 165 160 155 150 145 140 135 130 125 120 115
282             110 105 100 95 90 85 80 75 70 65 60 55 50 45 40 35 30 25 20 15 10 5 0 355 350
283             345 340 335 330 325 320 315 310 305 300 295 290 285 280 275 270 265 260 255
284             250 245 240 235 230 225 220 215 210 205 200 195 190 185);
285 0         0      $angle_interval = 5;
286               }
287               elsif ($angle_interval <= 10 && $angle_interval > 5) {
288 0         0      @label_degrees = qw(180 170 160 150 140 130 120 110 100 90 80 70 60 50 40
289             30 20 10 0 350 340 330 320 310 300 290 280 270 260 250 240 230 220 210 200 190);
290 0         0      $angle_interval = 10;
291               }
292               elsif ($angle_interval <= 15 && $angle_interval > 10) {
293 1         20      @label_degrees = qw(180 165 150 135 120 105 90 75 60 45 30 15 0 345 330 315 300
294             285 270 255 240 225 210 195);
295 1         9      $angle_interval = 15;
296               }
297               elsif ($angle_interval <=20 && $angle_interval > 15) {
298 0         0      @label_degrees = qw(180 160 140 120 100 80 60 40 20 0 340 320 300 280 260 240
299             220 200);
300 0         0      $angle_interval = 20;
301               }
302               elsif ($angle_interval <= 30 && $angle_interval > 20) {
303 1         310      @label_degrees = qw(180 150 120 90 60 30 0 330 300 270 240 210);
304 1         10      $angle_interval = 30;
305               }
306               elsif ($angle_interval <= 45 && $angle_interval > 30) {
307 2         29      @label_degrees = qw(180 135 90 45 0 315 270 225);
308 2         21      $angle_interval = 45;
309               }
310               elsif ($angle_interval <= 90 && $angle_interval > 45) {
311 0         0      @label_degrees = qw(180 90 0 270);
312 0         0      $angle_interval = 90;
313               }
314               else {
315 0         0      carp "The angle_interval must be between 0 and 90!\nCorrected value: 30";
316 0         0      @label_degrees = qw(180 150 120 90 60 30 0 330 300 270 240 210);
317 0         0      $angle_interval = 30;
318               }
319 4         38   $arc = 0;
320 4         404   foreach (@label_degrees) {
321             #calculated the coordinates of the end point of the line
322 52         670       $x = sin ($arc)*($diameter/2+10) + $centerX;
323 52         498       $y = cos ($arc)*($diameter/2+10) + $centerY;
324             #some ugly correcture
325 52 100       557       if ($_ == '270') { $y++;}
  4         40  
326             #draw the line
327 52         1102      &nb