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 2015-03-01
7# @version 2.4.10
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
18package Chart::Direction;
19
20use Chart::Base '2.4.10';
21use GD;
22use Carp;
23
24use strict;
25use POSIX;
26
27@Chart::Direction::ISA     = qw(Chart::Base);
28$Chart::Direction::VERSION = '2.4.10';
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#
47sub 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#
97sub 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#
129sub _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
299sub _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
381sub _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#
550sub _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
559sub _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[in] color Color to be used
854# @param[in] type  Type of line
855# @return status
856sub _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#
899sub _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#
992sub _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
10641;
1065