File Coverage

blib/lib/Tk/ForDummies/Graph/Utils.pm
Criterion Covered Total %
statement 18 239 7.5
branch 0 66 0.0
condition 0 18 0.0
subroutine 6 32 18.7
pod 0 11 0.0
total 24 366 6.5


line stmt bran cond sub pod time code
1             package Tk::ForDummies::Graph::Utils;
2            
3             #==================================================================
4             # Author : Djibril Ousmanou
5             # Copyright : 2010
6             # Update : 04/06/2010 21:28:09
7             # AIM : Private functions and public shared methods
8             # between Tk::ForDummies::Graph modules
9             #==================================================================
10 1     1   6 use warnings;
  1         2  
  1         39  
11 1     1   5 use strict;
  1         2  
  1         25  
12 1     1   5 use Carp;
  1         2  
  1         67  
13            
14 1     1   6 use vars qw($VERSION);
  1         1  
  1         58  
15             $VERSION = '1.08';
16            
17 1     1   4 use Exporter;
  1         2  
  1         41  
18 1     1   1166 use POSIX qw / floor /;
  1         7570  
  1         6  
19            
20             my @ModuleToExport = qw (
21             _MaxArray _MinArray _isANumber _roundValue
22             zoom zoomx zoomy clearchart
23             _Quantile _moy _NonOutlier _GetControlPoints
24             enabled_automatic_redraw disabled_automatic_redraw
25             _delete_array_doublon redraw add_data
26             delete_balloon set_balloon
27             );
28             my @ModulesDisplay = qw/ display_values /;
29             our @ISA = qw(Exporter);
30             our @EXPORT = @ModuleToExport;
31             our @EXPORT_OK = @ModulesDisplay;
32             our %EXPORT_TAGS = (
33             DUMMIES => \@ModuleToExport,
34             DISPLAY => \@ModulesDisplay,
35             );
36            
37             sub _delete_array_doublon {
38 0     0     my ($ref_tab) = @_;
39            
40 0           my %temp;
41 0           return grep { !$temp{$_}++ } @{$ref_tab};
  0            
  0            
42             }
43            
44             sub _MaxArray {
45 0     0     my ($RefNumber) = @_;
46 0           my $max;
47            
48 0           for my $chiffre ( @{$RefNumber} ) {
  0            
49 0 0 0       next unless ( defined $chiffre and _isANumber($chiffre) );
50 0           $max = _max( $max, $chiffre );
51             }
52            
53 0           return $max;
54             }
55            
56             sub _MinArray {
57 0     0     my ($RefNumber) = @_;
58 0           my $min;
59            
60 0           for my $chiffre ( @{$RefNumber} ) {
  0            
61 0 0 0       next unless ( defined $chiffre and _isANumber($chiffre) );
62            
63 0           $min = _min( $min, $chiffre );
64             }
65            
66 0           return $min;
67             }
68            
69             sub _max {
70 0     0     my ( $a, $b ) = @_;
71 0 0         if ( not defined $a ) { return $b; }
  0            
72 0 0         if ( not defined $b ) { return $a; }
  0            
73 0 0 0       if ( not defined $a and not defined $b ) { return; }
  0            
74            
75 0 0         if ( $a >= $b ) { return $a; }
  0            
76 0           else { return $b; }
77            
78 0           return;
79             }
80            
81             sub _min {
82 0     0     my ( $a, $b ) = @_;
83 0 0         if ( not defined $a ) { return $b; }
  0            
84 0 0         if ( not defined $b ) { return $a; }
  0            
85 0 0 0       if ( not defined $a and not defined $b ) { return; }
  0            
86            
87 0 0         if ( $a <= $b ) { return $a; }
  0            
88 0           else { return $b; }
89            
90 0           return;
91             }
92            
93             sub _moy {
94 0     0     my ($RefValues) = @_;
95            
96 0           my $TotalValues = scalar( @{$RefValues} );
  0            
97            
98 0 0         return if ( $TotalValues == 0 );
99            
100 0           my $moy = 0;
101 0           for my $value ( @{$RefValues} ) {
  0            
102 0           $moy += $value;
103             }
104            
105 0           $moy = ( $moy / $TotalValues );
106            
107 0           return $moy;
108             }
109            
110             sub _isPair {
111 0     0     my ($number) = @_;
112            
113 0 0 0       unless ( defined $number and $number =~ m{^\d+$} ) {
114 0           croak "$number not an integer\n";
115             }
116            
117 0 0         if ( $number % 2 == 0 ) {
118 0           return 1;
119             }
120            
121 0           return;
122             }
123            
124             sub _Median {
125 0     0     my ($RefValues) = @_;
126            
127             # sort data
128 0           my @values = sort { $a <=> $b } @{$RefValues};
  0            
  0            
129 0           my $TotalValues = scalar(@values);
130 0           my $median;
131            
132             # Number of data pair
133 0 0         if ( _isPair($TotalValues) ) {
134            
135             # 2 values for center
136 0           my $Value1 = $values[ $TotalValues / 2 ];
137 0           my $Value2 = $values[ ( $TotalValues - 2 ) / 2 ];
138 0           $median = ( $Value1 + $Value2 ) / 2;
139             }
140            
141             # Number of data impair
142             else {
143 0           $median = $values[ ( $TotalValues - 1 ) / 2 ];
144             }
145            
146 0           return $median;
147             }
148            
149             # The Quantile is calculated as the same excel algorithm and
150             # is equivalent to quantile type 7 in R quantile package.
151             sub _Quantile {
152 0     0     my ( $RefData, $QuantileNumber ) = @_;
153            
154 0           my @Values = sort { $a <=> $b } @{$RefData};
  0            
  0            
155 0 0         $QuantileNumber = 1 unless ( defined $QuantileNumber );
156            
157 0 0         return $Values[0] if ( $QuantileNumber == 0 );
158            
159 0           my $count = scalar @{$RefData};
  0            
160            
161 0 0         return $Values[ $count - 1 ] if ( $QuantileNumber == 4 );
162            
163 0           my $K_quantile = ( ( $QuantileNumber / 4 ) * ( $count - 1 ) + 1 );
164 0           my $F_quantile = $K_quantile - POSIX::floor($K_quantile);
165 0           $K_quantile = POSIX::floor($K_quantile);
166            
167             # interpolation
168 0           my $aK_quantile = $Values[ $K_quantile - 1 ];
169 0           my $aKPlus_quantile = $Values[$K_quantile];
170            
171             # Calcul quantile
172 0           my $quantile = $aK_quantile + ( $F_quantile * ( $aKPlus_quantile - $aK_quantile ) );
173            
174 0           return $quantile;
175             }
176            
177             sub _NonOutlier {
178 0     0     my ( $RefValues, $Q1, $Q3 ) = @_;
179            
180             # interquartile range,
181 0           my $IQR = $Q3 - $Q1;
182            
183             # low and up boundaries
184 0           my $LowBoundary = $Q1 - ( 1.5 * $IQR );
185 0           my $UpBoundary = $Q3 + ( 1.5 * $IQR );
186            
187             # largest non-outlier and smallest non-outlier
188 0           my ( $LnonOutlier, $SnonOutlier );
189 0           for my $Value ( sort { $a <=> $b } @{$RefValues} ) {
  0            
  0            
190 0 0         if ( $Value > $LowBoundary ) {
191 0           $SnonOutlier = $Value;
192 0           last;
193             }
194             }
195            
196 0           for my $Value ( sort { $b <=> $a } @{$RefValues} ) {
  0            
  0            
197 0 0         if ( $Value < $UpBoundary ) {
198 0           $LnonOutlier = $Value;
199 0           last;
200             }
201             }
202            
203 0           return ( $SnonOutlier, $LnonOutlier );
204             }
205            
206             sub _roundValue {
207 0     0     my ($Value) = @_;
208 0           return sprintf( "%.2g", $Value );
209             }
210            
211             # Test if value is a real number
212             sub _isANumber {
213 0     0     my ($Value) = @_;
214            
215 0 0         if ( $Value
216             =~ /^(?:(?i)(?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[0123456789]+))|))$/
217             )
218             {
219 0           return 1;
220             }
221            
222 0           return;
223             }
224            
225             sub _GetControlPoints {
226 0     0     my ( $CompositeWidget, $RefArray ) = @_;
227            
228 0           my $NbrElt = scalar @{$RefArray};
  0            
229            
230 0 0         unless ( $NbrElt > 4 ) {
231 0           return $RefArray;
232             }
233            
234             # First element
235 0           my @AllControlPoints = ( $RefArray->[0], $RefArray->[1] );
236            
237 0           for ( my $i = 0; $i <= $NbrElt; $i = $i + 2 ) {
238 0           my @PointA = ( $RefArray->[$i], $RefArray->[ $i + 1 ] );
239 0           my @PointB = ( $RefArray->[ $i + 2 ], $RefArray->[ $i + 3 ] );
240 0           my @PointC = ( $RefArray->[ $i + 4 ], $RefArray->[ $i + 5 ] );
241            
242 0 0         last unless ( defined $RefArray->[ $i + 5 ] );
243            
244             # Equation between PointA and PointC
245             # Coef = (yc -ya) / (xc -xa)
246             # D1 : Y = Coef * X + (ya - (Coef * xa))
247 0           my $coef = ( $PointC[1] - $PointA[1] ) / ( $PointC[0] - $PointA[0] );
248            
249             # Equation for D2 ligne paralelle to [AC] with PointB
250             # D2 : Y = (Coef * X) + yb - (coef * xb)
251             # The 2 control points
252             my $D2line = sub {
253 0     0     my ($x) = @_;
254            
255 0           my $y = ( $coef * $x ) + $PointB[1] - ( $coef * $PointB[0] );
256 0           return $y;
257 0           };
258            
259             # distance
260 0           my $distance = 0.95;
261            
262             # xc1 = ( (xb - xa ) / 2 ) + xa
263             # yc1 = via D2
264 0           my @ControlPoint1;
265 0           $ControlPoint1[0] = ( $distance * ( $PointB[0] - $PointA[0] ) ) + $PointA[0];
266 0           $ControlPoint1[1] = $D2line->( $ControlPoint1[0] );
267 0           push( @AllControlPoints, ( $ControlPoint1[0], $ControlPoint1[1] ) );
268            
269             # points
270 0           push( @AllControlPoints, ( $PointB[0], $PointB[1] ) );
271            
272             # xc2 = ( (xc - xb ) / 2 ) + xb
273             # yc2 = via D2
274 0           my @ControlPoint2;
275 0           $ControlPoint2[0] = ( ( 1 - $distance ) * ( $PointC[0] - $PointB[0] ) ) + $PointB[0];
276 0           $ControlPoint2[1] = $D2line->( $ControlPoint2[0] );
277            
278 0           push( @AllControlPoints, ( $ControlPoint2[0], $ControlPoint2[1] ) );
279             }
280            
281 0           push( @AllControlPoints, $RefArray->[ $NbrElt - 2 ], $RefArray->[ $NbrElt - 1 ] );
282            
283 0           return \@AllControlPoints;
284             }
285            
286             sub redraw {
287 0     0 0   my ($CompositeWidget) = @_;
288            
289 0           $CompositeWidget->_GraphForDummiesConstruction;
290 0           return;
291             }
292            
293             sub delete_balloon {
294 0     0 0   my ($CompositeWidget) = @_;
295            
296 0           $CompositeWidget->{RefInfoDummies}->{Balloon}{State} = 0;
297 0           $CompositeWidget->_Balloon();
298            
299 0           return;
300             }
301            
302             sub add_data {
303 0     0 0   my ( $CompositeWidget, $Refdata, $legend ) = @_;
304            
305             # Doesn't work for Pie graph
306 0 0         if ( $CompositeWidget->class eq 'Pie' ) {
307 0           $CompositeWidget->_error("This method 'add_data' not allowed for Tk::ForDummies::Graph::Pie\n");
308 0           return;
309             }
310            
311 0           push( @{ $CompositeWidget->{RefInfoDummies}->{Data}{RefAllData} }, $Refdata );
  0            
312 0 0         if ( $CompositeWidget->{RefInfoDummies}->{Legend}{NbrLegend} > 0 ) {
313 0           push @{ $CompositeWidget->{RefInfoDummies}->{Legend}{DataLegend} }, $legend;
  0            
314             }
315            
316 0           $CompositeWidget->plot( $CompositeWidget->{RefInfoDummies}->{Data}{RefAllData} );
317            
318 0           return;
319             }
320            
321             sub set_balloon {
322 0     0 0   my ( $CompositeWidget, %options ) = @_;
323            
324 0           $CompositeWidget->{RefInfoDummies}->{Balloon}{State} = 1;
325            
326 0 0         if ( defined $options{-colordatamouse} ) {
327 0 0         if ( scalar @{ $options{-colordatamouse} } < 2 ) {
  0            
328 0           $CompositeWidget->_error(
329             "Can't set -colordatamouse, you have to set 2 colors\n" . "Ex : -colordatamouse => ['red','green'],",
330             1
331             );
332             }
333             else {
334 0           $CompositeWidget->{RefInfoDummies}->{Balloon}{ColorData} = $options{-colordatamouse};
335             }
336             }
337 0 0         if ( defined $options{-morepixelselected} ) {
338 0           $CompositeWidget->{RefInfoDummies}->{Balloon}{MorePixelSelected} = $options{-morepixelselected};
339             }
340 0 0         if ( defined $options{-background} ) {
341 0           $CompositeWidget->{RefInfoDummies}->{Balloon}{Background} = $options{-background};
342             }
343            
344 0           $CompositeWidget->_Balloon();
345            
346 0           return;
347             }
348            
349             sub zoom {
350 0     0 0   my ( $CompositeWidget, $Zoom ) = @_;
351            
352 0           my ( $NewWidth, $NewHeight ) = $CompositeWidget->_ZoomCalcul( $Zoom, $Zoom );
353 0           $CompositeWidget->configure( -width => $NewWidth, -height => $NewHeight );
354 0           $CompositeWidget->toplevel->geometry('');
355            
356 0           return 1;
357             }
358            
359             sub zoomx {
360 0     0 0   my ( $CompositeWidget, $Zoom ) = @_;
361            
362 0           my ( $NewWidth, $NewHeight ) = $CompositeWidget->_ZoomCalcul( $Zoom, undef );
363 0           $CompositeWidget->configure( -width => $NewWidth );
364 0           $CompositeWidget->toplevel->geometry('');
365            
366 0           return 1;
367             }
368            
369             sub zoomy {
370 0     0 0   my ( $CompositeWidget, $Zoom ) = @_;
371            
372 0           my ( $NewWidth, $NewHeight ) = $CompositeWidget->_ZoomCalcul( undef, $Zoom );
373 0           $CompositeWidget->configure( -height => $NewHeight );
374 0           $CompositeWidget->toplevel->geometry('');
375            
376 0           return 1;
377             }
378            
379             # Clear the Canvas Widget
380             sub clearchart {
381 0     0 0   my ($CompositeWidget) = @_;
382            
383 0           $CompositeWidget->update;
384 0           $CompositeWidget->delete( $CompositeWidget->{RefInfoDummies}->{TAGS}{AllTagsDummiesGraph} );
385            
386 0           return;
387             }
388            
389             sub display_values {
390 0     0 0   my ( $CompositeWidget, $ref_data, %options ) = @_;
391            
392             # Doesn't work for Pie graph
393 0 0         if ( $CompositeWidget->class eq 'Pie' ) {
    0          
394 0           $CompositeWidget->_error("This method 'display_values' not allowed for Tk::ForDummies::Graph::Pie\n");
395 0           return;
396             }
397             elsif ( $CompositeWidget->class eq 'Bars' ) {
398 0           $CompositeWidget->_error("This method 'display_values' not allowed for Tk::ForDummies::Graph::Bars\n");
399 0           return;
400             }
401            
402 0 0 0       unless ( defined $ref_data and ref($ref_data) eq 'ARRAY' ) {
403 0           $CompositeWidget->_error( 'data not defined', 1 );
404 0           return;
405             }
406 0           $CompositeWidget->{RefInfoDummies}->{Data}{RefDataToDisplay} = $ref_data;
407 0           $CompositeWidget->{RefInfoDummies}->{Data}{RefOptionDataToDisplay} = \%options;
408            
409 0 0         if ( $CompositeWidget->class eq 'Areas' ) {
410 0           foreach my $ref_value ( @{$ref_data} ) {
  0            
411 0           unshift @{$ref_value}, undef;
  0            
412             }
413             }
414            
415 0 0         if ( defined $CompositeWidget->{RefInfoDummies}->{Data}{PlotDefined} ) {
416 0           $CompositeWidget->redraw;
417             }
418            
419 0           return;
420             }
421            
422             sub enabled_automatic_redraw {
423 0     0 0   my ($CompositeWidget) = @_;
424            
425 0           my $class = $CompositeWidget->class;
426 0           foreach my $key ( qw{ Down End Home Left Next Prior Right Up } ) {
427 0           $CompositeWidget->Tk::bind("Tk::ForDummies::Graph::$class", "", undef);
428 0           $CompositeWidget->Tk::bind("Tk::ForDummies::Graph::$class", "", undef);
429             }
430             # recreate graph after widget resize
431 0     0     $CompositeWidget->Tk::bind( '' => sub { $CompositeWidget->_GraphForDummiesConstruction; } );
  0            
432 0           return;
433             }
434            
435             sub disabled_automatic_redraw {
436 0     0 0   my ($CompositeWidget) = @_;
437            
438 0           my $class = $CompositeWidget->class;
439 0           foreach my $key ( qw{ Down End Home Left Next Prior Right Up } ) {
440 0           $CompositeWidget->Tk::bind("Tk::ForDummies::Graph::$class", "", undef);
441 0           $CompositeWidget->Tk::bind("Tk::ForDummies::Graph::$class", "", undef);
442             }
443             # recreate graph after widget resize
444 0           $CompositeWidget->Tk::bind( '' => undef );
445 0           return;
446             }
447            
448             1;