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