File Coverage

blib/lib/Chart/StackedBars.pm
Criterion Covered Total %
statement 172 200 86.0
branch 36 64 56.2
condition 3 8 37.5
subroutine 9 9 100.0
pod n/a
total 220 281 78.3


line stmt bran cond sub pod time code
1             #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#
2             # Chart::StackedBars #
3             # #
4             # written by david bonner #
5             # dbonner@cs.bu.edu #
6             # #
7             # maintained by the Chart Group #
8             # Chart@wettzell.ifag.de #
9             # #
10             # theft is treason, citizen #
11             #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<#
12              
13             package Chart::StackedBars;
14              
15 6     6   208 use Chart::Base 2.3;
  6         945  
  6         105  
16 6     6   123 use GD;
  6         54  
  6         112  
17 6     6   101 use Carp;
  6         56  
  6         104  
18 6     6   133 use strict;
  6         113  
  6         94  
19              
20             @Chart::StackedBars::ISA = qw(Chart::Base);
21             $Chart::StackedBars::VERSION = '2.3';
22              
23             #>>>>>>>>>>>>>>>>>>>>>>>>>>#
24             # public methods go here #
25             #<<<<<<<<<<<<<<<<<<<<<<<<<<#
26              
27             #>>>>>>>>>>>>>>>>>>>>>>>>>>>#
28             # private methods go here #
29             #<<<<<<<<<<<<<<<<<<<<<<<<<<<#
30              
31             ## override check_data to make sure we don't get datasets with positive
32             ## and negative values mixed
33             sub _check_data {
34 8     8   271   my $self = shift;
35 8         114   my $data = $self->{'dataref'};
36 8         74   my $length = 0;
37 8         71   my ($i, $j, $posneg);
38 8         69   my $composite;
39             # remember the number of datasets
40 8 100       92   if (defined $self->{'composite_info'}) {
41 5 50       76     if ($self->{'composite_info'}[0][0] =~ /^StackedBars$/i) {
42 5         75       $composite=0;
43                   }
44 5 50       91     if ($self->{'composite_info'}[1][0] =~ /^StackedBars$/i) {
45 0         0       $composite=1;
46                   }
47             # $self->{'num_datasets'} = $#{$data}; ###
48               
49 5         43   $self->{'num_datasets'} = ($#{$self->{'composite_info'}[$composite][1]})+1;
  5         66  
50               }
51               else {
52 3         28   $self->{'num_datasets'} = $#{$data};
  3         40  
53               }
54             # remember the number of points in the largest dataset
55 8         84   $self->{'num_datapoints'} = 0;
56 8         92   for (0..$self->{'num_datasets'}) {
57 36 100       288   if (scalar(@{$data->[$_]}) > $self->{'num_datapoints'}) {
  36         433  
58 8         71       $self->{'num_datapoints'} = scalar(@{$data->[$_]});
  8         87  
59                 }
60               }
61              
62             # make sure the datasets don't mix pos and neg values
63 8         203   for $i (0..$self->{'num_datapoints'}-1) {
64 88         922     $posneg = '';
65 88         852     for $j (1..$self->{'num_datasets'}) {
66 316 50       3869       if ($data->[$j][$i] > 0) {
    0          
67 316 50       2965 if ($posneg eq 'neg') {
68 0         0 croak "The values for a Chart::StackedBars data point must either be all positive or all negative";
69             }
70             else {
71 316         4583 $posneg = 'pos';
72             }
73                   }
74                   elsif ($data->[$j][$i] < 0) {
75 0 0       0 if ($posneg eq 'pos') {
76 0         0 croak "The values for a Chart::StackedBars data point must either be all positive or all negative";
77             }
78             else {
79 0         0 $posneg = 'neg';
80             }
81                   }
82                 }
83               }
84              
85             # find good min and max y-values for the plot
86 8         234   $self->_find_y_scale;
87              
88             # find the longest x-tick label
89 8         71   for (@{$data->[0]}) {
  8         119  
90 88 100       937     if (length($_) > $length) {
91 12         108       $length = length ($_);
92                 }
93               }
94              
95             # now store it in the object
96 8         84   $self->{'x_tick_label_length'} = $length;
97              
98               
99 8         96   return;
100             }
101              
102              
103             sub _find_y_range {
104 8     8   73   my $self = shift;
105               
106             # This finds the minimum and maximum point-sum over all x points,
107             # where the point-sum is the sum of the dataset values for that point.
108             # If the y value in any dataset is undef for a given x, it simply
109             # adds nothing to the sum.
110               
111               
112 8         79   my $data = $self->{'dataref'};
113 8         71   my $max = undef;
114 8         72   my $min = undef;
115 8         71   for my $i (0..$#{$data->[0]}) { # data point
  8         99  
116 88   50     1653     my $sum = $data->[1]->[$i] || 0;
117 88         1050     for my $dataset ( @$data[2..$#$data] ) { # order not important
118 228         1955       my $datum = $dataset->[$i];
119 228 50       6025       $sum += $datum if defined $datum;
120                 }
121 88 100       1018     if ( defined $max ) {
122 80 100       829       if ( $sum > $max ) { $max = $sum }
  12 100       107  
123 26         232       elsif ( $sum < $min ) { $min = $sum }
124                 }
125 8         92     else { $min = $max = $sum }
126               }
127              
128              
129             # make sure all-positive or all-negative charts get anchored at
130             # zero so that we don't cut out some parts of the bars
131 8 50 33     207   if (($max > 0) && ($min > 0)) {
132 8         78     $min = 0;
133               }
134 8 50 33     103   if (($min < 0) && ($max < 0)) {
135 0         0     $max = 0;
136               }
137              
138 8         118 ($min, $max);
139             }
140              
141              
142             # ## override _find_y_scale to account for stacked bars
143             # sub _find_y_scale {
144             # my $self = shift;
145             # my $raw = $self->{'dataref'};
146             # my $data = [@{$raw->[1]}];
147             # my ($i, $j, $max, $min);
148             # my ($order, $mult, $tmp);
149             # my ($range, $delta, @dec, $y_ticks);
150             # my $labels = [];
151             # my $length = 0;
152             #
153             # # use realy weird max and min values
154             # $max = -999999999999;
155             # $min = 999999999999;
156             #
157             # # go through and stack them
158             # for $i (0..$self->{'num_datapoints'}-1) {
159             # for $j (2..$self->{'num_datasets'}) {
160             # $data->[$i] += $raw->[$j][$i];
161             # }
162             # }
163             #
164             # # get max and min values
165             # for $i (0..$self->{'num_datapoints'}-1) {
166             # if ($data->[$i] > $max) {
167             # $max = $data->[$i];
168             # }
169             # if ($data->[$i] < $min) {
170             # $min = $data->[$i];
171             # }
172             # }
173             #
174             # # make sure all-positive or all-negative charts get anchored at
175             # # zero so that we don't cut out some parts of the bars
176             # if (($max > 0) && ($min > 0)) {
177             # $min = 0;
178             # }
179             # if (($min < 0) && ($max < 0)) {
180             # $max = 0;
181             # }
182             #
183             # # calculate good max value
184             # if ($max < -10) {
185             # $tmp = -$max;
186             # $order = int((log $tmp) / (log 10));
187             # $mult = int ($tmp / (10 ** $order));
188             # $tmp = ($mult - 1) * (10 ** $order);
189             # $max = -$tmp;
190             # }
191             # elsif ($max < 0) {
192             # $max = 0;
193             # }
194             # elsif ($max > 10) {
195             # $order = int((log $max) / (log 10));
196             # $mult = int ($max / (10 ** $order));
197             # $max = ($mult + 1) * (10 ** $order);
198             # }
199             # elsif ($max >= 0) {
200             # $max = 10;
201             # }
202             #
203             # # now go for a good min
204             # if ($min < -10) {
205             # $tmp = -$min;
206             # $order = int((log $tmp) / (log 10));
207             # $mult = int ($tmp / (10 ** $order));
208             # $tmp = ($mult + 1) * (10 ** $order);
209             # $min = -$tmp;
210             # }
211             # elsif ($min < 0) {
212             # $min = -10;
213             # }
214             # elsif ($min > 10) {
215             # $order = int ((log $min) / (log 10));
216             # $mult = int ($min / (10 ** $order));
217             # $min = $mult * (10 ** $order);
218             # }
219             # elsif ($min >= 0) {
220             # $min = 0;
221             # }
222             #
223             # # put the appropriate min and max values into the object if necessary
224             # unless (defined ($self->{'max_val'})) {
225             # $self->{'max_val'} = $max;
226             # }
227             # unless (defined ($self->{'min_val'})) {
228             # $self->{'min_val'} = $min;
229             # }
230             #
231             # # generate the y_tick labels, store them in the object
232             # # figure out which one is going to be the longest
233             # $range = $self->{'max_val'} - $self->{'min_val'};
234             # $y_ticks = $self->{'y_ticks'} - 1;
235             # ## Don't adjust y_ticks if the user specified custom labels
236             # if ($self->{'integer_ticks_only'} =~ /^true$/i && ! $self->{'y_tick_labels'}) {
237             # unless (($range % $y_ticks) == 0) {
238             # while (($range % $y_ticks) != 0) {
239             # $y_ticks++;
240             # }
241             # $self->{'y_ticks'} = $y_ticks + 1;
242             # }
243             # }
244             #
245             # $delta = $range / $y_ticks;
246             # for (0..$y_ticks) {
247             # $tmp = $self->{'min_val'} + ($delta * $_);
248             # @dec = split /\./, $tmp;
249             # if ($dec[1] && (length($dec[1]) > 3)) {
250             # $tmp = sprintf("%.3f", $tmp);
251             # }
252             # $labels->[$_] = $tmp;
253             # if (length($tmp) > $length) {
254             # $length = length($tmp);
255             # }
256             # }
257             #
258             # # store it in the object
259             # $self->{'y_tick_labels'} = $labels;
260             # $self->{'y_tick_label_length'} = $length;
261             #
262             # # and return
263             # return;
264             # }
265              
266              
267             ## finally get around to plotting the data
268             sub _draw_data {
269 6     6   61   my $self = shift;
270 6         61   my $raw = $self->{'dataref'};
271 6         87   my $data = [];
272 6         89   my $misccolor = $self->_color_role_to_index('misc');
273 6         57   my ($width, $height, $delta, $map, $mod);
274 6         59   my ($x1, $y1, $x2, $y2, $x3, $y3, $i, $j, $color, $cut);
275 6         100   my $pink = $self->{'gd_obj'}->colorAllocate(255,0,255);
276              
277             # init the imagemap data field if they want it
278 6 50       207   if ($self->{'imagemap'} =~ /^true$/i) {
279 0         0     $self->{'imagemap_data'} = [];
280               }
281                
282             # width and height of remaining area, delta for width of bars, mapping value
283 6         67   $width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
284                
285 6 100       101   if ($self->{'spaced_bars'} =~ /^true$/i) {
286 4         46    $delta = ($width / ($self->{'num_datapoints'} * 2));
287                 }
288               else {
289 2         74     $delta = $width / $self->{'num_datapoints'};
290                 }
291 6         63   $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
292 6         65   $map = $height / ($self->{'max_val'} - $self->{'min_val'});
293              
294             # get the base x and y values
295 6         642   $x1 = $self->{'curr_x_min'};
296 6 50       90   if ($self->{'min_val'} >= 0) {
    0          
297 6         60     $y1 = $self->{'curr_y_max'};
298 6         130     $mod = $self->{'min_val'};
299               }
300               elsif ($self->{'max_val'} <= 0) {
301 0         0     $y1 = $self->{'curr_y_min'};
302 0         0     $mod = $self->{'max_val'};
303               }
304               else {
305 0         0     $y1 = $self->{'curr_y_min'} + ($map * $self->{'max_val'});
306 0         0     $mod = 0;
307 0         0     $self->{'gd_obj'}->line ($self->{'curr_x_min'}, $y1,
308                                          $self->{'curr_x_max'}, $y1,
309             $misccolor);
310               }
311              
312               
313             # create another copy of the data, but stacked
314 6         56   $data->[1] = [@{$raw->[1]}];
  6         131  
315 6         136   for $i (0..$self->{'num_datapoints'}-1) {
316 62         725     for $j (2..$self->{'num_datasets'}) {
317 150         2261       $data->[$j][$i] = $data->[$j-1][$i] + $raw->[$j][$i];
318                 }
319               }
320              
321             # draw the damn bars
322 6         72   for $i (0..$self->{'num_datapoints'}-1) {
323             # init the y values for this datapoint
324 62         501     $y2 = $y1;
325                 
326                 
327 62         2272     for $j (1..$self->{'num_datasets'}) {
328             # get the color
329 212         3057       $color = $self->_color_role_to_index('dataset'.($j-1));
330                   
331             # set up the geometry for the bar
332 212 100       3237       if ($self->{'spaced_bars'} =~ /^true$/i) {
333 140         1335         $x2 = $x1 + (2 * $i * $delta) + ($delta / 2);
334 140         1185         $x3 = $x2 + $delta;
335            
336                   }
337                   else {
338 72         647         $x2 = $x1 + ($i * $delta);
339 72         610         $x3 = $x2 + $delta;
340                   }
341 212         2125       $y3 = $y1 - (($data->[$j][$i] - $mod) * $map);
342                  
343             #cut the bars off, if needed
344 212 50       2580       if ($data->[$j][$i] > $self->{'max_val'}) {
    50          
345 0         0            $y3 = $y1 - (($self->{'max_val'} - $mod ) * $map) ;
346 0         0            $cut = 1;
347                   }
348                   elsif ($data->[$j][$i] < $self->{'min_val'}) {
349 0         0            $y3 = $y1 - (($self->{'min_val'} - $mod ) * $map) ;
350 0 &