File Coverage

blib/lib/Chart/Direction.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             ## @file
2             # Implementation of Chart::Direction
3             #
4             # written by
5             # @author Chart Group at Geodetic Fundamental Station Wettzell (Chart@fs.wettzell.de)
6             # @date 2012-10-03
7             # @version 2.4.6
8             #
9              
10             # @section Chart::Direction
11             # Implements a circular oriented chart like rotating vectors
12             #
13              
14             ## @class Chart::Direction
15             # @brief Direction class derived class for Chart to implement direction
16             # charts
17              
18             package Chart::Direction;
19              
20 4     4   11458 use Chart::Base '2.4.6';
  0            
  0            
21             use GD;
22             use Carp;
23              
24             use strict;
25             use POSIX;
26              
27             @Chart::Direction::ISA = qw(Chart::Base);
28             $Chart::Direction::VERSION = '2.4.6';
29              
30             #>>>>>>>>>>>>>>>>>>>>>>>>>>#
31             # public methods go here #
32             #<<<<<<<<<<<<<<<<<<<<<<<<<<#
33              
34             ## @method int set(%opts)
35             # @param[in] %opts Hash of options to the Chart
36             # @return ok or croak
37             #
38             # @brief
39             # Set all options
40             #
41             # @details
42             # main method for customizing the chart, lets users
43             # specify values for different parameters\n
44             # dont check the number of points in the added datasets in a polarplot\n
45             # overwrite Base method
46             #
47             sub set
48             {
49             my $self = shift;
50             my %opts = @_;
51              
52             # basic error checking on the options, just warn 'em
53             unless ( $#_ % 2 )
54             {
55             carp "Whoops, some option to be set didn't have a value.\n", "You might want to look at that.\n";
56             }
57              
58             # set the options
59             for ( keys %opts )
60             {
61             $self->{$_} = $opts{$_};
62              
63             # if someone wants to change the grid_lines color, we should set all
64             # the colors of the grid_lines
65             if ( $_ =~ /^colors$/ )
66             {
67             my %hash = %{ $opts{$_} };
68             foreach my $key ( sort keys %hash )
69             {
70             if ( $key =~ /^grid_lines$/ )
71             {
72             $self->{'colors'}{'y_grid_lines'} = $hash{'grid_lines'};
73             $self->{'colors'}{'x_grid_lines'} = $hash{'grid_lines'};
74             $self->{'colors'}{'y2_grid_lines'} = $hash{'grid_lines'};
75             }
76             }
77             }
78             }
79              
80             if ( $self->false( $self->{'polar'} ) && ( defined $self->{'croak'} ) )
81             {
82             carp "New data set to be added has an incorrect number of points";
83             }
84              
85             # now return
86             return 1;
87             }
88              
89             ## @method int add_dataset(@data)
90             # Add many datasets to the dataref
91             #
92             # Graph API\n
93             # Overwrite Base method
94             #
95             # @param @data Dataset to add
96             #
97             sub add_dataset
98             {
99             my $self = shift;
100             my @data = @_;
101              
102             # error check the data (carp, don't croak)
103             if ( $self->{'dataref'} && ( $#{ $self->{'dataref'}->[0] } != $#data ) )
104             {
105              
106             # carp "New data set to be added has an incorrect number of points";
107             $self->{'croak'} = 'true';
108             }
109              
110             # copy it into the dataref
111             push @{ $self->{'dataref'} }, [@data];
112              
113             # now return
114             return 1;
115             }
116              
117             #>>>>>>>>>>>>>>>>>>>>>>>>>>>#
118             # private methods go here #
119             #<<<<<<<<<<<<<<<<<<<<<<<<<<<#
120              
121             ## @fn private int _find_y_scale()
122             # we use the find_y_scale methode to determine the labels of the circles
123             # and the amount of them
124             # @return status
125             #
126             # This function is an overwrite to the same function found in the base class
127             # Chart::Base
128             #
129             sub _find_y_scale
130             {
131             my $self = shift;
132              
133             # Predeclare vars.
134             my ( $d_min, $d_max ); # Dataset min & max.
135             my ( $p_min, $p_max ); # Plot min & max.
136             my ( $tickInterval, $tickCount, $skip );
137             my @tickLabels; # List of labels for each tick.
138             my $maxtickLabelLen = 0; # The length of the longest tick label.
139              
140             # Find the datatset minimum and maximum.
141             ( $d_min, $d_max ) = $self->_find_y_range();
142              
143             # Force the inclusion of zero if the user has requested it.
144             if ( $self->true( $self->{'include_zero'} ) )
145             {
146             if ( ( $d_min * $d_max ) > 0 ) # If both are non zero and of the same sign.
147             {
148             if ( $d_min > 0 ) # If the whole scale is positive.
149             {
150             $d_min = 0;
151             }
152             else # The scale is entirely negative.
153             {
154             $d_max = 0;
155             }
156             }
157             }
158              
159             # Allow the dataset range to be overidden by the user.
160             # f_min/max are booleans which indicate that the min & max should not be modified.
161             my $f_min = defined $self->{'min_val'};
162             $d_min = $self->{'min_val'} if $f_min;
163              
164             my $f_max = defined $self->{'max_val'};
165             $d_max = $self->{'max_val'} if $f_max;
166              
167             # Assert against the min is larger than the max.
168             if ( $d_min > $d_max )
169             {
170             croak "The the specified 'min_val' & 'max_val' values are reversed (min > max: $d_min>$d_max)";
171             }
172              
173             # Calculate the width of the dataset. (posibly modified by the user)
174             my $d_width = $d_max - $d_min;
175              
176             # If the width of the range is zero, forcibly widen it
177             # (to avoid division by zero errors elsewhere in the code).
178             if ( 0 == $d_width )
179             {
180             $d_min--;
181             $d_max++;
182             $d_width = 2;
183             }
184              
185             # Descale the range by converting the dataset width into
186             # a floating point exponent & mantisa pair.
187             my ( $rangeExponent, $rangeMantisa ) = $self->_sepFP($d_width);
188             my $rangeMuliplier = 10**$rangeExponent;
189              
190             # Find what tick
191             # to use & how many ticks to plot,
192             # round the plot min & max to suatable round numbers.
193             ( $tickInterval, $tickCount, $p_min, $p_max ) = $self->_calcTickInterval(
194             $d_min / $rangeMuliplier,
195             $d_max / $rangeMuliplier,
196             $f_min, $f_max,
197             $self->{'min_circles'} + 1,
198             $self->{'max_circles'} + 1
199             );
200              
201             # Restore the tickInterval etc to the correct scale
202             $_ *= $rangeMuliplier foreach ( $tickInterval, $p_min, $p_max );
203              
204             #get the precision for the labels
205             my $precision = $self->{'precision'};
206              
207             # Now sort out an array of tick labels.
208              
209             if ( $self->false( $self->{'polar'} ) )
210             {
211             for ( my $labelNum = $p_min ; $labelNum <= $p_max ; $labelNum += $tickInterval )
212             {
213             my $labelText;
214              
215             if ( defined $self->{f_y_tick} )
216             {
217              
218             # Is _default_f_tick function used?
219             if ( $self->{f_y_tick} == \&Chart::Base::_default_f_tick )
220             {
221             $labelText = sprintf( "%." . $precision . "f", $labelNum );
222             }
223             else
224             {
225              
226             # print \&_default_f_tick;
227             $labelText = $self->{f_y_tick}->($labelNum);
228             }
229             }
230             else
231             {
232             $labelText = sprintf( "%." . $precision . "f", $labelNum );
233             }
234             push @tickLabels, $labelText;
235             $maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
236             }
237             }
238             else
239             {
240              
241             # polar == true
242             for ( my $labelNum = $p_max ; $labelNum >= $p_min ; $labelNum -= $tickInterval )
243             {
244             my $labelText;
245              
246             if ( defined $self->{f_y_tick} )
247             {
248              
249             # Is _default_f_tick function used?
250             if ( $self->{f_y_tick} == \&Chart::Base::_default_f_tick )
251             {
252             $labelText = sprintf( "%." . $precision . "f", $labelNum );
253             }
254             else
255             {
256              
257             # print \&_default_f_tick;
258             $labelText = $self->{f_y_tick}->($labelNum);
259             }
260             }
261             else
262             {
263             $labelText = sprintf( "%." . $precision . "f", $labelNum );
264             }
265             push @tickLabels, $labelText;
266             $maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
267             }
268             }
269              
270             # Store the calculated data.
271             $self->{'min_val'} = $p_min,
272             $self->{'max_val'} = $p_max,
273             $self->{'y_ticks'} = $tickCount,
274             $self->{'y_tick_labels'} = \@tickLabels,
275             $self->{'y_tick_label_length'} = $maxtickLabelLen;
276              
277             # and return.
278             return 1;
279             }
280              
281             ## @fn private _calcTickInterval($dataset_min, $dataset_max, $flag_fixed_min, $flag_fixed_max, $minTicks, $maxTicks)
282             # @brief
283             # Calculates the ticks for direction in normalised units.
284             #
285             # @details
286             # Calculate the Interval between ticks in y direction
287             # and compare the number of ticks to
288             # the user given values min_y_ticks, max_y_ticks
289             #
290             # @param[in] $dataset_min Minimal value in y direction
291             # @param[in] $dataset_max Maximal value in y direction
292             # @param[in] $flag_fixed_min Indicator whether the dataset_min value is fixed
293             # @param[in] $flag_fixed_max Indicator whether the dataset_max value is fixed
294             # @param[in] $minTicks Minimal number of ticks wanted
295             # @param[in] $maxTicks Maximal number of ticks wanted
296             # @return $tickInterval, $tickCount, $pMin, $pMax
297             #
298              
299             sub _calcTickInterval
300             {
301             my $self = shift;
302             my (
303             $min, $max, # The dataset min & max.
304             $minF, $maxF, # Indicates if those min/max are fixed.
305             $minTicks, $maxTicks, # The minimum & maximum number of ticks.
306             ) = @_;
307              
308             # Verify the supplied 'min_y_ticks' & 'max_y_ticks' are sensible.
309             if ( $minTicks < 2 )
310             {
311             carp "Chart::Direction : Incorrect value for 'min_circles', too small.\n";
312             $minTicks = 2;
313             }
314              
315             if ( $maxTicks < 5 * $minTicks )
316             {
317             carp "Chart::Direction : Incorrect value for 'max_circles', too small.\n";
318             $maxTicks = 5 * $minTicks;
319             }
320              
321             my $width = $max - $min;
322             my @divisorList;
323              
324             for ( my $baseMul = 1 ; ; $baseMul *= 10 )
325             {
326             TRY: foreach my $tryMul ( 1, 2, 5 )
327             {
328              
329             # Calc a fresh, smaller tick interval.
330             my $divisor = $baseMul * $tryMul;
331              
332             # Count the number of ticks.
333             my ( $tickCount, $pMin, $pMax ) = $self->_countTicks( $min, $max, 1 / $divisor );
334              
335             # Look a the number of ticks.
336             if ( $maxTicks < $tickCount )
337             {
338              
339             # If it is too high, Backtrack.
340             $divisor = pop @divisorList;
341              
342             # just for security:
343             if ( !defined($divisor) || $divisor == 0 ) { $divisor = 1; }
344             ( $tickCount, $pMin, $pMax ) = $self->_countTicks( $min, $max, 1 / $divisor );
345             carp "Chart::Direction : Caution: Tick limit of $maxTicks exceeded. Backing of to an interval of "
346             . 1 / $divisor
347             . " which plots $tickCount ticks\n";
348              
349             return ( 1 / $divisor, $tickCount, $pMin, $pMax );
350             }
351             elsif ( $minTicks > $tickCount )
352             {
353              
354             # If it is too low, try again.
355             next TRY;
356             }
357             else
358             {
359              
360             # Store the divisor for possible later backtracking.
361             push @divisorList, $divisor;
362              
363             # if the min or max is fixed, check they will fit in the interval.
364             next TRY if ( $minF && ( int( $min * $divisor ) != ( $min * $divisor ) ) );
365             next TRY if ( $maxF && ( int( $max * $divisor ) != ( $max * $divisor ) ) );
366              
367             # If everything passes the tests, return.
368             return ( 1 / $divisor, $tickCount, $pMin, $pMax );
369             }
370             }
371             }
372             die "can't happen!";
373             }
374              
375             ## @fn private int _draw_y_ticks()
376             # draw the circles and the axes
377             #
378             # Overwrites _draw_y_ticks() of Base class
379             #
380             # @return status
381             sub _draw_y_ticks
382             {
383             my $self = shift;
384             my $data = $self->{'dataref'};
385             my $misccolor = $self->_color_role_to_index('misc');
386             my $textcolor = $self->_color_role_to_index('text');
387             my $background = $self->_color_role_to_index('background');
388             my @labels = @{ $self->{'y_tick_labels'} };
389             my ( $width, $height, $centerX, $centerY, $diameter );
390             my ( $pi, $font, $fontW, $fontH, $labelX, $labelY, $label_offset );
391             my ( $dia_delta, $dia, $x, $y, @label_degrees, $arc, $angle_interval );
392              
393             # set up initial constant values
394             $pi = 3.14159265358979323846,
395             $font = $self->{'legend_font'},
396             $fontW = $self->{'legend_font'}->width,
397             $fontH = $self->{'legend_font'}->height,
398             $angle_interval = $self->{'angle_interval'};
399              
400             if ( $self->true( $self->{'grey_background'} ) )
401             {
402             $background = $self->_color_role_to_index('grey_background');
403             }
404              
405             # init the imagemap data field if they wanted it
406             if ( $self->true( $self->{'imagemap'} ) )
407             {
408             $self->{'imagemap_data'} = [];
409             }
410              
411             # find width and height
412             $width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
413             $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
414              
415             # find center point, from which the pie will be drawn around
416             $centerX = int( $width / 2 + $self->{'curr_x_min'} );
417             $centerY = int( $height / 2 + $self->{'curr_y_min'} );
418              
419             # always draw a circle, which means the diameter will be the smaller
420             # of the width and height. let enough space for the labels.
421              
422             ## @todo calculate the width of the labels
423              
424             if ( $width < $height )
425             {
426             $diameter = $width - 110;
427             }
428             else
429             {
430             $diameter = $height - 80;
431             }
432              
433             #the difference between the diameter of two following circles;
434             $dia_delta = ceil( $diameter / ( $self->{'y_ticks'} - 1 ) );
435              
436             #store the calculated data
437             $self->{'centerX'} = $centerX;
438             $self->{'centerY'} = $centerY;
439             $self->{'diameter'} = $diameter;
440              
441             #draw the axes and its labels
442             # set up an array of labels for the axes
443             if ( $angle_interval == 0 )
444             {
445             @label_degrees = ();
446             }
447             elsif ( $angle_interval <= 5 && $angle_interval > 0 )
448             {
449             @label_degrees = qw(180 175 170 165 160 155 150 145 140 135 130 125 120 115
450             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
451             345 340 335 330 325 320 315 310 305 300 295 290 285 280 275 270 265 260 255
452             250 245 240 235 230 225 220 215 210 205 200 195 190 185);
453             $angle_interval = 5;
454             }
455             elsif ( $angle_interval <= 10 && $angle_interval > 5 )
456             {
457             @label_degrees = qw(180 170 160 150 140 130 120 110 100 90 80 70 60 50 40
458             30 20 10 0 350 340 330 320 310 300 290 280 270 260 250 240 230 220 210 200 190);
459             $angle_interval = 10;
460             }
461             elsif ( $angle_interval <= 15 && $angle_interval > 10 )
462             {
463             @label_degrees = qw(180 165 150 135 120 105 90 75 60 45 30 15 0 345 330 315 300
464             285 270 255 240 225 210 195);
465             $angle_interval = 15;
466             }
467             elsif ( $angle_interval <= 20 && $angle_interval > 15 )
468             {
469             @label_degrees = qw(180 160 140 120 100 80 60 40 20 0 340 320 300 280 260 240
470             220 200);
471             $angle_interval = 20;
472             }
473             elsif ( $angle_interval <= 30 && $angle_interval > 20 )
474             {
475             @label_degrees = qw(180 150 120 90 60 30 0 330 300 270 240 210);
476             $angle_interval = 30;
477             }
478             elsif ( $angle_interval <= 45 && $angle_interval > 30 )
479             {
480             @label_degrees = qw(180 135 90 45 0 315 270 225);
481             $angle_interval = 45;
482             }
483             elsif ( $angle_interval <= 90 && $angle_interval > 45 )
484             {
485             @label_degrees = qw(180 90 0 270);
486             $angle_interval = 90;
487             }
488             else
489             {
490             carp "The angle_interval must be between 0 and 90!\nCorrected value: 30";
491             @label_degrees = qw(180 150 120 90 60 30 0 330 300 270 240 210);
492             $angle_interval = 30;
493             }
494             $arc = 0;
495              
496             foreach (@label_degrees)
497             {
498              
499             #calculated the coordinates of the end point of the line
500             $x = sin($arc) * ( $diameter / 2 + 10 ) + $centerX;
501             $y = cos($arc) * ( $diameter / 2 + 10 ) + $centerY;
502              
503             #some ugly correcture
504             if ( $_ == '270' ) { $y++; }
505              
506             #draw the line
507             $self->{'gd_obj'}->line( $centerX, $centerY, $x, $y, $misccolor );
508              
509             #calculate the string point
510             $x = sin($arc) * ( $diameter / 2 + 30 ) + $centerX - 8;
511             $y = cos($arc) * ( $diameter / 2 + 28 ) + $centerY - 6;
512              
513             #draw the labels
514             $self->{'gd_obj'}->string( $font, $x, $y, $_ . '°', $textcolor );
515             $arc += ( ($angle_interval) / 360 ) * 2 * $pi;
516             }
517              
518             #draw the circles
519             $dia = 0;
520             foreach (@labels)
521             {
522             $self->{'gd_obj'}->arc( $centerX, $centerY, $dia, $dia, 0, 360, $misccolor );
523             $dia += $dia_delta;
524             }
525              
526             $self->{'gd_obj'}->filledRectangle(
527             $centerX - length( $labels[0] ) / 2 * $fontW - 2,
528             $centerY + 2,
529             $centerX + 2 + $diameter / 2,
530             $centerY + $fontH + 2, $background
531             );
532              
533             #draw the labels of the circles
534             $dia = 0;
535             foreach (@labels)
536             {
537             $self->{'gd_obj'}->string( $font, $centerX + $dia / 2 - length($_) / 2 * $fontW, $centerY + 2, $_, $textcolor );
538             $dia += $dia_delta;
539             }
540              
541             return 1;
542             }
543              
544             ## @fn private int _draw_x_ticks()
545             # We don't need x ticks, it's all done in _draw_y_ticks
546             # @return status
547             #
548             # Overwrites the corresponding function in Base
549             #
550             sub _draw_x_ticks
551             {
552             my $self = shift;
553              
554             return 1;
555             }
556              
557             ## @fn private _draw_data
558             # finally get around to plotting the data for direction charts
559             sub _draw_data
560             {
561             my $self = shift;
562             my $data = $self->{'dataref'};
563             my $misccolor = $self->_color_role_to_index('misc');
564             my $textcolor = $self->_color_role_to_index('text');
565             my $background = $self->_color_role_to_index('background');
566             my ( $width, $height, $centerX, $centerY, $diameter );
567             my ( $mod, $map, $i, $j, $brush, $color, $x, $y, $winkel, $first_x, $first_y );
568             my ( $arrow_x, $arrow_y, $m );
569             $color = 1;
570              
571             my $pi = 3.14159265358979323846;
572             my $len = 10;
573             my $alpha = 1;
574             my $last_x = undef;
575             my $last_y = undef;
576             my $diff;
577             my $n = 0;
578              
579             if ( $self->true( $self->{'pairs'} ) )
580             {
581             my $a = $self->{'num_datasets'} / 2;
582             my $b = ceil($a);
583             my $c = $b - $a;
584              
585             if ( $c == 0 )
586             {
587             croak "Wrong number of datasets for 'pairs'";
588             }
589             }
590              
591             # init the imagemap data field if they wanted it
592             if ( $self->true( $self->{'imagemap'} ) )
593             {
594             $self->{'imagemap_data'} = [];
595             }
596              
597             # find width and height
598             $width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
599             $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
600              
601             # get the base values
602              
603             if ( $self->false( $self->{'polar'} ) )
604             {
605             $mod = $self->{'min_val'};
606             }
607             else
608             {
609             $mod = $self->{'max_val'};
610             }
611             $centerX = $self->{'centerX'};
612             $centerY = $self->{'centerY'};
613             $diameter = $self->{'diameter'};
614             $diff = $self->{'max_val'} - $self->{'min_val'};
615             $diff = 1 if $diff < 1;
616             $map = $diameter / 2 / $diff;
617              
618             $brush = $self->_prepare_brush( $color, 'point' );
619             $self->{'gd_obj'}->setBrush($brush);
620              
621             # draw every line for this dataset
622              
623             if ( $self->false( $self->{'pairs'} ) )
624             {
625             for $j ( 1 .. $self->{'num_datasets'} )
626             {
627             $color = $self->_color_role_to_index( 'dataset' . ( $j - 1 ) );
628              
629             for $i ( 0 .. $self->{'num_datapoints'} - 1 )
630             {
631              
632             # don't try to draw anything if there's no data
633             if ( defined( $data->[$j][$i] )
634             && $data->[$j][$i] <= $self->{'max_val'}
635             && $data->[$j][$i] >= $self->{'min_val'} )
636             { #calculate the point
637             $winkel = ( 180 - ( $data->[0][$i] % 360 ) ) / 360 * 2 * $pi;
638              
639             if ( $self->false( $self->{'polar'} ) )
640             {
641             $x = ceil( $centerX + sin($winkel) * ( $data->[$j][$i] - $mod ) * $map );
642             $y = ceil( $centerY + cos($winkel) * ( $data->[$j][$i] - $mod ) * $map );
643             }
644             else
645             {
646             $x = ceil( $centerX + sin($winkel) * ( $mod - $data->[$j][$i] ) * $map );
647             $y = ceil( $centerY + cos($winkel) * ( $mod - $data->[$j][$i] ) * $map );
648             }
649              
650             # set the x and y values back
651             if ( $i == 0 )
652             {
653             $first_x = $x;
654             $first_y = $y;
655             $last_x = $x;
656             $last_y = $y;
657             }
658              
659             if ( $self->true( $self->{'point'} ) )
660             {
661             $brush = $self->_prepare_brush( $color, 'point' );
662             $self->{'gd_obj'}->setBrush($brush);
663              
664             #draw the point
665             $self->{'gd_obj'}->line( $x + 1, $y, $x, $y, gdBrushed );
666             }
667             if ( $self->true( $self->{'line'} ) )
668             {
669             $brush = $self->_prepare_brush( $color, 'line' );
670             $self->{'gd_obj'}->setBrush($brush);
671              
672             #draw the line
673             if ( defined $last_x )
674             {
675             $self->{'gd_obj'}->line( $x, $y, $last_x, $last_y, gdBrushed );
676             }
677             }
678              
679             if ( $self->true( $self->{'arrow'} ) )
680             {
681             $brush = $self->_prepare_brush( $color, 'line' );
682             $self->{'gd_obj'}->setBrush($brush);
683              
684             #draw the arrow
685             if ( $data->[$j][$i] > $self->{'min_val'} )
686             {
687             $self->{'gd_obj'}->line( $x, $y, $centerX, $centerY, gdBrushed );
688              
689             $arrow_x = $x - cos( $winkel - $alpha ) * $len;
690             $arrow_y = $y + sin( $winkel - $alpha ) * $len;
691             $self->{'gd_obj'}->line( $x, $y, $arrow_x, $arrow_y, gdBrushed );
692              
693             $arrow_x = $x + sin( $pi / 2 - $winkel - $alpha ) * $len;
694             $arrow_y = $y - cos( $pi / 2 - $winkel - $alpha ) * $len;
695             $self->{'gd_obj'}->line( $x, $y, $arrow_x, $arrow_y, gdBrushed );
696              
697             }
698             }
699              
700             $last_x = $x;
701             $last_y = $y;
702              
703             # store the imagemap data if they asked for it
704             if ( $self->true( $self->{'imagemap'} ) )
705             {
706             $self->{'imagemap_data'}->[$j][$i] = [ $x, $y ];
707             }
708             }
709             else
710             {
711             if ( $self->true( $self->{'imagemap'} ) )
712             {
713             $self->{'imagemap_data'}->[$j][$i] = [ undef(), undef() ];
714             }
715             }
716             } # end for
717              
718             # draw the last line to the first point
719             if ( $self->true( $self->{'line'} ) )
720             {
721             $self->{'gd_obj'}->line( $x, $y, $first_x, $first_y, gdBrushed );
722             }
723              
724             } # end for $j
725             }
726              
727             if ( $self->true( $self->{'pairs'} ) )
728             {
729             for ( $j = 1 ; $j <= $self->{'num_datasets'} ; $j += 2 )
730             {
731             if ( $j == 1 )
732             {
733             $color = $self->_color_role_to_index( 'dataset' . ( $j - 1 ) );
734             }
735             else
736             {
737             $color = $self->_color_role_to_index( 'dataset' . ( $j / 2 - 0.5 ) );
738             }
739              
740             ##### $color = $self->_color_role_to_index('dataset'.(1)); #####################
741              
742             for $i ( 0 .. $self->{'num_datapoints'} - 1 )
743             {
744              
745             # don't try to draw anything if there's no data
746             if ( defined( $data->[$j][$i] )
747             && $data->[$j][$i] <= $self->{'max_val'}
748             && $data->[$j][$i] >= $self->{'min_val'} )
749             {
750              
751             # calculate the point
752             $winkel = ( 180 - ( $data->[$n][$i] % 360 ) ) / 360 * 2 * $pi;
753              
754             if ( $self->false( $self->{'polar'} ) )
755             {
756             $x = ceil( $centerX + sin($winkel) * ( $data->[$j][$i] - $mod ) * $map );
757             $y = ceil( $centerY + cos($winkel) * ( $data->[$j][$i] - $mod ) * $map );
758             }
759             else
760             {
761             $x = ceil( $centerX + sin($winkel) * ( $mod - $data->[$j][$i] ) * $map );
762             $y = ceil( $centerY + cos($winkel) * ( $mod - $data->[$j][$i] ) * $map );
763             }
764              
765             # set the x and y values back
766             if ( $i == 0 )
767             {
768             $first_x = $x;
769             $first_y = $y;
770             $last_x = $x;
771             $last_y = $y;
772             }
773              
774             if ( $self->true( $self->{'point'} ) )
775             {
776             $brush = $self->_prepare_brush( $color, 'point' );
777             $self->{'gd_obj'}->setBrush($brush);
778              
779             #draw the point
780             $self->{'gd_obj'}->line( $x + 1, $y, $x, $y, gdBrushed );
781             }
782             if ( $self->true( $self->{'line'} ) )
783             {
784             $brush = $self->_prepare_brush( $color, 'line' );
785             $self->{'gd_obj'}->setBrush($brush);
786              
787             #draw the line
788             if ( defined $last_x )
789             {
790             $self->{'gd_obj'}->line( $x, $y, $last_x, $last_y, gdBrushed );
791             }
792             else { }
793             }
794              
795             if ( $self->true( $self->{'arrow'} ) )
796             {
797             $brush = $self->_prepare_brush( $color, 'line' );
798             $self->{'gd_obj'}->setBrush($brush);
799              
800             #draw the arrow
801             if ( $data->[$j][$i] > $self->{'min_val'} )
802             {
803             $self->{'gd_obj'}->line( $x, $y, $centerX, $centerY, gdBrushed );
804              
805             $arrow_x = $x - cos( $winkel - $alpha ) * $len;
806             $arrow_y = $y + sin( $winkel - $alpha ) * $len;
807             $self->{'gd_obj'}->line( $x, $y, $arrow_x, $arrow_y, gdBrushed );
808              
809             $arrow_x = $x + sin( $pi / 2 - $winkel - $alpha ) * $len;
810             $arrow_y = $y - cos( $pi / 2 - $winkel - $alpha ) * $len;
811             $self->{'gd_obj'}->line( $x, $y, $arrow_x, $arrow_y, gdBrushed );
812             }
813             }
814              
815             $last_x = $x;
816             $last_y = $y;
817              
818             # store the imagemap data if they asked for it
819             if ( $self->true( $self->{'imagemap'} ) )
820             {
821             $self->{'imagemap_data'}->[$j][$i] = [ $x, $y ];
822             }
823             } # end if ( defined ...
824             else
825             {
826             if ( $self->true( $self->{'imagemap'} ) )
827             {
828             $self->{'imagemap_data'}->[$j][$i] = [ undef(), undef() ];
829             }
830             }
831             } #end for $i
832              
833             # draw the last line to the first point
834             if ( $self->true( $self->{'line'} ) )
835             {
836             $self->{'gd_obj'}->line( $x, $y, $first_x, $first_y, gdBrushed );
837             }
838             $n += 2;
839             } # end for $j
840             } # end if pairs
841              
842             # now outline it
843             $self->{'gd_obj'}
844             ->rectangle( $self->{'curr_x_min'}, $self->{'curr_y_min'}, $self->{'curr_x_max'}, $self->{'curr_y_max'}, $misccolor );
845              
846             return;
847             }
848              
849             ## @fn private int _prepare_brush($color,$type)
850             # set the gdBrush object to trick GD into drawing fat lines
851             #
852             #
853             # @param $color
854             # @param $type
855             # @return status
856             sub _prepare_brush
857             {
858             my $self = shift;
859             my $color = shift;
860             my $type = shift;
861             my ( $radius, @rgb, $brush, $white, $newcolor );
862              
863             @rgb = $self->{'gd_obj'}->rgb($color);
864              
865             # get the appropriate brush size
866             if ( $type eq 'line' )
867             {
868             $radius = $self->{'brush_size'} / 2;
869             }
870             elsif ( $type eq 'point' )
871             {
872             $radius = $self->{'pt_size'} / 2;
873             }
874              
875             # create the new image
876             $brush = GD::Image->new( $radius * 2, $radius * 2 );
877              
878             # get the colors, make the background transparent
879             $white = $brush->colorAllocate( 255, 255, 255 );
880             $newcolor = $brush->colorAllocate(@rgb);
881             $brush->transparent($white);
882              
883             # draw the circle
884             $brush->arc( $radius - 1, $radius - 1, $radius, $radius, 0, 360, $newcolor );
885              
886             # fill it if we're using lines
887             $brush->fill( $radius - 1, $radius - 1, $newcolor );
888              
889             # set the new image as the main object's brush
890             return $brush;
891             }
892              
893             ## @fn private int _draw_legend()
894             # let them know what all the pretty colors mean
895             # @return status
896             #
897             # Overwrite corresponding function of Base
898             #
899             sub _draw_legend
900             {
901             my $self = shift;
902             my $length;
903              
904             # check to see if legend type is none..
905             if ( $self->{'legend'} =~ /^none$/ )
906             {
907             return 1;
908             }
909              
910             # check to see if they have as many labels as datasets,
911             # warn them if not
912             if ( ( $#{ $self->{'legend_labels'} } >= 0 )
913             && ( ( scalar( @{ $self->{'legend_labels'} } ) ) != $self->{'num_datasets'} ) )
914             {
915             carp "The number of legend labels and datasets doesn\'t match";
916             }
917              
918             # init a field to store the length of the longest legend label
919             unless ( $self->{'max_legend_label'} )
920             {
921             $self->{'max_legend_label'} = 0;
922             }
923              
924             # fill in the legend labels, find the longest one
925              
926             if ( $self->false( $self->{'pairs'} ) )
927             {
928             for ( 1 .. $self->{'num_datasets'} )
929             {
930             unless ( $self->{'legend_labels'}[ $_ - 1 ] )
931             {
932             $self->{'legend_labels'}[ $_ - 1 ] = "Dataset $_";
933             }
934             $length = length( $self->{'legend_labels'}[ $_ - 1 ] );
935             if ( $length > $self->{'max_legend_label'} )
936             {
937             $self->{'max_legend_label'} = $length;
938             }
939             } #end for
940             }
941              
942             if ( $self->true( $self->{'pairs'} ) )
943             {
944              
945             for ( 1 .. ceil( $self->{'num_datasets'} / 2 ) )
946             {
947             unless ( $self->{'legend_labels'}[ $_ - 1 ] )
948             {
949             $self->{'legend_labels'}[ $_ - 1 ] = "Dataset $_";
950             }
951             $length = length( $self->{'legend_labels'}[ $_ - 1 ] );
952             if ( $length > $self->{'max_legend_label'} )
953             {
954             $self->{'max_legend_label'} = $length;
955             }
956             }
957             }
958              
959             # different legend types
960             if ( $self->{'legend'} eq 'bottom' )
961             {
962             $self->_draw_bottom_legend;
963             }
964             elsif ( $self->{'legend'} eq 'right' )
965             {
966             $self->_draw_right_legend;
967             }
968             elsif ( $self->{'legend'} eq 'left' )
969             {
970             $self->_draw_left_legend;
971             }
972             elsif ( $self->{'legend'} eq 'top' )
973             {
974             $self->_draw_top_legend;
975             }
976             else
977             {
978             carp "I can't put a legend there (at " . $self->{'legend'} . ")\n";
979             }
980              
981             # and return
982             return 1;
983             }
984              
985             ## @fn private array _find_y_range()
986             # Find minimum and maximum value of y data sets.
987             #
988             # @return ( min, max, flag_all_integers )
989             #
990             # Overwrites corresponding Base function
991             #
992             sub _find_y_range
993             {
994             my $self = shift;
995             my $data = $self->{'dataref'};
996              
997             my $max = undef;
998             my $min = undef;
999             my $k = 1;
1000             my $dataset = 1;
1001             my $datum;
1002              
1003             if ( $self->false( $self->{'pairs'} ) )
1004             {
1005             for $dataset ( @$data[ 1 .. $#$data ] )
1006             {
1007              
1008             # print "dataset @$dataset\n";
1009             for $datum (@$dataset)
1010             {
1011             if ( defined $datum )
1012             {
1013              
1014             # Prettier, but probably slower:
1015             # $max = $datum unless defined $max && $max >= $datum;
1016             # $min = $datum unless defined $min && $min <= $datum;
1017             if ( defined $max )
1018             {
1019             if ( $datum > $max ) { $max = $datum; }
1020             elsif ( $datum < $min ) { $min = $datum; }
1021             }
1022             else
1023             {
1024             $min = $max = $datum;
1025             }
1026             } #endif defined
1027             } # end for
1028             }
1029             }
1030              
1031             if ( $self->true( $self->{'pairs'} ) )
1032             {
1033              
1034             # only every second dataset must be checked
1035             for $dataset ( @$data[$k] )
1036             {
1037             for $datum (@$dataset)
1038             {
1039             if ( defined $datum )
1040             {
1041              
1042             # Prettier, but probably slower:
1043             # $max = $datum unless defined $max && $max >= $datum;
1044             # $min = $datum unless defined $min && $min <= $datum;
1045             if ( defined $max )
1046             {
1047             if ( $datum > $max ) { $max = $datum; }
1048             elsif ( $datum < $min ) { $min = $datum; }
1049             }
1050             else
1051             {
1052             $min = $max = $datum;
1053             }
1054             }
1055             }
1056             $k += 2;
1057             }
1058             }
1059              
1060             ( $min, $max );
1061             }
1062              
1063             ## be a good module and return 1
1064             1;