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#
15package Chart::HorizontalBars;
16
17use Chart::Base '2.4.10';
18use GD;
19use Carp;
20use 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#
38sub _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
169sub _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
373sub _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
565sub _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
7161;
717