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         11  
  8         688  
38 8     8   75 use vars qw(@ISA);
  8         10  
  8         333  
39 8     8   6335 use Imager::Graph;
  8         49  
  8         2134  
40             @ISA = qw(Imager::Graph);
41 8     8   6834 use Imager::Fill;
  8         12353  
  8         411  
42              
43             our $VERSION = "0.11";
44              
45 8     8   60 use constant STARTING_MIN_VALUE => 99999;
  8         12  
  8         80249  
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 4841 my $self = shift;
58 16         50 my $data_ref = shift;
59 16         29 my $series_name = shift;
60              
61 16         107 my $series_type = $self->_get_default_series_type();
62 16         94 $self->_add_data_series($series_type, $data_ref, $series_name);
63              
64 16         28 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 234 my ($self, %opts) = @_;
198              
199 12 50       105 if (!$self->_valid_input()) {
200 0         0 return;
201             }
202              
203 12         99 $self->_style_setup(\%opts);
204              
205 12         31 my $style = $self->{_style};
206              
207 12 50       101 $self->_make_img
208             or return;
209              
210 12 50       318 my $img = $self->_get_image()
211             or return;
212              
213 12         75 my @image_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 );
214 12         447 $self->_set_image_box(\@image_box);
215              
216 12         43 my @chart_box = ( 0, 0, $img->getwidth-1, $img->getheight-1 );
217 12         269 $self->_draw_legend(\@chart_box);
218 12 100       51 if ($style->{title}{text}) {
219 1 50       10 $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       82 return unless $self->_get_data_range();
225 12         101 $self->_remove_tics_from_chart_box(\@chart_box, \%opts);
226 12         90 my $column_count = $self->_get_column_count();
227              
228 12         42 my $width = $self->_get_number('width');
229 12         43 my $height = $self->_get_number('height');
230              
231 12         30 my $graph_width = $chart_box[2] - $chart_box[0];
232 12         22 my $graph_height = $chart_box[3] - $chart_box[1];
233              
234 12         35 my $col_width = ($graph_width - 1) / $column_count;
235 12 100       41 if ($col_width > 1) {
236 10         27 $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         39 my $tic_count = $self->_get_y_tics();
243 12         36 my $tic_distance = ($graph_height-1) / ($tic_count - 1);
244 12         26 $graph_height = int($tic_distance * ($tic_count - 1));
245              
246 12         19 my $top = $chart_box[1];
247 12         19 my $left = $chart_box[0];
248              
249 12         36 $self->{'_style'}{'graph_width'} = $graph_width;
250 12         30 $self->{'_style'}{'graph_height'} = $graph_height;
251              
252 12         56 my @graph_box = ($left, $top, $left + $graph_width, $top + $graph_height);
253 12         80 $self->_set_graph_box(\@graph_box);
254              
255 12         37 my @fill_box = ( $left, $top, $left+$graph_width, $top+$graph_height );
256 12 50       110 if ($self->_feature_enabled("graph_outline")) {
257 12 50       99 my @line = $self->_get_line("graph.outline")
258             or return;
259              
260 12         176 $self->_box(
261             @line,
262             box => \@fill_box,
263             img => $img,
264             );
265 12         1474 ++$fill_box[0];
266 12         25 ++$fill_box[1];
267 12         19 --$fill_box[2];
268 12         62 --$fill_box[3];
269             }
270              
271             $img->box(
272 12         53 $self->_get_fill('graph.fill'),
273             box => \@fill_box,
274             );
275              
276 12         10006 my $min_value = $self->_get_min_value();
277 12         43 my $max_value = $self->_get_max_value();
278 12         114 my $value_range = $max_value - $min_value;
279              
280 12         17 my $zero_position;
281 12 50       45 if ($value_range) {
282 12         52 $zero_position = $top + $graph_height - (-1*$min_value / $value_range) * ($graph_height-1);
283             }
284              
285 12 100       36 if ($min_value < 0) {
286 3         19 my @neg_box = ( $left + 1, $zero_position, $left+$graph_width- 1, $top+$graph_height - 1 );
287 3 50       14 my @neg_fill = $self->_get_fill('negative_bg', \@neg_box)
288             or return;
289 3         71 $img->box(
290             @neg_fill,
291             box => \@neg_box,
292             );
293 3         2117 $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         362 $self->_reset_series_counter();
303              
304 12 100       53 if ($self->_get_data_series()->{'stacked_column'}) {
305 2 50       7 return unless $self->_draw_stacked_columns();
306             }
307 12 100       47 if ($self->_get_data_series()->{'column'}) {
308 3 50       17 return unless $self->_draw_columns();
309             }
310 12 100       60 if ($self->_get_data_series()->{'line'}) {
311 5 50       43 return unless $self->_draw_lines();
312             }
313 12 100       82 if ($self->_get_data_series()->{'area'}) {
314 2 50       11 return unless $self->_draw_area();
315             }
316              
317 12 100       54 if ($self->_get_y_tics()) {
318 2         17 $self->_draw_y_tics();
319             }
320 12 50       71 if ($self->_get_labels(\%opts)) {
321 12         88 $self->_draw_x_tics(\%opts);
322             }
323              
324 12         807 return $self->_get_image();
325             }
326              
327             sub _get_data_range {
328 12     12   25 my $self = shift;
329              
330 12         20 my $max_value = 0;
331 12         57 my $min_value = 0;
332 12         20 my $column_count = 0;
333              
334 12         83 my ($sc_min, $sc_max, $sc_cols) = $self->_get_stacked_column_range();
335 12         80 my ($c_min, $c_max, $c_cols) = $self->_get_column_range();
336 12         84 my ($l_min, $l_max, $l_cols) = $self->_get_line_range();
337 12         95 my ($a_min, $a_max, $a_cols) = $self->_get_area_range();
338              
339             # These are side by side...
340 12         33 $sc_cols += $c_cols;
341              
342 12         80 $min_value = $self->_min(STARTING_MIN_VALUE, $sc_min, $c_min, $l_min, $a_min);
343 12         99 $max_value = $self->_max(0, $sc_max, $c_max, $l_max, $a_max);
344              
345 12         63 my $config_min = $self->_get_number('y_min');
346 12         45 my $config_max = $self->_get_number('y_max');
347              
348 12 50 33     64 if (defined $config_max && $config_max < $max_value) {
349 0         0 $config_max = undef;
350             }
351 12 50 33     49 if (defined $config_min && $config_min > $min_value) {
352 0         0 $config_min = undef;
353             }
354              
355 12         38 my $range_padding = $self->_get_number('range_padding');
356 12 50       50 if (defined $config_min) {
357 0         0 $min_value = $config_min;
358             }
359             else {
360 12 100       47 if ($min_value > 0) {
361 9         17 $min_value = 0;
362             }
363 12 50 33     55 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       39 if (defined $config_max) {
372 0         0 $max_value = $config_max;
373             }
374             else {
375 12 50 33     51 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       41 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         83 $self->_set_max_value($max_value);
403 12         106 $self->_set_min_value($min_value);
404 12         55 $self->_set_column_count($column_count);
405              
406 12         45 return 1;
407             }
408              
409             sub _min {
410 12     12   20 my $self = shift;
411 12         23 my $min = shift;
412              
413 12         33 foreach my $value (@_) {
414 48 100       96 next unless defined $value;
415 12 50       54 if ($value < $min) { $min = $value; }
  12         24  
416             }
417 12         35 return $min;
418             }
419              
420             sub _max {
421 24     24   38 my $self = shift;
422 24         27 my $min = shift;
423              
424 24         49 foreach my $value (@_) {
425 84 100       143 next unless defined $value;
426 48 100       112 if ($value > $min) { $min = $value; }
  24         38  
427             }
428 24         53 return $min;
429             }
430              
431             sub _get_line_range {
432 12     12   24 my $self = shift;
433 12         47 my $series = $self->_get_data_series()->{'line'};
434 12 100       48 return (undef, undef, 0) unless $series;
435              
436 5         10 my $max_value = 0;
437 5         43 my $min_value = STARTING_MIN_VALUE;
438 5         7 my $column_count = 0;
439              
440 5         22 my @series = @{$series};
  5         12  
441 5         12 foreach my $series (@series) {
442 5         9 my @data = @{$series->{'data'}};
  5         160  
443              
444 5 50       22 if (scalar @data > $column_count) {
445 5         8 $column_count = scalar @data;
446             }
447              
448 5         12 foreach my $value (@data) {
449 2016 100       2541 if ($value > $max_value) { $max_value = $value; }
  2011         1461  
450 2016 100       2727 if ($value < $min_value) { $min_value = $value; }
  10         18  
451             }
452             }
453              
454 5         25 return ($min_value, $max_value, $column_count);
455             }
456              
457             sub _get_area_range {
458 12     12   23 my $self = shift;
459 12         49 my $series = $self->_get_data_series()->{'area'};
460 12 100       65 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         2 my $column_count = 0;
465              
466 2         4 my @series = @{$series};
  2         5  
467 2         5 foreach my $series (@series) {
468 3         5 my @data = @{$series->{'data'}};
  3         13  
469              
470 3 100       8 if (scalar @data > $column_count) {
471 2         4 $column_count = scalar @data;
472             }
473              
474 3         6 foreach my $value (@data) {
475 21 100       35 if ($value > $max_value) { $max_value = $value; }
  5         6  
476 21 100       34 if ($value < $min_value) { $min_value = $value; }
  12         17  
477             }
478             }
479              
480 2         7 return ($min_value, $max_value, $column_count);
481             }
482              
483              
484             sub _get_column_range {
485 12     12   23 my $self = shift;
486              
487 12         44 my $series = $self->_get_data_series()->{'column'};
488 12 100       55 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         3 my $column_count = 0;
493              
494 3         6 my @series = @{$series};
  3         6  
495 3         8 foreach my $series (@series) {
496 5         7 my @data = @{$series->{'data'}};
  5         21  
497              
498 5         8 foreach my $value (@data) {
499 35         19 $column_count++;
500 35 100       51 if ($value > $max_value) { $max_value = $value; }
  6         5  
501 35 100       53 if ($value < $min_value) { $min_value = $value; }
  20         27  
502             }
503             }
504              
505 3         8 return ($min_value, $max_value, $column_count);
506             }
507              
508             sub _get_stacked_column_range {
509 12     12   26 my $self = shift;
510              
511 12         23 my $max_value = 0;
512 12         19 my $min_value = STARTING_MIN_VALUE;
513 12         21 my $column_count = 0;
514              
515 12 100       57 return (undef, undef, 0) unless $self->_get_data_series()->{'stacked_column'};
516 2         3 my @series = @{$self->_get_data_series()->{'stacked_column'}};
  2         6  
517              
518 2         4 my @max_entries;
519             my @min_entries;
520 2         9 for (my $i = scalar @series - 1; $i >= 0; $i--) {
521 3         6 my $series = $series[$i];
522 3         5 my $data = $series->{'data'};
523              
524 3         11 for (my $i = 0; $i < scalar @$data; $i++) {
525 21         15 my $value = 0;
526 21 100       39 if ($data->[$i] > 0) {
    50          
527 18   100     42 $value = $data->[$i] + ($max_entries[$i] || 0);
528 18         16 $data->[$i] = $value;
529 18         20 $max_entries[$i] = $value;
530             }
531             elsif ($data->[$i] < 0) {
532 3   50     12 $value = $data->[$i] + ($min_entries[$i] || 0);
533 3         6 $data->[$i] = $value;
534 3         7 $min_entries[$i] = $value;
535             }
536 21 100       34 if ($value > $max_value) { $max_value = $value; }
  6         5  
537 21 100       44 if ($value < $min_value) { $min_value = $value; }
  7         13  
538             }
539 3 100       10 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   26 my $self = shift;
549 12         21 my $chart_box = shift;
550 12         28 my $style = $self->{'_style'};
551              
552 12         17 my @labels;
553 12         36 my $img = $self->_get_image();
554 12 100       56 if (my $series = $self->_get_data_series()->{'stacked_column'}) {
555 2         6 push @labels, map { $_->{'series_name'} } @$series;
  3         9  
556             }
557 12 100       35 if (my $series = $self->_get_data_series()->{'column'}) {
558 3         9 push @labels, map { $_->{'series_name'} } @$series;
  5         16  
559             }
560 12 100       38 if (my $series = $self->_get_data_series()->{'line'}) {
561 5         14 push @labels, map { $_->{'series_name'} } @$series;
  5         22  
562             }
563 12 100       35 if (my $series = $self->_get_data_series()->{'area'}) {
564 2         5 push @labels, map { $_->{'series_name'} } @$series;
  3         11  
565             }
566              
567 12 50 100     66 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         29 return;
572             }
573              
574             sub _draw_flat_legend {
575 1     1   4 return 1;
576             }
577              
578             sub _draw_lines {
579 5     5   9 my $self = shift;
580 5         34 my $style = $self->{'_style'};
581              
582 5         24 my $img = $self->_get_image();
583              
584 5         15 my $max_value = $self->_get_max_value();
585 5         15 my $min_value = $self->_get_min_value();
586 5         15 my $column_count = $self->_get_column_count();
587              
588 5         13 my $value_range = $max_value - $min_value;
589              
590 5         20 my $width = $self->_get_number('width');
591 5         20 my $height = $self->_get_number('height');
592              
593 5         77 my $graph_width = $self->_get_number('graph_width');
594 5         20 my $graph_height = $self->_get_number('graph_height');
595              
596 5         19 my $line_series = $self->_get_data_series()->{'line'};
597 5   50     65 my $series_counter = $self->_get_series_counter() || 0;
598              
599 5 50 33     16 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         36 my $graph_box = $self->_get_graph_box();
604 5         39 my $left = $graph_box->[0] + 1;
605 5         9 my $bottom = $graph_box->[1];
606              
607 5         41 my $zero_position = $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height - 1);
608              
609 5         19 my $line_aa = $self->_get_number("lineaa");
610 5         19 foreach my $series (@$line_series) {
611 5         7 my @data = @{$series->{'data'}};
  5         190  
612 5         18 my $data_size = scalar @data;
613              
614 5         6 my $interval;
615 5 50       184 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         41 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         9 my @marker_positions;
625 5         23 for (my $i = 0; $i < $data_size - 1; $i++) {
626 2011         125949 my $x1 = $left + $i * $interval;
627 2011         2346 my $x2 = $left + ($i + 1) * $interval;
628              
629 2011         2204 $x1 += $has_columns * $interval / 2;
630 2011         1844 $x2 += $has_columns * $interval / 2;
631              
632 2011         2827 my $y1 = $bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height;
633 2011         2790 my $y2 = $bottom + ($value_range - $data[$i + 1] + $min_value)/$value_range * $graph_height;
634              
635 2011         4189 push @marker_positions, [$x1, $y1];
636 2011 50       5006 $img->line(x1 => $x1, y1 => $y1, x2 => $x2, y2 => $y2, aa => $line_aa, color => $color) || die $img->errstr;
637             }
638              
639 5         434 my $x2 = $left + ($data_size - 1) * $interval;
640 5         17 $x2 += $has_columns * $interval / 2;
641              
642 5         19 my $y2 = $bottom + ($value_range - $data[$data_size - 1] + $min_value)/$value_range * $graph_height;
643              
644 5 50       46 if ($self->_feature_enabled("linemarkers")) {
645 5         19 push @marker_positions, [$x2, $y2];
646 5         16 foreach my $position (@marker_positions) {
647 2016         399746 $self->_draw_line_marker($position->[0], $position->[1], $series_counter);
648             }
649             }
650 5         1397 $series_counter++;
651             }
652              
653 5         59 $self->_set_series_counter($series_counter);
654 5         32 return 1;
655             }
656              
657             sub _area_data_fill {
658 3     3   6 my ($self, $index, $box) = @_;
659              
660 3         9 my %fill = $self->_data_fill($index, $box);
661              
662 3         14 my $opacity = $self->_get_number("area.opacity");
663 3 50       11 $opacity == 1
664             and return %fill;
665              
666 3         4 my $orig_fill = $fill{fill};
667 3 50       8 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         16 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         8 my $img = $self->_get_image();
690              
691 2         7 my $max_value = $self->_get_max_value();
692 2         6 my $min_value = $self->_get_min_value();
693 2         5 my $column_count = $self->_get_column_count();
694              
695 2         4 my $value_range = $max_value - $min_value;
696              
697 2         7 my $width = $self->_get_number('width');
698 2         6 my $height = $self->_get_number('height');
699              
700 2         9 my $graph_width = $self->_get_number('graph_width');
701 2         6 my $graph_height = $self->_get_number('graph_height');
702              
703 2         7 my $area_series = $self->_get_data_series()->{'area'};
704 2   50     9 my $series_counter = $self->_get_series_counter() || 0;
705              
706 2         6 my $col_width = int($graph_width / $column_count) -1;
707              
708 2         7 my $graph_box = $self->_get_graph_box();
709 2         5 my $left = $graph_box->[0] + 1;
710 2         4 my $bottom = $graph_box->[1];
711 2         4 my $right = $graph_box->[2];
712 2         2 my $top = $graph_box->[3];
713              
714 2         6 my $zero_position = $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height - 1);
715              
716 2         4 my $line_aa = $self->_get_number("lineaa");
717 2         5 foreach my $series (@$area_series) {
718 3         6 my @data = @{$series->{'data'}};
  3         11  
719 3         6 my $data_size = scalar @data;
720              
721 3         6 my $interval = $graph_width / ($data_size - 1);
722              
723 3         19 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         28 my $x1 = $left + $i * $interval;
730              
731 18         28 my $y1 = $bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height;
732              
733 18 100       28 if ($i == 0) {
734 3         8 push @polygon_points, [$x1, $top];
735             }
736 18         27 push @polygon_points, [$x1, $y1];
737              
738 18         48 push @marker_positions, [$x1, $y1];
739             }
740              
741 3         6 my $x2 = $left + ($data_size - 1) * $interval;
742              
743 3         8 my $y2 = $bottom + ($value_range - $data[$data_size - 1] + $min_value)/$value_range * $graph_height;
744 3         5 push @polygon_points, [$x2, $y2];
745 3         6 push @polygon_points, [$x2, $top];
746 3         5 push @polygon_points, $polygon_points[0];
747              
748 3         16 my @fill = $self->_area_data_fill($series_counter, [$left, $bottom, $right, $top]);
749 3         596 $img->polygon(points => [@polygon_points], @fill);
750              
751 3 100       6243 if ($self->_feature_enabled("areamarkers")) {
752 1         5 push @marker_positions, [$x2, $y2];
753 1         4 foreach my $position (@marker_positions) {
754 7         1269 $self->_draw_line_marker($position->[0], $position->[1], $series_counter);
755             }
756             }
757 3         280 $series_counter++;
758             }
759              
760 2         16 $self->_set_series_counter($series_counter);
761 2         11 return 1;
762             }
763              
764             sub _draw_columns {
765 3     3   6 my $self = shift;
766 3         6 my $style = $self->{'_style'};
767              
768 3         15 my $img = $self->_get_image();
769              
770 3         19 my $max_value = $self->_get_max_value();
771 3         10 my $min_value = $self->_get_min_value();
772 3         12 my $column_count = $self->_get_column_count();
773              
774 3         7 my $value_range = $max_value - $min_value;
775              
776 3         15 my $width = $self->_get_number('width');
777 3         10 my $height = $self->_get_number('height');
778              
779 3         8 my $graph_width = $self->_get_number('graph_width');
780 3         21 my $graph_height = $self->_get_number('graph_height');
781              
782              
783 3         18 my $graph_box = $self->_get_graph_box();
784 3         7 my $left = $graph_box->[0] + 1;
785 3         6 my $bottom = $graph_box->[1];
786 3         13 my $zero_position = int($bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height -1));
787              
788 3         6 my $bar_width = $graph_width / $column_count;
789              
790 3         3 my $outline_color;
791 3 100       14 if ($style->{'features'}{'outline'}) {
792 2         7 $outline_color = $self->_get_color('outline.line');
793             }
794              
795 3   50     16 my $series_counter = $self->_get_series_counter() || 0;
796 3         8 my $col_series = $self->_get_data_series()->{'column'};
797 3   50     15 my $column_padding_percent = $self->_get_number('column_padding') || 0;
798 3         11 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         6 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       10 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         9 my $series = $col_series->[$series_pos];
808 5         7 my @data = @{$series->{'data'}};
  5         77  
809 5         8 my $data_size = scalar @data;
810 5         18 for (my $i = 0; $i < $data_size; $i++) {
811 35         671 my $part1 = $bar_width * (scalar @$col_series * $i);
812 35         29 my $part2 = ($series_pos) * $bar_width;
813 35         44 my $x1 = $left + $part1 + $part2;
814 35 50       49 if ($has_stacked_columns) {
815 0         0 $x1 += ($bar_width * ($i+1));
816             }
817 35         30 $x1 = int($x1);
818              
819 35         52 my $x2 = int($x1 + $bar_width - $column_padding)-1;
820             # Special case for when bar_width is less than 1.
821 35 50       47 if ($x2 < $x1) {
822 0         0 $x2 = $x1;
823             }
824              
825 35         61 my $y1 = int($bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height);
826              
827 35         83 my $color = $self->_data_color($series_counter);
828              
829 35 100       56 if ($data[$i] > 0) {
830 29         92 my @fill = $self->_data_fill($series_counter, [$x1, $y1, $x2, $zero_position-1]);
831 29         93 $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position-1, @fill);
832 29 100       6852 if ($style->{'features'}{'outline'}) {
833 22         65 $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position, color => $outline_color);
834             }
835             }
836             else {
837 6         25 my @fill = $self->_data_fill($series_counter, [$x1, $zero_position+1, $x2, $y1]);
838 6         28 $img->box(xmin => $x1, xmax => $x2, ymin => $zero_position+1, ymax => $y1, @fill);
839 6 50       1103 if ($style->{'features'}{'outline'}) {
840 6         24 $img->box(xmin => $x1, xmax => $x2, ymin => $zero_position+1, ymax => $y1+1, color => $outline_color);
841             }
842             }
843             }
844              
845 5         119 $series_counter++;
846 5         25 $column_series++;
847             }
848 3         19 $self->_set_series_counter($series_counter);
849 3         22 return 1;
850             }
851              
852             sub _draw_stacked_columns {
853 2     2   2 my $self = shift;
854 2         4 my $style = $self->{'_style'};
855              
856 2         4 my $img = $self->_get_image();
857              
858 2         4 my $max_value = $self->_get_max_value();
859 2         4 my $min_value = $self->_get_min_value();
860 2         5 my $column_count = $self->_get_column_count();
861 2         3 my $value_range = $max_value - $min_value;
862              
863 2         7 my $graph_box = $self->_get_graph_box();
864 2         5 my $left = $graph_box->[0] + 1;
865 2         2 my $bottom = $graph_box->[1];
866              
867 2         5 my $graph_width = $self->_get_number('graph_width');
868 2         5 my $graph_height = $self->_get_number('graph_height');
869              
870 2         2 my $bar_width = $graph_width / $column_count;
871 2         2 my $column_series = 0;
872 2 50       11 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     4 my $column_padding_percent = $self->_get_number('column_padding') || 0;
878 2 50       7 if ($column_padding_percent < 0) {
879 0         0 return $self->_error("Column padding less than 0");
880             }
881 2 50       4 if ($column_padding_percent > 100) {
882 0         0 return $self->_error("Column padding greater than 0");
883             }
884 2         3 my $column_padding = int($column_padding_percent * $bar_width / 100);
885              
886 2         2 my $outline_color;
887 2 100       6 if ($style->{'features'}{'outline'}) {
888 1         3 $outline_color = $self->_get_color('outline.line');
889             }
890              
891 2         8 my $zero_position = $bottom + $graph_height - (-1*$min_value / $value_range) * ($graph_height -1);
892 2         5 my $col_series = $self->_get_data_series()->{'stacked_column'};
893 2   50     8 my $series_counter = $self->_get_series_counter() || 0;
894              
895 2         38 foreach my $series (@$col_series) {
896 3         5 my @data = @{$series->{'data'}};
  3         12  
897 3         5 my $data_size = scalar @data;
898 3         10 for (my $i = 0; $i < $data_size; $i++) {
899 21         365 my $part1 = $bar_width * $i * $column_series;
900 21         21 my $part2 = 0;
901 21         32 my $x1 = int($left + $part1 + $part2);
902 21         28 my $x2 = int($x1 + $bar_width - $column_padding) - 1;
903             # Special case for when bar_width is less than 1.
904 21 50       36 if ($x2 < $x1) {
905 0         0 $x2 = $x1;
906             }
907              
908 21         35 my $y1 = int($bottom + ($value_range - $data[$i] + $min_value)/$value_range * $graph_height);
909              
910 21 100       36 if ($data[$i] > 0) {
911 18         80 my @fill = $self->_data_fill($series_counter, [$x1, $y1, $x2, $zero_position-1]);
912 18         62 $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position-1, @fill);
913 18 100       5676 if ($style->{'features'}{'outline'}) {
914 11         33 $img->box(xmin => $x1, xmax => $x2, ymin => $y1, ymax => $zero_position, color => $outline_color);
915             }
916             }
917             else {
918 3         13 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       606 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         58 $series_counter++;
927             }
928 2         19 $self->_set_series_counter($series_counter);
929 2         15 return 1;
930             }
931              
932             sub _add_data_series {
933 16     16   25 my $self = shift;
934 16         24 my $series_type = shift;
935 16         22 my $data_ref = shift;
936 16         26 my $series_name = shift;
937              
938 16   100     95 my $graph_data = $self->{'graph_data'} || {};
939              
940 16   100     67 my $series = $graph_data->{$series_type} || [];
941              
942 16         99 push @$series, { data => $data_ref, series_name => $series_name };
943              
944 16         31 $graph_data->{$series_type} = $series;
945              
946 16         33 $self->{'graph_data'} = $graph_data;
947 16         26 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