File Coverage

blib/lib/Chart/HorizontalBars.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             ## @file
2             # Implementation of Chart::HorizontalBars
3             #
4             # maintained and written by the
5             # @author Chart Group at Geodetic Fundamental Station Wettzell (Chart@fs.wettzell.de)
6             # @date 2015-03-01
7             # @version 2.4.10
8              
9             ## @class Chart::HorizontalBars
10             # HorizontalBars class derived from class Base.
11             #
12             # This class provides all functions which are specific to
13             # horizontal bars
14             #
15             package Chart::HorizontalBars;
16              
17 4     4   6772 use Chart::Base '2.4.10';
  0            
  0            
18             use GD;
19             use Carp;
20             use strict;
21              
22             @Chart::HorizontalBars::ISA = qw(Chart::Base);
23             $Chart::HorizontalBars::VERSION = '2.4.10';
24              
25             #>>>>>>>>>>>>>>>>>>>>>>>>>>#
26             # public methods go here #
27             #<<<<<<<<<<<<<<<<<<<<<<<<<<#
28              
29             #>>>>>>>>>>>>>>>>>>>>>>>>>>>#
30             # private methods go here #
31             #<<<<<<<<<<<<<<<<<<<<<<<<<<<#
32              
33             ## @method private int _draw_x_ticks()
34             # draw the x-ticks and their labels
35             # Overwrites this function of Chart::Base
36             # @return status
37             #
38             sub _draw_x_ticks
39             {
40             my $self = shift;
41             my $data = $self->{'dataref'};
42             my $font = $self->{'tick_label_font'};
43             my $textcolor = $self->_color_role_to_index('text');
44             my $misccolor = $self->_color_role_to_index('misc');
45             my ( $h, $w, $x1, $y1, $y2, $x2, $delta, $width, $label );
46             my @labels = @{ $self->{'y_tick_labels'} };
47              
48             $self->{'grid_data'}->{'x'} = [];
49              
50             #make sure we have a real font
51             unless ( ( ref $font ) eq 'GD::Font' )
52             {
53             croak "The tick label font you specified isn't a GD font object";
54             }
55              
56             #get height and width of the font
57             ( $h, $w ) = ( $font->height, $font->width );
58              
59             #get the right x-value and width
60             if ( $self->{'y_axes'} =~ /^right$/i )
61             {
62             $x1 = $self->{'curr_x_min'};
63             $width =
64             $self->{'curr_x_max'} - $x1 - $self->{'tick_len'} - $self->{'text_space'} - $w * $self->{'x_tick_label_length'};
65             }
66             elsif ( $self->{'y_axes'} =~ /^both$/i )
67             {
68             $x1 = $self->{'curr_x_min'} + $self->{'text_space'} + $w * $self->{'x_tick_label_length'} + $self->{'tick_len'};
69             $width =
70             $self->{'curr_x_max'} - $x1 - $self->{'tick_len'} - $self->{'text_space'} - $w * $self->{'x_tick_label_length'};
71             }
72             else
73             {
74             $x1 = $self->{'curr_x_min'} + $self->{'text_space'} + $w * $self->{'x_tick_label_length'} + $self->{'tick_len'};
75             $width = $self->{'curr_x_max'} - $x1;
76             }
77              
78             #get the delta value
79             $delta = $width / ( $self->{'y_ticks'} - 1 );
80              
81             #draw the labels
82             $y2 = $y1;
83              
84             if ( $self->{'x_ticks'} =~ /^normal/i )
85             { #just normal ticks
86             #get the point for updating later
87             $y1 = $self->{'curr_y_max'} - 2 * $self->{'text_space'} - $h - $self->{'tick_len'};
88              
89             #get the start point
90             $y2 = $y1 + $self->{'tick_len'} + $self->{'text_space'};
91             for ( 0 .. $#labels )
92             {
93             $label = $self->{'y_tick_labels'}[$_];
94             $x2 = $x1 + ( $delta * $_ ) - ( $w * length($label) / 2 );
95             $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );
96             }
97             }
98             elsif ( $self->{'x_ticks'} =~ /^staggered/i )
99             { #staggered ticks
100             #get the point for updating later
101             $y1 = $self->{'curr_y_max'} - 3 * $self->{'text_space'} - 2 * $h - $self->{'tick_len'};
102              
103             for ( 0 .. $#labels )
104             {
105             $label = $self->{'y_tick_labels'}[$_];
106             $x2 = $x1 + ( $delta * $_ ) - ( $w * length($label) / 2 );
107             unless ( $_ % 2 )
108             {
109             $y2 = $y1 + $self->{'text_space'} + $self->{'tick_len'};
110             $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );
111             }
112             else
113             {
114             $y2 = $y1 + $h + 2 * $self->{'text_space'} + $self->{'tick_len'};
115             $self->{'gd_obj'}->string( $font, $x2, $y2, $label, $textcolor );
116             }
117             }
118              
119             }
120              
121             elsif ( $self->{'x_ticks'} =~ /^vertical/i )
122             { #vertical ticks
123             #get the point for updating later
124             $y1 = $self->{'curr_y_max'} - 2 * $self->{'text_space'} - $w * $self->{'y_tick_label_length'} - $self->{'tick_len'};
125              
126             for ( 0 .. $#labels )
127             {
128             $label = $self->{'y_tick_labels'}[$_];
129              
130             #get the start point
131             $y2 = $y1 + $self->{'tick_len'} + $w * length($label) + $self->{'text_space'};
132              
133             $x2 = $x1 + ( $delta * $_ ) - ( $h / 2 );
134             $self->{'gd_obj'}->stringUp( $font, $x2, $y2, $label, $textcolor );
135             }
136              
137             }
138              
139             else
140             {
141             carp "I don't understand the type of x-ticks you specified";
142             }
143              
144             #update the curr x and y max value
145             $self->{'curr_y_max'} = $y1;
146             $self->{'curr_x_max'} = $x1 + $width;
147              
148             #draw the ticks
149             $y1 = $self->{'curr_y_max'};
150             $y2 = $self->{'curr_y_max'} + $self->{'tick_len'};
151             for ( 0 .. $#labels )
152             {
153             $x2 = $x1 + ( $delta * $_ );
154             $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
155             if ( $self->true( $self->{'grid_lines'} )
156             or $self->true( $self->{'x_grid_lines'} ) )
157             {
158             $self->{'grid_data'}->{'x'}->[$_] = $x2;
159             }
160             }
161              
162             return 1;
163             }
164              
165             ## @fn private int _draw_y_ticks()
166             # draw the y-ticks and their labels
167             # Overwrites this function of Chart::Base
168             # @return status
169             sub _draw_y_ticks
170             {
171             my $self = shift;
172             my $side = shift || 'left';
173             my $data = $self->{'dataref'};
174             my $font = $self->{'tick_label_font'};
175             my $textcolor = $self->_color_role_to_index('text');
176             my $misccolor = $self->_color_role_to_index('misc');
177             my ( $h, $w, $x1, $x2, $y1, $y2 );
178             my ( $width, $height, $delta );
179              
180             $self->{'grid_data'}->{'y'} = [];
181              
182             #make sure that is a real font
183             unless ( ( ref $font ) eq 'GD::Font' )
184             {
185             croak "The tick label font isn't a GD Font object!";
186             }
187              
188             #get the size of the font
189             ( $h, $w ) = ( $font->height, $font->width );
190              
191             #figure out, where to draw
192             if ( $side =~ /^right$/i )
193             {
194              
195             #get the right startposition
196             $x1 = $self->{'curr_x_max'};
197             $y1 = $self->{'curr_y_max'} - $h / 2;
198              
199             #get the delta values
200             $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
201             $delta = ($height) / ( $self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1 );
202             $y1 -= ( $delta / 2 );
203              
204             #look if skipping is desired
205             if ( !defined( $self->{'skip_y_ticks'} ) )
206             {
207             $self->{'skip_y_ticks'} = 1;
208             }
209              
210             #draw the labels
211             for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_y_ticks'} ) )
212             {
213             $y2 = $y1 - ($delta) * ( $_ * $self->{'skip_y_ticks'} );
214             $x2 = $x1 + $self->{'tick_len'} + $self->{'text_space'};
215             $self->{'gd_obj'}
216             ->string( $font, $x2, $y2, $self->{f_y_tick}->( $data->[0][ $_ * $self->{'skip_y_ticks'} ] ), $textcolor );
217             }
218              
219             #draw the ticks
220             $x1 = $self->{'curr_x_max'};
221             $x2 = $self->{'curr_x_max'} + $self->{'tick_len'};
222             $y1 += $h / 2;
223             for ( 0 .. ( $self->{'num_datapoints'} - 1 / $self->{'skip_y_ticks'} ) )
224             {
225             $y2 = $y1 - ( $delta * $_ );
226             $self->{'gd_obj'}->line( $x1, $y2, $x2, $y2, $misccolor );
227             if ( $self->true( $self->{'grid_lines'} )
228             or $self->true( $self->{'x_grid_lines'} ) )
229             {
230             $self->{'grid_data'}->{'y'}->[$_] = $y2;
231             }
232             }
233              
234             }
235             elsif ( $side =~ /^both$/i )
236             {
237              
238             #get the right startposition
239             $x1 = $self->{'curr_x_max'};
240             $y1 = $self->{'curr_y_max'} - $h / 2;
241              
242             #get the delta values
243             $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
244             $delta = ($height) / ( $self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1 );
245             $y1 -= ( $delta / 2 );
246              
247             #look if skipping is desired
248             if ( !defined( $self->{'skip_y_ticks'} ) )
249             {
250             $self->{'skip_y_ticks'} = 1;
251             }
252              
253             #first draw the right labels
254             for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_y_ticks'} ) )
255             {
256             $y2 = $y1 - ($delta) * ( $_ * $self->{'skip_y_ticks'} );
257             $x2 = $x1 + $self->{'tick_len'} + $self->{'text_space'};
258             $self->{'gd_obj'}
259             ->string( $font, $x2, $y2, $self->{f_y_tick}->( $data->[0][ $_ * $self->{'skip_y_ticks'} ] ), $textcolor );
260             }
261              
262             #then draw the right ticks
263             $x1 = $self->{'curr_x_max'};
264             $x2 = $self->{'curr_x_max'} + $self->{'tick_len'};
265             $y1 += $h / 2;
266             for ( 0 .. ( $self->{'num_datapoints'} - 1 / $self->{'skip_y_ticks'} ) )
267             {
268             $y2 = $y1 - ( $delta * $_ );
269             $self->{'gd_obj'}->line( $x1, $y2, $x2, $y2, $misccolor );
270             if ( $self->true( $self->{'grid_lines'} )
271             or $self->true( $self->{'x_grid_lines'} ) )
272             {
273             $self->{'grid_data'}->{'y'}->[$_] = $y2;
274             }
275             }
276              
277             #get the right startposition
278             $x1 = $self->{'curr_x_min'};
279             $y1 = $self->{'curr_y_max'} - $h / 2;
280              
281             #get the delta values for positioning
282             $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
283             $delta = ($height) / ( $self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1 );
284             $y1 -= ( $delta / 2 );
285              
286             #then draw the left labels
287             for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_y_ticks'} ) )
288             {
289             $y2 = $y1 - ($delta) * ( $_ * $self->{'skip_y_ticks'} );
290             $x2 =
291             $x1 -
292             $w * length( $self->{f_y_tick}->( $data->[0][ $_ * $self->{'skip_y_ticks'} ] ) ) #print the Labels right-sided
293             + $w * $self->{'x_tick_label_length'};
294             $self->{'gd_obj'}
295             ->string( $font, $x2, $y2, $self->{f_y_tick}->( $data->[0][ $_ * $self->{'skip_y_ticks'} ] ), $textcolor );
296             }
297              
298             #update the curr_x_min val
299             $self->{'curr_x_min'} = $x1 + $self->{'text_space'} + $w * $self->{'x_tick_label_length'} + $self->{'tick_len'};
300              
301             #finally draw the left ticks
302             $x1 = $self->{'curr_x_min'};
303             $x2 = $self->{'curr_x_min'} - $self->{'tick_len'};
304             $y1 += $h / 2;
305             for ( 0 .. ( $self->{'num_datapoints'} - 1 / $self->{'skip_y_ticks'} ) )
306             {
307             $y2 = $y1 - ( $delta * $_ );
308             $self->{'gd_obj'}->line( $x1, $y2, $x2, $y2, $misccolor );
309             if ( $self->true( $self->{'grid_lines'} )
310             or $self->true( $self->{'x_grid_lines'} ) )
311             {
312             $self->{'grid_data'}->{'y'}->[$_] = $y2;
313             }
314             }
315             }
316              
317             else
318             {
319              
320             #get the right startposition
321             $x1 = $self->{'curr_x_min'};
322             $y1 = $self->{'curr_y_max'} - $h / 2;
323              
324             #get the delta values for positioning
325             $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
326             $delta = ($height) / ( $self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1 );
327             $y1 -= ( $delta / 2 );
328              
329             if ( !defined( $self->{'skip_y_ticks'} ) )
330             {
331             $self->{'skip_y_ticks'} = 1;
332             }
333              
334             #draw the labels
335             for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_y_ticks'} ) )
336             {
337             $y2 = $y1 - ($delta) * ( $_ * $self->{'skip_y_ticks'} );
338             $x2 =
339             $x1 -
340             $w * length( $self->{f_y_tick}->( $data->[0][ $_ * $self->{'skip_y_ticks'} ] ) ) #print the Labels right-sided
341             + $w * $self->{'x_tick_label_length'};
342             $self->{'gd_obj'}
343             ->string( $font, $x2, $y2, $self->{f_y_tick}->( $data->[0][ $_ * $self->{'skip_y_ticks'} ] ), $textcolor );
344             }
345              
346             #update the curr_x_min val
347             $self->{'curr_x_min'} = $x1 + $self->{'text_space'} + $w * $self->{'x_tick_label_length'} + $self->{'tick_len'};
348              
349             #draw the ticks
350             $x1 = $self->{'curr_x_min'};
351             $x2 = $self->{'curr_x_min'} - $self->{'tick_len'};
352             $y1 += $h / 2;
353             for ( 0 .. ( $self->{'num_datapoints'} - 1 / $self->{'skip_y_ticks'} ) )
354             {
355             $y2 = $y1 - ( $delta * $_ );
356             $self->{'gd_obj'}->line( $x1, $y2, $x2, $y2, $misccolor );
357             if ( $self->true( $self->{'grid_lines'} )
358             or $self->true( $self->{'x_grid_lines'} ) )
359             {
360             $self->{'grid_data'}->{'y'}->[$_] = $y2;
361             }
362             }
363             }
364              
365             #now return
366             return 1;
367             }
368              
369             ## @fn private int _find_y_scale()
370             # find good values for the minimum and maximum y-value on the chart
371             # overwrite the find_y_scale function, only to get the right f_x_ticks !!!!!
372             # @return status
373             sub _find_y_scale
374             {
375             my $self = shift;
376              
377             # Predeclare vars.
378             my ( $d_min, $d_max ); # Dataset min & max.
379             my ( $p_min, $p_max ); # Plot min & max.
380             my ( $tickInterval, $tickCount );
381             my @tickLabels; # List of labels for each tick.
382             my $maxtickLabelLen = 0; # The length of the longest tick label.
383              
384             # Find the datatset minimum and maximum.
385             ( $d_min, $d_max ) = $self->_find_y_range();
386              
387             # Force the inclusion of zero if the user has requested it.
388             if ( $self->true( $self->{'include_zero'} ) )
389             {
390             if ( ( $d_min * $d_max ) > 0 ) # If both are non zero and of the same sign.
391             {
392             if ( $d_min > 0 ) # If the whole scale is positive.
393             {
394             $d_min = 0;
395             }
396             else # The scale is entirely negative.
397             {
398             $d_max = 0;
399             }
400             }
401             }
402              
403             if ( $self->{'integer_ticks_only'} =~ /^\d$/ )
404             {
405             if ( $self->{'integer_ticks_only'} == 1 )
406             {
407             $self->{'integer_ticks_only'} = 'true';
408             }
409             else
410             {
411             $self->{'integer_ticks_only'} = 'false';
412             }
413             }
414             if ( $self->true( $self->{'integer_ticks_only'} ) )
415             {
416              
417             # Allow the dataset range to be overidden by the user.
418             # f_min/max are booleans which indicate that the min & max should not be modified.
419             my $f_min = defined $self->{'min_val'};
420             $d_min = $self->{'min_val'} if $f_min;
421              
422             my $f_max = defined $self->{'max_val'};
423             $d_max = $self->{'max_val'} if $f_max;
424              
425             # Assert against the min is larger than the max.
426             if ( $d_min > $d_max )
427             {
428             croak "The the specified 'min_val' & 'max_val' values are reversed (min > max: $d_min>$d_max)";
429             }
430              
431             # The user asked for integer ticks, force the limits to integers.
432             # & work out the range directly.
433             $p_min = $self->_round2Tick( $d_min, 1, -1 );
434             $p_max = $self->_round2Tick( $d_max, 1, 1 );
435              
436             my $skip = $self->{skip_int_ticks};
437              
438             $tickInterval = $skip;
439             $tickCount = ( $p_max - $p_min ) / $skip + 1;
440              
441             # Now sort out an array of tick labels.
442              
443             for ( my $labelNum = $p_min ; $labelNum <= $p_max ; $labelNum += $tickInterval )
444             {
445             my $labelText;
446              
447             if ( defined $self->{f_x_tick} )
448             {
449              
450             # Is _default_f_tick function used?
451             if ( $self->{f_x_tick} == \&_default_f_tick )
452             {
453             $labelText = sprintf( "%d", $labelNum );
454             }
455             else
456             {
457             $labelText = $self->{f_x_tick}->($labelNum);
458             }
459             }
460              
461             else
462             {
463             $labelText = sprintf( "%d", $labelNum );
464             }
465              
466             #print "labelText = $labelText\n";
467             push @tickLabels, $labelText;
468             $maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
469             }
470              
471             }
472             else
473             {
474              
475             # Allow the dataset range to be overidden by the user.
476             # f_min/max are booleans which indicate that the min & max should not be modified.
477             my $f_min = defined $self->{'min_val'};
478             $d_min = $self->{'min_val'} if $f_min;
479              
480             my $f_max = defined $self->{'max_val'};
481             $d_max = $self->{'max_val'} if $f_max;
482              
483             # Assert against the min is larger than the max.
484             if ( $d_min > $d_max )
485             {
486             croak "The the specified 'min_val' & 'max_val' values are reversed (min > max: $d_min>$d_max)";
487             }
488              
489             # Calculate the width of the dataset. (posibly modified by the user)
490             my $d_width = $d_max - $d_min;
491              
492             # If the width of the range is zero, forcibly widen it
493             # (to avoid division by zero errors elsewhere in the code).
494             if ( 0 == $d_width )
495             {
496             $d_min--;
497             $d_max++;
498             $d_width = 2;
499             }
500              
501             # Descale the range by converting the dataset width into
502             # a floating point exponent & mantisa pair.
503             my ( $rangeExponent, $rangeMantisa ) = $self->_sepFP($d_width);
504             my $rangeMuliplier = 10**$rangeExponent;
505              
506             # Find what tick
507             # to use & how many ticks to plot,
508             # round the plot min & max to suatable round numbers.
509             ( $tickInterval, $tickCount, $p_min, $p_max ) = $self->_calcTickInterval(
510             $d_min / $rangeMuliplier,
511             $d_max / $rangeMuliplier,
512             $f_min, $f_max,
513             $self->{'min_y_ticks'},
514             $self->{'max_y_ticks'}
515             );
516              
517             # Restore the tickInterval etc to the correct scale
518             $_ *= $rangeMuliplier foreach ( $tickInterval, $p_min, $p_max );
519              
520             #get teh precision for the labels
521             my $precision = $self->{'precision'};
522              
523             # Now sort out an array of tick labels.
524             for ( my $labelNum = $p_min ; $labelNum <= $p_max ; $labelNum += $tickInterval )
525             {
526             my $labelText;
527              
528             if ( defined $self->{f_x_tick} )
529             {
530              
531             # Is _default_f_tick function used?
532             if ( $self->{f_x_tick} == \&_default_f_tick )
533             {
534             $labelText = sprintf( "%." . $precision . "f", $labelNum );
535             }
536             else
537             {
538             $labelText = $self->{f_x_tick}->($labelNum);
539             }
540             }
541             else
542             {
543             $labelText = sprintf( "%." . $precision . "f", $labelNum );
544             }
545              
546             #print "labelText = $labelText\n";
547             push @tickLabels, $labelText;
548             $maxtickLabelLen = length $labelText if $maxtickLabelLen < length $labelText;
549             }
550             }
551              
552             # Store the calculated data.
553             $self->{'min_val'} = $p_min;
554             $self->{'max_val'} = $p_max;
555             $self->{'y_ticks'} = $tickCount;
556             $self->{'y_tick_labels'} = \@tickLabels;
557             $self->{'y_tick_label_length'} = $maxtickLabelLen;
558              
559             # and return.
560             return 1;
561             }
562              
563             ## @fn private _draw_data
564             # finally get around to plotting the data for (horizontal) bars
565             sub _draw_data
566             {
567             my $self = shift;
568             my $data = $self->{'dataref'};
569             my $misccolor = $self->_color_role_to_index('misc');
570             my ( $x1, $x2, $x3, $y1, $y2, $y3 );
571             my $cut = 0;
572             my ( $width, $height, $delta1, $delta2, $map, $mod, $pink );
573             my ( $i, $j, $color );
574              
575             # init the imagemap data field if they wanted it
576             if ( $self->true( $self->{'imagemap'} ) )
577             {
578             $self->{'imagemap_data'} = [];
579             }
580              
581             # find both delta values ($delta1 for stepping between different
582             # datapoint names, $delta2 for setpping between datasets for that
583             # point) and the mapping constant
584             $width = $self->{'curr_x_max'} - $self->{'curr_x_min'};
585             $height = $self->{'curr_y_max'} - $self->{'curr_y_min'};
586             $delta1 = $height / ( $self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1 );
587             $map = $width / ( $self->{'max_val'} - $self->{'min_val'} );
588             if ( $self->true( $self->{'spaced_bars'} ) )
589             {
590             $delta2 = $delta1 / ( $self->{'num_datasets'} + 2 );
591             }
592             else
593             {
594             $delta2 = $delta1 / $self->{'num_datasets'};
595             }
596              
597             # get the base x-y values
598             $y1 = $self->{'curr_y_max'} - $delta2;
599             if ( $self->{'min_val'} >= 0 )
600             {
601             $x1 = $self->{'curr_x_min'};
602             $mod = $self->{'min_val'};
603             }
604             elsif ( $self->{'max_val'} <= 0 )
605             {
606             $x1 = $self->{'curr_x_max'};
607             $mod = $self->{'max_val'};
608             }
609             else
610             {
611             $x1 = $self->{'curr_x_min'} + abs( $map * $self->{'min_val'} );
612             $mod = 0;
613             $self->{'gd_obj'}->line( $x1, $self->{'curr_y_min'}, $x1, $self->{'curr_y_max'}, $misccolor );
614             }
615              
616             # draw the bars
617             for $i ( 1 .. $self->{'num_datasets'} )
618             {
619              
620             # get the color for this dataset
621             $color = $self->_color_role_to_index( 'dataset' . ( $i - 1 ) );
622              
623             # draw every bar for this dataset
624             for $j ( 0 .. $self->{'num_datapoints'} )
625             {
626              
627             # don't try to draw anything if there's no data
628             if ( defined( $data->[$i][$j] ) )
629             {
630              
631             # find the bounds of the rectangle
632             if ( $self->true( $self->{'spaced_bars'} ) )
633             {
634             $y2 = $y1 - ( $j * $delta1 ) - ( $self->{'num_datasets'} * $delta2 ) + ( ( $i - 1 ) * $delta2 );
635             }
636             else
637             {
638             $y2 = $y1 - ( $j * $delta1 ) - ( $self->{'num_datasets'} * $delta2 ) + ( ($i) * $delta2 );
639             }
640             $x2 = $x1;
641             $y3 = $y2 + $delta2;
642              
643             #cut the bars off, if needed
644             if ( $data->[$i][$j] > $self->{'max_val'} )
645             {
646             $x3 = $x1 + ( ( $self->{'max_val'} - $mod ) * $map ) - 1;
647             $cut = 1;
648             }
649             elsif ( $data->[$i][$j] < $self->{'min_val'} )
650             {
651             $x3 = $x1 + ( ( $self->{'min_val'} - $mod ) * $map ) + 1;
652             $cut = 1;
653             }
654             else
655             {
656             $x3 = $x1 + ( ( $data->[$i][$j] - $mod ) * $map );
657             $cut = 0;
658             }
659              
660             # draw the bar
661             ## y2 and y3 are reversed in some cases because GD's fill
662             ## algorithm is lame
663             if ( $data->[$i][$j] < 0 )
664             {
665             $self->{'gd_obj'}->filledRectangle( $x3, $y2, $x2, $y3, $color );
666             if ( $self->true( $self->{'imagemap'} ) )
667             {
668             $self->{'imagemap_data'}->[$i][$j] = [ $x3, $y2, $x2, $y3 ];
669             }
670              
671             $self->{'gd_obj'}->filledRectangle( $x3, $y2, $x2, $y3, $color );
672             if ( $self->true( $self->{'imagemap'} ) )
673             {
674             $self->{'imagemap_data'}->[$i][$j] = [ $x3, $y2, $x2, $y3 ];
675             }
676             }
677             else
678             {
679             $self->{'gd_obj'}->filledRectangle( $x2, $y2, $x3, $y3, $color );
680             if ( $self->true( $self->{'imagemap'} ) )
681             {
682             $self->{'imagemap_data'}->[$i][$j] = [ $x2, $y2, $x3, $y3 ];
683             }
684             }
685              
686             # now outline it. outline red if the bar had been cut off
687             unless ($cut)
688             {
689             $self->{'gd_obj'}->rectangle( $x2, $y3, $x3, $y2, $misccolor );
690             }
691             else
692             {
693             $pink = $self->{'gd_obj'}->colorAllocate( 255, 0, 255 );
694             $self->{'gd_obj'}->rectangle( $x2, $y3, $x3, $y2, $pink );
695             }
696              
697             }
698             else
699             {
700             if ( $self->true( $self->{'imagemap'} ) )
701             {
702             $self->{'imagemap_data'}->[$i][$j] = [ undef(), undef(), undef(), undef() ];
703             }
704             }
705             }
706             }
707              
708             # and finaly box it off
709             $self->{'gd_obj'}
710             ->rectangle( $self->{'curr_x_min'}, $self->{'curr_y_min'}, $self->{'curr_x_max'}, $self->{'curr_y_max'}, $misccolor );
711             return;
712              
713             }
714              
715             ## be a good module and return 1
716             1;