File Coverage

lib/Graph/Chart.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Graph::Chart;
2              
3             ###########################################################
4             # RPN package with DICT
5             # Gnu GPL2 license
6             #
7             # Fabrice Dulaunoy
8             ###########################################################
9             # ChangeLog:
10             #
11             ###########################################################
12              
13             =head1 SYNOPSIS
14              
15             =over 3
16              
17             B
18              
19             A Wrapper around GD to easyly graph chart
20              
21             =back
22              
23             =cut
24              
25 1     1   7664 use strict;
  1         4  
  1         62  
26              
27 1     1   4 use Carp;
  1         2  
  1         99  
28 1     1   2135 use Data::Dumper;
  1         17075  
  1         88  
29              
30 1     1   1248 use Clone qw(clone);
  1         26741  
  1         132  
31 1     1   4594 use Compress::Zlib;
  1         171918  
  1         384  
32 1     1   2253 use Data::Serializer;
  1         3777  
  1         45  
33             # use fields qw{ size };
34 1     1   10599 use GD;
  0            
  0            
35             use GD::Polyline;
36             use List::Util qw[min max sum];
37             use POSIX;
38              
39             use vars qw( $VERSION );
40              
41             use constant PI => 4 * atan2( 1, 1 );
42             # use constant NEPER => 2.718281828459045;
43             # use constant LOG10 => 2.30258509299405;
44              
45             $VERSION = '0.65';
46              
47             ###########################################################################
48              
49             ###########################################################################
50             ### class creator ###
51             ###########################################################################
52              
53             =head1 METHODS
54            
55             OO interface
56              
57             =head2 new
58              
59             =over
60              
61             Create a new Chart
62              
63             =over
64              
65             my $graph = Graph::Chart->new( \%options );
66              
67             %options could be defined like this:
68              
69              
70              
71             size => [ W, H ] # the size ( Width, Height ) in pixel of the real graph ( without border )
72             bg_color => '0xfffff0' # an ARRAY with all possible section
73             frame => { color => '0xff00ff', thickness => 1 }, # an optional frame around the real chart
74             border => [ 150, 80, 100, 100 ], # extra space around the graph in pixel [ left side, right side , top side, bottom side ]"
75            
76             grid => { # a grid over the graph
77             debord => [ 5, 20, 10, 30 ], # some extension of the grid size ( same order as border ) B
78             x => { # vertical grid
79             color => '0xff00ff' # color of the grid ( hex HTML value )
80             number => 5, # number of grid division
81             thickness => 1, # size of the division's line ( default = 1 )
82             type => log, # create a log graduation (only one modules). If missing, normal graduation.
83            
84             label => { # an optional label on the left side
85             font => '/usr/lib/cinelerra/fonts/trebucbi.ttf', # a TrueType font to use
86             color => '0xff0000', # the color of the label
87             size => 10, # the size of the font
88             text => [ 'toto', undef, 'truc', 'bazar', 122 ], # the text to render ( a undef element is not ploted, this allow to skip some label )
89             space => 80, # an extra space between the division and the text
90             align => 'right', # align the text on the right ( = aligned on the division )
91             rotation => 30, # a rotation of the text in degree
92             kerning_correction => 0.85, # a kerning correcting to correct align of text when rotated ( default 0.91 )
93             surround => { color => '0x0000ff' , thickness => 1 }, # create a frame around the text with the specified color and thickness
94             },
95            
96             x_up => { # vertical grid on the upper half of the graph ( to use with up_ graph)
97             color => '0xff00ff' # color of the grid ( hex HTML value )
98             number => 5, # number of grid division
99             thickness => 1, # size of the division's line ( default = 1 )
100             type => log, # create a log graduation (only one modules). If missing, normal graduation.
101            
102             label => { # an optional label on the left side
103             font => '/usr/lib/cinelerra/fonts/trebucbi.ttf', # a TrueType font to use
104             color => '0xff0000', # the color of the label
105             size => 10, # the size of the font
106             text => [ 'toto', undef, 'truc', 'bazar', 122 ], # the text to render ( a undef element is not ploted, this allow to skip some label )
107             space => 80, # an extra space between the division and the text
108             align => 'right', # align the text on the right ( = aligned on the division )
109             rotation => 30, # a rotation of the text in degree
110             kerning_correction => 0.85, # a kerning correcting to correct align of text when rotated ( default 0.91 )
111             surround => { color => '0x0000ff' , thickness => 1 }, # create a frame around the text with the specified color and thickness
112             },
113            
114             x_down => { # vertical grid on the lower half of the graph ( to use with down_ graph)
115             color => '0xff00ff' # color of the grid ( hex HTML value )
116             number => 5, # number of grid division
117             thickness => 1, # size of the division's line ( default = 1 )
118             type => log, # create a log graduation (only one modules). If missing, normal graduation.
119            
120             label => { # an optional label on the left side
121             font => '/usr/lib/cinelerra/fonts/trebucbi.ttf', # a TrueType font to use
122             color => '0xff0000', # the color of the label
123             size => 10, # the size of the font
124             text => [ 'toto', undef, 'truc', 'bazar', 122 ], # the text to render ( a undef element is not ploted, this allow to skip some label )
125             space => 80, # an extra space between the division and the text
126             align => 'right', # align the text on the right ( = aligned on the division )
127             rotation => 30, # a rotation of the text in degree
128             kerning_correction => 0.85, # a kerning correcting to correct align of text when rotated ( default 0.91 )
129             surround => { color => '0x0000ff' , thickness => 1 }, # create a frame around the text with the specified color and thickness
130             },
131            
132             label2 => { # an optional label on the right side
133             font => '/usr/lib/cinelerra/fonts/lucon.ttf', # a TrueType font to use
134             color => '0xff0000', # the color of the label
135             size => 10, # the size of the font
136             text => [ 'toto', undef, 'truc', 'bazar', 122 ],, # the text to render ( a undef element is not ploted, this allow to skip some label )
137             space => 50, # an extra space between the division and the text
138             align => 'right', # align the text on the right ( not really useful )
139             rotation => -30, # an rotation of the text in degree
140             kerning_correction => 0.85, # a kerning correcting to correct align of text when rotated ( default 0.91 )
141             surround => { color => '0x0000ff' , thickness => 1 }, # create a frame around the text with the specified color and thickness
142              
143             },
144             }, "
145             y => { # horizontal grid
146             color => '0x00fff0',' # color of the grid ( hex HTML value )
147             number => 8, # number of grid division
148             thickness => 1, # size of the division's line ( default = 1 )
149             label => { # an optional label on the bottom side
150             font => '/usr/lib/cinelerra/fonts/trebuc.ttf', # a TrueType font to use
151             color => '0xff0000',', # the color of the label
152             size => 12, # the size of the font
153             text => [ 100, undef, '20', undef, 1585, undef, 555 ], # the text to render ( a undef element is not ploted, this allow to skip some label )
154             # space => 10, # an extra space between the division and the text
155             rotation => 45, # an rotation of the text in degree
156             kerning_correction => 0.85, # a kerning correcting to correct align of text when rotated ( default 0.91 )
157             surround => { color => '0x0000ff' , thickness => 1 }, # create a frame around the text with the specified color and thickness
158             },
159             # label2 => { # an optional label on the top side
160             # font => '/usr/lib/cinelerra/fonts/trebuc.ttf', # a TrueType font to use
161             color => '0xff0000',', # the color of the label
162             size => 12, # the size of the font
163             text => [ 100, undef, '20', undef, 1585, undef, 555 ], # the text to render ( a undef element is not ploted, this allow to skip some label )
164             # space => 10, # an extra space between the division and the text
165             rotation => 45, # an rotation of the text in degree
166             kerning_correction => 0.85, # a kerning correcting to correct align of text when rotated ( default 0.91 )
167             surround => { color => '0x0000ff' , thickness => 1 }, # create a frame around the text with the specified color and thickness
168             # }
169             }
170              
171              
172             reticle => { # when the Chart's type is of any circular shape, create polar division
173             debord => 30, # the extra debord of the division
174             color => '0xff0000', # the color of the division
175             number => 10, # the number of division
176             label_middle => { # the label to write between 2 division
177             font => '/usr/lib/cinelerra/fonts/lucon.ttf', # a TrueType font to use
178             kerning_correction => 0.85, # a kerning correcting to correct align of text when rotated ( default 0.91 )
179             color => '0xff0000', # the text color
180             size => 10, # the font size to use
181             # space => 10, # an extra space between the division and the text
182             # rotate => 'follow', # rotate the text to be following the division direction
183             rotate => 'perpendicular', # rotate the the to be perpendicular to the division
184             # if missing write the text without rotation
185             text => [700031220,45,90,135,180,225,270,31500 , 1 ,2], # the text to render ( a undef element is not ploted, this allow to skip some label )
186             },
187             # label => { # the label to write at the division
188             font => '/usr/lib/cinelerra/fonts/lucon.ttf', # a TrueType font to use
189             kerning_correction => 0.85, # a kerning correcting to correct align of text when rotated ( default 0.91 )
190             color => '0xff0000', # the text color
191             size => 10, # the font size to use
192             # space => 10, # an extra space between the division and the text
193             # rotate => 'follow', # rotate the text to be following the division direction
194             rotate => 'perpendicular', # rotate the the to be perpendicular to the division
195             # if missing write the text without rotation
196             text => [700031220,45,90,135,180,225,270,31500 , 1 ,2], # the text to render ( a undef element is not ploted, this allow to skip some label )
197             # },
198              
199             overlay=> { # add an overlay to the graph (useful to show an alert period )
200             layer => 10, # the layer where the data is plotted ( the lowest number is the deepest layer ) If missing, the layer is created by call order of the method data
201             set => \@alarm, # a array ref with the data ( the number of dot plotted is the number W provided by the size parameter/method
202             type => 'pie', # the type of graph ( dot, line, bar, up_dot, up_bar, up_line , down_dot,down_line, down_bar, pie, target, radial )
203             color => '0xFFD2D2', # color of the plotted element
204             type => 'pie', # if missing normal overlay are used, if present use a polar structure ( data are in the range of 0 to 360 ° )
205             merge => 1, # if present and not = 0 all overlay are overwrited by the overlay from a higer layer
206             opacity => 100, # when merge is missing, the overlay % of opacity copied on the chart
207             debord => 50, # the debord of the overlay. if missing use the full graph height and in polar ( pie ) use the smallest vertical border ( top or bottom )
208             },
209            
210             glyph => { # add some ornament on the graph like line, text or polygon
211             x => $graph->{x}{min}+200, # the origin of the glyph, all other position are relative to this origin
212             y => $graph->{x}{max} , # either in pixel x =>0 , y=> 0 = corner lower left
213             # see the active method
214             type => 'filled', # type of glyph ( missing = open polygyn, 'filled' = filled polygon, 'text' = text )
215             color => '0x00FFff', # color of the glyph
216             data => [ # if one of the polygon type, the data is a set of point to plot ( value relative to the origin )
217             [ 0, 0 ],
218             [ 8, 10 ],
219             [ 0, 10 ],
220             [ 0, 10 + 20 ],
221             [ 0, 10 ],
222             [ -8, 10 ],
223             [ 0, 0 ]
224             ],
225            
226             glyph => {
227             x => 100,
228             y => 'active_max',
229             type => 'text',
230             color => '0xff0000',
231             size => 12, # if the glyph's type is 'text', this is the font size
232             font => '/usr/lib/cinelerra/fonts/lucon.ttf', # the TrueType font to use
233             data => [ # the data set contain an array with all the text to plot followed by the relative position + the optional rotation
234             [ 'hello world', 0, 0, 30 ], #
235             [ 'hello universe', 100, 0, 0 ],
236             ],
237             },
238             },
239             },
240              
241             all these parameters are optional except the size
242              
243             my $a = B->new( size => [ 800,400 ]
244             );
245              
246             =back
247              
248             =back
249              
250             =cut
251              
252             sub new
253             {
254             my ( $class ) = shift;
255             # no strict "refs";
256             # my $fields_ref = \%{ "${class}::FIELDS" };
257             # my $self = $fields_ref;
258             my $self;
259              
260             $self->{ size } = { @_ }->{ size };
261             $self->{ bg_color } = _re_color( { @_ }->{ bg_color }, 'ffffffff' );
262             if ( exists { @_ }->{ frame } )
263             {
264             $self->{ frame } = { @_ }->{ frame };
265             if ( exists { @_ }->{ frame }{ color } )
266             {
267             $self->{ frame }{ color } = _re_color( { @_ }->{ frame }{ color }, '00000000' );
268             }
269             $self->{ frame }{ thickness } = { @_ }->{ frame }{ thickness } || 1;
270             }
271              
272             $self->{ border } = { @_ }->{ border } || [ 0, 0, 0, 0 ];
273              
274             if ( exists { @_ }->{ grid } )
275             {
276             $self->{ grid } = { @_ }->{ grid };
277             unless ( exists $self->{ grid }->{ debord } )
278             {
279             $self->{ grid }->{ debord } = [ 0, 0, 0, 0 ];
280             }
281             }
282             if ( exists { @_ }->{ reticle } )
283             {
284             $self->{ reticle } = { @_ }->{ reticle };
285             if ( !exists { @_ }->{ reticle }->{ debord } )
286             {
287             $self->{ reticle }{ debord } = 0;
288             }
289             if ( !exists $self->{ reticle }{ number } )
290             {
291             $self->{ reticle }->{ number } = 2;
292             }
293             }
294              
295             if ( exists { @_ }->{ overlay } )
296             {
297             if ( exists { @_ }->{ overlay }{ layer } )
298             {
299             $self->{ overlay }[ { @_ }->{ overlay }{ layer } ] = clone( { @_ }->{ overlay } );
300             }
301             else
302             {
303             push @{ $self->{ overlay } }, clone( { @_ }->{ overlay } );
304             }
305             }
306             if ( exists { @_ }->{ glyph } )
307             {
308             if ( exists { @_ }->{ glyph }{ layer } )
309             {
310             $self->{ glyph }[ { @_ }->{ glyph }{ layer } ] = clone( { @_ }->{ glyph } );
311             }
312             else
313             {
314             push @{ $self->{ glyph } }, clone( { @_ }->{ glyph } );
315             }
316             }
317              
318             bless( $self, $class );
319             return $self;
320             }
321              
322             sub _color_allocate
323             {
324             my $col = shift;
325             my $def = shift;
326             my $graph = shift;
327              
328             if ( ref $col eq 'ARRAY' )
329             {
330             my @style;
331             foreach my $c ( @{ $col } )
332             {
333             my ( $r, $g, $b, $a ) = unpack "a2 a2 a2 a2 ", _re_color( $c, 'ffffffff' );
334             push @style, $graph->colorAllocateAlpha( hex $r, hex $g, hex $b, hex $a );
335             }
336             $graph->setStyle( @style );
337             return gdStyled;
338             }
339             else
340             {
341             if ( $col =~ /^(0x)??([[:xdigit:]]{6})$/i )
342             {
343             $col = $2 . '00';
344             }
345             elsif ( $col =~ /^(0x)??([[:xdigit:]]{8})$/i )
346             {
347             $col = $2;
348             }
349             else
350             {
351             $col = $def;
352             }
353             my ( $r, $g, $b, $a ) = unpack "a2 a2 a2 a2 ", $col;
354             return $graph->colorAllocateAlpha( hex $r, hex $g, hex $b, hex $a );
355             }
356             }
357              
358             sub _re_color
359             {
360             my $col = shift;
361             my $def = shift;
362             my $graph = shift;
363              
364             if ( $col =~ /^(0x)??([[:xdigit:]]{6})$/i )
365             {
366             $col = $2 . '00';
367             }
368             elsif ( $col =~ /^(0x)??([[:xdigit:]]{8})$/i )
369             {
370             $col = $2;
371             }
372             else
373             {
374             $col = $def;
375             }
376             return $col;
377             }
378             ###########################################################################
379              
380             ###########################################################################
381             sub img_from
382             {
383             my $self = shift;
384             my $object = shift;
385              
386             my $file = $object->{ file };
387             my $image;
388             {
389             local $/ = undef;
390             open IMG, $file;
391             binmode IMG;
392             $image = ;
393             close IMG;
394             }
395             my $image_gd = GD::Image->new( $image );
396             my $image_png = $image_gd->png;
397             my @chunks;
398             my $chunks_nbr = 0;
399             substr( $image, 0, 33, '' );
400             while ( 1 )
401             {
402             my $slice = substr( $image, 0, 8, '' );
403             my ( $len, $type ) = unpack( "Na4", $slice );
404             last if $type eq 'IEND';
405             if ( $type eq 'tEXt' )
406             {
407             my $tEXt = substr( $image, 0, $len, '' );
408             my @all = split( /\0/, $tEXt, 2 );
409             my $obj = Data::Serializer->new();
410             my $tags = $obj->deserialize( $all[1] );
411             foreach my $tag ( keys %{ $tags } )
412             {
413             next if ( $tag eq 'Graph::Chart' );
414             $self->{ $tag } = $tags->{ $tag };
415             }
416             foreach my $tag ( keys %{ $tags->{ 'Graph::Chart' } } )
417             {
418             $self->{ $tag } = $tags->{ 'Graph::Chart' }{ $tag };
419             }
420             last;
421             }
422             }
423             $self->{ img } = $image_png;
424             if ( !exists $self->{ size_tot } )
425             {
426             ( $self->{ size_tot }->[0], $self->{ size_tot }->[1] ) = ( $image_gd->getBounds() )[ 0, 1 ];
427             }
428             $self;
429             }
430              
431             ###########################################################################
432             ### method to reduce a set of data ###
433             ### with specific polling time ###
434             ### to fit the dot size ###
435             ###########################################################################
436              
437             =head2 reduce
438              
439             get a set of data as input and return the data to fill the array with the plotting values
440             if more input data then the dot in the graph, process by averaging for a sample calculated on the target size
441             if lower input data then the dot in the graph, repeat the input data in the slice related
442             if called in array context return a ref to the array with reduced data and a ref to a hash with the statistical data
443             in sclar context return a ref to the array with reduced data
444            
445             my $dr= $graph->reduce(
446             {
447             start => 5, # start to fill the destination array at that element ( optional, default = 0 )
448             end => 50, # fill the destination array until that element ( optional, default = plot width )
449             data => \@dot, # the input data set
450             init => 0, # a default value for the destination set if not filled ( optional, default = undef )
451             type => 'line' # type of interpollation if lower element in the input data set then in the target
452             # default = step, the value is duplicate to fill-in all the destination dot for the slice
453             # if line, the dot are filled with an increasing/decreasing step created by the to adjacent value/ by the number of dot in the slice
454             # if nrz = keep the previous value if now value == 0
455             percentile => 0.90 # a percentile to use (default = 0.95 )
456             }
457             );
458              
459             =cut
460              
461             sub reduce
462             {
463             my $self = shift;
464             my $object = shift;
465              
466             my $width_out = $self->{ size }->[0];
467             my $start = $object->{ start } || 0;
468             my $percentile_value = $object->{ percentile } || 0.95;
469             my $end = $object->{ end } || $width_out;
470             my @data_in = @{ $object->{ data } };
471             my $data_in_size = scalar @data_in;
472             my @perc = sort { $a <=> $b } @data_in[$start .. $end ] ;
473             my $prec_ind = int( scalar( @perc ) * $percentile_value);
474            
475             my @data_out;
476             my %STATS;
477            
478             $STATS{ percentile } = $perc[$prec_ind];
479             $STATS{ min } = min @perc;
480             $STATS{ max } = max @data_in;
481             $STATS{ sum } = sum @data_in;
482              
483             $STATS{ avg } = $STATS{ sum } / scalar( @perc );
484            
485             $#data_out = $width_out;
486             my $width_in = $end - $start + 1;
487            
488             my $data_dot = ( scalar @data_in ) / $width_in;
489             my $data_dot_int = int( $data_dot + 0.5 );
490             my @chars;
491              
492             if ( exists $object->{ init } )
493             {
494             @data_out = map( $object->{ init }, @data_out );
495             }
496             if ( $#data_out <= $#data_in )
497             {
498             my $old_val = 0;
499             for ( my $dot = $start ; $dot <= $end ; $dot++ )
500             {
501             my $s = ( $dot - $start ) * $data_dot;
502             my $e = $s + $data_dot - 1;
503             my @slice = @data_in[ $s .. $e ];
504             if ( scalar( @slice ) )
505             {
506             if ( $object->{ type } =~ /^nrz$/i )
507             {
508             foreach my $idx ( 0 .. $#slice )
509             {
510             if ( $slice[$idx] == 0 )
511             {
512             $slice[$idx] = $old_val;
513             }
514             else
515             {
516             $old_val = $slice[$idx];
517             }
518             }
519             }
520             $data_out[$dot] = sum( @slice ) / scalar( @slice );
521             }
522             else
523             {
524             $data_out[$dot] = 0;
525             }
526             $STATS{ last } = $dot;
527             $STATS{ last_val } = $data_in[ -1 ] ;
528             }
529             }
530             else
531             {
532             if ( exists $object->{ type } && $object->{ type } =~ /^line|nrz$/i )
533             {
534             my $dot = 0;
535             my $old_val = 0;
536             W: while ( $dot <= $width_in )
537             {
538             my $ind = ( int( ( $dot / ( $width_in / $data_in_size ) ) ) );
539             my $val1 = $ind > $#data_in ? $data_in[-1] : $data_in[$ind];
540             my $val2 = ( $ind + 1 ) > $#data_in ? $data_in[-1] : $data_in[ ( $ind + 1 ) ];
541             my $inc = ( $val2 - $val1 ) / ( ( $width_in / $data_in_size ) );
542             my $val = $val1 || 0;
543             for ( 0 .. ( $width_in / $data_in_size ) )
544             {
545             $STATS{ last } = $dot;
546             last W if ( $dot >= $width_in );
547             if ( $object->{ type } =~ /^nrz$/i && ( !$val2 || !$val ) )
548             {
549             $data_out[ $dot + $start ] = $old_val;
550             }
551             else
552             {
553             $data_out[ $dot + $start ] = $val;
554             $old_val = $val;
555             $val += $inc;
556             }
557             if ( $inc > 0 )
558             {
559             $val = $val > $val2 ? $val2 : $val;
560             }
561             else
562             {
563             $val = $val < $val2 ? $val2 : $val;
564             }
565              
566             $dot++;
567             }
568             }
569             }
570             else
571             {
572             for ( my $dot = 1 ; $dot <= $width_in ; $dot++ )
573             {
574             $STATS{ last } = $dot;
575             my $ind = ( int( ( $dot / ( $width_in / $data_in_size ) ) ) );
576             $data_out[ $dot + $start - 1 ] = $ind > $#data_in ? $data_in[-1] : $data_in[$ind];
577             }
578             }
579             }
580             return wantarray ? ( \@data_out, \%STATS ) : \@data_out;
581             # return \@data_out, \%STATS;
582             }
583             ###########################################################################
584              
585             ###########################################################################
586             ### method to set the grid ###
587             ###########################################################################
588              
589             =head2 grid
590              
591             set the grid
592              
593             use the same parameter as the new()
594             if the option is already present, overwrite this option
595              
596             =cut
597              
598             sub grid
599             {
600             my $self = shift;
601             my $object = shift;
602              
603             if ( $object )
604             {
605             foreach my $item ( keys %{ $object } )
606             {
607             if ( ref( $object->{ $item } ) eq 'HASH' )
608             {
609             foreach my $sub_item ( keys %{ $object->{ $item } } )
610             {
611             $self->{ grid }{ $item }{ $sub_item } = $object->{ $item }{ $sub_item };
612             }
613             }
614             else
615             {
616             $self->{ grid }{ $item } = $object->{ $item };
617             }
618             unless ( exists $self->{ grid }->{ debord } )
619             {
620             $self->{ grid }->{ debord } = [ 0, 0, 0, 0 ];
621             }
622             }
623             }
624             return $self->{ grid };
625             }
626              
627             ###########################################################################
628              
629             ###########################################################################
630             ### method to set the reticle ###
631             ###########################################################################
632              
633             =head2 reticle
634              
635             set the reticle
636             the reticle are the division when using a polar chart ( pie, target .... )
637              
638             use the same parameter as the new()
639             if the option is already present, overwrite this option
640              
641             =cut
642              
643             sub reticle
644             {
645             my $self = shift;
646             my $object = shift;
647              
648             if ( $object )
649             {
650             foreach my $item ( keys %{ $object } )
651             {
652             if ( ref( $object->{ $item } ) eq 'HASH' )
653             {
654             foreach my $sub_item ( %{ $object->{ $item } } )
655             {
656             $self->{ reticle }{ $item }{ $sub_item } = $object->{ $item }{ $sub_item };
657             }
658             }
659             else
660             {
661             $self->{ reticle }{ $item } = $object->{ $item };
662             }
663             unless ( exists $self->{ reticle }->{ debord } )
664             {
665             $self->{ reticle }->{ debord } = 0;
666             }
667             }
668             }
669             return $self->{ reticle };
670             }
671              
672             ###########################################################################
673              
674             ###########################################################################
675             ### method to set the frame ###
676             ###########################################################################
677              
678             =head2 frame
679              
680             set the frame
681              
682             use the same parameter as the new()
683             if the option is already present, overwrite this option
684              
685             =cut
686              
687             sub frame
688             {
689             my $self = shift;
690             my $object = shift;
691              
692             if ( $object )
693             {
694             $self->{ frame } = $object;
695             foreach my $item ( keys %{ $object } )
696             {
697             if ( ref( $object->{ $item } ) eq 'HASH' )
698             {
699             foreach my $sub_item ( %{ $object->{ $item } } )
700             {
701             $self->{ frame }{ $item }{ $sub_item } = $object->{ $item }{ $sub_item };
702             }
703             }
704             else
705             {
706             $self->{ frame }{ $item } = $object->{ $item };
707             }
708             }
709             if ( exists $object->{ color } )
710             {
711             $self->{ frame }{ color } = _re_color( $object->{ color }, '00000000' );
712             }
713             }
714             return $self->{ frame };
715             }
716              
717             ###########################################################################
718              
719             ###########################################################################
720             ### method to set the size ###
721             ###########################################################################
722              
723             =head2 size
724              
725             set the size ( this is the only mandatory option )
726              
727             use the same parameter as the new()
728             if the option is already present, overwrite this option
729              
730             =cut
731              
732             sub size
733             {
734             my $self = shift;
735             my $object = shift;
736              
737             if ( $object )
738             {
739             $self->{ size } = $object;
740             }
741             return $self->{ size };
742             }
743             ###########################################################################
744              
745             ###########################################################################
746             ### method to get the active border size ###
747             ###########################################################################
748              
749             =head2 active
750              
751             get the active border size
752              
753             return a hash ref with
754             $ref->{ x }{ max } ==> left border of the main graph
755             $ref->{ x }{ min } ==> right border of the main graph
756             $ref->{ y }{ max } ==> upper border of the main graph
757             $ref->{ y }{ min } ==> lower border of the main graph
758            
759             =cut
760              
761             sub active
762             {
763             my $self = shift;
764             my %tmp;
765             $tmp{ x }{ max } = $self->{ border }->[0] + $self->{ size }->[0];
766             $tmp{ x }{ min } = $self->{ border }->[0];
767             $tmp{ y }{ max } = $self->{ border }->[3] + $self->{ size }->[1];
768             $tmp{ y }{ min } = $self->{ border }->[2];
769             return \%tmp;
770             }
771             ###########################################################################
772              
773             ###########################################################################
774             ### method to set the bg_color ###
775             ###########################################################################
776              
777             =head2 bg_color
778              
779             set the bg_color
780             set the background color of the graph
781              
782             use the same parameter as the new()
783             if the option is already present, overwrite this option
784              
785             =cut
786              
787             sub bg_color
788             {
789             my $self = shift;
790             my $object = shift;
791              
792             if ( $object )
793             {
794             $self->{ bg_color } = $object;
795             }
796             return $self->{ bg_color };
797             }
798             ###########################################################################
799              
800             ###########################################################################
801             ### method to provide the data to plot ###
802             ###########################################################################
803              
804             =head2 data
805              
806             set the data to be plotted
807              
808              
809             $graph->data(
810             {
811             layer => 10, # the layer where the data is plotted ( the lowest number is the deepest layer ) If missing, the layer is created by call order of the method data
812             set => \@dot, # a array ref with the data ( the number of dot plotted is the number W provided by the size parameter/method
813             type => 'pie', # the type of graph ( dot, line, bar, up_dot, up_bar, up_line , down_dot,down_line, down_bar, pie, target, radial )
814             bar_size => 1, # if any type of bar used, this is an extra width of the bar created, if not defined, the bar width= 1 if set to 1 the size of the bar became 3 ( 1 before, 1 for the bar and one after )
815             color => '0x0000ff', # color of the plotted element
816             thickness => 1, # for any type of dot and line, the thiskness to used ( default = 1 )
817             scale => '90%', # a vertical scale on the value provided ( a decimal number scale all the data value using the value ( data could be outside of the graph) 1 = 100%
818             # a percent value like, '90%' scale the graph to that percentage ( lower then 100% = some data are plotted outside the graph )
819             # missing or '100%' resize the graph using the maximal value
820             # 'auto' or '110%' allow to always have a small extra gap and never reach to extremity of the graph area,
821             max => 3000, # a maximal value to use to create the graph ( if missing, max = maximal value from the data set )
822            
823             }
824             );
825             =cut
826              
827             sub data
828             {
829             my $self = shift;
830             my $object = shift;
831              
832             if ( $object )
833             {
834             if ( exists $object->{ layer } )
835             {
836             $self->{ data }[ $object->{ layer } ] = clone( $object );
837             }
838             else
839             {
840             push @{ $self->{ data } }, clone( $object );
841             }
842             }
843             return $self->{ data };
844             }
845              
846             ###########################################################################
847              
848             ###########################################################################
849             ### method to put an overlay on top of the graph ###
850             ###########################################################################
851              
852             =head2 overlay
853              
854             method to put an overlay on top of the graph ( to show alarm period ... )
855              
856              
857             use the same parameter as the new()
858             if the same layer is already present, overwrite this layer
859              
860             =cut
861              
862             sub overlay
863             {
864             my $self = shift;
865             my $object = shift;
866              
867             if ( $object )
868             {
869             if ( exists $object->{ layer } )
870             {
871             $self->{ overlay }[ $object->{ layer } ] = clone( $object );
872             }
873             else
874             {
875             push @{ $self->{ overlay } }, clone( $object );
876             }
877             }
878             return $self->{ overlay };
879             }
880             ###########################################################################
881              
882             ###########################################################################
883             ### method to put a glyph on the graph ###
884             ###########################################################################
885              
886             =head2 overlay
887              
888             method to put a glyph on the graph ( to show the latest data polled, or a trend value, ... )
889              
890              
891             use the same parameter as the new()
892             if the same layer is already present, overwrite this layer
893              
894             =cut
895              
896             sub glyph
897             {
898             my $self = shift;
899             my $object = shift;
900              
901             if ( $object )
902             {
903             if ( exists $object->{ layer } )
904             {
905             $self->{ glyph }[ $object->{ layer } ] = clone( $object );
906             }
907             else
908             {
909             push @{ $self->{ glyph } }, clone( $object );
910             }
911             }
912             return $self->{ glyph };
913             }
914             ###########################################################################
915              
916             ###########################################################################
917             ### method to add a png data TAG ( not standard ) ###
918             ###########################################################################
919              
920             =head2 png_zEXt
921              
922             method to add a png data TAG
923             This tag is not a PNG standard, but allowed by the RFC
924             see code in img_info.pl
925            
926             my $png_out1 =$graph->png_zEXt( { eerer => 1, ggg => 'zed' } );
927             this overwrite the png TAG data with the new value and return the new image
928              
929             =cut
930              
931             sub png_zEXt
932             {
933             my $self = shift;
934             my $object = shift;
935             $self->{ size_tot }->[0] = $self->{ size }->[0] + $self->{ border }->[0] + $self->{ border }->[1];
936             $self->{ size_tot }->[1] = $self->{ size }->[1] + $self->{ border }->[2] + $self->{ border }->[3];
937             my $tmp = clone( $self );
938             # delete $tmp->{ data };
939             foreach my $idx (0 .. scalar @{$tmp->{ data }})
940             {
941            
942             next if ( ! defined $tmp->{ data }[ $idx ] );
943             delete $tmp->{ data }[ $idx]{ set};
944             }
945             delete $tmp->{ img };
946              
947             my $obj = Data::Serializer->new( 'compress' => 1 );
948             $object->{ 'Graph::Chart' } = $tmp;
949             my $tag = $obj->serialize( $object );
950             my $png_out;
951             my $ihdr; # IHDR chunk
952             my %tEXt; # tEXt chunks to insert
953             my $sig; # PNG signature
954             my $pos; # position in $png
955             my $pngsize; # Total size of png
956             my $text; # 'string' of all tEXt chunks with CRC, etc.
957             my $tchunk; # content of text chunk
958             $tEXt{ data } = $tag;
959             ( $sig, $ihdr, $png_out ) = unpack "a8 a25 a*", $self->{ img };
960             $png_out =~ /(.*)(....PLTE.*)/s;
961              
962             my $old_tag = $1;
963             my $end_png = $2;
964              
965             foreach my $keyword ( keys %tEXt )
966             {
967              
968             #* A tEXt chunk contains:
969             #*
970             #* Keyword: 1-79 bytes (character string)
971             #* Null separator: 1 byte
972             #* Compression method: 1 byte
973             #* Compressed text: n bytes
974             my $tbuffer;
975             $tbuffer = $tEXt{ $keyword };
976             $tbuffer =~ s/\\([tnrfbae])/control_char($1)/eg;
977             $tchunk = sprintf "%s%c%s", $keyword, 0, $tbuffer;
978             $text .= pack "N A* N", ( length( $tchunk ), 'tEXt' . $tchunk, &crc32( 'tEXt' . $tchunk ) );
979             $pngsize += length( $tchunk ) + 8;
980             }
981             $png_out = $sig . $ihdr . $text . $end_png;
982             $self->{ img } = $png_out;
983             return $self->{ img };
984             }
985              
986             ###########################################################################
987             sub update
988             {
989             my $self = shift;
990             my $object = shift;
991             # carp Dumper($self);
992             my $image_gd = GD::Image->new( $self->{img});
993             # carp $image_gd;
994             #
995             # $image->copy($sourceImage,$dstX,$dstY, $srcX,$srcY,$width,$height)
996              
997             }
998              
999              
1000              
1001              
1002             ###########################################################################
1003             ### method to render the Chart ###
1004             ###########################################################################
1005              
1006             =head2 render
1007              
1008             render the chart and return a png image
1009              
1010              
1011             my $img = $graph->render( \%tag )
1012            
1013            
1014             the hash ref contain data to put in the PNG meta tag.
1015             the tools img_info.pl allow to see the result.
1016             the tag is serialized in the png
1017            
1018             the returned value could be writted in a file like this:
1019             my $png_out = $graph->render();
1020            
1021             open( my $IMG, '>', $file ) or die $!;
1022             binmode $IMG;
1023             print $IMG $png_out;
1024             close $IMG;
1025             );
1026              
1027             =cut
1028              
1029             sub render
1030             {
1031             my $self = shift;
1032             my $object = shift;
1033              
1034             my $frame = new GD::Image( $self->{ size }->[0] + $self->{ border }->[0] + $self->{ border }->[1], $self->{ size }->[1] + $self->{ border }->[2] + $self->{ border }->[3] );
1035             my $bg_color = _color_allocate( $self->{ bg_color }, 'ffffffff', $frame );
1036             my $bg_color = _color_allocate( $self->{ bg_color }, 'ffffffff', $frame );
1037             $frame->transparent( $bg_color );
1038             $frame->interlaced( 'true' );
1039              
1040             ### plot overlay
1041             if ( exists $self->{ overlay } )
1042             {
1043             foreach my $layer ( @{ $self->{ overlay } } )
1044             {
1045             next unless ( ref $layer eq 'HASH' );
1046             my $col_graph;
1047             my $frame_over;
1048             if ( exists $layer->{ merge } && $layer->{ merge } )
1049             {
1050             $col_graph = _color_allocate( $layer->{ color }, '00000000', $frame );
1051             }
1052             else
1053             {
1054             $frame_over = new GD::Image( $self->{ size }->[0] + $self->{ border }->[0] + $self->{ border }->[1], $self->{ size }->[1] + $self->{ border }->[2] + $self->{ border }->[3] );
1055             my ( $r, $g, $b, $a ) = unpack "a2 a2 a2 a2 ", $self->{ bg_color };
1056             my $bg_color_over = $frame_over->colorAllocateAlpha( hex $r, hex $g, hex $b, hex $a );
1057             $frame_over->transparent( $bg_color_over );
1058             $frame_over->interlaced( 'true' );
1059             $frame_over->setThickness( 1 );
1060              
1061             $col_graph = _color_allocate( $layer->{ color }, '00000000', $frame );
1062             }
1063             my $extra =
1064             $self->{ border }->[2] > $self->{ border }->[3]
1065             ? $self->{ border }->[3]
1066             : $self->{ border }->[2];
1067             if ( exists $layer->{ debord } )
1068             {
1069             $extra = $layer->{ debord };
1070             }
1071             my $dot = -1;
1072             my $last_pie;
1073             foreach my $raw_val ( @{ $layer->{ set } } )
1074             {
1075             $dot++;
1076             next if ( !defined $raw_val || !$raw_val );
1077             my $plot_dot = $self->{ border }->[0] + $dot;
1078             my $plot_val = $self->{ border }->[2] + $self->{ border }->[3] + $self->{ size }->[1];
1079              
1080             if ( exists $layer->{ merge } && $layer->{ merge } )
1081             {
1082             if ( exists $layer->{ type } && $layer->{ type } eq 'pie' )
1083             {
1084             $frame->filledArc( $self->{ size }->[0] / 2 + $self->{ border }->[0], ( $self->{ size }->[1] / 2 ) + $self->{ border }->[2], ( $self->{ size }->[1] + ( 2 * $extra ) ), ( $self->{ size }->[1] + ( 2 * $extra ) ), $dot, $dot + 1, $col_graph, gdEdged );
1085             $last_pie = $dot;
1086             }
1087             else
1088             {
1089             $frame->line( $plot_dot, 0, $plot_dot, $plot_val, $col_graph );
1090             }
1091             }
1092             else
1093             {
1094             if ( exists $layer->{ type } && $layer->{ type } eq 'pie' )
1095             {
1096             $frame_over->filledArc( $self->{ size }->[0] / 2 + $self->{ border }->[0], ( $self->{ size }->[1] / 2 ) + $self->{ border }->[2], ( $self->{ size }->[1] + ( 2 * $extra ) ), ( $self->{ size }->[1] + ( 2 * $extra ) ), $dot, $dot + 1, $col_graph, gdEdged );
1097             }
1098             else
1099             {
1100             $frame_over->line( $plot_dot, 0, $plot_dot, $plot_val, $col_graph );
1101             }
1102             }
1103             }
1104              
1105             if ( exists $layer->{ merge } && $layer->{ merge } )
1106             {
1107             }
1108             else
1109             {
1110             my $trans = $layer->{ opacity } || 20;
1111             $frame->copyMerge( $frame_over, 0, 0, 0, 0, $self->{ size }->[0] + $self->{ border }->[0] + $self->{ border }->[1], $self->{ size }->[1] + $self->{ border }->[2] + $self->{ border }->[3], $trans );
1112             }
1113              
1114             }
1115             }
1116             ### end plot overlay
1117              
1118             ### plot data
1119             if ( exists $self->{ data } )
1120             {
1121             my $last_pie;
1122             foreach my $layer ( @{ $self->{ data } } )
1123             {
1124             next unless ( ref $layer eq 'HASH' );
1125             my $max = max( @{ $layer->{ set } } );
1126             my $min = min( @{ $layer->{ set } } );
1127             my $scale = 1;
1128             my $pre_scale = 1;
1129             my $bar_size = $layer->{ bar_size } || 1;
1130             if ( exists $layer->{ scale } )
1131             {
1132              
1133             if ( $layer->{ scale } =~ /^(\d*\.*\d*)%$/ )
1134             {
1135             $pre_scale = $1 / 100;
1136             }
1137             if ( $layer->{ scale } =~ /^(\d*\.*\d*)$/ )
1138             {
1139             $pre_scale = $1;
1140             }
1141             elsif ( $layer->{ scale } eq 'auto' )
1142             {
1143             $pre_scale = 1.1;
1144             }
1145             }
1146             if ( exists $layer->{ max } )
1147             {
1148             $max = $layer->{ max };
1149             }
1150             $scale = $self->{ size }->[1] / ( $pre_scale * $max );
1151             if ( exists $layer->{ type } && $layer->{ type } =~ /(up|down)/ )
1152             {
1153             $scale /= 2;
1154             }
1155              
1156             my $thickness = $layer->{ thickness } || 1;
1157             $frame->setThickness( $thickness );
1158             my $col_graph = _color_allocate( $layer->{ color }, '00000000', $frame );
1159              
1160             if ( !exists $layer->{ type } || $layer->{ type } =~ /line|dot|bar/ )
1161             {
1162             my $poly = new GD::Polygon;
1163             my $dot = -1;
1164             foreach my $raw_val ( @{ $layer->{ set } } )
1165             {
1166             $dot++;
1167             next if ( !defined $raw_val );
1168             last if ( $dot >= $self->{ size }->[0] );
1169             my $offset = $layer->{ offset } || 0;
1170             my $val = ( $scale * $raw_val ) + $offset;
1171              
1172             if ( exists $layer->{ scale } && $layer->{ scale } eq 'log' )
1173             {
1174             $raw_val = $raw_val <= 0 ? $min : $raw_val;
1175             next if ( $raw_val <= 0 );
1176             $val = log10( $raw_val ) + $offset;
1177             }
1178             elsif ( exists $layer->{ scale } && $layer->{ scale } eq 'ln' )
1179             {
1180             $raw_val = $raw_val <= 0 ? $min : $raw_val;
1181             next if ( $raw_val <= 0 );
1182             $val = log( $raw_val ) + $offset;
1183             }
1184              
1185             $val = $val > $self->{ size }->[1] ? $self->{ size }->[1] : $val;
1186             $val = $val < 0 ? 0 : $val;
1187             my $plot_dot = $self->{ border }->[0] + $dot;
1188              
1189             my $plot_val = $self->{ border }->[2] + $self->{ size }->[1] - $val;
1190              
1191             my $y_size = $self->{ size }->[1];
1192             if ( $layer->{ type } =~ /up/ )
1193             {
1194             $y_size /= 2;
1195             $val = $val > $y_size ? $y_size : $val;
1196             $plot_val = $self->{ border }->[2] + $y_size - $val;
1197             }
1198             elsif ( $layer->{ type } =~ /down/ )
1199             {
1200             $y_size /= 2;
1201             $val = $val > $y_size ? $y_size : $val;
1202             $plot_val = $self->{ border }->[2] + $y_size + $val;
1203             }
1204             if ( $layer->{ type } =~ /line/ )
1205             {
1206             $poly->addPt( $plot_dot, $plot_val );
1207             }
1208             elsif ( $layer->{ type } =~ /dot/ )
1209             {
1210             $frame->filledEllipse( $plot_dot, $plot_val, $thickness, $thickness, $col_graph );
1211             }
1212             elsif ( $layer->{ type } =~ /bar/ )
1213             {
1214             $frame->filledRectangle( $plot_dot - $bar_size, $self->{ border }->[2] + $y_size - $layer->{ offset }, $plot_dot + $bar_size, $plot_val, $col_graph );
1215             }
1216             }
1217             $frame->unclosedPolygon( $poly, $col_graph ) if ( $layer->{ type } =~ /line/ );
1218             }
1219             elsif ( $layer->{ type } eq 'pie' )
1220             {
1221             my $img_width = $self->{ size }->[0];
1222             my $img_height = $self->{ size }->[1];
1223             my $graph_offset = 0;
1224             my $alarm_border = 0;
1225             my $target_value_graph;
1226             my $scale = 1;
1227              
1228             my $bar_size = $layer->{ bar_size } || 1;
1229             if ( exists $layer->{ scale } )
1230             {
1231             if ( $layer->{ scale } =~ /^\d*\.*\d*$/ )
1232             {
1233             $scale = $layer->{ scale };
1234             }
1235             }
1236             $frame->filledArc( $img_width / 2 + $self->{ border }->[0], ( $img_height / 2 ) + $self->{ border }->[2], ( $img_height ) * $scale, ( $img_height ) * $scale, $last_pie, $layer->{ set }[-1] + $last_pie, $col_graph, gdEdged );
1237             $last_pie = $layer->{ set }[-1],;
1238             }
1239             elsif ( $layer->{ type } eq 'target' )
1240             {
1241             my $img_width = $self->{ size }->[0];
1242             my $img_height = $self->{ size }->[1];
1243             my $graph_offset = 0;
1244             my $alarm_border = 0;
1245             my $target_value_graph;
1246             my $scale = 1;
1247              
1248             my $bar_size = $layer->{ bar_size } || 1;
1249             if ( exists $layer->{ scale } )
1250             {
1251             if ( $layer->{ scale } =~ /^\d*\.*\d*$/ )
1252             {
1253             $scale = $layer->{ scale };
1254             }
1255             }
1256             $frame->filledArc( $img_width / 2 + $self->{ border }->[0], ( $img_height / 2 ) + $self->{ border }->[2], ( $img_height ) * $scale, ( $img_height ) * $scale, 0, $layer->{ set }[-1], $col_graph, gdEdged );
1257             }
1258             elsif ( $layer->{ type } eq 'radial' )
1259             {
1260             my $img_width = $self->{ size }->[0];
1261             my $img_height = $self->{ size }->[1];
1262             my $graph_offset = 0;
1263             my $alarm_border = 0;
1264             my $target_value_graph;
1265             my $tot = $self->{ size }->[1];
1266             my $max;
1267             my $scale = 1;
1268             my $pre_scale = 1;
1269             my $bar_size = $layer->{ bar_size } || 1;
1270              
1271             if ( exists $layer->{ scale } || $layer->{ scale } eq 'auto' )
1272             {
1273             if ( $layer->{ scale } =~ /^\d*\.*\d*$/ )
1274             {
1275             $pre_scale = $layer->{ scale };
1276             }
1277             $max = max( @{ $layer->{ set } } );
1278             $scale = $self->{ size }->[1] / ( $pre_scale * $max );
1279             }
1280             my $dot = -1;
1281             foreach my $raw_val ( @{ $layer->{ set } } )
1282             {
1283             my $plot_val = $raw_val * $scale;
1284             $dot++;
1285             $frame->filledArc( $img_width / 2 + $self->{ border }->[0], ( $img_height / 2 ) + $self->{ border }->[2], ( $plot_val ), ( $plot_val ), $dot, $dot + 1, $col_graph, gdEdged );
1286             }
1287             }
1288             }
1289             }
1290             ### end plot
1291              
1292             ### plot grid + label
1293             if ( exists $self->{ grid } )
1294             {
1295             if ( exists $self->{ grid }{ y } )
1296             {
1297             $frame->setThickness( $self->{ grid }{ y }{ thickness } );
1298             my $grid_color = _color_allocate( $self->{ grid }{ y }{ color }, 'ffffffff', $frame );
1299             for my $nbr ( 0 .. ( $self->{ grid }{ y }{ number } - 1 ) )
1300             {
1301             my $val = ( ( $nbr ) * ( ( ( ( $self->{ size }->[0] ) / ( $self->{ grid }{ y }{ number } - 1 ) ) ) ) );
1302             $frame->line( $self->{ border }->[0] + $val, $self->{ border }->[2] - $self->{ grid }{ debord }->[2], $self->{ border }->[0] + $val, $self->{ size }->[1] + $self->{ border }->[2] + $self->{ grid }{ debord }->[3], $grid_color );
1303             if ( defined $self->{ grid }{ y }{ label }{ text }->[$nbr] )
1304             {
1305             my $text_color = $grid_color;
1306             if ( exists $self->{ grid }{ y }{ label }{ color } )
1307             {
1308             $text_color = _color_allocate( $self->{ grid }{ y }{ label }{ color }, 'ffffffff', $frame );
1309             }
1310             my $radian = ( $self->{ grid }{ y }{ label }{ rotation } / 180 ) * PI || 0;
1311             my $kerning = $self->{ grid }{ y }{ label }{ kerning_correction } || 0.91;
1312             my $cos = cos( $radian );
1313             my $sin = sin( $radian );
1314             my $Xoff;
1315             my $Yoff;
1316             my $len = length( $self->{ grid }{ y }{ label }{ text }->[$nbr] );
1317              
1318             if ( $self->{ grid }{ y }{ label }{ rotation } )
1319             {
1320             $Xoff = ( $cos * ( $self->{ grid }{ y }{ label }{ size } ) ) - ( $cos * ( ( $len**$kerning ) * $self->{ grid }{ y }{ label }{ size } ) );
1321             $Yoff = ( $sin * ( ( $len**$kerning ) * $self->{ grid }{ y }{ label }{ size } ) ) + ( $sin * $self->{ grid }{ y }{ label }{ size } );
1322             }
1323             else
1324             {
1325             $Xoff = -( ( $len**$kerning ) * $self->{ grid }{ y }{ label }{ size } / 2 );
1326             $Yoff = $self->{ grid }{ y }{ label }{ size };
1327             }
1328             if ( $self->{ grid }{ y }{ label }{ rotation } == 90 )
1329             {
1330             $Xoff = $self->{ grid }{ y }{ label }{ size } / 2;
1331             $Yoff = ( ( $len**$kerning ) * $self->{ grid }{ y }{ label }{ size } );
1332             }
1333             my @b = $frame->stringFT(
1334             $text_color,
1335             $self->{ grid }{ y }{ label }{ font },
1336             $self->{ grid }{ y }{ label }{ size },
1337             $radian,
1338             $self->{ border }->[0] + $val + $Xoff,
1339             $self->{ size }->[1] +
1340             $self->{ border }->[2] +
1341             $self->{ grid }{ debord }->[3] +
1342             ( $self->{ grid }{ y }{ label }{ space } || 0 ) +
1343             $Yoff,
1344             $self->{ grid }{ y }{ label }{ text }->[$nbr],
1345             # { resolution => "95,95" }
1346             );
1347             if ( exists $self->{ grid }{ y }{ label }{ surround } )
1348             {
1349             my $surround_color = $grid_color;
1350             if ( exists $self->{ grid }{ y }{ label }{ surround }{ color } )
1351             {
1352             $surround_color = _color_allocate( $self->{ grid }{ y }{ label }{ surround }{ color }, $self->{ grid }{ y }{ label }{ color }, $frame );
1353             }
1354             $frame->setThickness( $self->{ grid }{ y }{ label }{ surround }{ thickness } )
1355             if ( exists $self->{ grid }{ y }{ label }{ surround }{ thickness } );
1356             my $polyT = new GD::Polygon;
1357             $polyT->addPt( $b[0], $b[1] );
1358             $polyT->addPt( $b[2], $b[3] );
1359             $polyT->addPt( $b[4], $b[5] );
1360             $polyT->addPt( $b[6], $b[7] );
1361             $frame->openPolygon( $polyT, $surround_color );
1362             }
1363             }
1364              
1365             if ( exists $self->{ grid }{ y }{ label }{ text } && defined $self->{ grid }{ y }{ label2 }{ text }->[$nbr] )
1366             {
1367             my $text_color = $grid_color;
1368             if ( exists $self->{ grid }{ y }{ label2 }{ color } )
1369             {
1370             $text_color = _color_allocate( $self->{ grid }{ y }{ label2 }{ color }, 'ffffffff', $frame );
1371             }
1372             my $radian = ( $self->{ grid }{ y }{ label2 }{ rotation } / 180 ) * PI || 0;
1373             my $kerning = $self->{ grid }{ y }{ label2 }{ kerning_correction } || 0.91;
1374             my $cos = cos( $radian );
1375             my $sin = sin( $radian );
1376             my $Xoff = 0;
1377             my $Yoff = 0;
1378             my $len = length( $self->{ grid }{ y }{ label2 }{ text }->[$nbr] );
1379              
1380             unless ( $self->{ grid }{ y }{ label2 }{ rotation } )
1381             {
1382             $Xoff = -( ( $len**$kerning ) * $self->{ grid }{ y }{ label2 }{ size } / 2 );
1383             }
1384             if ( $self->{ grid }{ y }{ label2 }{ rotation } == 90 )
1385             {
1386             $Xoff = $self->{ grid }{ y }{ label2 }{ size } / 2;
1387             }
1388              
1389             my @b = $frame->stringFT(
1390             $text_color,
1391             $self->{ grid }{ y }{ label2 }{ font },
1392             $self->{ grid }{ y }{ label2 }{ size },
1393             $radian,
1394             $self->{ border }->[0] + $val + $Xoff,
1395             $self->{ border }->[2] - $self->{ grid }{ debord }->[2] - ( $self->{ grid }{ y }{ label2 }{ space } || 0 ) - $Yoff,
1396             $self->{ grid }{ y }{ label2 }{ text }->[$nbr],
1397             # { resolution => "95,95" }
1398             );
1399             if ( exists $self->{ grid }{ y }{ label2 }{ surround } )
1400             {
1401             my $surround_color = $grid_color;
1402             if ( exists $self->{ grid }{ y }{ label2 }{ surround }{ color } )
1403             {
1404             $surround_color = _color_allocater( $self->{ grid }{ y }{ label2 }{ surround }{ color }, $self->{ grid }{ y }{ label2 }{ color }, $frame );
1405             }
1406             $frame->setThickness( $self->{ grid }{ y }{ label2 }{ surround }{ thickness } )
1407             if ( exists $self->{ grid }{ y }{ label2 }{ surround }{ thickness } );
1408             my $polyT = new GD::Polygon;
1409             $polyT->addPt( $b[0], $b[1] );
1410             $polyT->addPt( $b[2], $b[3] );
1411             $polyT->addPt( $b[4], $b[5] );
1412             $polyT->addPt( $b[6], $b[7] );
1413             $frame->openPolygon( $polyT, $surround_color );
1414             }
1415             $frame->setThickness( 1 );
1416             }
1417             }
1418             }
1419             if ( exists $self->{ grid }{ x } )
1420             {
1421             if ( exists $self->{ grid }{ x }{ thickness } )
1422             {
1423             $frame->setThickness( $self->{ grid }{ x }{ thickness } );
1424             }
1425             my $grid_color = _color_allocate( $self->{ grid }{ x }{ color }, 'ffffffff', $frame );
1426             for ( my $nbr = $self->{ grid }{ x }{ number } - 1 ; $nbr >= 0 ; $nbr-- )
1427             {
1428             my $val = ( ( $nbr ) * ( ( ( ( $self->{ size }->[1] ) / ( $self->{ grid }{ x }{ number } - 1 ) ) ) ) );
1429             my $text_indx = $self->{ grid }{ x }{ number } - $nbr - 1;
1430              
1431             if ( exists $self->{ grid }{ x }{ type } && $self->{ grid }{ x }{ type } eq 'log' )
1432             {
1433             $text_indx = $nbr;
1434             my $s = $self->{ size }->[1] / log( $self->{ grid }{ x }{ number } );
1435             $val = $self->{ size }->[1] - ( log( $nbr + 1 ) * $s );
1436             }
1437             $frame->line( $self->{ border }->[0] - $self->{ grid }{ debord }->[0], $self->{ border }->[2] + $val, $self->{ border }->[0] + $self->{ size }->[0] + $self->{ grid }{ debord }->[1], $self->{ border }->[2] + $val, $grid_color );
1438             if ( defined $self->{ grid }{ x }{ label }{ text }->[$text_indx] )
1439             {
1440             my $text_color = $grid_color;
1441             if ( exists $self->{ grid }{ x }{ label }{ color } )
1442             {
1443             $text_color = _color_allocate( $self->{ grid }{ x }{ label }{ color }, 'ffffffff', $frame );
1444             }
1445              
1446             my $radian = ( $self->{ grid }{ x }{ label }{ rotation } / 180 ) * PI || 0;
1447             my $kerning = $self->{ grid }{ x }{ label }{ kerning_correction } || 0.91;
1448             my $cos = cos( $radian );
1449             my $sin = sin( $radian );
1450             my $len = length( $self->{ grid }{ x }{ label }{ text }->[$text_indx] );
1451             my $Xoff;
1452             my $Yoff;
1453              
1454             if ( $self->{ grid }{ x }{ label }{ align } eq 'right' )
1455             {
1456             $Xoff = -( ( $len**$kerning ) * $self->{ grid }{ x }{ label }{ size } );
1457             }
1458             if ( $self->{ grid }{ x }{ label }{ rotation } )
1459             {
1460             $Xoff = ( $cos * ( $self->{ grid }{ x }{ label }{ size } ) ) - ( $cos * ( ( $len**$kerning ) * $self->{ grid }{ x }{ label }{ size } ) );
1461              
1462             $Yoff = ( $sin * ( ( $len**$kerning ) * $self->{ grid }{ x }{ label }{ size } ) ) - ( $sin * $self->{ grid }{ x }{ label }{ size } );
1463             }
1464             $frame->stringFT( $text_color, $self->{ grid }{ x }{ label }{ font }, $self->{ grid }{ x }{ label }{ size }, $radian, $self->{ border }->[0] - $self->{ grid }{ debord }->[0] + $Xoff - ( $self->{ grid }{ x }{ label }{ space } || 0 ), $self->{ border }->[2] + ( $self->{ grid }{ x }{ label }{ size } / 2 ) + $val + $Yoff, $self->{ grid }{ x }{ label }{ text }->[$text_indx] );
1465             }
1466              
1467             if ( defined $self->{ grid }{ x }{ label2 }{ text }->[$text_indx] )
1468             {
1469             my $text_color = $grid_color;
1470             if ( exists $self->{ grid }{ x }{ label2 }{ color } )
1471             {
1472             $text_color = _color_allocate( $self->{ grid }{ x }{ label2 }{ color }, 'ffffffff', $frame );
1473             }
1474             my $radian = ( $self->{ grid }{ x }{ label2 }{ rotation } / 180 ) * PI || 0;
1475             my $kerning = $self->{ grid }{ x }{ label2 }{ kerning_correction } || 0.91;
1476             my $cos = cos( $radian );
1477             my $sin = sin( $radian );
1478             my $len = length( $self->{ grid }{ x }{ label2 }{ text }->[$text_indx] );
1479             my $Xoff = 0;
1480             my $Yoff = 0;
1481              
1482             if ( $self->{ grid }{ x }{ label2 }{ align } eq 'right' )
1483             {
1484             $Xoff = -( ( $len**$kerning ) * $self->{ grid }{ x }{ label2 }{ size } );
1485             }
1486             $frame->stringFT(
1487             $text_color,
1488             $self->{ grid }{ x }{ label2 }{ font },
1489             $self->{ grid }{ x }{ label2 }{ size },
1490             $radian,
1491             $self->{ border }->[0] + $self->{ grid }{ debord }->[1] + $Xoff + $self->{ grid }{ x }{ label2 }{ space } + $self->{ size }->[0],
1492             $self->{ border }->[2] + ( $self->{ grid }{ x }{ label2 }{ size } / 2 ) + $val + $Yoff,
1493             $self->{ grid }{ x }{ label2 }{ text }->[$text_indx]
1494             );
1495             }
1496             }
1497             }
1498             if ( exists $self->{ grid }{ x_up } )
1499             {
1500             $frame->setThickness( $self->{ grid }{ x_up }{ thickness } );
1501             my $grid_color = _color_allocate( $self->{ grid }{ x_up }{ color }, 'ffffffff', $frame );
1502              
1503             for ( my $nbr = $self->{ grid }{ x_up }{ number } ; $nbr >= 1 ; $nbr-- )
1504             {
1505             my $val = ( $nbr - 1 ) * ( int( ( $self->{ size }->[1] ) / ( $self->{ grid }{ x_up }{ number } - 1 ) ) );
1506             my $text_indx = $self->{ grid }{ x_up }{ number } - $nbr;
1507             if ( exists $self->{ grid }{ x_up }{ type } && $self->{ grid }{ x_up }{ type } eq 'log' )
1508             {
1509             $text_indx = $nbr - 1;
1510             my $s = $self->{ size }->[1] / log( $self->{ grid }{ x_up }{ number } ) / 2;
1511             $val = ( $self->{ size }->[1] / 2 ) - ( log( $nbr ) * $s );
1512             }
1513             else
1514             {
1515             $val /= 2;
1516             }
1517              
1518             $frame->line( $self->{ border }->[0] - $self->{ grid }{ debord }->[0], $self->{ border }->[2] + 1 + $val, $self->{ border }->[0] + $self->{ size }->[0] + $self->{ grid }{ debord }->[1], $self->{ border }->[2] + 1 + $val, $grid_color );
1519             if ( defined $self->{ grid }{ x_up }{ label }{ text }->[$text_indx] )
1520             {
1521             my $text_color = $grid_color;
1522             if ( exists $self->{ grid }{ x_up }{ label }{ color } )
1523             {
1524             $text_color = _color_allocate( $self->{ grid }{ x_up }{ label }{ color }, 'ffffffff', $frame );
1525             }
1526              
1527             my $radian = ( $self->{ grid }{ x_up }{ label }{ rotation } / 180 ) * PI || 0;
1528             my $kerning = $self->{ grid }{ x_up }{ label }{ kerning_correction } || 0.91;
1529             my $cos = cos( $radian );
1530             my $sin = sin( $radian );
1531             my $len = length( $self->{ grid }{ x_up }{ label }{ text }->[$text_indx] );
1532             my $Xoff;
1533             my $Yoff;
1534              
1535             if ( $self->{ grid }{ x_up }{ label }{ align } eq 'right' )
1536             {
1537             $Xoff = -( ( $len**$kerning ) * $self->{ grid }{ x_up }{ label }{ size } );
1538             }
1539             if ( $self->{ grid }{ x_up }{ label }{ rotation } )
1540             {
1541             $Xoff = ( $cos * ( $self->{ grid }{ x_up }{ label }{ size } ) ) - ( $cos * ( ( $len**$kerning ) * $self->{ grid }{ x_up }{ label }{ size } ) );
1542              
1543             $Yoff = ( $sin * ( ( $len**$kerning ) * $self->{ grid }{ x_up }{ label }{ size } ) ) - ( $sin * $self->{ grid }{ x_up }{ label }{ size } );
1544             }
1545             $frame->stringFT( $text_color, $self->{ grid }{ x_up }{ label }{ font }, $self->{ grid }{ x_up }{ label }{ size }, $radian, $self->{ border }->[0] - $self->{ grid }{ debord }->[0] + $Xoff - $self->{ grid }{ x_up }{ label }{ space }, $self->{ border }->[2] + ( $self->{ grid }{ x_up }{ label }{ size } / 2 ) + $val + $Yoff, $self->{ grid }{ x_up }{ label }{ text }->[$text_indx] );
1546             }
1547              
1548             if ( defined $self->{ grid }{ x_up }{ label2 }{ text }->[$text_indx] )
1549             {
1550             my $text_color = $grid_color;
1551             if ( exists $self->{ grid }{ x_up }{ label2 }{ color } )
1552             {
1553             $text_color = _color_allocate( $self->{ grid }{ x_up }{ label2 }{ color }, 'ffffffff', $frame );
1554             }
1555             my $radian = ( $self->{ grid }{ x_up }{ label2 }{ rotation } / 180 ) * PI || 0;
1556             my $kerning = $self->{ grid }{ x_up }{ label2 }{ kerning_correction } || 0.91;
1557             my $cos = cos( $radian );
1558             my $sin = sin( $radian );
1559             my $len = length( $self->{ grid }{ x_up }{ label2 }{ text }->[$text_indx] );
1560             my $Xoff;
1561             my $Yoff;
1562              
1563             if ( $self->{ grid }{ x_up }{ label2 }{ align } eq 'right' )
1564             {
1565             $Xoff = -( ( $len**$kerning ) * $self->{ grid }{ x_up }{ label2 }{ size } );
1566             }
1567             $frame->stringFT(
1568             $text_color,
1569             $self->{ grid }{ x_up }{ label2 }{ font },
1570             $self->{ grid }{ x_up }{ label2 }{ size },
1571             $radian,
1572             $self->{ border }->[0] + $self->{ grid }{ debord }->[1] + $Xoff + $self->{ grid }{ x_up }{ label2 }{ space } + $self->{ size }->[0],
1573             $self->{ border }->[2] + ( $self->{ grid }{ x_up }{ label2 }{ size } / 2 ) + $val + $Yoff,
1574             $self->{ grid }{ x_up }{ label2 }{ text }->[$text_indx]
1575             );
1576             }
1577             }
1578             }
1579             if ( exists $self->{ grid }{ x_down } )
1580             {
1581             $frame->setThickness( $self->{ grid }{ x_down }{ thickness } );
1582             my $grid_color = _color_allocate( $self->{ grid }{ x_down }{ color }, 'ffffffff', $frame );
1583             for ( my $nbr = $self->{ grid }{ x_down }{ number } ; $nbr >= 1 ; $nbr-- )
1584             {
1585             my $val = ( $nbr - 1 ) * ( int( ( $self->{ size }->[1] ) / ( $self->{ grid }{ x_down }{ number } - 1 ) ) );
1586             my $text_indx = $self->{ grid }{ x_down }{ number } - $nbr;
1587             my $x_offset = 0;
1588             if ( exists $self->{ grid }{ x_down }{ type } && $self->{ grid }{ x_down }{ type } eq 'log' )
1589             {
1590             $text_indx = $nbr - 1;
1591             my $s = $self->{ size }->[1] / log( $self->{ grid }{ x_down }{ number } ) / 2;
1592             $val = ( $self->{ size }->[1] / 2 ) + ( log( $nbr ) * $s );
1593             }
1594             else
1595             {
1596             $x_offset = $self->{ size }->[1] / 2;
1597             $val /= 2;
1598             }
1599             $frame->line( $self->{ border }->[0] - $self->{ grid }{ debord }->[0], $self->{ border }->[2] + 1 + $val + $x_offset, $self->{ border }->[0] + $self->{ size }->[0] + $self->{ grid }{ debord }->[1], $self->{ border }->[2] + 1 + $val + $x_offset, $grid_color );
1600             if ( defined $self->{ grid }{ x_down }{ label }{ text }->[$text_indx] )
1601             {
1602             my $text_color = $grid_color;
1603             if ( exists $self->{ grid }{ x_down }{ label }{ color } )
1604             {
1605             $text_color = _color_allocate( $self->{ grid }{ x_down }{ label }{ color }, 'ffffffff', $frame );
1606             }
1607              
1608             my $radian = ( $self->{ grid }{ x_down }{ label }{ rotation } / 180 ) * PI || 0;
1609             my $kerning = $self->{ grid }{ x_down }{ label }{ kerning_correction } || 0.91;
1610             my $cos = cos( $radian );
1611             my $sin = sin( $radian );
1612             my $len = length( $self->{ grid }{ x_down }{ label }{ text }->[$text_indx] );
1613             my $Xoff;
1614             my $Yoff;
1615              
1616             if ( $self->{ grid }{ x_down }{ label }{ align } eq 'right' )
1617             {
1618             $Xoff = -( ( $len**$kerning ) * $self->{ grid }{ x_down }{ label }{ size } );
1619             }
1620             if ( $self->{ grid }{ x_down }{ label }{ rotation } )
1621             {
1622             $Xoff = ( $cos * ( $self->{ grid }{ x_down }{ label }{ size } ) ) - ( $cos * ( ( $len**$kerning ) * $self->{ grid }{ x_down }{ label }{ size } ) );
1623              
1624             $Yoff = ( $sin * ( ( $len**$kerning ) * $self->{ grid }{ x_down }{ label }{ size } ) ) - ( $sin * $self->{ grid }{ x_down }{ label }{ size } );
1625             }
1626             if ( exists $self->{ grid }{ x_down }{ type } && $self->{ grid }{ x_down }{ type } eq 'log' )
1627             {
1628             $x_offset = 0;
1629             $val *= -1;
1630             }
1631             else
1632             {
1633             $x_offset = $self->{ size }->[1];
1634             }
1635             $frame->stringFT(
1636             $text_color,
1637             $self->{ grid }{ x_down }{ label }{ font },
1638             $self->{ grid }{ x_down }{ label }{ size },
1639             $radian,
1640             $self->{ border }->[0] - $self->{ grid }{ debord }->[0] + $Xoff - $self->{ grid }{ x_down }{ label }{ space },
1641             $self->{ border }->[2] + ( $self->{ grid }{ x_down }{ label }{ size } / 2 ) - $val + $Yoff + $x_offset,
1642             $self->{ grid }{ x_down }{ label }{ text }->[$text_indx]
1643             );
1644             }
1645              
1646             if ( defined $self->{ grid }{ x_down }{ label2 }{ text }->[$text_indx] )
1647             {
1648             my $text_color = $grid_color;
1649             if ( exists $self->{ grid }{ x_down }{ label2 }{ color } )
1650             {
1651             $text_color = _color_allocate( $self->{ grid }{ x_down }{ label2 }{ color }, 'ffffffff', $frame );
1652             }
1653             my $radian = ( $self->{ grid }{ x_down }{ label2 }{ rotation } / 180 ) * PI || 0;
1654             my $kerning = $self->{ grid }{ x_down }{ label2 }{ kerning_correction } || 0.91;
1655             my $cos = cos( $radian );
1656             my $sin = sin( $radian );
1657             my $len = length( $self->{ grid }{ x_down }{ label2 }{ text }->[$text_indx] );
1658             my $Xoff;
1659             my $Yoff;
1660              
1661             if ( $self->{ grid }{ x_down }{ label2 }{ align } eq 'right' )
1662             {
1663             $Xoff = -( ( $len**$kerning ) * $self->{ grid }{ x_down }{ label2 }{ size } );
1664             }
1665             if ( exists $self->{ grid }{ x_down }{ type } && $self->{ grid }{ x_down }{ type } eq 'log' )
1666             {
1667             $x_offset = 0;
1668             $val *= -1;
1669             }
1670             else
1671             {
1672             $x_offset = $self->{ size }->[1];
1673             }
1674             $frame->stringFT(
1675             $text_color,
1676             $self->{ grid }{ x_down }{ label2 }{ font },
1677             $self->{ grid }{ x_down }{ label2 }{ size },
1678             $radian,
1679             $self->{ border }->[0] + $self->{ grid }{ debord }->[1] + $Xoff + $self->{ grid }{ x_down }{ label2 }{ space } + $self->{ size }->[0],
1680             $self->{ border }->[2] + ( $self->{ grid }{ x_down }{ label2 }{ size } / 2 ) - $val + $Yoff + $x_offset,
1681             $self->{ grid }{ x_down }{ label2 }{ text }->[$text_indx]
1682             );
1683             }
1684             }
1685             }
1686             }
1687             ### end plot grid +label
1688             $frame->setThickness( 1 );
1689              
1690             ### plot reticle +label
1691             if ( exists $self->{ reticle } )
1692             {
1693             $frame->setThickness( $self->{ reticle }{ thickness } ) || 1;
1694             my $grid_color = _color_allocate( $self->{ reticle }{ color }, '00000000', $frame );
1695             my $angle_inc = ( PI ) / ( $self->{ reticle }{ number } / 2 );
1696              
1697             for my $nbr ( 1 .. ( $self->{ reticle }{ number } ) )
1698             {
1699             my $polyline = new GD::Polyline;
1700             my $text_angle = 0;
1701             my $angle = ( $angle_inc * ( -$nbr ) ) + ( PI / 2 );
1702             $polyline->addPt( ( $self->{ size }[0] / 2 ) + $self->{ border }[0], $self->{ border }[2] + ( $self->{ size }[1] / 2 ) );
1703             $polyline->addPt( ( $self->{ size }[0] / 2 ) + $self->{ border }[0], $self->{ border }[2] + $self->{ size }[1] + $self->{ reticle }{ debord } );
1704             $polyline->rotate( $angle, ( $self->{ size }[0] / 2 ) + $self->{ border }[0], $self->{ border }[2] + ( $self->{ size }[1] / 2 ) );
1705             $frame->polydraw( $polyline, $grid_color );
1706             my $val = ( $nbr - 1 ) * ( int( ( $self->{ size }->[1] ) / ( $self->{ reticle }{ number } - 1 ) ) );
1707              
1708             if ( defined $self->{ reticle }{ label_middle }{ text }->[ $nbr - 1 ] )
1709             {
1710             my $text_color = $grid_color;
1711             if ( exists $self->{ reticle }{ label_middle }{ color } )
1712             {
1713             $text_color = _color_allocate( $self->{ reticle }{ label_middle }{ color }, 'ffffffff', $frame );
1714             }
1715             my $kerning = $self->{ reticle }{ label_middle }{ kerning_correction } || 0.91;
1716             my $len = length( $self->{ reticle }{ label_middle }{ text }->[ $nbr - 1 ] );
1717             my $beta;
1718             my $c;
1719             my $pos_angle = ( $angle_inc * ( $nbr ) ) + PI - ( PI / $self->{ reticle }{ number } );
1720             if ( exists $self->{ reticle }{ label_middle }{ rotate } )
1721             {
1722              
1723             if ( $self->{ reticle }{ label_middle }{ rotate } eq 'perpendicular' )
1724             {
1725             $text_angle = ( PI / 2 ) + ( $angle_inc * ( -$nbr ) ) + ( PI / $self->{ reticle }{ number } );
1726             $c = ( ( ( ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } + $self->{ reticle }{ label_middle }{ space } )**2 ) + ( ( ( ( $len**$kerning ) * $self->{ reticle }{ label_middle }{ size } ) / 2 )**2 ) )**.5;
1727             $beta = asin( ( ( ( $len**$kerning ) * $self->{ reticle }{ label_middle }{ size } ) / 2 ) / $c );
1728             }
1729             else
1730             {
1731             $text_angle = ( $angle_inc * ( -$nbr ) ) + ( PI / $self->{ reticle }{ number } );
1732             $c = ( ( ( ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } + $self->{ reticle }{ label_middle }{ space } )**2 ) + ( ( ( $self->{ reticle }{ label }{ size } ) / 2 )**2 ) )**.5;
1733             $beta = asin( ( ( $self->{ reticle }{ label }{ size } ) / 2 ) / $c );
1734             }
1735             }
1736             my $cos = cos( $pos_angle + $beta );
1737             my $sin = sin( $pos_angle + $beta );
1738             my $Xoff = $cos * ( $self->{ reticle }{ label_middle }{ space } + ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } );
1739             my $Yoff = $sin * ( $self->{ reticle }{ label_middle }{ space } + ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } );
1740              
1741             if ( exists $self->{ reticle }{ label_middle }{ rotate } )
1742             {
1743             if ( $self->{ reticle }{ label_middle }{ rotate } eq 'perpendicular' )
1744             {
1745             $Xoff = $cos * ( $self->{ reticle }{ label_middle }{ space } + $c + ( $self->{ reticle }{ label_middle }{ size } ) );
1746             $Yoff = $sin * ( $self->{ reticle }{ label_middle }{ space } + $c + ( $self->{ reticle }{ label_middle }{ size } ) );
1747             }
1748             else
1749             {
1750             $Xoff = $cos * ( $self->{ reticle }{ label_middle }{ size } + $self->{ reticle }{ label_middle }{ space } + ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } );
1751             $Yoff = $sin * ( $self->{ reticle }{ label_middle }{ size } + $self->{ reticle }{ label_middle }{ space } + ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } );
1752             }
1753             }
1754             $frame->stringFT( $text_color, $self->{ reticle }{ label_middle }{ font }, $self->{ reticle }{ label_middle }{ size }, $text_angle, ( $self->{ size }[0] / 2 ) + $self->{ border }[0] - $Xoff, $self->{ border }[2] + ( $self->{ size }[1] / 2 ) - $Yoff, $self->{ reticle }{ label_middle }{ text }->[ $nbr - 1 ] );
1755             }
1756              
1757             if ( defined $self->{ reticle }{ label }{ text }->[ $nbr - 1 ] )
1758             {
1759             my $text_color = $grid_color;
1760             if ( exists $self->{ reticle }{ label }{ color } )
1761             {
1762             $text_color = _color_allocate( $self->{ reticle }{ label }{ color }, 'ffffffff', $frame );
1763             }
1764             my $kerning = $self->{ reticle }{ label }{ kerning_correction } || 0.91;
1765             my $len = length( $self->{ reticle }{ label }{ text }->[ $nbr - 1 ] );
1766             my $beta;
1767             my $c;
1768             my $pos_angle = ( $angle_inc * ( $nbr ) ) + PI - ( 2 * PI / $self->{ reticle }{ number } );
1769              
1770             if ( exists $self->{ reticle }{ label }{ rotate } )
1771             {
1772             if ( $self->{ reticle }{ label }{ rotate } eq 'perpendicular' )
1773             {
1774             $text_angle = ( PI / 2 ) + ( $angle_inc * ( -$nbr ) ) + ( 2 * PI / $self->{ reticle }{ number } );
1775             $c = ( ( ( ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } + $self->{ reticle }{ label_middle }{ space } )**2 ) + ( ( ( ( $len**$kerning ) * $self->{ reticle }{ label }{ size } ) / 2 )**2 ) )**.5;
1776             $beta = asin( ( ( ( $len**$kerning ) * $self->{ reticle }{ label }{ size } ) / 2 ) / $c );
1777             }
1778             else
1779             {
1780             $text_angle = ( $angle_inc * ( -$nbr ) ) + ( 2 * PI / $self->{ reticle }{ number } );
1781             $c = ( ( ( ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } + $self->{ reticle }{ label_middle }{ space } )**2 ) + ( ( ( $self->{ reticle }{ label }{ size } ) / 2 )**2 ) )**.5;
1782             $beta = asin( ( ( $self->{ reticle }{ label }{ size } ) / 2 ) / $c );
1783             }
1784             }
1785             my $cos = cos( $pos_angle + $beta );
1786             my $sin = sin( $pos_angle + $beta );
1787             my $Xoff = $cos * ( $self->{ reticle }{ label }{ space } + ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } );
1788             my $Yoff = $sin * ( $self->{ reticle }{ label }{ space } + ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } );
1789              
1790             if ( exists $self->{ reticle }{ label }{ rotate } )
1791             {
1792             if ( $self->{ reticle }{ label }{ rotate } eq 'perpendicular' )
1793             {
1794             $Xoff = $cos * ( $self->{ reticle }{ label }{ space } + $c + ( $self->{ reticle }{ label }{ size } ) );
1795             $Yoff = $sin * ( $self->{ reticle }{ label }{ space } + $c + ( $self->{ reticle }{ label }{ size } ) );
1796             }
1797             else
1798             {
1799             $Xoff = $cos * ( $self->{ reticle }{ label }{ size } + $self->{ reticle }{ label }{ space } + ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } );
1800             $Yoff = $sin * ( $self->{ reticle }{ label }{ size } + $self->{ reticle }{ label }{ space } + ( $self->{ size }[1] / 2 ) + $self->{ reticle }{ debord } );
1801             }
1802             }
1803             $frame->stringFT( $text_color, $self->{ reticle }{ label }{ font }, $self->{ reticle }{ label }{ size }, $text_angle, ( $self->{ size }[0] / 2 ) + $self->{ border }[0] - $Xoff, $self->{ border }[2] + ( $self->{ size }[1] / 2 ) - $Yoff, $self->{ reticle }{ label }{ text }->[ $nbr - 1 ] );
1804             }
1805             }
1806             }
1807             ### end plot reticle +label
1808              
1809             ### plot frame around main chart
1810             if ( exists $self->{ frame } )
1811             {
1812             my $frame_color = _color_allocate( $self->{ frame }{ color }, '00000000', $frame );
1813              
1814             my $polyF = new GD::Polygon;
1815             $frame->setThickness( $self->{ frame }{ thickness } );
1816             $polyF->addPt( $self->{ border }->[0], $self->{ border }->[2] );
1817             $polyF->addPt( $self->{ border }->[0], $self->{ border }->[2] + $self->{ size }->[1] );
1818             $polyF->addPt( $self->{ border }->[0] + $self->{ size }->[0], $self->{ border }->[2] + $self->{ size }->[1] );
1819             $polyF->addPt( $self->{ border }->[0] + $self->{ size }->[0], $self->{ border }->[2] );
1820             $frame->openPolygon( $polyF, $frame_color );
1821             }
1822             ### end plot frame
1823              
1824             ### plot glyph on the main chart
1825             if ( exists $self->{ glyph } )
1826             {
1827             foreach my $item ( @{ $self->{ glyph } } )
1828             {
1829             my $X = 1;
1830             my $Y = 1;
1831              
1832             $X += $item->{ x };
1833             $Y += $item->{ y };
1834              
1835             my $glyph_color = _color_allocate( $item->{ color }, '00000000', $frame );
1836             if ( exists $item->{ type } && $item->{ type } eq 'filled' )
1837             {
1838             my $polyG = new GD::Polygon;
1839             foreach my $point ( @{ $item->{ data } } )
1840             {
1841             next unless ( ref $point eq 'ARRAY' );
1842             $polyG->addPt( $X + $point->[0], $self->{ border }->[3] + $self->{ border }->[2] + $self->{ size }->[1] - $Y - $point->[1] );
1843             }
1844              
1845             $frame->filledPolygon( $polyG, $glyph_color );
1846             }
1847             elsif ( exists $item->{ type } && $item->{ type } eq 'text' )
1848             {
1849             foreach my $point ( @{ $item->{ data } } )
1850             {
1851             my $text_angle = 0;
1852             if ( exists $point->[3] )
1853             {
1854             $text_angle = ( $point->[3] / 180 ) * PI;
1855             }
1856             $frame->stringFT( $glyph_color, $item->{ font }, $item->{ size }, $text_angle, $X + $point->[1], $self->{ border }->[3] + $self->{ border }->[2] + $self->{ size }->[1] - $Y - $point->[2], $point->[0] );
1857             }
1858             }
1859             else
1860             {
1861             my $polyG = new GD::Polygon;
1862             foreach my $point ( @{ $item->{ data } } )
1863             {
1864             next unless ( ref $point eq 'ARRAY' );
1865             $polyG->addPt( $X + $point->[0], $self->{ border }->[3] + $self->{ border }->[2] + $self->{ size }->[1] - $Y - $point->[1] );
1866             }
1867             $frame->openPolygon( $polyG, $glyph_color );
1868             }
1869             }
1870             }
1871             ### end plot glyph
1872              
1873             $self->{ img } = $frame->png;
1874             if ( $object )
1875             {
1876             $self->png_zEXt( $object );
1877             }
1878             return $self->{ img };
1879             }
1880              
1881             # sub log10
1882             # {
1883             # my $n = shift;
1884             # return log( $n ) / log( 10 );
1885             # }
1886              
1887             1;
1888              
1889             __END__