File Coverage

blib/lib/Imager/Graph/Vertical.pm
Criterion Covered Total %
statement 593 687 86.3
branch 154 222 69.3
condition 47 78 60.2
subroutine 47 62 75.8
pod 20 20 100.0
total 861 1069 80.5


line stmt bran cond sub pod time code
1             package Imager::Graph::Vertical;
2              
3             =head1 NAME
4              
5             Imager::Graph::Vertical- A super class for line/bar/column/area charts
6              
7             =head1 SYNOPSIS
8              
9             use Imager::Graph::Vertical;
10              
11             my $vert = Imager::Graph::Vertical->new;
12             $vert->add_column_data_series(\@data, "My data");
13             $vert->add_area_data_series(\@data2, "Area data");
14             $vert->add_stacked_column_data_series(\@data3, "stacked data");
15             $vert->add_line_data_series(\@data4, "line data");
16             my $img = $vert->draw();
17              
18             use Imager::Graph::Column;
19             my $column = Imager::Graph::Column->new;
20             $column->add_data_series(\@data, "my data");
21             my $img = $column->draw();
22              
23             =head1 DESCRIPTION
24              
25             This is a base class that implements the functionality for column,
26             stacked column, line and area charts where the dependent variable is
27             represented in changes in the vertical position.
28              
29             The subclasses, L,
30             L, L and
31             L simply provide default data series types.
32              
33             =head1 METHODS
34              
35             =cut
36              
37 8     8   36 use strict;
  8         15  
  8         243  
38 8     8   38 use vars qw(@ISA);
  8         11  
  8         253  
39 8     8   7366 use Imager::Graph;
  8         28  
  8         3798  
40             @ISA = qw(Imager::Graph);
41 8     8   12325 use Imager::Fill;
  8         19370  
  8         373  
42              
43             our $VERSION = "0.11";
44              
45 8     8   55 use constant STARTING_MIN_VALUE => 99999;
  8         15  
  8         80203  
