1## @file
2# Implementation of Chart::Composite
3#
4# written by
5# @author david bonner (dbonner@cs.bu.edu)
6#
7# maintained by the
8# @author Chart Group at Geodetic Fundamental Station Wettzell (Chart@fs.wettzell.de)
9# @date 2015-03-01
10# @version 2.4.10
11#
12#---------------------------------------------------------------------
13# History:
14#----------
15
16## @class Chart::Composite
17# Composite class derived from class Base.\n
18# This class provides all functions which are specific to
19# composite charts
20
21package Chart::Composite;
22
23use Chart::Base '2.4.10';
24use GD;
25use Carp;
26use strict;
27
28@Chart::Composite::ISA     = qw(Chart::Base);
29$Chart::Composite::VERSION = '2.4.10';
30
31#>>>>>>>>>>>>>>>>>>>>>>>>>>#
32#  public methods go here  #
33#<<<<<<<<<<<<<<<<<<<<<<<<<<#
34
35## @method int set(%opts)
36# @param[in] %opts Hash of options to the Chart
37# @return ok or croak
38#
39# @brief
40# Set all options
41#
42# @details
43# Overwrite the set function of class Base to pass
44# options to the sub-objects later
45sub set
46{
47    my $self = shift;
48    my %opts = @_;
49
50    # basic error checking on the options, just warn 'em
51    unless ( $#_ % 2 )
52    {
53        carp "Whoops, some option to be set didn't have a value.\n", "You might want to look at that.\n";
54    }
55
56    # store the options they gave us
57    unless ( $self->{'opts'} )
58    {
59        $self->{'opts'} = {};
60    }
61
62    # now set 'em
63    for ( keys %opts )
64    {
65        $self->{$_} = $opts{$_};
66        $self->{'opts'}{$_} = $opts{$_};
67    }
68
69    # now return
70    return;
71}
72
73## @fn imagemap_dump()
74# @brief
75# Overwrite function imagemap_dump of base class
76#
77# @details
78# Get the information to turn the chart into an imagemap
79# had to override it to reassemble the \@data array correctly
80#
81# @return Reference to an array of the image
82sub imagemap_dump
83{
84    my $self = shift;
85    my ( $i, $j );
86    my @map;
87    my $dataset_count = 0;
88
89    # croak if they didn't ask me to remember the data, or if they're asking
90    # for the data before I generate it
91    unless ( ( $self->true( $self->{'imagemap'} ) ) && $self->{'imagemap_data'} )
92    {
93        croak "You need to set the imagemap option to true, and then call the png method, before you can get the imagemap data";
94    }
95
96    #make a copy of the imagemap data
97    #this is the data of the first component
98    for $i ( 1 .. $#{ $self->{'sub_0'}->{'imagemap_data'} } )
99    {
100        for $j ( 0 .. $#{ $self->{'sub_0'}->{'imagemap_data'}->[$i] } )
101        {
102            $map[$i][$j] = \@{ $self->{'sub_0'}->{'imagemap_data'}->[$i][$j] };
103        }
104        $dataset_count++;
105    }
106
107    #and add the data of the second component
108    for $i ( 1 .. $#{ $self->{'sub_1'}->{'imagemap_data'} } )
109    {
110        for $j ( 0 .. $#{ $self->{'sub_1'}->{'imagemap_data'}->[$i] } )
111        {
112            $map[ $i + $dataset_count ][$j] = \@{ $self->{'sub_1'}->{'imagemap_data'}->[$i][$j] };
113        }
114    }
115
116    # return their copy
117    return \@map;
118
119}
120
121# private routine
122sub __print_array
123{
124    my @a = @_;
125    my $i;
126
127    my $li = $#a;
128
129    $li++;
130    print STDERR "Anzahl der Elemente = $li\n";
131    $li--;
132
133    for ( $i = 0 ; $i <= $li ; $i++ )
134    {
135        print STDERR "\t$i\t$a[$i]\n";
136    }
137}
138
139#>>>>>>>>>>>>>>>>>>>>>>>>>>>#
140#  private methods go here  #
141#<<<<<<<<<<<<<<<<<<<<<<<<<<<#
142
143## @fn private int _check_data
144# Overwrite _check_data of Chart::Base and check the internal data to be displayed.
145#
146# Make sure the data isn't really weird
147#  and collect some basic info about it\n
148# @return status of check
149sub _check_data
150{
151    my $self   = shift;
152    my $length = 0;
153
154    # first things first, make sure we got the composite_info
155    unless ( ( $self->{'composite_info'} ) && ( $#{ $self->{'composite_info'} } == 1 ) )
156    {
157        croak "Chart::Composite needs to be told what kind of components to use";
158    }
159
160    # make sure we don't end up dividing by zero if they ask for
161    # just one y_tick
162    if ( $self->{'y_ticks'} == 1 )
163    {
164        $self->{'y_ticks'} = 2;
165        carp "The number of y_ticks displayed must be at least 2";
166    }
167
168    # remember the number of datasets
169    $self->{'num_datasets'} = $#{ $self->{'dataref'} };
170
171    # remember the number of points in the largest dataset
172    $self->{'num_datapoints'} = 0;
173    for ( 0 .. $self->{'num_datasets'} )
174    {
175        if ( scalar( @{ $self->{'dataref'}[$_] } ) > $self->{'num_datapoints'} )
176        {
177            $self->{'num_datapoints'} = scalar( @{ $self->{'dataref'}[$_] } );
178        }
179    }
180
181    # find the longest x-tick label, and remember how long it is
182    for ( @{ $self->{'dataref'}[0] } )
183    {
184        if ( length($_) > $length )
185        {
186            $length = length($_);
187        }
188    }
189    $self->{'x_tick_label_length'} = $length;
190
191    # now split the data into sub-objects
192    $self->_split_data;
193
194    return;
195}
196
197## @fn private _split_data
198# split data to the composited classes
199#
200# create sub-objects for each type, store the appropriate
201# data sets in each one, and stick the correct values into
202# them (ie. 'gd_obj');
203sub _split_data
204{
205    my $self = shift;
206    my @types = ( $self->{'composite_info'}[0][0], $self->{'composite_info'}[1][0] );
207    my ( $ref, $i, $j );
208
209    # Already checked for number of components in _check_data, above.
210    # we can only do two at a time
211    #   if ($self->{'composite_info'}[2]) {
212    #     croak "Sorry, Chart::Composite can only do two chart types at a time";
213    #   }
214
215    # load the individual modules
216    require "Chart/" . $types[0] . ".pm";
217    require "Chart/" . $types[1] . ".pm";
218
219    # create the sub-objects
220    $self->{'sub_0'} = ( "Chart::" . $types[0] )->new();
221    $self->{'sub_1'} = ( "Chart::" . $types[1] )->new();
222
223    # set the options (set the min_val, max_val, brush_size, y_ticks,
224    #
225    # options intelligently so that the sub-objects don't get
226    # confused)
227    $self->{'sub_0'}->set( %{ $self->{'opts'} } );
228    $self->{'sub_1'}->set( %{ $self->{'opts'} } );
229    if ( defined( $self->{'opts'}{'min_val1'} ) )
230    {
231        $self->{'sub_0'}->set( 'min_val' => $self->{'opts'}{'min_val1'} );
232    }
233    if ( defined( $self->{'opts'}{'max_val1'} ) )
234    {
235        $self->{'sub_0'}->set( 'max_val' => $self->{'opts'}{'max_val1'} );
236    }
237    if ( defined( $self->{'opts'}{'min_val2'} ) )
238    {
239        $self->{'sub_1'}->set( 'min_val' => $self->{'opts'}{'min_val2'} );
240    }
241    if ( defined( $self->{'opts'}{'max_val2'} ) )
242    {
243        $self->{'sub_1'}->set( 'max_val' => $self->{'opts'}{'max_val2'} );
244    }
245    if ( $self->{'opts'}{'y_ticks1'} )
246    {
247        $self->{'sub_0'}->set( 'y_ticks' => $self->{'opts'}{'y_ticks1'} );
248    }
249    if ( $self->{'opts'}{'y_ticks2'} )
250    {
251        $self->{'sub_1'}->set( 'y_ticks' => $self->{'opts'}{'y_ticks2'} );
252    }
253    if ( $self->{'opts'}{'brush_size1'} )
254    {
255        $self->{'sub_0'}->set( 'brush_size' => $self->{'opts'}{'brush_size1'} );
256    }
257    if ( $self->{'opts'}{'brush_size2'} )
258    {
259        $self->{'sub_1'}->set( 'brush_size' => $self->{'opts'}{'brush_size2'} );
260    }
261
262    if ( $self->{'opts'}{'brushStyle1'} )
263    {
264        $self->{'sub_0'}->set( 'brushStyle' => $self->{'opts'}{'brushStyle1'} );
265    }
266    if ( $self->{'opts'}{'brushStyle2'} )
267    {
268        $self->{'sub_1'}->set( 'brushStyle' => $self->{'opts'}{'brushStyle2'} );
269    }
270
271    #  f_y_tick for left and right axis
272    if ( defined( $self->{'opts'}{'f_y_tick1'} ) )
273    {
274        $self->{'sub_0'}->set( 'f_y_tick' => $self->{'opts'}{'f_y_tick1'} );
275    }
276    if ( defined( $self->{'opts'}{'f_y_tick2'} ) )
277    {
278        $self->{'sub_1'}->set( 'f_y_tick' => $self->{'opts'}{'f_y_tick2'} );
279    }
280
281    # replace the gd_obj fields
282    $self->{'sub_0'}->{'gd_obj'} = $self->{'gd_obj'};
283    $self->{'sub_1'}->{'gd_obj'} = $self->{'gd_obj'};
284
285    # let the sub-objects know they're sub-objects
286    $self->{'sub_0'}->{'component'} = 'true';
287    $self->{'sub_1'}->{'component'} = 'true';
288
289    # give each sub-object its data
290    $self->{'component_datasets'} = [];
291    for $i ( 0 .. 1 )
292    {
293        $ref = [];
294        $self->{'component_datasets'}[$i] = $self->{'composite_info'}[$i][1];
295        push @{$ref}, $self->{'dataref'}[0];
296        for $j ( @{ $self->{'composite_info'}[$i][1] } )
297        {
298            $self->_color_role_to_index( 'dataset' . ( $j - 1 ) );    # allocate color index
299            push @{$ref}, $self->{'dataref'}[$j];
300        }
301        $self->{ 'sub_' . $i }->_copy_data($ref);
302    }
303
304    # and let them check it
305    $self->{'sub_0'}->_check_data;
306    $self->{'sub_1'}->_check_data;
307
308    # realign the y-axes if they want
309    if ( $self->true( $self->{'same_y_axes'} ) )
310    {
311        if ( $self->{'sub_0'}{'min_val'} < $self->{'sub_1'}{'min_val'} )
312        {
313            $self->{'sub_1'}{'min_val'} = $self->{'sub_0'}{'min_val'};
314        }
315        else
316        {
317            $self->{'sub_0'}{'min_val'} = $self->{'sub_1'}{'min_val'};
318        }
319
320        if ( $self->{'sub_0'}{'max_val'} > $self->{'sub_1'}{'max_val'} )
321        {
322            $self->{'sub_1'}{'max_val'} = $self->{'sub_0'}{'max_val'};
323        }
324        else
325        {
326            $self->{'sub_0'}{'max_val'} = $self->{'sub_1'}{'max_val'};
327        }
328
329        $self->{'sub_0'}->_check_data;
330        $self->{'sub_1'}->_check_data;
331    }
332
333    # find out how big the y-tick labels will be from sub_0 and sub_1
334    $self->{'y_tick_label_length1'} = $self->{'sub_0'}->{'y_tick_label_length'};
335    $self->{'y_tick_label_length2'} = $self->{'sub_1'}->{'y_tick_label_length'};
336
337    # now return
338    return;
339}
340
341## @fn private int _draw_legend()
342# let the user know what all the pretty colors mean
343# @return status
344#
345sub _draw_legend
346{
347    my $self = shift;
348    my ($length);
349
350    # check to see if they have as many labels as datasets,
351    # warn them if not
352    if (   ( $#{ $self->{'legend_labels'} } >= 0 )
353        && ( ( scalar( @{ $self->{'legend_labels'} } ) ) != $self->{'num_datasets'} ) )
354    {
355        carp "The number of legend labels and datasets doesn\'t match";
356    }
357
358    # init a field to store the length of the longest legend label
359    unless ( $self->{'max_legend_label'} )
360    {
361        $self->{'max_legend_label'} = 0;
362    }
363
364    # fill in the legend labels, find the longest one
365    for ( 1 .. $self->{'num_datasets'} )
366    {
367        unless ( $self->{'legend_labels'}[ $_ - 1 ] )
368        {
369            $self->{'legend_labels'}[ $_ - 1 ] = "Dataset $_";
370        }
371        $length = length( $self->{'legend_labels'}[ $_ - 1 ] );
372        if ( $length > $self->{'max_legend_label'} )
373        {
374            $self->{'max_legend_label'} = $length;
375        }
376    }
377
378    # different legend types
379    if ( $self->{'legend'} eq 'bottom' )
380    {
381        $self->_draw_bottom_legend;
382    }
383    elsif ( $self->{'legend'} eq 'right' )
384    {
385        $self->_draw_right_legend;
386    }
387    elsif ( $self->{'legend'} eq 'left' )
388    {
389        $self->_draw_left_legend;
390    }
391    elsif ( $self->{'legend'} eq 'top' )
392    {
393        $self->_draw_top_legend;
394    }
395    elsif ( $self->{'legend'} eq 'none' )
396    {
397        $self->_draw_none_legend;
398    }
399    else
400    {
401        carp "I can't put a legend there\n";
402    }
403
404    # and return
405    return 1;
406}
407
408## @fn private int _draw_top_legend()
409# put the legend on the top of the data plot
410#
411# Overwrite the base class _draw_top_legend
412#
413# @return status
414sub _draw_top_legend
415{
416    my $self   = shift;
417    my @labels = @{ $self->{'legend_labels'} };
418    my ( $x1, $y1, $x2, $y2, $empty_width, $max_label_width );
419    my ( $cols, $rows, $color );
420    my ( $col_width, $row_height, $i, $j, $r, $c, $index, $x, $y, $sub, $w, $h );
421    my ( $yh, $yi );    #  for boxing legends
422    my $font = $self->{'legend_font'};
423    my ( %colors, @datasets );
424    my $max_legend_example = 0;
425    $yh = 0;
426
427    # copy the current boundaries into the sub-objects
428    $self->_sub_update;
429
430    # init the legend_example_height
431    $self->_legend_example_height_init;
432
433    ## Make datasetI numbers match indexes of @{ $self->{'dataref'} }[1.....].
434    #   # modify the dataset color table entries to avoid duplicating
435    #   # dataset colors (this limits the number of possible data sets
436    #   # for each component to 8)
437    #   for (0..7) {
438    #     $self->{'sub_1'}{'color_table'}{'dataset'.$_}
439    #       = $self->{'color_table'}{'dataset'.($_+8)};
440    #   }
441
442    # modify the dataset color table entries to avoid duplicating
443    # dataset colors.
444    my ( $n0, $n1 ) = map { scalar @{ $self->{'composite_info'}[$_][1] } } 0 .. 1;
445    for ( 0 .. $n1 - 1 )
446    {
447        $self->{'sub_1'}{'color_table'}{ 'dataset' . $_ } = $self->{'color_table'}{ 'dataset' . ( $_ + $n0 ) };
448    }
449
450    # make sure we use the right colors for the legend
451    @datasets = @{ $self->{'composite_info'}[0][1] };
452    $i        = 0;
453    for ( 0 .. $#datasets )
454    {
455        $colors{ $datasets[$_] - 1 } = $self->{'color_table'}{ 'dataset' . ($i) };
456        $i++;
457    }
458    @datasets = @{ $self->{'composite_info'}[1][1] };
459    $i        = 0;
460    for ( 0 .. $#datasets )
461    {
462        $colors{ $datasets[$_] - 1 } = $self->{'color_table'}{ 'dataset' . ( $i + $n0 ) };
463        $i++;
464    }
465
466    # make sure we're using a real font
467    unless ( ( ref($font) ) eq 'GD::Font' )
468    {
469        croak "The subtitle font you specified isn\'t a GD Font object";
470    }
471
472    # get the size of the font
473    ( $h, $w ) = ( $font->height, $font->width );
474
475    # get some base x coordinates
476    $x1 =
477      $self->{'curr_x_min'} +
478      $self->{'graph_border'} +
479      $self->{'y_tick_label_length1'} * $self->{'tick_label_font'}->width +
480      $self->{'tick_len'} +
481      ( 3 * $self->{'text_space'} );
482    $x2 =
483      $self->{'curr_x_max'} -
484      $self->{'graph_border'} -
485      $self->{'y_tick_label_length2'} * $self->{'tick_label_font'}->width -
486      $self->{'tick_len'} -
487      ( 3 * $self->{'text_space'} );
488    if ( $self->{'y_label'} )
489    {
490        $x1 += $self->{'label_font'}->height + 2 * $self->{'text_space'};
491    }
492    if ( $self->{'y_label2'} )
493    {
494        $x2 -= $self->{'label_font'}->height + 2 * $self->{'text_space'};
495    }
496
497    # figure out how wide the widest label is, then figure out how many
498    # columns we can fit into the allotted space
499    $empty_width = $x2 - $x1 - ( 2 * $self->{'legend_space'} );
500    $max_label_width =
501      $self->{'max_legend_label'} * $self->{'legend_font'}->width + 4 * $self->{'text_space'} + $self->{'legend_example_size'};
502    $cols = int( $empty_width / $max_label_width );
503    unless ($cols)
504    {
505        $cols = 1;
506    }
507    $col_width = $empty_width / $cols;
508
509    # figure out how many rows we need and how tall they are
510    $rows = int( $self->{'num_datasets'} / $cols );
511    unless ( ( $self->{'num_datasets'} % $cols ) == 0 )
512    {
513        $rows++;
514    }
515    unless ($rows)
516    {
517        $rows = 1;
518    }
519    $row_height = $h + $self->{'text_space'};
520
521    # box the legend off
522    $y1 = $self->{'curr_y_min'};
523    $y2 = $self->{'curr_y_min'} + $self->{'text_space'} + ( $rows * $row_height ) + ( 2 * $self->{'legend_space'} );
524    $self->{'gd_obj'}->rectangle( $x1, $y1, $x2, $y2, $self->_color_role_to_index('misc') );
525
526    $max_legend_example = $y2 - $y1;
527
528    # leave some space inside the legend
529    $x1 += $self->{'legend_space'} + $self->{'text_space'};
530    $x2 -= $self->{'legend_space'};
531    $y1 += $self->{'legend_space'} + $self->{'text_space'};
532    $y2 -= $self->{'legend_space'} + $self->{'text_space'};
533
534    # draw in the actual legend
535    $r  = 0;    # current row
536    $c  = 0;    # current column
537    $yi = 0;    # current dataset
538
539    for $i ( 0 .. 1 )
540    {
541        for $j ( 0 .. $#{ $self->{'component_datasets'}[$i] } )
542        {
543
544            # get the color
545            $color = $self->{ 'sub_' . $i }->{'color_table'}{ 'dataset' . $j };
546            $index = $self->{'component_datasets'}[$i][$j] - 1;                   # index in label list
547
548            # find the x-y coordinates for the beginning of the example line
549            $x = $x1 + ( $col_width * $c );
550            $y = $y1 + ( $row_height * $r ) + $h / 2;
551
552            # draw the example line if legend_example_height==1 or ==0
553            if ( $rows == 1 )
554            {
555                if ( $self->{ 'legend_example_height' . $yi } < $max_legend_example )
556                {
557                    $yh = $self->{ 'legend_example_height' . $yi };
558                }
559                else
560                {
561                    $yh = $max_legend_example;
562                }
563            }
564            else
565            {
566                if ( $self->{ 'legend_example_height' . $yi } < $row_height )
567                {
568                    $yh = $self->{ 'legend_example_height' . $yi };
569                }
570                else
571                {
572                    $yh = $row_height;
573                }
574            }
575            $yi++;
576            if ( $yh <= 1 )
577            {
578                $self->{'gd_obj'}->line( $x, $y, $x + $self->{'legend_example_size'}, $y, $color );
579            }
580            else
581            {
582
583                #  draw the example bar if legend_example_height > 1
584                $yh = int( $yh / 2 );
585                $self->{'gd_obj'}->filledRectangle( $x, $y - $yh, $x + $self->{'legend_example_size'}, $y + $yh, $color );
586            }
587
588            # find the x-y coordinates for the beginning of the label
589            $x += $self->{'legend_example_size'} + 2 * $self->{'text_space'};
590            $y -= $h / 2;
591
592            # now draw the label
593            $self->{'gd_obj'}->string( $font, $x, $y, $labels[$index], $color );
594
595            # keep track of which row/column we're using
596            $r = ( $r + 1 ) % $rows;
597            if ( $r == 0 )
598            {
599                $c++;
600            }
601        }
602    }
603
604    # mark of the space used
605    $self->{'curr_y_min'} += $rows * $row_height + $self->{'text_space'} + 2 * $self->{'legend_space'};
606
607    return;
608}
609
610## @fn private int _draw_right_legend()
611# put the legend on the right of the chart
612#
613# Overwrite the base class _draw_right_legend
614#
615# @return status
616sub _draw_right_legend
617{
618    my $self   = shift;
619    my @labels = @{ $self->{'legend_labels'} };
620    my ( $x1, $x2, $x3, $y1, $y2, $width, $color, $misccolor, $w, $h );
621    my ($yh) = 0;                        # for boxing legend
622    my $font = $self->{'legend_font'};
623    my ( %colors, @datasets, $i );
624    my $max_legend_example = 0;
625
626    # copy the current boundaries and colors into the sub-objects
627    $self->_sub_update;
628
629    # init the legend exapmle height
630    $self->_legend_example_height_init;
631
632    #   # modify the dataset color table entries to avoid duplicating
633    #   # dataset colors (this limits the number of possible data sets
634    #   # for each component to 8)
635    #   for (0..7) {
636    #     $self->{'sub_1'}{'color_table'}{'dataset'.$_}
637    #       = $self->{'color_table'}{'dataset'.($_+8)};
638    #   }
639    # modify the dataset color table entries to avoid duplicating
640    # dataset colors.
641    my ( $n0, $n1 ) = map { scalar @{ $self->{'composite_info'}[$_][1] } } 0 .. 1;
642
643    for ( 0 .. $n1 - 1 )
644    {
645        $self->{'sub_1'}{'color_table'}{ 'dataset' . $_ } = $self->{'color_table'}{ 'dataset' . ( $_ + $n0 ) };
646    }
647
648    # make sure we use the right colors for the legend
649    @datasets = @{ $self->{'composite_info'}[0][1] };
650    $i        = 0;
651    for ( 0 .. $#datasets )
652    {
653        $colors{ $datasets[$_] - 1 } = $self->{'color_table'}{ 'dataset' . ($_) };
654        $i++;
655    }
656
657    @datasets = @{ $self->{'composite_info'}[1][1] };
658    $i        = 0;
659    for ( 0 .. $#datasets )
660    {
661        $colors{ $datasets[$_] - 1 } = $self->{'color_table'}{ 'dataset' . ( $i + $n0 ) };
662        $i++;
663    }
664
665    # make sure we're using a real font
666    unless ( ( ref($font) ) eq 'GD::Font' )
667    {
668        croak "The subtitle font you specified isn\'t a GD Font object";
669    }
670
671    # get the size of the font
672    ( $h, $w ) = ( $font->height, $font->width );
673
674    # get the miscellaneous color
675    $misccolor = $self->_color_role_to_index('misc');
676
677    # find out how wide the largest label is
678    $width =
679      ( 2 * $self->{'text_space'} ) +
680      ( $self->{'max_legend_label'} * $w ) +
681      $self->{'legend_example_size'} +
682      ( 2 * $self->{'legend_space'} );
683
684    # box the thing off
685    $x1 = $self->{'curr_x_max'} - $width;
686    $x2 = $self->{'curr_x_max'};
687    $y1 = $self->{'curr_y_min'} + $self->{'graph_border'};
688    $y2 =
689      $self->{'curr_y_min'} +
690      $self->{'graph_border'} +
691      $self->{'text_space'} +
692      ( $self->{'num_datasets'} * ( $h + $self->{'text_space'} ) ) +
693      ( 2 * $self->{'legend_space'} );
694    $self->{'gd_obj'}->rectangle( $x1, $y1, $x2, $y2, $misccolor );
695
696    # leave that nice space inside the legend box
697    $x1 += $self->{'legend_space'};
698    $y1 += $self->{'legend_space'} + $self->{'text_space'};
699
700    # now draw the actual legend
701    for ( 0 .. $#labels )
702    {
703
704        # get the color
705        $color = $colors{$_};
706
707        # find the max_legend_example
708        $max_legend_example = $self->{'legend_space'} + $h;
709
710        # find the x-y coords
711        $x2 = $x1;
712        $x3 = $x2 + $self->{'legend_example_size'};
713        $y2 = $y1 + ( $_ * ( $self->{'text_space'} + $h ) ) + $h / 2;
714
715        # draw the example line if legend_example_height==1 or ==0
716        if ( $self->{ 'legend_example_height' . $_ } < $max_legend_example )
717        {
718            $yh = $self->{ 'legend_example_height' . $_ };
719        }
720        else
721        {
722            $yh = $max_legend_example;
723        }
724        if ( $yh <= 1 )
725        {
726            $self->{'gd_obj'}->line( $x2, $y2, $x3, $y2, $color );
727        }
728        else
729        {
730            $yh = int( $yh / 2 );
731            $self->{'gd_obj'}->filledRectangle( $x2, $y2 - $yh, $x3, $y2 + $yh, $color );
732        }
733
734        # now the label
735        $x2 = $x3 + ( 2 * $self->{'text_space'} );
736        $y2 -= $h / 2;
737        $self->{'gd_obj'}->string( $font, $x2, $y2, $labels[$_], $color );
738    }
739
740    # mark off the used space
741    $self->{'curr_x_max'} -= $width;
742
743    # and return
744    return;
745}
746
747## @fn private int _draw_left_legend()
748#  draw the legend at the left of the data plot
749#
750# Overwrite the base class _draw_left_legend
751#
752# @return status
753sub _draw_left_legend
754{
755    my $self   = shift;
756    my @labels = @{ $self->{'legend_labels'} };
757    my ( $x1, $x2, $x3, $y1, $y2, $width, $color, $misccolor, $w, $h );
758    my $yh;    # for boxing legend
759    my $font = $self->{'legend_font'};
760    my ( %colors, @datasets, $i );
761    my $max_legend_example = 0;
762
763    # copy the current boundaries and colors into the sub-objects
764    $self->_sub_update;
765
766    # init the legend_example height
767    $self->_legend_example_height_init;
768
769    #   # modify the dataset color table entries to avoid duplicating
770    #   # dataset colors (this limits the number of possible data sets
771    #   # for each component to 8)
772    #   for (0..7) {
773    #     $self->{'sub_1'}{'color_table'}{'dataset'.$_}
774    #       = $self->{'color_table'}{'dataset'.($_+8)};
775    #   }
776    # modify the dataset color table entries to avoid duplicating
777    # dataset colors.
778    my ( $n0, $n1 ) = map { scalar @{ $self->{'composite_info'}[$_][1] } } 0 .. 1;
779    for ( 0 .. $n1 - 1 )
780    {
781        $self->{'sub_1'}{'color_table'}{ 'dataset' . $_ } = $self->{'color_table'}{ 'dataset' . ( $_ + $n0 ) };
782    }
783
784    # make sure we use the right colors for the legend
785    @datasets = @{ $self->{'composite_info'}[0][1] };
786    $i        = 0;
787    for ( 0 .. $#datasets )
788    {
789        $colors{ $datasets[$_] - 1 } = $self->{'color_table'}{ 'dataset' . ($i) };
790        $i++;
791    }
792    @datasets = @{ $self->{'composite_info'}[1][1] };
793    $i        = 0;
794    for ( 0 .. $#datasets )
795    {
796        $colors{ $datasets[$_] - 1 } = $self->{'color_table'}{ 'dataset' . ( $i + $n0 ) };
797        $i++;
798    }
799
800    # make sure we're using a real font
801    unless ( ( ref($font) ) eq 'GD::Font' )
802    {
803        croak "The subtitle font you specified isn\'t a GD Font object";
804    }
805
806    # get the size of the font
807    ( $h, $w ) = ( $font->height, $font->width );
808
809    # get the miscellaneous color
810    $misccolor = $self->_color_role_to_index('misc');
811
812    # find out how wide the largest label is
813    $width =
814      ( 2 * $self->{'text_space'} ) +
815      ( $self->{'max_legend_label'} * $w ) +
816      $self->{'legend_example_size'} +
817      ( 2 * $self->{'legend_space'} );
818
819    # get some base x-y coordinates
820    $x1 = $self->{'curr_x_min'};
821    $x2 = $self->{'curr_x_min'} + $width;
822    $y1 = $self->{'curr_y_min'} + $self->{'graph_border'};
823    $y2 =
824      $self->{'curr_y_min'} +
825      $self->{'graph_border'} +
826      $self->{'text_space'} +
827      ( $self->{'num_datasets'} * ( $h + $self->{'text_space'} ) ) +
828      ( 2 * $self->{'legend_space'} );
829
830    # box the legend off
831    $self->{'gd_obj'}->rectangle( $x1, $y1, $x2, $y2, $misccolor );
832
833    # leave that nice space inside the legend box
834    $x1 += $self->{'legend_space'};
835    $y1 += $self->{'legend_space'} + $self->{'text_space'};
836
837    # now draw the actual legend
838    for ( 0 .. $#labels )
839    {
840
841        # get the color
842        $color = $colors{$_};
843
844        # find the max_legend_example
845        $max_legend_example = $self->{'legend_space'} + $h;
846
847        # find the x-y coords
848        $x2 = $x1;
849        $x3 = $x2 + $self->{'legend_example_size'};
850        $y2 = $y1 + ( $_ * ( $self->{'text_space'} + $h ) ) + $h / 2;
851
852        # draw the example line if legend_example_height==1 or ==0
853        if ( $self->{ 'legend_example_height' . $_ } < $max_legend_example )
854        {
855            $yh = $self->{ 'legend_example_height' . $_ };
856        }
857        else
858        {
859            $yh = $max_legend_example;
860        }
861        if ( $yh <= 1 )
862        {
863            $self->{'gd_obj'}->line( $x2, $y2, $x3, $y2, $color );
864        }
865        else
866        {
867
868            # draw the example bar if legend_example_height > 1
869            $yh = int( $yh / 2 );
870            $self->{'gd_obj'}->filledRectangle( $x2, $y2 - $yh, $x3, $y2 + $yh, $color );
871        }
872
873        # now the label
874        $x2 = $x3 + ( 2 * $self->{'text_space'} );
875        $y2 -= $h / 2;
876        $self->{'gd_obj'}->string( $font, $x2, $y2, $labels[$_], $color );
877    }
878
879    # mark off the used space
880    $self->{'curr_x_min'} += $width;
881
882    # and return
883    return 1;
884}
885
886## @fn private int _draw_bottom_legend()
887# put the legend on the bottom of the chart
888#
889# Overwrite the base class _draw_bottom_legend
890#
891# @return status
892sub _draw_bottom_legend
893{
894    my $self   = shift;
895    my @labels = @{ $self->{'legend_labels'} };
896    my ( $x1, $y1, $x2, $y2, $empty_width, $max_label_width, $cols, $rows, $color );
897    my ( $col_width, $row_height, $i, $j, $r, $c, $index, $x, $y, $sub, $w, $h );
898    my ( $yh, $yi );    # for boxing legend
899    my $font = $self->{'legend_font'};
900    my ( %colors, @datasets );
901    my $max_legend_example = 0;
902    $yh = 0;
903
904    # copy the current boundaries and colors into the sub-objects
905    $self->_sub_update;
906
907    # init the legend example height
908    $self->_legend_example_height_init;
909
910    #   # modify the dataset color table entries to avoid duplicating
911    #   # dataset colors (this limits the number of possible data sets
912    #   # for each component to 8)
913    #   for (0..7) {
914    #     $self->{'sub_1'}{'color_table'}{'dataset'.$_}
915    #       = $self->{'color_table'}{'dataset'.($_+8)};
916    #   }
917    # modify the dataset color table entries to avoid duplicating
918    # dataset colors.
919    my ( $n0, $n1 ) = map { scalar @{ $self->{'composite_info'}[$_][1] } } 0 .. 1;
920    for ( 0 .. $n1 - 1 )
921    {
922        $self->{'sub_1'}{'color_table'}{ 'dataset' . $_ } = $self->{'color_table'}{ 'dataset' . ( $_ + $n0 ) };
923    }
924
925    @datasets = @{ $self->{'composite_info'}[0][1] };
926    $i        = 0;
927    for ( 0 .. $#datasets )
928    {
929        $colors{ $datasets[$_] - 1 } = $self->{'color_table'}{ 'dataset' . ($i) };
930        $i++;
931    }
932    @datasets = @{ $self->{'composite_info'}[1][1] };
933    $i        = 0;
934    for ( 0 .. $#datasets )
935    {
936        $colors{ $datasets[$_] - 1 } = $self->{'color_table'}{ 'dataset' . ( $i + $n0 ) };
937        $i++;
938    }
939
940    # make sure we're using a real font
941    unless ( ( ref($font) ) eq 'GD::Font' )
942    {
943        croak "The subtitle font you specified isn\'t a GD Font object";
944    }
945
946    # get the size of the font
947    ( $h, $w ) = ( $font->height, $font->width );
948
949    # figure out how many columns we can fit
950    $x1 =
951      $self->{'curr_x_min'} +
952      $self->{'graph_border'} +
953      $self->{'y_tick_label_length1'} * $self->{'tick_label_font'}->width +
954      $self->{'tick_len'} +
955      ( 3 * $self->{'text_space'} );
956    $x2 =
957      $self->{'curr_x_max'} -
958      $self->{'graph_border'} -
959      $self->{'y_tick_label_length2'} * $self->{'tick_label_font'}->width -
960      $self->{'tick_len'} -
961      ( 3 * $self->{'text_space'} );
962    if ( $self->{'y_label'} )
963    {
964        $x1 += $self->{'label_font'}->height + 2 * $self->{'text_space'};
965    }
966    if ( $self->{'y_label2'} )
967    {
968        $x2 -= $self->{'label_font'}->height + 2 * $self->{'text_space'};
969    }
970    $empty_width = $x2 - $x1 - ( 2 * $self->{'legend_space'} );
971    $max_label_width =
972      $self->{'max_legend_label'} * $self->{'legend_font'}->width + 4 * $self->{'text_space'} + $self->{'legend_example_size'};
973    $cols = int( $empty_width / $max_label_width );
974    unless ($cols)
975    {
976        $cols = 1;
977    }
978    $col_width = $empty_width / $cols;
979
980    # figure out how many rows we need
981    $rows = int( $self->{'num_datasets'} / $cols );
982    unless ( ( $self->{'num_datasets'} % $cols ) == 0 )
983    {
984        $rows++;
985    }
986    unless ($rows)
987    {
988        $rows = 1;
989    }
990    $row_height = $h + $self->{'text_space'};
991
992    # box it off
993    $y1 = $self->{'curr_y_max'} - $self->{'text_space'} - ( $rows * $row_height ) - ( 2 * $self->{'legend_space'} );
994    $y2 = $self->{'curr_y_max'};
995    $self->{'gd_obj'}->rectangle( $x1, $y1, $x2, $y2, $self->_color_role_to_index('misc') );
996
997    # get the max_legend_example_height
998    $max_legend_example = $y2 - $y1;
999
1000    $x1 += $self->{'legend_space'} + $self->{'text_space'};
1001    $x2 -= $self->{'legend_space'};
1002    $y1 += $self->{'legend_space'} + $self->{'text_space'};
1003    $y2 -= $self->{'legend_space'} + $self->{'text_space'};
1004
1005    # draw in the actual legend
1006    $r  = 0;
1007    $c  = 0;
1008    $yi = 0;    # current dataset
1009    for $i ( 0 .. 1 )
1010    {
1011        for $j ( 0 .. $#{ $self->{'component_datasets'}[$i] } )
1012        {
1013            $color = $self->{ 'sub_' . $i }->{'color_table'}{ 'dataset' . $j };
1014            $index = $self->{'component_datasets'}[$i][$j] - 1;
1015
1016            $x = $x1 + ( $col_width * $c );
1017            $y = $y1 + ( $row_height * $r ) + $h / 2;
1018
1019            #  draw the example line if legend_example_height==1 or ==0
1020            if ( $rows == 1 )
1021            {
1022                if ( $self->{ 'legend_example_height' . $yi } < $max_legend_example )
1023                {
1024                    $yh = $self->{ 'legend_example_height' . $yi };
1025                }
1026                else
1027                {
1028                    $yh = $max_legend_example;
1029                }
1030            }
1031            else
1032            {
1033                if ( $self->{ 'legend_example_height' . $yi } < $row_height )
1034                {
1035                    $yh = $self->{ 'legend_example_height' . $yi };
1036                }
1037                else
1038                {
1039                    $yh = $row_height;
1040                }
1041            }
1042            $yi++;
1043            if ( $yh <= 1 )
1044            {
1045                $self->{'gd_obj'}->line( $x, $y, $x + $self->{'legend_example_size'}, $y, $color );
1046            }
1047            else
1048            {
1049
1050                # draw the example bar if legend_example_height > 1
1051                $yh = int( $yh / 2 );
1052                $self->{'gd_obj'}->filledRectangle( $x, $y - $yh, $x + $self->{'legend_example_size'}, $y + $yh, $color );
1053            }
1054
1055            $x += $self->{'legend_example_size'} + 2 * $self->{'text_space'};
1056            $y -= $h / 2;
1057            $self->{'gd_obj'}->string( $font, $x, $y, $labels[$index], $color );
1058
1059            # keep track of which row/column we're using
1060            $r = ( $r + 1 ) % $rows;
1061            if ( $r == 0 )
1062            {
1063                $c++;
1064            }
1065        }
1066    }
1067
1068    # mark of the space used
1069    $self->{'curr_y_max'} -= ( $rows * $row_height ) + 2 * $self->{'text_space'} + 2 * $self->{'legend_space'};
1070
1071    return;
1072}
1073
1074## @fn private int _draw_none_legend()
1075# no legend to draw.. just update the color tables for subs
1076#
1077# This routine overwrites this function of the Base class
1078#
1079# @return status
1080sub _draw_none_legend
1081{
1082    my $self   = shift;
1083    my $status = 1;
1084
1085    $self->_sub_update();
1086
1087    #   for (0..7) {
1088    #     $self->{'sub_1'}{'color_table'}{'dataset'.$_}
1089    #        = $self->{'color_table'}{'dataset'.($_+8)};
1090    #    }
1091    # modify the dataset color table entries to avoid duplicating
1092    # dataset colors.
1093    my ( $n0, $n1 ) = map { scalar @{ $self->{'composite_info'}[$_][1] } } 0 .. 1;
1094    for ( 0 .. $n1 - 1 )
1095    {
1096        $self->{'sub_1'}{'color_table'}{ 'dataset' . $_ } = $self->{'color_table'}{ 'dataset' . ( $_ + $n0 ) };
1097    }
1098
1099    return $status;
1100}
1101
1102## @fn private int _draw_ticks()
1103# draw the ticks and tick labels
1104#
1105# Overwrites function _draw_ticks() of base class
1106#
1107# @return status
1108sub _draw_ticks
1109{
1110    my $self = shift;
1111
1112    #draw the x ticks again
1113    if ( $self->true( $self->{'xy_plot'} ) )
1114    {
1115        $self->_find_x_scale;
1116
1117        # The following statement is necessary as the
1118        # _draw_x_number_ticks() located in Base.pm does nothing know
1119        # about different y_tick_label_length variables!
1120        # This is a hack here
1121        $self->{'y_tick_label_length'} = $self->{'y_tick_label_length1'};
1122        $self->_draw_x_number_ticks;
1123    }
1124    else
1125    {
1126        $self->_draw_x_ticks;
1127    }
1128
1129    # update the boundaries in the sub-objects
1130    $self->_boundary_update( $self, $self->{'sub_0'} );
1131    $self->_boundary_update( $self, $self->{'sub_1'} );
1132
1133    # now the y ticks
1134    $self->_draw_y_ticks;
1135
1136    # then return
1137    return;
1138}
1139
1140## @fn private int _draw_x_ticks()
1141# draw the x-ticks and their labels
1142#
1143# Overwrites function _draw_x_ticks() of base class
1144#
1145# @return status
1146sub _draw_x_ticks
1147{
1148    my $self      = shift;
1149    my $data      = $self->{'dataref'};
1150    my $font      = $self->{'tick_label_font'};
1151    my $textcolor = $self->_color_role_to_index('text');
1152    my $misccolor = $self->_color_role_to_index('misc');
1153    my ( $h,     $w );
1154    my ( $x1,    $x2, $y1, $y2 );
1155    my ( $width, $delta );
1156    my ($stag);
1157
1158    $self->{'grid_data'}->{'x'} = [];
1159
1160    # make sure we got a real font
1161    unless ( ( ref $font ) eq 'GD::Font' )
1162    {
1163        croak "The tick label font you specified isn\'t a GD Font object";
1164    }
1165
1166    # get the height and width of the font
1167    ( $h, $w ) = ( $font->height, $font->width );
1168
1169    # allow for the amount of space the y-ticks will push the
1170    # axes over to the right and to the left
1171## _draw_y_ticks allows 3 * text_space, not 2 * ;  this caused mismatch between
1172## the ticks (and grid lines) and the data.
1173    #   $x1 = $self->{'curr_x_min'} + ($w * $self->{'y_tick_label_length1'})
1174    #          + (2 * $self->{'text_space'}) + $self->{'tick_len'};
1175    #   $x2 = $self->{'curr_x_max'} - ($w * $self->{'y_tick_label_length2'})
1176    #          - (2 * $self->{'text_space'}) - $self->{'tick_len'};
1177
1178    $x1 =
1179      $self->{'curr_x_min'} + ( $w * $self->{'y_tick_label_length1'} ) + ( 3 * $self->{'text_space'} ) + $self->{'tick_len'};
1180    $x2 =
1181      $self->{'curr_x_max'} - ( $w * $self->{'y_tick_label_length2'} ) - ( 3 * $self->{'text_space'} ) - $self->{'tick_len'};
1182    $y1 = $self->{'curr_y_max'} - $h - $self->{'text_space'};
1183
1184    # get the delta value, figure out how to draw the labels
1185    $width = $x2 - $x1;
1186    $delta = $width / ( $self->{'num_datapoints'} > 0 ? $self->{'num_datapoints'} : 1 );
1187    if ( $delta <= ( $self->{'x_tick_label_length'} * $w ) )
1188    {
1189        unless ( $self->{'x_ticks'} =~ /^vertical$/i )
1190        {
1191            $self->{'x_ticks'} = 'staggered';
1192        }
1193    }
1194
1195    # now draw the labels
1196    if ( $self->{'x_ticks'} =~ /^normal$/i )
1197    {    # normal ticks
1198        if ( $self->{'skip_x_ticks'} )
1199        {
1200            for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_x_ticks'} ) )
1201            {
1202                $x2 =
1203                  $x1 +
1204                  ( $delta / 2 ) +
1205                  ( $delta * ( $_ * $self->{'skip_x_ticks'} ) ) -
1206                  ( $w * length( $self->{'f_x_tick'}->( $data->[0][ $_ * $self->{'skip_x_ticks'} ] ) ) ) / 2;
1207                $self->{'gd_obj'}
1208                  ->string( $font, $x2, $y1, $self->{'f_x_tick'}->( $data->[0][ $_ * $self->{'skip_x_ticks'} ] ), $textcolor );
1209            }
1210        }
1211        elsif ( $self->{'custom_x_ticks'} )
1212        {
1213            for ( @{ $self->{'custom_x_ticks'} } )
1214            {
1215                $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ ) - ( $w * length( $self->{'f_x_tick'}->( $data->[0][$_] ) ) ) / 2;
1216                $self->{'gd_obj'}->string( $font, $x2, $y1, $self->{'f_x_tick'}->( $data->[0][$_] ), $textcolor );
1217            }
1218        }
1219        else
1220        {
1221            for ( 0 .. $self->{'num_datapoints'} - 1 )
1222            {
1223                $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ ) - ( $w * length( $self->{'f_x_tick'}->( $data->[0][$_] ) ) ) / 2;
1224                $self->{'gd_obj'}->string( $font, $x2, $y1, $self->{'f_x_tick'}->( $data->[0][$_] ), $textcolor );
1225            }
1226        }
1227    }
1228    elsif ( $self->{'x_ticks'} =~ /^staggered$/i )
1229    {    # staggered ticks
1230        if ( $self->{'skip_x_ticks'} )
1231        {
1232            $stag = 0;
1233            for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_x_ticks'} ) )
1234            {
1235                $x2 =
1236                  $x1 +
1237                  ( $delta / 2 ) +
1238                  ( $delta * ( $_ * $self->{'skip_x_ticks'} ) ) -
1239                  ( $w * length( $self->{'f_x_tick'}->( $data->[0][ $_ * $self->{'skip_x_ticks'} ] ) ) ) / 2;
1240                if ( ( $stag % 2 ) == 1 )
1241                {
1242                    $y1 -= $self->{'text_space'} + $h;
1243                }
1244                $self->{'gd_obj'}
1245                  ->string( $font, $x2, $y1, $self->{'f_x_tick'}->( $data->[0][ $_ * $self->{'skip_x_ticks'} ] ), $textcolor );
1246                if ( ( $stag % 2 ) == 1 )
1247                {
1248                    $y1 += $self->{'text_space'} + $h;
1249                }
1250                $stag++;
1251            }
1252        }
1253        elsif ( $self->{'custom_x_ticks'} )
1254        {
1255            $stag = 0;
1256            for ( sort ( @{ $self->{'custom_x_ticks'} } ) )
1257            {
1258                $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ ) - ( $w * length( $self->{'f_x_tick'}->( $data->[0][$_] ) ) ) / 2;
1259                if ( ( $stag % 2 ) == 1 )
1260                {
1261                    $y1 -= $self->{'text_space'} + $h;
1262                }
1263                $self->{'gd_obj'}->string( $font, $x2, $y1, $self->{'f_x_tick'}->( $data->[0][$_] ), $textcolor );
1264                if ( ( $stag % 2 ) == 1 )
1265                {
1266                    $y1 += $self->{'text_space'} + $h;
1267                }
1268                $stag++;
1269            }
1270        }
1271        else
1272        {
1273            for ( 0 .. $self->{'num_datapoints'} - 1 )
1274            {
1275                $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ ) - ( $w * length( $self->{'f_x_tick'}->( $data->[0][$_] ) ) ) / 2;
1276                if ( ( $_ % 2 ) == 1 )
1277                {
1278                    $y1 -= $self->{'text_space'} + $h;
1279                }
1280                $self->{'gd_obj'}->string( $font, $x2, $y1, $self->{'f_x_tick'}->( $data->[0][$_] ), $textcolor );
1281                if ( ( $_ % 2 ) == 1 )
1282                {
1283                    $y1 += $self->{'text_space'} + $h;
1284                }
1285            }
1286        }
1287    }
1288    elsif ( $self->{'x_ticks'} =~ /^vertical$/i )
1289    {    # vertical ticks
1290        $y1 = $self->{'curr_y_max'} - $self->{'text_space'};
1291        if ( defined( $self->{'skip_x_ticks'} ) && $self->{'skip_x_ticks'} > 1 )
1292        {
1293            for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_x_ticks'} ) )
1294            {
1295                $x2 = $x1 + ( $delta / 2 ) + ( $delta * ( $_ * $self->{'skip_x_ticks'} ) ) - $h / 2;
1296                $y2 = $y1 - (
1297                    (
1298                        $self->{'x_tick_label_length'} -
1299                          length( $self->{'f_x_tick'}->( $data->[0][ $_ * $self->{'skip_x_ticks'} ] ) )
1300                    ) * $w
1301                );
1302                $self->{'gd_obj'}
1303                  ->stringUp( $font, $x2, $y2, $self->{'f_x_tick'}->( $data->[0][ $_ * $self->{'skip_x_ticks'} ] ),
1304                    $textcolor );
1305            }
1306        }
1307        elsif ( $self->{'custom_x_ticks'} )
1308        {
1309            for ( @{ $self->{'custom_x_ticks'} } )
1310            {
1311                $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ ) - $h / 2;
1312                $y2 = $y1 - ( ( $self->{'x_tick_label_length'} - length( $self->{'f_x_tick'}->( $data->[0][$_] ) ) ) * $w );
1313                $self->{'gd_obj'}->stringUp( $font, $x2, $y2, $self->{'f_x_tick'}->( $data->[0][$_] ), $textcolor );
1314            }
1315        }
1316        else
1317        {
1318            for ( 0 .. $self->{'num_datapoints'} - 1 )
1319            {
1320                $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ ) - $h / 2;
1321                $y2 = $y1 - ( ( $self->{'x_tick_label_length'} - length( $self->{'f_x_tick'}->( $data->[0][$_] ) ) ) * $w );
1322                $self->{'gd_obj'}->stringUp( $font, $x2, $y2, $self->{'f_x_tick'}->( $data->[0][$_] ), $textcolor );
1323            }
1324        }
1325    }
1326    else
1327    {    # error time
1328        carp "I don't understand the type of x-ticks you specified";
1329    }
1330
1331    # update the current y-max value
1332    if ( $self->{'x_ticks'} =~ /^normal$/i )
1333    {
1334        $self->{'curr_y_max'} -= $h + ( 2 * $self->{'text_space'} );
1335    }
1336    elsif ( $self->{'x_ticks'} =~ /^staggered$/i )
1337    {
1338        $self->{'curr_y_max'} -= ( 2 * $h ) + ( 3 * $self->{'text_space'} );
1339    }
1340    elsif ( $self->{'x_ticks'} =~ /^vertical$/i )
1341    {
1342        $self->{'curr_y_max'} -= ( $w * $self->{'x_tick_label_length'} ) + ( 2 * $self->{'text_space'} );
1343    }
1344
1345    # now plot the ticks
1346    $y1 = $self->{'curr_y_max'};
1347    $y2 = $self->{'curr_y_max'} - $self->{'tick_len'};
1348    if ( $self->{'skip_x_ticks'} )
1349    {
1350        for ( 0 .. int( ( $self->{'num_datapoints'} - 1 ) / $self->{'skip_x_ticks'} ) )
1351        {
1352            $x2 = $x1 + ( $delta / 2 ) + ( $delta * ( $_ * $self->{'skip_x_ticks'} ) );
1353            $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
1354            if (   $self->true( $self->{'grid_lines'} )
1355                or $self->true( $self->{'x_grid_lines'} ) )
1356            {
1357                $self->{'grid_data'}->{'x'}->[$_] = $x2;
1358            }
1359        }
1360    }
1361    elsif ( $self->{'custom_x_ticks'} )
1362    {
1363        for ( @{ $self->{'custom_x_ticks'} } )
1364        {
1365            $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ );
1366            $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
1367            if (   $self->true( $self->{'grid_lines'} )
1368                or $self->true( $self->{'x_grid_lines'} ) )
1369            {
1370                $self->{'grid_data'}->{'x'}->[$_] = $x2;
1371            }
1372        }
1373    }
1374    else
1375    {
1376        for ( 0 .. $self->{'num_datapoints'} - 1 )
1377        {
1378            $x2 = $x1 + ( $delta / 2 ) + ( $delta * $_ );
1379            $self->{'gd_obj'}->line( $x2, $y1, $x2, $y2, $misccolor );
1380            if (   $self->true( $self->{'grid_lines'} )
1381                or $self->true( $self->{'x_grid_lines'} ) )
1382            {
1383                $self->{'grid_data'}->{'x'}->[$_] = $x2;
1384            }
1385        }
1386    }
1387
1388    # update the current y-max value
1389    $self->{'curr_y_max'} -= $self->{'tick_len'};
1390
1391    # and return
1392    return;
1393}
1394
1395## @fn private int _draw_y_ticks()
1396# draw the y-ticks and their labels
1397#
1398# Overwrites function _draw_y_ticks() of base class
1399#
1400# @return status
1401sub _draw_y_ticks
1402{
1403    my $self = shift;
1404
1405    # let the first guy do his
1406    $self->{'sub_0'}->_draw_y_ticks('left');
1407
1408    # and update the other two objects
1409    $self->_boundary_update( $self->{'sub_0'}, $self );
1410    $self->_boundary_update( $self->{'sub_0'}, $self->{'sub_1'} );
1411
1412    # now draw the other ones
1413    $self->{'sub_1'}->_draw_y_ticks('right');
1414
1415    # and update the other two objects
1416    $self->_boundary_update( $self->{'sub_1'}, $self );
1417    $self->_boundary_update( $self->{'sub_1'}, $self->{'sub_0'} );
1418
1419    # then return
1420    return;
1421}
1422
1423## @fn private _draw_data
1424# finally get around to plotting the data for composite chart
1425sub _draw_data
1426{
1427    my $self = shift;
1428
1429    # do a grey background if they want it
1430    if ( $self->true( $self->{'grey_background'} ) )
1431    {
1432        $self->_grey_background;
1433        $self->{'sub_0'}->{'grey_background'} = 'false';
1434        $self->{'sub_1'}->{'grey_background'} = 'false';
1435    }
1436
1437    # draw grid again if necessary (if grey background ruined it..)
1438    unless ( !$self->true( $self->{grey_background} ) )
1439    {
1440        $self->_draw_grid_lines    if ( $self->true( $self->{grid_lines} ) );
1441        $self->_draw_x_grid_lines  if ( $self->true( $self->{x_grid_lines} ) );
1442        $self->_draw_y_grid_lines  if ( $self->true( $self->{y_grid_lines} ) );
1443        $self->_draw_y2_grid_lines if ( $self->true( $self->{y2_grid_lines} ) );
1444    }
1445
1446    # do a final bounds update
1447    $self->_boundary_update( $self, $self->{'sub_0'} );
1448    $self->_boundary_update( $self, $self->{'sub_1'} );
1449
1450    # init the imagemap data field if they wanted it
1451    if ( $self->true( $self->{'imagemap'} ) )
1452    {
1453        $self->{'imagemap_data'} = [];
1454    }
1455
1456    # now let the component modules go to work
1457
1458    $self->{'sub_0'}->_draw_data;
1459    $self->{'sub_1'}->_draw_data;
1460
1461    return;
1462}
1463
1464## @fn private _sub_update()
1465# update all the necessary information in the sub-objects
1466#
1467# Only for Chart::Composite
1468sub _sub_update
1469{
1470    my $self = shift;
1471    my $sub0 = $self->{'sub_0'};
1472    my $sub1 = $self->{'sub_1'};
1473
1474    # update the boundaries
1475    $self->_boundary_update( $self, $sub0 );
1476    $self->_boundary_update( $self, $sub1 );
1477
1478    # copy the color tables
1479    $sub0->{'color_table'} = { %{ $self->{'color_table'} } };
1480    $sub1->{'color_table'} = { %{ $self->{'color_table'} } };
1481
1482    # now return
1483    return;
1484}
1485
1486## @fn private _boundary_update()
1487# copy the current gd_obj boundaries from one object to another
1488#
1489# Only for Chart::Composite
1490sub _boundary_update
1491{
1492    my $self = shift;
1493    my $from = shift;
1494    my $to   = shift;
1495
1496    $to->{'curr_x_min'} = $from->{'curr_x_min'};
1497    $to->{'curr_x_max'} = $from->{'curr_x_max'};
1498    $to->{'curr_y_min'} = $from->{'curr_y_min'};
1499    $to->{'curr_y_max'} = $from->{'curr_y_max'};
1500
1501    return;
1502}
1503
1504## @fn private int _draw_y_grid_lines()
1505# draw grid_lines for y
1506#
1507# Overwrites this function of Base
1508sub _draw_y_grid_lines
1509{
1510    my ($self) = shift;
1511    $self->{'sub_0'}->_draw_y_grid_lines();
1512    return;
1513}
1514
1515## @fn private int _draw_y2_grid_lines()
1516# draw grid_lines for y
1517#
1518# Overwrites this function of Base
1519sub _draw_y2_grid_lines
1520{
1521    my ($self) = shift;
1522    $self->{'sub_1'}->_draw_y2_grid_lines();
1523    return;
1524}
1525
1526## @fn private _legend_example_height_values
1527# init the legend_example_height_values
1528#
1529sub _legend_example_height_init
1530{
1531    my $self = shift;
1532    my $a    = $self->{'num_datasets'};
1533    my ( $b, $e ) = ( 0, 0 );
1534    my $bis = '..';
1535
1536    if ( $self->false( $self->{'legend_example_height'} ) )
1537    {
1538        for my $i ( 0 .. $a )
1539        {
1540            $self->{ 'legend_example_height' . $i } = 1;
1541        }
1542    }
1543
1544    if ( $self->true( $self->{'legend_example_height'} ) )
1545    {
1546        for my $i ( 0 .. $a )
1547        {
1548            if ( defined( $self->{ 'legend_example_height' . $i } ) ) { }
1549            else
1550            {
1551                ( $self->{ 'legend_example_height' . $i } ) = 1;
1552            }
1553        }
1554
1555        for $b ( 0 .. $a )
1556        {
1557            for $e ( 0 .. $a )
1558            {
1559                my $anh = sprintf( $b . $bis . $e );
1560                if ( defined( $self->{ 'legend_example_height' . $anh } ) )
1561                {
1562                    if ( $b > $e )
1563                    {
1564                        croak "Please reverse the datasetnumber in legend_example_height\n";
1565                    }
1566                    for ( my $n = $b ; $n <= $e ; $n++ )
1567                    {
1568                        $self->{ 'legend_example_height' . $n } = $self->{ 'legend_example_height' . $anh };
1569                    }
1570                }
1571            }
1572        }
1573    }
1574}
1575
1576## be a good module and return 1
15771;
1578