1package Imager::Graph::Horizontal; 2 3=head1 NAME 4 5 Imager::Graph::Horizontal - A super class for line/bar charts 6 7=cut 8 9use strict; 10use vars qw(@ISA); 11use Imager::Graph; 12@ISA = qw(Imager::Graph); 13 14use constant STARTING_MIN_VALUE => 99999; 15 16our $VERSION = "0.10"; 17 18=over 4 19 20=item add_data_series(\@data, $series_name) 21 22Add a data series to the graph, of the default type. 23 24=cut 25 26sub add_data_series { 27 my $self = shift; 28 my $data_ref = shift; 29 my $series_name = shift; 30 31 my $series_type = $self->_get_default_series_type(); 32 $self->_add_data_series($series_type, $data_ref, $series_name); 33 34 return; 35} 36 37=item add_bar_data_series(\@data, $series_name) 38 39Add a bar data series to the graph. 40 41=cut 42 43sub add_bar_data_series { 44 my $self = shift; 45 my $data_ref = shift; 46 my $series_name = shift; 47 48 $self->_add_data_series('bar', $data_ref, $series_name); 49 50 return; 51} 52 53=item add_line_data_series(\@data, $series_name) 54 55Add a line data series to the graph. 56 57=cut 58 59sub add_line_data_series { 60 my $self = shift; 61 my $data_ref = shift; 62 my $series_name = shift; 63 64 $self->_add_data_series('line', $data_ref, $series_name); 65 66 return; 67} 68 69=item set_column_padding($int) 70 71Sets the number of pixels that should go between columns of data. 72 73=cut 74 75sub set_column_padding { 76 $_[0]->{'custom_style'}->{'column_padding'} = $_[1]; 77} 78 79=item set_negative_background($color) 80 81Sets the background color or fill used below the y axis. 82 83=cut 84 85sub set_negative_background { 86 $_[0]->{'custom_style'}->{'negative_bg'} = $_[1]; 87} 88 89=item draw() 90 91Draw the graph 92 93=cut 94 95sub draw { 96 my ($self, %opts) = @_; 97 98 if (!$self->_valid_input()) { 99 return; 100 } 101 102 $self->_style_setup(\%opts); 103 104 my $style = $self->{_style}; 105 106 $self->_make_img 107 or return; 108 109 my $img = $self->_get_image() 110 or return; 111 112 my @image_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 ); 113 $self->_set_image_box(\@image_box); 114 115 my @chart_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 ); 116 $self->_draw_legend(\@chart_box); 117 if ($style->{title}{text}) { 118 $self->_draw_title($img, \@chart_box) 119 or return; 120 } 121 122 # Scale the graph box down to the widest graph that can cleanly hold the # of columns. 123 return unless $self->_get_data_range(); 124 $self->_remove_tics_from_chart_box(\@chart_box, \%opts); 125 my $column_count = $self->_get_column_count(); 126 127 my $width = $self->_get_number('width'); 128 my $height = $self->_get_number('height'); 129 130 my $graph_width = $chart_box[2] - $chart_box[0]; 131 my $graph_height = $chart_box[3] - $chart_box[1]; 132 133 my $col_height = ($graph_height - 1) / $column_count; 134 if ($col_height > 1) { 135 $graph_height = int($col_height) * $column_count + 1; 136 } 137 else { 138 $graph_height = $col_height * $column_count + 1; 139 } 140 141 my $tic_count = $self->_get_x_tics(); 142 my $tic_distance = int(($graph_width -1) / ($tic_count - 1)); 143 $graph_width = $tic_distance * ($tic_count - 1); 144 145 my $top = $chart_box[1]; 146 my $left = $chart_box[0]; 147 148 $self->{'_style'}{'graph_width'} = $graph_width; 149 $self->{'_style'}{'graph_height'} = $graph_height; 150 151 my @graph_box = ($left, $top, $left + $graph_width, $top + $graph_height); 152 153 $self->_set_graph_box(\@graph_box); 154 155 my @fill_box = @graph_box; 156 157 if ($self->_feature_enabled("graph_outline")) { 158 my @line = $self->_get_line("graph.outline") 159 or return; 160 161 $self->_box( 162 @line, 163 box => \@fill_box, 164 img => $img, 165 ); 166 ++$fill_box[0]; 167 ++$fill_box[1]; 168 --$fill_box[2]; 169 --$fill_box[3]; 170 } 171 172 { 173 my @back_fill = $self->_get_fill("graph.fill", \@fill_box) 174 or return; 175 $img->box( 176 @back_fill, 177 box => \@fill_box, 178 ); 179 } 180 181 my $min_value = $self->_get_min_value(); 182 my $max_value = $self->_get_max_value(); 183 my $value_range = $max_value - $min_value; 184 185 my $zero_position; 186 if ($value_range) { 187 $zero_position = $left + (-1*$min_value / $value_range) * ($graph_width-1); 188 } 189 190 if ($min_value < 0) { 191 my @neg_box = ( $left+1, $top+1, $zero_position, $top+$graph_height - 1 ); 192 my @neg_fill = $self->_get_fill('negative_bg', \@neg_box) 193 or return; 194 195 $img->box( 196 @neg_fill, 197 box => \@neg_box, 198 ); 199 $img->line( 200 x1 => $zero_position, 201 y1 => $top, 202 x2 => $zero_position, 203 y2 => $top + $graph_height, 204 color => $self->_get_color('outline.line'), 205 ); 206 } 207 208 $self->_reset_series_counter(); 209 210 if ($self->_get_data_series()->{'bar'}) { 211 $self->_draw_bars(); 212 } 213 if ($self->_get_data_series()->{'line'}) { 214 $self->_draw_lines(); 215 } 216 217 if ($self->_get_x_tics()) { 218 $self->_draw_x_tics(); 219 } 220 if ($self->_get_labels(\%opts)) { 221 $self->_draw_y_tics(\%opts); 222 } 223 224 return $self->_get_image(); 225} 226 227sub _get_data_range { 228 my $self = shift; 229 230 my $max_value = 0; 231 my $min_value = 0; 232 my $column_count = 0; 233 234 my ($b_min, $b_max, $b_cols) = $self->_get_bar_range(); 235 my ($l_min, $l_max, $l_cols) = $self->_get_line_range(); 236 237 $min_value = $self->_min(STARTING_MIN_VALUE, $b_min, $l_min); 238 $max_value = $self->_max(0, $b_max, $l_max); 239 $column_count = $self->_max(0, $b_cols, $l_cols); 240 241 my $config_min = $self->_get_number('x_min'); 242 my $config_max = $self->_get_number('x_max'); 243 244 if (defined $config_max && $config_max < $max_value) { 245 $config_max = undef; 246 } 247 if (defined $config_min && $config_min > $min_value) { 248 $config_min = undef; 249 } 250 251 my $range_padding = $self->_get_number('range_padding'); 252 if (defined $config_min) { 253 $min_value = $config_min; 254 } 255 else { 256 if ($min_value > 0) { 257 $min_value = 0; 258 } 259 if ($range_padding && $min_value < 0) { 260 my $difference = $min_value * $range_padding / 100; 261 if ($min_value < -1 && $difference > -1) { 262 $difference = -1; 263 } 264 $min_value += $difference; 265 } 266 } 267 if (defined $config_max) { 268 $max_value = $config_max; 269 } 270 else { 271 if ($range_padding && $max_value > 0) { 272 my $difference = $max_value * $range_padding / 100; 273 if ($max_value > 1 && $difference < 1) { 274 $difference = 1; 275 } 276 $max_value += $difference; 277 } 278 } 279 280 if ($self->_get_number('automatic_axis')) { 281 # In case this was set via a style, and not by the api method 282 eval { require Chart::Math::Axis; }; 283 if ($@) { 284 return $self->_error("Can't use automatic_axis - $@"); 285 } 286 287 my $axis = Chart::Math::Axis->new(); 288 $axis->include_zero(); 289 $axis->add_data($min_value, $max_value); 290 $max_value = $axis->top; 291 $min_value = $axis->bottom; 292 my $ticks = $axis->ticks; 293 # The +1 is there because we have the bottom tick as well 294 $self->set_x_tics($ticks+1); 295 } 296 297 $self->_set_max_value($max_value); 298 $self->_set_min_value($min_value); 299 $self->_set_column_count($column_count); 300 301 return 1; 302} 303 304sub _min { 305 my $self = shift; 306 my $min = shift; 307 308 foreach my $value (@_) { 309 next unless defined $value; 310 if ($value < $min) { $min = $value; } 311 } 312 return $min; 313} 314 315sub _max { 316 my $self = shift; 317 my $min = shift; 318 319 foreach my $value (@_) { 320 next unless defined $value; 321 if ($value > $min) { $min = $value; } 322 } 323 return $min; 324} 325 326sub _get_line_range { 327 my $self = shift; 328 my $series = $self->_get_data_series()->{'line'}; 329 return (undef, undef, 0) unless $series; 330 331 my $max_value = 0; 332 my $min_value = STARTING_MIN_VALUE; 333 my $column_count = 0; 334 335 my @series = @{$series}; 336 foreach my $series (@series) { 337 my @data = @{$series->{'data'}}; 338 339 if (scalar @data > $column_count) { 340 $column_count = scalar @data; 341 } 342 343 foreach my $value (@data) { 344 if ($value > $max_value) { $max_value = $value; } 345 if ($value < $min_value) { $min_value = $value; } 346 } 347 } 348 349 return ($min_value, $max_value, $column_count); 350} 351 352 353 354sub _get_bar_range { 355 my $self = shift; 356 357 my $series = $self->_get_data_series()->{'bar'}; 358 return (undef, undef, 0) unless $series; 359 360 my $max_value = 0; 361 my $min_value = STARTING_MIN_VALUE; 362 my $column_count = 0; 363 364 my @series = @{$series}; 365 foreach my $series (@series) { 366 my @data = @{$series->{'data'}}; 367 368 foreach my $value (@data) { 369 $column_count++; 370 if ($value > $max_value) { $max_value = $value; } 371 if ($value < $min_value) { $min_value = $value; } 372 } 373 } 374 375 return ($min_value, $max_value, $column_count); 376} 377 378 379sub _draw_legend { 380 my $self = shift; 381 my $chart_box = shift; 382 my $style = $self->{'_style'}; 383 384 my @labels; 385 my $img = $self->_get_image(); 386 if (my $series = $self->_get_data_series()->{'bar'}) { 387 push @labels, map { $_->{'series_name'} } @$series; 388 } 389 390 if ($style->{features}{legend} && (scalar @labels)) { 391 $self->SUPER::_draw_legend($self->_get_image(), \@labels, $chart_box) 392 or return; 393 } 394 return; 395} 396 397sub _draw_flat_legend { 398 return 1; 399} 400 401sub _draw_lines { 402 my $self = shift; 403 my $style = $self->{'_style'}; 404 405 my $img = $self->_get_image(); 406 407 my $max_value = $self->_get_max_value(); 408 my $min_value = $self->_get_min_value(); 409 my $column_count = $self->_get_column_count(); 410 411 my $value_range = $max_value - $min_value; 412 413 my $width = $self->_get_number('width'); 414 my $height = $self->_get_number('height'); 415 416 my $graph_width = $self->_get_number('graph_width'); 417 my $graph_height = $self->_get_number('graph_height'); 418 419 my $line_series = $self->_get_data_series()->{'line'}; 420 my $series_counter = $self->_get_series_counter() || 0; 421 422 my $has_columns = (defined $self->_get_data_series()->{'column'} || $self->_get_data_series->{'stacked_column'}) ? 1 : 0; 423 424 my $col_height = int($graph_height / $column_count) -1; 425 426 my $graph_box = $self->_get_graph_box(); 427 my $left = $graph_box->[0] + 1; 428 my $bottom = $graph_box->[1]; 429 430 my $zero_position = $left + $graph_width - (-1*$min_value / $value_range) * ($graph_width - 1); 431 432 my $line_aa = $self->_get_number("lineaa"); 433 foreach my $series (@$line_series) { 434 my @data = @{$series->{'data'}}; 435 my $data_size = scalar @data; 436 437 my $interval; 438 if ($has_columns) { 439 $interval = $graph_height / ($data_size); 440 } 441 else { 442 $interval = $graph_height / ($data_size - 1); 443 } 444 my $color = $self->_data_color($series_counter); 445 446 # We need to add these last, otherwise the next line segment will overwrite half of the marker 447 my @marker_positions; 448 for (my $i = 0; $i < $data_size - 1; $i++) { 449 my $y1 = $bottom + $i * $interval; 450 my $y2 = $bottom + ($i + 1) * $interval; 451 452 $y1 += $has_columns * $interval / 2; 453 $y2 += $has_columns * $interval / 2; 454 455 my $x1 = $left + ($value_range - $data[$i] + $min_value)/$value_range * $graph_width; 456 my $x2 = $left + ($value_range - $data[$i + 1] + $min_value)/$value_range * $graph_width; 457 458 push @marker_positions, [$x1, $y1]; 459 $img->line(x1 => $x1, y1 => $y1, x2 => $x2, y2 => $y2, aa => $line_aa, color => $color) || die $img->errstr; 460 } 461 462 463 my $y2 = $bottom + ($data_size - 1) * $interval; 464 $y2 += $has_columns * $interval / 2; 465 466 my $x2 = $left + ($value_range - $data[$data_size - 1] + $min_value)/$value_range * $graph_width; 467 468 if ($self->_feature_enabled("linemarkers")) { 469 push @marker_positions, [$x2, $y2]; 470 foreach my $position (@marker_positions) { 471 $self->_draw_line_marker($position->[0], $position->[1], $series_counter); 472 } 473 } 474 $series_counter++; 475 } 476 477 $self->_set_series_counter($series_counter); 478 return; 479} 480 481sub _draw_bars { 482 my $self = shift; 483 my $style = $self->{'_style'}; 484 485 my $img = $self->_get_image(); 486 487 my $max_value = $self->_get_max_value(); 488 my $min_value = $self->_get_min_value(); 489 my $column_count = $self->_get_column_count(); 490 491 my $value_range = $max_value - $min_value; 492 493 my $width = $self->_get_number('width'); 494 my $height = $self->_get_number('height'); 495 496 my $graph_width = $self->_get_number('graph_width'); 497 my $graph_height = $self->_get_number('graph_height'); 498 499 500 my $graph_box = $self->_get_graph_box(); 501 my $bottom = $graph_box->[1] + 1; 502 my $left = $graph_box->[0]; 503 504 my $zero_position = int($left + (-1*$min_value / $value_range) * ($graph_width-1)); 505 506 my $bar_height = $graph_height / $column_count; 507 508 my $outline_color; 509 if ($style->{'features'}{'outline'}) { 510 $outline_color = $self->_get_color('outline.line'); 511 } 512 513 my $series_counter = $self->_get_series_counter() || 0; 514 my $col_series = $self->_get_data_series()->{'bar'}; 515 my $column_padding = $self->_get_number('column_padding') || 0; 516 517 # This tracks the series we're in relative to the starting series - this way colors stay accurate, but the columns don't start out too far to the right. 518 my $column_series = 0; 519 520 for (my $series_pos = 0; $series_pos < scalar @$col_series; $series_pos++) { 521 my $series = $col_series->[$series_pos]; 522 my @data = @{$series->{'data'}}; 523 my $data_size = scalar @data; 524 for (my $i = 0; $i < $data_size; $i++) { 525 526 my $part1 = $bar_height * (scalar @$col_series * $i); 527 my $part2 = ($series_pos) * $bar_height; 528 my $y1 = int($bottom + $part1 + $part2); 529 530 my $y2 = int($y1 + $bar_height - $column_padding)-1; 531 # Special case for when bar_height is less than 1. 532 if ($y2 < $y1) { 533 $y2 = $y1; 534 } 535 536 my $x1 = int($left - ($min_value - $data[$i]) / $value_range * $graph_width); 537 538 my $color = $self->_data_color($series_counter); 539 540 if ($data[$i] > 0) { 541 my @fill = $self->_data_fill($series_counter, [$zero_position+1, $y1, $x1, $y2]); 542 $img->box(xmax => $x1, xmin => $zero_position+1, ymin => $y1, ymax => $y2, @fill); 543 if ($style->{'features'}{'outline'}) { 544 $img->box(xmax => $x1, xmin => $zero_position, ymin => $y1, ymax => $y2, color => $outline_color); 545 } 546 } 547 elsif ($data[$i] == 0) { 548 } 549 else { 550 my @fill = $self->_data_fill($series_counter, [$x1, $y1, $zero_position, $y2]); 551 $img->box(xmax => $zero_position , xmin => $x1, ymin => $y1, ymax => $y2, @fill); 552 if ($style->{'features'}{'outline'}) { 553 $img->box(xmax => $zero_position, xmin => $x1, ymin => $y1, ymax => $y2, color => $outline_color); 554 } 555 } 556 } 557 558 $series_counter++; 559 $column_series++; 560 } 561 $self->_set_series_counter($series_counter); 562 return; 563} 564 565sub _add_data_series { 566 my $self = shift; 567 my $series_type = shift; 568 my $data_ref = shift; 569 my $series_name = shift; 570 571 my $graph_data = $self->{'graph_data'} || {}; 572 573 my $series = $graph_data->{$series_type} || []; 574 575 push @$series, { data => $data_ref, series_name => $series_name }; 576 577 $graph_data->{$series_type} = $series; 578 579 $self->{'graph_data'} = $graph_data; 580 return; 581} 582 583=over 584 585=item show_vertical_gridlines() 586 587Shows vertical gridlines at the y-tics. 588 589Feature: vertical_gridlines 590 591=cut 592 593sub show_vertical_gridlines { 594 $_[0]->{'custom_style'}{features}{'vertical_gridlines'} = 1; 595} 596 597=item set_vertical_gridline_style(color => ..., style => ...) 598 599Set the color and style of the lines drawn for gridlines. 600 601Style equivalent: vgrid 602 603=cut 604 605sub set_vertical_gridline_style { 606 my ($self, %opts) = @_; 607 608 $self->{custom_style}{vgrid} ||= {}; 609 @{$self->{custom_style}{vgrid}}{keys %opts} = values %opts; 610 611 return 1; 612} 613 614=item show_line_markers() 615 616=item show_line_markers($value) 617 618Feature: linemarkers. 619 620If $value is missing or true, draw markers on a line data series. 621 622Note: line markers are drawn by default. 623 624=cut 625 626sub show_line_markers { 627 my ($self, $value) = @_; 628 629 @_ > 1 or $value = 1; 630 631 $self->{custom_style}{features}{linemarkers} = $value; 632 633 return 1; 634} 635 636=item use_automatic_axis() 637 638Automatically scale the Y axis, based on L<Chart::Math::Axis>. If Chart::Math::Axis isn't installed, this sets an error and returns undef. Returns 1 if it is installed. 639 640=cut 641 642sub use_automatic_axis { 643 eval { require Chart::Math::Axis; }; 644 if ($@) { 645 return $_[0]->_error("use_automatic_axis - $@\nCalled from ".join(' ', caller)."\n"); 646 } 647 $_[0]->{'custom_style'}->{'automatic_axis'} = 1; 648 return 1; 649} 650 651 652=item set_x_tics($count) 653 654Set the number of X tics to use. Their value and position will be determined by the data range. 655 656=cut 657 658sub set_x_tics { 659 $_[0]->{'x_tics'} = $_[1]; 660} 661 662sub _get_x_tics { 663 return $_[0]->{'x_tics'} || 0; 664} 665 666sub _remove_tics_from_chart_box { 667 my ($self, $chart_box, $opts) = @_; 668 669 # XXX - bad default 670 my $tic_width = $self->_get_y_tic_width($opts) || 10; 671 my @y_tic_box = ($chart_box->[0], $chart_box->[1], $chart_box->[0] + $tic_width, $chart_box->[3]); 672 673 # XXX - bad default 674 my $tic_height = $self->_get_x_tic_height() || 10; 675 my @x_tic_box = ($chart_box->[0], $chart_box->[3] - $tic_height, $chart_box->[2], $chart_box->[3]); 676 677 $self->_remove_box($chart_box, \@y_tic_box); 678 $self->_remove_box($chart_box, \@x_tic_box); 679 680 # If there's no title, the y-tics will be part off-screen. Half of the x-tic height should be more than sufficient. 681 my @y_tic_tops = ($chart_box->[0], $chart_box->[1], $chart_box->[2], $chart_box->[1] + int($tic_height / 2)); 682 $self->_remove_box($chart_box, \@y_tic_tops); 683 684 if (my @box = $self->_text_bbox($self->_get_max_value(), 'legend')) { 685 my @remove_box = ($chart_box->[2] - int($box[2] / 2) - 1, 686 $chart_box->[1], 687 $chart_box->[2], 688 $chart_box->[3] 689 ); 690 691 $self->_remove_box($chart_box, \@remove_box); 692 } 693 694 695} 696 697sub _get_y_tic_width { 698 my ($self, $opts) = @_; 699 700 my $labels = $self->_get_labels($opts); 701 702 if (!$labels) { 703 return; 704 } 705 706 my %text_info = $self->_text_style('legend') 707 or return; 708 709 my $max_width = 0; 710 foreach my $label (@$labels) { 711 my @box = $self->_text_bbox($label, 'legend'); 712 my $width = $box[2] + 5; 713 # For the tic itself... 714 $width += 10; 715 if ($width > $max_width) { 716 $max_width = $width; 717 } 718 } 719 return $max_width; 720} 721 722sub _get_x_tic_height { 723 my $self = shift; 724 725 my $min = $self->_get_min_value(); 726 my $max = $self->_get_max_value(); 727 my $tic_count = $self->_get_x_tics(); 728 729 my $interval = ($max - $min) / ($tic_count - 1); 730 731 my %text_info = $self->_text_style('legend') 732 or return; 733 734 my $max_height = 0; 735 for my $count (0 .. $tic_count - 1) { 736 my $value = sprintf("%.2f", ($count*$interval)+$min); 737 738 my @box = $self->_text_bbox($value, 'legend'); 739 my $height = $box[3] - $box[1]; 740 741 # For the tic width 742 $height += 10; 743 if ($height > $max_height) { 744 $max_height = $height; 745 } 746 } 747 748 749 return $max_height; 750} 751 752sub _draw_y_tics { 753 my ($self, $opts) = @_; 754 755 my $img = $self->_get_image(); 756 my $graph_box = $self->_get_graph_box(); 757 my $image_box = $self->_get_image_box(); 758 759 my $labels = $self->_get_labels($opts); 760 761 my $tic_count = (scalar @$labels) - 1; 762 763 my $has_columns = defined $self->_get_data_series()->{'bar'}; 764 765 # If we have columns, we want the x-ticks to show up in the middle of the column, not on the left edge 766 my $denominator = $tic_count; 767 if ($has_columns) { 768 $denominator ++; 769 } 770 my $tic_distance = ($graph_box->[3] - $graph_box->[1]) / ($denominator); 771 my %text_info = $self->_text_style('legend') 772 or return; 773 774 for my $count (0 .. $tic_count) { 775 my $label = $labels->[$count]; 776 777 my $x1 = $graph_box->[0] - 5; 778 my $x2 = $graph_box->[0] + 5; 779 780 my $y1 = $graph_box->[1] + ($tic_distance * $count); 781 782 if ($has_columns) { 783 $y1 += $tic_distance / 2; 784 } 785 786 $img->line(x1 => $x1, x2 => $x2, y1 => $y1, y2 => $y1, aa => 1, color => '000000'); 787 788 my @box = $self->_text_bbox($label, 'legend') 789 or return; 790 791 my $width = $box[2]; 792 my $height = $box[3]; 793 794 $img->string(%text_info, 795 x => ($x1 - ($width + 5)), 796 y => ($y1 + ($height / 2)), 797 text => $label 798 ); 799 800 } 801 802} 803 804sub _draw_x_tics { 805 my $self = shift; 806 807 my $img = $self->_get_image(); 808 my $graph_box = $self->_get_graph_box(); 809 my $image_box = $self->_get_image_box(); 810 811 my $tic_count = $self->_get_x_tics(); 812 my $min = $self->_get_min_value(); 813 my $max = $self->_get_max_value(); 814 my $interval = ($max - $min) / ($tic_count - 1); 815 816 # If we have columns, we want the x-ticks to show up in the middle of the column, not on the left edge 817 my $tic_distance = ($graph_box->[2] - $graph_box->[0]) / ($tic_count -1); 818 819 my %text_info = $self->_text_style('legend') 820 or return; 821 822 my $show_gridlines = $self->{_style}{features}{'vertical_gridlines'}; 823 my @grid_line = $self->_get_line("vgrid"); 824 for my $count (0 .. $tic_count-1) { 825 my $x1 = $graph_box->[0] + ($tic_distance * $count); 826 827 my $y1 = $graph_box->[3] + 5; 828 my $y2 = $graph_box->[3] - 5; 829 830 my $value = ($count*$interval)+$min; 831 832 $img->line(x1 => $x1, x2 => $x1, y1 => $y1, y2 => $y2, aa => 1, color => '000000'); 833 834 my @box = $self->_text_bbox($value, 'legend') 835 or return; 836 837 my $width = $box[2]; 838 my $height = $box[3]; 839 840 $img->string(%text_info, 841 x => ($x1 - ($width / 2)), 842 y => ($y1 + $height + 5), 843 text => $value 844 ); 845 846 if ($show_gridlines && $x1 != $graph_box->[0] && $x1 != $graph_box->[2]) { 847 $self->_line(x1 => $x1, x2 => $x1, 848 y1 => $graph_box->[1], y2 => $graph_box->[3], 849 img => $img, 850 @grid_line); 851 } 852 } 853} 854 855sub _valid_input { 856 my $self = shift; 857 858 if (!defined $self->_get_data_series() || !keys %{$self->_get_data_series()}) { 859 return $self->_error("No data supplied"); 860 } 861 862 my $data = $self->_get_data_series(); 863 if (defined $data->{'line'} && !scalar @{$data->{'line'}->[0]->{'data'}}) { 864 return $self->_error("No values in data series"); 865 } 866 if (defined $data->{'column'} && !scalar @{$data->{'column'}->[0]->{'data'}}) { 867 return $self->_error("No values in data series"); 868 } 869 if (defined $data->{'stacked_column'} && !scalar @{$data->{'stacked_column'}->[0]->{'data'}}) { 870 return $self->_error("No values in data series"); 871 } 872 873 return 1; 874} 875 876sub _set_column_count { $_[0]->{'column_count'} = $_[1]; } 877sub _set_min_value { $_[0]->{'min_value'} = $_[1]; } 878sub _set_max_value { $_[0]->{'max_value'} = $_[1]; } 879sub _set_image_box { $_[0]->{'image_box'} = $_[1]; } 880sub _set_graph_box { $_[0]->{'graph_box'} = $_[1]; } 881sub _set_series_counter { $_[0]->{'series_counter'} = $_[1]; } 882sub _get_column_count { return $_[0]->{'column_count'} } 883sub _get_min_value { return $_[0]->{'min_value'} } 884sub _get_max_value { return $_[0]->{'max_value'} } 885sub _get_image_box { return $_[0]->{'image_box'} } 886sub _get_graph_box { return $_[0]->{'graph_box'} } 887sub _reset_series_counter { $_[0]->{series_counter} = 0 } 888sub _get_series_counter { return $_[0]->{'series_counter'} } 889 890sub _style_defs { 891 my ($self) = @_; 892 893 my %work = %{$self->SUPER::_style_defs()}; 894 push @{$work{features}}, qw/graph_outline graph_fill linemarkers/; 895 $work{vgrid} = 896 { 897 color => "lookup(fg)", 898 style => "solid", 899 }; 900 901 return \%work; 902} 903 904sub _composite { 905 my ($self) = @_; 906 return ( $self->SUPER::_composite(), "graph", "vgrid" ); 907} 908 9091; 910 911