46              
47             =over
48              
49             =item add_data_series(\@data, $series_name)
50              
51             Add a data series to the graph, of the default type. This requires
52             that the graph object be one of the derived graph classes.
53              
54             =cut
55              
56             sub add_data_series {
57 16     16 1 5538 my $self = shift;
58 16         59 my $data_ref = shift;
59 16         31 my $series_name = shift;
60              
61 16         136 my $series_type = $self->_get_default_series_type();
62 16         101 $self->_add_data_series($series_type, $data_ref, $series_name);
63              
64 16         32 return;
65             }
66              
67             =item add_column_data_series(\@data, $series_name)
68              
69             Add a column data series to the graph.
70              
71             =cut
72              
73             sub add_column_data_series {
74 0     0 1 0 my $self = shift;
75 0         0 my $data_ref = shift;
76 0         0 my $series_name = shift;
77              
78 0         0 $self->_add_data_series('column', $data_ref, $series_name);
79              
80 0         0 return;
81             }
82              
83             =item add_stacked_column_data_series(\@data, $series_name)
84              
85             Add a stacked column data series to the graph.
86              
87             =cut
88              
89             sub add_stacked_column_data_series {
90 0     0 1 0 my $self = shift;
91 0         0 my $data_ref = shift;
92 0         0 my $series_name = shift;
93              
94 0         0 $self->_add_data_series('stacked_column', $data_ref, $series_name);
95              
96 0         0 return;
97             }
98              
99             =item add_line_data_series(\@data, $series_name)
100              
101             Add a line data series to the graph.
102              
103             =cut
104              
105             sub add_line_data_series {
106 0     0 1 0 my $self = shift;
107 0         0 my $data_ref = shift;
108 0         0 my $series_name = shift;
109              
110 0         0 $self->_add_data_series('line', $data_ref, $series_name);
111              
112 0         0 return;
113             }
114              
115             =item add_area_data_series(\@data, $series_name)
116              
117             Add a area data series to the graph.
118              
119             =cut
120              
121             sub add_area_data_series {
122 0     0 1 0 my $self = shift;
123 0         0 my $data_ref = shift;
124 0         0 my $series_name = shift;
125              
126 0         0 $self->_add_data_series('area', $data_ref, $series_name);
127              
128 0         0 return;
129             }
130              
131             =item set_y_max($value)
132              
133             Sets the maximum y value to be displayed. This will be ignored if the
134             y_max is lower than the highest value.
135              
136             =cut
137              
138             sub set_y_max {
139 0     0 1 0 $_[0]->{'custom_style'}->{'y_max'} = $_[1];
140             }
141              
142             =item set_y_min($value)
143              
144             Sets the minimum y value to be displayed. This will be ignored if the
145             y_min is higher than the lowest value.
146              
147             =cut
148              
149             sub set_y_min {
150 0     0 1 0 $_[0]->{'custom_style'}->{'y_min'} = $_[1];
151             }
152              
153             =item set_column_padding($int)
154              
155             Sets the padding between columns. This is a percentage of the column
156             width. Defaults to 0.
157              
158             =cut
159              
160             sub set_column_padding {
161 0     0 1 0 $_[0]->{'custom_style'}->{'column_padding'} = $_[1];
162             }
163              
164             =item set_range_padding($percentage)
165              
166             Sets the padding to be used, as a percentage. For example, if your
167             data ranges from 0 to 10, and you have a 20 percent padding, the y
168             axis will go to 12.
169              
170             Defaults to 10. This attribute is ignored for positive numbers if
171             set_y_max() has been called, and ignored for negative numbers if
172             set_y_min() has been called.
173              
174             =cut
175              
176             sub set_range_padding {
177 0     0 1 0 $_[0]->{'custom_style'}->{'range_padding'} = $_[1];
178             }
179              
180             =item set_negative_background($color)
181              
182             Sets the background color or fill used below the x axis.
183              
184             =cut
185              
186             sub set_negative_background {
187 0     0 1 0 $_[0]->{'custom_style'}->{'negative_bg'} = $_[1];
188             }
189              
190             =item draw()
191              
192             Draw the graph
193              
194             =cut
195              
196             sub draw {
197 12     12 1 245 my ($self, %opts) = @_;
198              
199 12 50       118 if (!$self->_valid_input()) {
200 0         0 return;
201             }
202              
203 12         114 $self->_style_setup(\%opts);
204              
205 12         27 my $style = $self->{_style};
206              
207 12 50       107 $self->_make_img
208             or return;
209              
210 12 50       295 my $img = $self->_get_image()
211             or return;
212              
213 12         70 my @image_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 );
214 12         464 $self->_set_image_box(\@image_box);
215              
216 12         44 my @chart_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 );
217 12         331 $self->_draw_legend(\@chart_box);
218 12 100       82 if ($style->{title}{text}) {
219 1 50       8 $self->_draw_title($img, \@chart_box)
220             or return;
221             }
222              
223             # Scale the graph box down to the widest graph that can cleanly hold the # of columns.
224 12 50       86 return unless $self->_get_data_range();
225 12         97 $self->_remove_tics_from_chart_box(\@chart_box, \%opts);
226 12         145 my $column_count = $self->_get_column_count();
227              
228 12         62 my $width = $self->_get_number('width');
229 12         44 my $height = $self->_get_number('height');
230              
231 12         28 my $graph_width = $chart_box[2] - $chart_box[0];
232 12         23 my $graph_height = $chart_box[3] - $chart_box[1];
233              
234 12         31 my $col_width = ($graph_width - 1) / $column_count;
235 12 100       44 if ($col_width > 1) {
236 10         26 $graph_width = int($col_width) * $column_count + 1;
237             }
238             else {
239 2         6 $graph_width = $col_width * $column_count + 1;
240             }
241              
242 12         36 my $tic_count = $self->_get_y_tics();
243 12         34 my $tic_distance = ($graph_height-1) / ($tic_count - 1);
244 12         32 $graph_height = int($tic_distance * ($tic_count - 1));
245              
246 12         18 my $top = $chart_box[1];
247 12         25 my $left = $chart_box[0];
248              
249 12         34 $self->{'_style'}{'graph_width'} = $graph_width;
250 12         31 $self->{'_style'}{'graph_height'} = $graph_height;
251              
252 12         33 my @graph_box = ($left, $top, $left + $graph_width, $top + $graph_height);
253 12         179 $self->_set_graph_box(\@graph_box);
254              
255 12         33 my @fill_box = ( $left, $top, $left+$graph_width, $top+$graph_height );
256 12 50       102 if ($self->_feature_enabled("graph_outline")) {
257 12 50       127 my @line = $self->_get_line("graph.outline")
258             or return;
259              
260 12         182 $self->_box(
261             @line,
262             box => \@fill_box,
263             img => $img,
264             );
265 12         1409 ++$fill_box[0];
266 12         22 ++$fill_box[1];
267 12         17 --$fill_box[2];
268 12         79 --$fill_box[3];
269             }
270              
271             $img->box(
272 12         59 $self->_get_fill('graph.fill'),
273             box => \@fill_box,
274             );
275              
276 12         9849 my $min_value = $self->_get_min_value();
277 12         33 my $max_value = $self->_get_max_value();
278 12         116 my $value_range = $max_value - $min_value;
279              
280 12         18 my $zero_position;
281 12 50       41 if ($value_range) {
282 12         47 $zero_position = $top + $graph_height - (-1*$min_value / $value_range) * ($graph_height-1);
283             }
284              
285 12 100       38 if ($min_value < 0) {
286 3         12 my @neg_box = ( $left + 1, $zero_position, $left+$graph_width- 1, $top+$graph_height - 1 );
287 3 50       12 my @neg_fill = $self->_get_fill('negative_bg', \@neg_box)
288             or return;
289 3         56 $img->box(
290             @neg_fill,
291             box => \@neg_box,
292             );
293 3         2035 $img->line(
294             x1 => $left+1,
295             y1 => $zero_position,
296             x2 => $left + $graph_width,
297             y2 => $zero_position,
298             color => $self->_get_color('outline.line'),
299             );
300             }
301              
302 12         314 $self->_reset_series_counter();
303              
304 12 100       50 if ($self->_get_data_series()->{'stacked_column'}) {
305 2 50       11 return unless $self->_draw_stacked_columns();
306             }
307 12 100       50 if ($self->_get_data_series()->{'column'}) {
308 3 50       16 return unless $self->_draw_columns();
309             }
310 12 100       56 if ($self->_get_data_series()->{'line'}) {
311 5 50       44 return unless $self->_draw_lines();
312             }
313 12 100       50 if ($self->_get_data_series()->{'area'}) {
314 2 50       11 return unless $self->_draw_area();
315             }
316              
317 12 100       47 if ($self->_get_y_tics()) {
318 2         32 $self->_draw_y_tics();
319             }
320 12 50       69 if ($self->_get_labels(\%opts)) {
321 12         95 $self->_draw_x_tics(\%opts);
322             }
323              
324 12         697 return $self->_get_image();
325             }
326              
327             sub _get_data_range {
328 12     12   23 my $self = shift;
329              
330 12         27 my $max_value = 0;
331 12         21 my $min_value = 0;
332 12         24 my $column_count = 0;
333              
334 12         84 my ($sc_min, $sc_max, $sc_cols) = $self->_get_stacked_column_range();
335 12         86 my ($c_min, $c_max, $c_cols) = $self->_get_column_range();
336 12         81 my ($l_min, $l_max, $l_cols) = $self->_get_line_range();
337 12         86 my ($a_min, $a_max, $a_cols) = $self->_get_area_range();
338              
339             # These are side by side...
340 12         25 $sc_cols += $c_cols;
341              
342 12         94 $min_value = $self->_min(STARTING_MIN_VALUE, $sc_min, $c_min, $l_min, $a_min);
343 12         78 $max_value = $self->_max(0, $sc_max, $c_max, $l_max, $a_max);
344              
345 12         54 my $config_min = $self->_get_number('y_min');
346 12         59 my $config_max = $self->_get_number('y_max');
347              
348 12 50 33     75 if (defined $config_max && $config_max < $max_value) {
349 0         0 $config_max = undef;
350             }
351 12 50 33     360 if (defined $config_min && $config_min > $min_value) {
352 0         0 $config_min = undef;
353             }
354              
355 12         48 my $range_padding = $self->_get_number('range_padding');
356 12 50       43 if (defined $config_min) {
357 0         0 $min_value = $config_min;
358             }
359             else {
360 12 100       41 if ($min_value > 0) {
361 9         24 $min_value = 0;
362             }
363 12 50 33     50 if ($range_padding && $min_value < 0) {
364 0         0 my $difference = $min_value * $range_padding / 100;
365 0 0 0     0 if ($min_value < -1 && $difference > -1) {
366 0         0 $difference = -1;
367             }
368 0         0 $min_value += $difference;
369             }
370             }
371 12 50       46 if (defined $config_max) {
372 0         0 $max_value = $config_max;
373             }
374             else {
375 12 50 33     54 if ($range_padding && $max_value > 0) {
376 0         0 my $difference = $max_value * $range_padding / 100;
377 0 0 0     0 if ($max_value > 1 && $difference < 1) {
378 0         0 $difference = 1;
379             }
380 0         0 $max_value += $difference;
381             }
382             }
383 12         38 $column_count = $self->_max(0, $sc_cols, $l_cols, $a_cols);
384              
385 12 50       44 if ($self->_get_number('automatic_axis')) {
386             # In case this was set via a style, and not by the api method
387 0         0 eval { require Chart::Math::Axis; };
  0         0  
388 0 0       0 if ($@) {
389 0         0 return $self->_error("Can't use automatic_axis - $@");
390             }
391              
392 0         0 my $axis = Chart::Math::Axis->new();
393 0         0 $axis->include_zero();
394 0         0 $axis->add_data($min_value, $max_value);
395 0         0 $max_value = $axis->top;
396 0         0 $min_value = $axis->bottom;
397 0         0 my $ticks = $axis->ticks;
398             # The +1 is there because we have the bottom tick as well
399 0         0 $self->set_y_tics($ticks+1);
400             }
401              
402 12         79 $self->_set_max_value($max_value);
403 12         111 $self->_set_min_value($min_value);
404 12         64 $self->_set_column_count($column_count);
405              
406 12         71 return 1;
407             }
408              
409             sub _min {
410 12     12   42 my $self = shift;
411 12         24 my $min = shift;
412              
413 12         32 foreach my $value (@_) {
414 48 100       125 next unless defined $value;
415 12 50       54 if ($value < $min) { $min = $value; }
  12         35  
416             }
417 12         29 return $min;
418             }
419              
420             sub _max {
421 24     24   39 my $self = shift;
422 24         48 my $min = shift;
423              
424 24         45 foreach my $value (@_) {
425 84 100       172 next unless defined $value;
426 48 100       144 if ($value > $min) { $min = $value; }
  24         49  
427             }
428 24         59 return $min;
429             }
430              
431             sub _get_line_range {
432 12     12   20 my $self = shift;
433 12         46 my $series = $self->_get_data_series()->{'line'};
434 12 100       50 return (undef, undef, 0) unless $series;
435              
436 5         12 my $max_value = 0;
437 5         9 my $min_value = STARTING_MIN_VALUE;
438 5         10 my $column_count = 0;
439              
440 5         8 my @series = @{$series};
  5         14  
441 5         17 foreach my $series (@series) {
442 5         9 my @data = @{$series->{'data'}};
  5         231  
443              
444 5 50       25 if (scalar @data > $column_count) {
445 5         8 $column_count = scalar @data;
446             }
447              
448 5         12 foreach my $value (@data) {
449 2016 100       3089 if ($value > $max_value) { $max_value = $value; }
  2011         1835  
450 2016 100       3134 if ($value < $min_value) { $min_value = $value; }
  10         20  
451             }
452             }
453              
454 5         24 return ($min_value, $max_value, $column_count);
455             }
456              
457             sub _get_area_range {
458 12     12   24 my $self = shift;
459 12         40 my $series = $self->_get_data_series()->{'area'};
460 12 100       59 return (undef, undef, 0) unless $series;
461              
462 2         2 my $max_value = 0;
463 2         4 my $min_value = STARTING_MIN_VALUE;
464 2         12 my $column_count = 0;
465              
466 2         3 my @series = @{$series};
  2         6  
467 2         5 foreach my $series (@series) {
468 3         3 my @data = @{$series->{'data'}};
  3         10  
469              
470 3 100       7 if (scalar @data > $column_count) {
471 2         3 $column_count = scalar @data;
472             }
473              
474 3         4 foreach my $value (@data) {
475 21 100       41 if ($value > $max_value) { $max_value = $value; }
  5         5  
476 21 100       37 if ($value < $min_value) { $min_value = $value; }
  12         18  
477             }
478             }
479              
480 2         7 return ($min_value, $max_value, $column_count);
481             }
482              
483              
484             sub _get_column_range {
485 12     12   22 my $self = shift;
486              
487 12         49 my $series = $self->_get_data_series()->{'column'};
488 12 100       61 return (undef, undef, 0) unless $series;
489              
490 3         4 my $max_value = 0;
491 3         4 my $min_value = STARTING_MIN_VALUE;
492 3         5 my $column_count = 0;
493              
494 3         5 my @series = @{$series};
  3         28  
495 3         7 foreach my $series (@series) {
496 5         9 my @data = @{$series->{'data'}};
  5         17  
497              
498 5         10 foreach my $value (@data) {
499 35         33 $column_count++;
500 35 100       68 if ($value > $max_value) { $max_value = $value; }
  6         7  
501 35 100       90 if ($value < $min_value) { $min_value = $value; }
  20         35  
502             }
503             }
504              
505 3         12 return ($min_value, $max_value, $column_count);
506             }
507              
508             sub _get_stacked_column_range {
509 12     12   21 my $self = shift;
510              
511 12         52 my $max_value = 0;
512 12         23 my $min_value = STARTING_MIN_VALUE;
513 12         17 my $column_count = 0;
514              
515 12 100       65 return (undef, undef, 0) unless $self->_get_data_series()->{'stacked_column'};
516 2         4 my @series = @{$self->_get_data_series()->{'stacked_column'}};
  2         6  
517              
518 2         4 my @max_entries;
519             my @min_entries;
520 2         10 for (my $i = scalar @series - 1; $i >= 0; $i--) {
521 3         5 my $series = $series[$i];
522 3         7 my $data = $series->{'data'};
523              
524 3         9 for (my $i = 0; $i < scalar @$data; $i++) {
525 21         16 my $value = 0;
526 21 100       49 if ($data->[$i] > 0) {
    50          
527 18   100     100 $value = $data->[$i] + ($max_entries[$i] || 0);
528 18         18 $data->[$i] = $value;
529 18         33 $max_entries[$i] = $value;
530             }
531             elsif ($data->[$i] < 0) {
532 3   50     15 $value = $data->[$i] + ($min_entries[$i] || 0);
533 3         4 $data->[$i] = $value;
534 3         6 $min_entries[$i] = $value;
535             }
536 21 100       33 if ($value > $max_value) { $max_value = $value; }
  6         7  
537 21 100       76 if ($value < $min_value) { $min_value = $value; }
  7         48  
538             }
539 3 100       9 if (scalar @$data > $column_count) {
540 2         6 $column_count = scalar @$data;
541             }
542             }
543              
544 2         8 return ($min_value, $max_value, $column_count);
545             }
546              
547             sub _draw_legend {
548 12     12   22 my $self = shift;
549 12         23 my $chart_box = shift;
550 12         28 my $style = $self->{'_style'};
551              
552 12         22 my @labels;
553 12         39 my $img = $self->_get_image();
554 12 100       61 if (my $series = $self->_get_data_series()->{'stacked_column'}) {
555 2         6 push @labels, map { $_->{'series_name'} } @$series;
  3         12  
556             }
557 12 100       40 if (my $series = $self->_get_data_series()->{'column'}) {
558 3         8 push @labels, map { $_->{'series_name'} } @$series;
  5         14  
559             }
560 12 100       45 if (my $series = $self->_get_data_series()->{'line'}) {
561 5         24 push @labels, map { $_->{'series_name'} } @$series;
  5         30  
562             }
563 12 100       42 if (my $series = $self->_get_data_series()->{'area'}) {
564 2         4 push @labels, map { $_->{'series_name'} } @$series;
  3         9  
565             }
566              
567 12 50 100     68 if ($style->{features}{legend} && (scalar @labels)) {
568 2 50       7 $self->SUPER::_draw_legend($self->_get_image(), \@labels, $chart_box)
569             or return;
570             }
571 12         34 return;
572             }
573              
574             sub _draw_flat_legend {
575 1     1   4 return 1;
576             }
577              
578             sub _draw_lines {
579 5     5   11 my $self = shift;
580 5         12 my $style = $self->{'_style'};
581              
582 5         23 my $img = $self->_get_image();
583              
584 5         22 my $max_value = $self->_get_max_value();
585 5         23 my $min_value = $self->_get_min_value();
586 5         16 my $column_count = $self->_get_column_count();
587              
588 5         11 my $value_range = $max_value - $min_value;
589              
590 5         19 my $width = $self->_get_number('width');
591 5         18 my $height = $self->_get_number('height');
592              
593 5         41 my $graph_width = $self->_get_number('graph_width');
594 5         21 my $graph_height = $self->_get_number('graph_height');
595              
596 5         20 my $line_series = $self->_get_data_series()->{'line'};
597 5   50     39 my $series_counter = $self->_get_series_counter() || 0;
598              
599 5 50 33     20 my $has_columns = (defined $self->_get_data_series()->{'column'} || $self->_get_data_series->{'stacked_column'}) ? 1 : 0;
600              
601 5         18 my $col_width = int($graph_width / $column_count) -1;
602              
603 5         43 my $graph_box = $self->_get_graph_box();
604 5         14 my $left = $graph_box->[0] + 1;
605 5         12 my $bottom = $graph_box->[1];
606              
607 5         17 my $zero_position = $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height - 1);
608              
609 5         18 my $line_aa = $self->_get_number("lineaa");
610 5         25 foreach my $series (@$line_series) {
611 5         9 my @data = @{$series->{'data'}};
  5         220  
612 5         16 my $data_size = scalar @data;
613              
614 5         22 my $interval;
615 5 50       145 if ($has_columns) {
616 0         0 $interval = $graph_width / ($data_size);
617             }
618             else {
619 5         16 $interval = $graph_width / ($data_size - 1);
620             }
621 5         42 my $color = $self->_data_color($series_counter);
622              
623             # We need to add these last, otherwise the next line segment will overwrite half of the marker
624 5         15 my @marker_positions;
625 5         27 for (my $i = 0; $i < $data_size - 1; $i++) {
626 2011         125173 my $x1 = $left + $i * $interval;
627 2011         2731 my $x2 = $left + ($i + 1) * $interval;
628              
629 2011         2886 $x1 += $has_columns * $interval / 2;
630 2011         2411 $x2 += $has_columns * $interval / 2;
631              
632 2011         3227 my $y1 = $bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height;
633 2011         2856 my $y2 = $bottom + ($value_range - $data[$i + 1] + $min_value)/$value_range * $graph_height;
634              
635 2011         4336 push @marker_positions, [$x1, $y1];
636 2011 50       5763 $img->line(x1 => $x1, y1 => $y1, x2 => $x2, y2 => $y2, aa => $line_aa, color => $color) || die $img->errstr;
637             }
638              
639 5         501 my $x2 = $left + ($data_size - 1) * $interval;
640 5         15 $x2 += $has_columns * $interval / 2;
641              
642 5         21 my $y2 = $bottom + ($value_range - $data[$data_size - 1] + $min_value)/$value_range * $graph_height;
643              
644 5 50       44 if ($self->_feature_enabled("linemarkers")) {
645 5         26 push @marker_positions, [$x2, $y2];
646 5         16 foreach my $position (@marker_positions) {
647 2016         504313 $self->_draw_line_marker($position->[0], $position->[1], $series_counter);
648             }
649             }
650 5         1602 $series_counter++;
651             }
652              
653 5         62 $self->_set_series_counter($series_counter);
654 5         31 return 1;
655             }
656              
657             sub _area_data_fill {
658 3     3   5 my ($self, $index, $box) = @_;
659              
660 3         10 my %fill = $self->_data_fill($index, $box);
661              
662 3         16 my $opacity = $self->_get_number("area.opacity");
663 3 50       9 $opacity == 1
664             and return %fill;
665              
666 3         5 my $orig_fill = $fill{fill};
667 3 50       6 unless ($orig_fill) {
668 0         0 $orig_fill = Imager::Fill->new
669             (
670             solid => $fill{color},
671             combine => "normal",
672             );
673             }
674             return
675             (
676 3         13 fill => Imager::Fill->new
677             (
678             type => "opacity",
679             other => $orig_fill,
680             opacity => $opacity,
681             ),
682             );
683             }
684              
685             sub _draw_area {
686 2     2   3 my $self = shift;
687 2         4 my $style = $self->{'_style'};
688              
689 2         7 my $img = $self->_get_image();
690              
691 2         6 my $max_value = $self->_get_max_value();
692 2         6 my $min_value = $self->_get_min_value();
693 2         8 my $column_count = $self->_get_column_count();
694              
695 2         3 my $value_range = $max_value - $min_value;
696              
697 2         15 my $width = $self->_get_number('width');
698 2         7 my $height = $self->_get_number('height');
699              
700 2         6 my $graph_width = $self->_get_number('graph_width');
701 2         7 my $graph_height = $self->_get_number('graph_height');
702              
703 2         6 my $area_series = $self->_get_data_series()->{'area'};
704 2   50     9 my $series_counter = $self->_get_series_counter() || 0;
705              
706 2         4 my $col_width = int($graph_width / $column_count) -1;
707              
708 2         9 my $graph_box = $self->_get_graph_box();
709 2         4 my $left = $graph_box->[0] + 1;
710 2         2 my $bottom = $graph_box->[1];
711 2         3 my $right = $graph_box->[2];
712 2         3 my $top = $graph_box->[3];
713              
714 2         10 my $zero_position = $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height - 1);
715              
716 2         6 my $line_aa = $self->_get_number("lineaa");
717 2         6 foreach my $series (@$area_series) {
718 3         3 my @data = @{$series->{'data'}};
  3         11  
719 3         4 my $data_size = scalar @data;
720              
721 3         5 my $interval = $graph_width / ($data_size - 1);
722              
723 3         13 my $color = $self->_data_color($series_counter);
724              
725             # We need to add these last, otherwise the next line segment will overwrite half of the marker
726 3         4 my @marker_positions;
727             my @polygon_points;
728 3         8 for (my $i = 0; $i < $data_size - 1; $i++) {
729 18         26 my $x1 = $left + $i * $interval;
730              
731 18         25 my $y1 = $bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height;
732              
733 18 100       27 if ($i == 0) {
734 3         7 push @polygon_points, [$x1, $top];
735             }
736 18         26 push @polygon_points, [$x1, $y1];
737              
738 18         42 push @marker_positions, [$x1, $y1];
739             }
740              
741 3         6 my $x2 = $left + ($data_size - 1) * $interval;
742              
743 3         6 my $y2 = $bottom + ($value_range - $data[$data_size - 1] + $min_value)/$value_range * $graph_height;
744 3         8 push @polygon_points, [$x2, $y2];
745 3         5 push @polygon_points, [$x2, $top];
746 3         4 push @polygon_points, $polygon_points[0];
747              
748 3         20 my @fill = $self->_area_data_fill($series_counter, [$left, $bottom, $right, $top]);
749 3         532 $img->polygon(points => [@polygon_points], @fill);
750              
751 3 100       6773 if ($self->_feature_enabled("areamarkers")) {
752 1         4 push @marker_positions, [$x2, $y2];
753 1         3 foreach my $position (@marker_positions) {
754 7         1203 $self->_draw_line_marker($position->[0], $position->[1], $series_counter);
755             }
756             }
757 3         209 $series_counter++;
758             }
759              
760 2         11 $self->_set_series_counter($series_counter);
761 2         8 return 1;
762             }
763              
764             sub _draw_columns {
765 3     3   5 my $self = shift;
766 3         6 my $style = $self->{'_style'};
767              
768 3         18 my $img = $self->_get_image();
769              
770 3         9 my $max_value = $self->_get_max_value();
771 3         7 my $min_value = $self->_get_min_value();
772 3         8 my $column_count = $self->_get_column_count();
773              
774 3         128 my $value_range = $max_value - $min_value;
775              
776 3         13 my $width = $self->_get_number('width');
777 3         34 my $height = $self->_get_number('height');
778              
779 3         7 my $graph_width = $self->_get_number('graph_width');
780 3         19 my $graph_height = $self->_get_number('graph_height');
781              
782              
783 3         17 my $graph_box = $self->_get_graph_box();
784 3         8 my $left = $graph_box->[0] + 1;
785 3         5 my $bottom = $graph_box->[1];
786 3         12 my $zero_position = int($bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height -1));
787              
788 3         5 my $bar_width = $graph_width / $column_count;
789              
790 3         4 my $outline_color;
791 3 100       12 if ($style->{'features'}{'outline'}) {
792 2         6 $outline_color = $self->_get_color('outline.line');
793             }
794              
795 3   50     14 my $series_counter = $self->_get_series_counter() || 0;
796 3         10 my $col_series = $self->_get_data_series()->{'column'};
797 3   50     35 my $column_padding_percent = $self->_get_number('column_padding') || 0;
798 3         10 my $column_padding = int($column_padding_percent * $bar_width / 100);
799              
800             # 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.
801 3         4 my $column_series = 0;
802              
803             # If there are stacked columns, non-stacked columns need to start one to the right of where they would otherwise
804 3 50       9 my $has_stacked_columns = (defined $self->_get_data_series()->{'stacked_column'} ? 1 : 0);
805              
806 3         14 for (my $series_pos = 0; $series_pos < scalar @$col_series; $series_pos++) {
807 5         15 my $series = $col_series->[$series_pos];
808 5         6 my @data = @{$series->{'data'}};
  5         76  
809 5         9 my $data_size = scalar @data;
810 5         14 for (my $i = 0; $i < $data_size; $i++) {
811 35         869 my $part1 = $bar_width * (scalar @$col_series * $i);
812 35         64 my $part2 = ($series_pos) * $bar_width;
813 35         102 my $x1 = $left + $part1 + $part2;
814 35 50       67 if ($has_stacked_columns) {
815 0         0 $x1 += ($bar_width * ($i+1));
816             }
817 35         43 $x1 = int($x1);
818              
819 35         56 my $x2 = int($x1 + $bar_width - $column_padding)-1;
820             # Special case for when bar_width is less than 1.
821 35 50       65 if ($x2 < $x1) {
822 0         0 $x2 = $x1;
823             }
824              
825 35         78 my $y1 = int($bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height);
826              
827 35         90 my $color = $self->_data_color($series_counter);
828              
829 35 100       97 if ($data[$i] > 0) {
830 29         120 my @fill = $self->_data_fill($series_counter, [$x1, $y1, $x2, $zero_position-1]);
831 29         123 $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position-1, @fill);
832 29 100       11831 if ($style->{'features'}{'outline'}) {
833 22         195 $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position, color => $outline_color);
834             }
835             }
836             else {
837 6         22 my @fill = $self->_data_fill($series_counter, [$x1, $zero_position+1, $x2, $y1]);
838 6         30 $img->box(xmin => $x1, xmax => $x2, ymin => $zero_position+1, ymax => $y1, @fill);
839 6 50       1255 if ($style->{'features'}{'outline'}) {
840 6         21 $img->box(xmin => $x1, xmax => $x2, ymin => $zero_position+1, ymax => $y1+1, color => $outline_color);
841             }
842             }
843             }
844              
845 5         129 $series_counter++;
846 5         17 $column_series++;
847             }
848 3         15 $self->_set_series_counter($series_counter);
849 3         18 return 1;
850             }
851              
852             sub _draw_stacked_columns {
853 2     2   3 my $self = shift;
854 2         5 my $style = $self->{'_style'};
855              
856 2         5 my $img = $self->_get_image();
857              
858 2         6 my $max_value = $self->_get_max_value();
859 2         6 my $min_value = $self->_get_min_value();
860 2         6 my $column_count = $self->_get_column_count();
861 2         4 my $value_range = $max_value - $min_value;
862              
863 2         18 my $graph_box = $self->_get_graph_box();
864 2         5 my $left = $graph_box->[0] + 1;
865 2         7 my $bottom = $graph_box->[1];
866              
867 2         7 my $graph_width = $self->_get_number('graph_width');
868 2         5 my $graph_height = $self->_get_number('graph_height');
869              
870 2         3 my $bar_width = $graph_width / $column_count;
871 2         3 my $column_series = 0;
872 2 50       13 if (my $column_series_data = $self->_get_data_series()->{'column'}) {
873 0         0 $column_series = (scalar @$column_series_data);
874             }
875 2         3 $column_series++;
876              
877 2   100     5 my $column_padding_percent = $self->_get_number('column_padding') || 0;
878 2 50       5 if ($column_padding_percent < 0) {
879 0         0 return $self->_error("Column padding less than 0");
880             }
881 2 50       6 if ($column_padding_percent > 100) {
882 0         0 return $self->_error("Column padding greater than 0");
883             }
884 2         5 my $column_padding = int($column_padding_percent * $bar_width / 100);
885              
886 2         2 my $outline_color;
887 2 100       5 if ($style->{'features'}{'outline'}) {
888 1         5 $outline_color = $self->_get_color('outline.line');
889             }
890              
891 2         11 my $zero_position = $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height -1);
892 2         11 my $col_series = $self->_get_data_series()->{'stacked_column'};
893 2   50     11 my $series_counter = $self->_get_series_counter() || 0;
894              
895 2         7 foreach my $series (@$col_series) {
896 3         3 my @data = @{$series->{'data'}};
  3         10  
897 3         4 my $data_size = scalar @data;
898 3         9 for (my $i = 0; $i < $data_size; $i++) {
899 21         358 my $part1 = $bar_width * $i * $column_series;
900 21         21 my $part2 = 0;
901 21         26 my $x1 = int($left + $part1 + $part2);
902 21         31 my $x2 = int($x1 + $bar_width - $column_padding) - 1;
903             # Special case for when bar_width is less than 1.
904 21 50       33 if ($x2 < $x1) {
905 0         0 $x2 = $x1;
906             }
907              
908 21         37 my $y1 = int($bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height);
909              
910 21 100       30 if ($data[$i] > 0) {
911 18         72 my @fill = $self->_data_fill($series_counter, [$x1, $y1, $x2, $zero_position-1]);
912 18         69 $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position-1, @fill);
913 18 100       5943 if ($style->{'features'}{'outline'}) {
914 11         31 $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position, color => $outline_color);
915             }
916             }
917             else {
918 3         12 my @fill = $self->_data_fill($series_counter, [$x1, $zero_position+1, $x2, $y1]);
919 3         12 $img->box(xmin => $x1, xmax => $x2, ymin => $zero_position+1, ymax => $y1, @fill);
920 3 50       665 if ($style->{'features'}{'outline'}) {
921 3         11 $img->box(xmin => $x1, xmax => $x2, ymin => $zero_position+1, ymax => $y1+1, color => $outline_color);
922             }
923             }
924             }
925              
926 3         72 $series_counter++;
927             }
928 2         12 $self->_set_series_counter($series_counter);
929 2         11 return 1;
930             }
931              
932             sub _add_data_series {
933 16     16   28 my $self = shift;
934 16         35 my $series_type = shift;
935 16         22 my $data_ref = shift;
936 16         30 my $series_name = shift;
937              
938 16   100     110 my $graph_data = $self->{'graph_data'} || {};
939              
940 16   100     85 my $series = $graph_data->{$series_type} || [];
941              
942 16         107 push @$series, { data => $data_ref, series_name => $series_name };
943              
944 16         40 $graph_data->{$series_type} = $series;
945              
946 16         36 $self->{'graph_data'} = $graph_data;
947 16         36 return;
948             }
949              
950             =back
951              
952             =head1 FEATURES
953              
954             =over
955              
956             =item show_horizontal_gridlines()
957              
958             Feature: horizontal_gridlines
959             XX
960              
961             Enables the C feature, which shows horizontal
962             gridlines at the y-tics.
963              
964             The style of the gridlines can be controlled with the
965             set_horizontal_gridline_style() method (or by setting the hgrid
966             style).
967              
968             =cut
969              
970             sub show_horizontal_gridlines {
971 1     1 1 12 $_[0]->{'custom_style'}{features}{'horizontal_gridlines'} = 1;
972             }
973              
974             =item set_horizontal_gridline_style(style => $style, color => $color)
975              
976             Style: hgrid.
977             XX