File Coverage

blib/lib/Tk/Chart/Utils.pm
Criterion Covered Total %
statement 21 274 7.6
branch 0 78 0.0
condition 0 15 0.0
subroutine 7 35 20.0
pod 0 11 0.0
total 28 413 6.7


line stmt bran cond sub pod time code
1             package Tk::Chart::Utils;
2            
3             #=====================================================================================
4             # $Author : Djibril Ousmanou $
5             # $Copyright : 2011 $
6             # $Update : 21/10/2011 12:44:13 $
7             # $AIM : Private functions and public shared methods between Tk::Chart modules $
8             #=====================================================================================
9            
10 1     1   6 use warnings;
  1         2  
  1         25  
11 1     1   43 use strict;
  1         2  
  1         20  
12 1     1   4 use Carp;
  1         1  
  1         55  
13            
14 1     1   6 use vars qw($VERSION);
  1         2  
  1         44  
15             $VERSION = '1.04';
16            
17 1     1   4 use Exporter;
  1         2  
  1         28  
18 1     1   360 use POSIX qw / floor /;
  1         4440  
  1         5  
19            
20             my @module_export = qw (
21             _maxarray _minarray _isanumber _roundvalue
22             zoom zoomx zoomy clearchart
23             _quantile _moy _nonoutlier _get_controlpoints
24             enabled_automatic_redraw disabled_automatic_redraw
25             _delete_array_doublon redraw add_data
26             delete_balloon set_balloon
27             _isainteger _set_data_cumulate_percent
28             );
29             my @modules_display = qw/ display_values /;
30            
31 1     1   1105 use base qw/ Exporter /;
  1         2  
  1         2085  
32             our @EXPORT = @module_export;
33             our @EXPORT_OK = @modules_display;
34             our %EXPORT_TAGS = (
35             DUMMIES => \@module_export,
36             DISPLAY => \@modules_display,
37             );
38            
39             my $EMPTY = q{};
40            
41             sub _delete_array_doublon {
42 0     0     my ($ref_tab) = @_;
43            
44 0           my %temp;
45 0           return grep { !$temp{$_}++ } @{$ref_tab};
  0            
  0            
46             }
47            
48             sub _maxarray {
49 0     0     my ($ref_number) = @_;
50 0           my $max;
51            
52 0           for my $chiffre ( @{$ref_number} ) {
  0            
53 0 0         next if ( !_isanumber($chiffre) );
54 0           $max = _max( $max, $chiffre );
55             }
56            
57 0           return $max;
58             }
59            
60             sub _minarray {
61 0     0     my ($ref_number) = @_;
62 0           my $min;
63            
64 0           for my $chiffre ( @{$ref_number} ) {
  0            
65 0 0         next if ( !_isanumber($chiffre) );
66            
67 0           $min = _min( $min, $chiffre );
68             }
69            
70 0           return $min;
71             }
72            
73             sub _max {
74 0     0     my ( $a, $b ) = @_;
75 0 0         if ( not defined $a ) { return $b; }
  0            
76 0 0         if ( not defined $b ) { return $a; }
  0            
77 0 0 0       if ( not defined $a and not defined $b ) { return; }
  0            
78            
79 0 0         if ( $a >= $b ) { return $a; }
  0            
80 0           else { return $b; }
81            
82 0           return;
83             }
84            
85             sub _min {
86 0     0     my ( $a, $b ) = @_;
87 0 0         if ( not defined $a ) { return $b; }
  0            
88 0 0         if ( not defined $b ) { return $a; }
  0            
89 0 0 0       if ( not defined $a and not defined $b ) { return; }
  0            
90            
91 0 0         if ( $a <= $b ) { return $a; }
  0            
92 0           else { return $b; }
93            
94 0           return;
95             }
96            
97             sub _moy {
98 0     0     my ($ref_values) = @_;
99            
100 0           my $total_values = scalar @{$ref_values};
  0            
101            
102 0 0         return if ( $total_values == 0 );
103            
104 0           my $moy = 0;
105 0           for my $value ( @{$ref_values} ) {
  0            
106 0           $moy += $value;
107             }
108            
109 0           $moy = ( $moy / $total_values );
110            
111 0           return $moy;
112             }
113            
114             sub _ispair {
115 0     0     my ($number) = @_;
116            
117 0 0         if ( !_isainteger($number) ) {
118 0           croak "$number not an integer\n";
119             }
120            
121 0 0         if ( $number % 2 == 0 ) {
122 0           return 1;
123             }
124            
125 0           return;
126             }
127            
128             sub _isainteger {
129 0     0     my ($number) = @_;
130            
131 0 0 0       if ( ( defined $number ) and ( $number =~ m{^\d+$} ) ) {
132 0           return 1;
133             }
134            
135 0           return;
136             }
137            
138             sub _median {
139 0     0     my ($ref_values) = @_;
140            
141             # sort data
142 0           my @values = sort { $a <=> $b } @{$ref_values};
  0            
  0            
143 0           my $total_values = scalar @values;
144 0           my $median;
145            
146             # Number of data pair
147 0 0         if ( _ispair($total_values) ) {
148            
149             # 2 values for center
150 0           my $value1 = $values[ $total_values / 2 ];
151 0           my $value2 = $values[ ( $total_values - 2 ) / 2 ];
152 0           $median = ( $value1 + $value2 ) / 2;
153             }
154            
155             # Number of data impair
156             else {
157 0           $median = $values[ ( $total_values - 1 ) / 2 ];
158             }
159            
160 0           return $median;
161             }
162            
163             # The Quantile is calculated as the same excel algorithm and
164             # is equivalent to quantile type 7 in R quantile package.
165             sub _quantile {
166 0     0     my ( $ref_data, $quantile_number ) = @_;
167            
168 0           my @values = sort { $a <=> $b } @{$ref_data};
  0            
  0            
169 0 0         if ( not defined $quantile_number ) { $quantile_number = 1; }
  0            
170            
171 0 0         if ( $quantile_number == 0 ) { return $values[0]; }
  0            
172            
173 0           my $count = scalar @{$ref_data};
  0            
174            
175 0 0         if ( $quantile_number == 4 ) { return $values[ $count - 1 ]; }
  0            
176            
177 0           my $k_quantile = ( ( $quantile_number / 4 ) * ( $count - 1 ) + 1 );
178 0           my $f_quantile = $k_quantile - POSIX::floor($k_quantile);
179 0           $k_quantile = POSIX::floor($k_quantile);
180            
181             # interpolation
182 0           my $ak_quantile = $values[ $k_quantile - 1 ];
183 0           my $akplus_quantile = $values[$k_quantile];
184            
185             # Calcul quantile
186 0           my $quantile = $ak_quantile + ( $f_quantile * ( $akplus_quantile - $ak_quantile ) );
187            
188 0           return $quantile;
189             }
190            
191             sub _nonoutlier {
192 0     0     my ( $ref_values, $q1, $q3 ) = @_;
193            
194             # interquartile range,
195 0           my $iqr = $q3 - $q1;
196            
197             # low and up boundaries
198 0           my $low_boundary = $q1 - ( 1.5 * $iqr );
199 0           my $up_boundary = $q3 + ( 1.5 * $iqr );
200            
201             # largest non-outlier and smallest non-outlier
202 0           my ( $l_nonoutlier, $s_nonoutlier );
203 0           for my $value ( sort { $a <=> $b } @{$ref_values} ) {
  0            
  0            
204 0 0         if ( $value > $low_boundary ) {
205 0           $s_nonoutlier = $value;
206 0           last;
207             }
208             }
209            
210 0           for my $value ( reverse sort { $a <=> $b } @{$ref_values} ) {
  0            
  0            
211 0 0         if ( $value < $up_boundary ) {
212 0           $l_nonoutlier = $value;
213 0           last;
214             }
215             }
216            
217 0           return ( $s_nonoutlier, $l_nonoutlier );
218             }
219            
220             sub _roundvalue {
221 0     0     my ($value) = @_;
222 0 0         if ( $value > 10000 ) {
223 0           return sprintf '%.2e', $value;
224             }
225 0           return sprintf '%.5g', $value;
226             }
227            
228             # Test if value is a real number
229             sub _isanumber {
230 0     0     my ($value) = @_;
231            
232 0 0         if ( not defined $value ) {
233 0           return;
234             }
235 0 0         if ( $value
236             =~ /^(?:(?i)(?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[+-]?)(?:[0123456789]+))|))$/
237             )
238             {
239 0           return 1;
240             }
241            
242 0           return;
243             }
244            
245             sub _get_controlpoints {
246 0     0     my ( $cw, $ref_array ) = @_;
247            
248 0           my $nbrelt = scalar @{$ref_array};
  0            
249            
250 0 0         if ( $nbrelt <= 4 ) {
251 0           return $ref_array;
252             }
253            
254             # First element
255 0           my @all_controlpoints = ( $ref_array->[0], $ref_array->[1] );
256            
257 0           for ( my $i = 0; $i <= $nbrelt; $i = $i + 2 ) {
258 0           my @point_a = ( $ref_array->[$i], $ref_array->[ $i + 1 ] );
259 0           my @point_b = ( $ref_array->[ $i + 2 ], $ref_array->[ $i + 3 ] );
260 0           my @point_c = ( $ref_array->[ $i + 4 ], $ref_array->[ $i + 5 ] );
261            
262 0 0         last if ( !$ref_array->[ $i + 5 ] );
263            
264             # Equation between pointa and PointC
265             # Coef = (yc -ya) / (xc -xa)
266             # D1 : Y = Coef * X + (ya - (Coef * xa))
267 0           my $coef = ( $point_c[1] - $point_a[1] ) / ( $point_c[0] - $point_a[0] );
268            
269             # Equation for D2 ligne paralelle to [AC] with PointB
270             # D2 : Y = (Coef * X) + yb - (coef * xb)
271             # The 2 control points
272             my $d2line = sub {
273 0     0     my ($x) = @_;
274            
275 0           my $y = ( $coef * $x ) + $point_b[1] - ( $coef * $point_b[0] );
276 0           return $y;
277 0           };
278            
279             # distance
280 0           my $distance = 0.95;
281            
282             # xc1 = ( (xb - xa ) / 2 ) + xa
283             # yc1 = via D2
284 0           my @control_point1;
285 0           $control_point1[0] = ( $distance * ( $point_b[0] - $point_a[0] ) ) + $point_a[0];
286 0           $control_point1[1] = $d2line->( $control_point1[0] );
287 0           push @all_controlpoints, ( $control_point1[0], $control_point1[1] );
288            
289             # points
290 0           push @all_controlpoints, ( $point_b[0], $point_b[1] );
291            
292             # xc2 = ( (xc - xb ) / 2 ) + xb
293             # yc2 = via D2
294 0           my @control_point2;
295 0           $control_point2[0] = ( ( 1 - $distance ) * ( $point_c[0] - $point_b[0] ) ) + $point_b[0];
296 0           $control_point2[1] = $d2line->( $control_point2[0] );
297            
298 0           push @all_controlpoints, ( $control_point2[0], $control_point2[1] );
299             }
300            
301 0           push @all_controlpoints, $ref_array->[ $nbrelt - 2 ], $ref_array->[ $nbrelt - 1 ];
302            
303 0           return \@all_controlpoints;
304             }
305            
306             sub _set_data_cumulate_percent {
307 0     0     my ( $cw, $ref_data ) = @_;
308            
309             # x-axis
310 0           my @new_data = ( $ref_data->[0] );
311            
312             # Number of data and values in a data
313 0           my $number_values = scalar @{ $ref_data->[0] };
  0            
314 0           my $number_data = scalar @{$ref_data} - 1;
  0            
315 0           push @new_data, [] for ( 1 .. $number_data );
316            
317             # Change data to set percent data instead values
318 0           for my $index_value ( 0 .. $number_values - 1 ) {
319 0           my $sum = 0;
320            
321             # Sum calculate
322 0           for my $index_data ( 1 .. $number_data ) {
323 0 0         if ( $ref_data->[$index_data][$index_value] ) { $sum += $ref_data->[$index_data][$index_value]; }
  0            
324             }
325            
326             # Change value
327 0           for my $index_data ( 1 .. $number_data ) {
328 0 0         next if ( ! $ref_data->[$index_data][$index_value] );
329 0           my $new_value = ( $ref_data->[$index_data][$index_value] / $sum ) * 100;
330 0           $new_data[$index_data][$index_value] = sprintf '%.5g', $new_value;
331             }
332             }
333            
334 0           return \@new_data;
335             }
336            
337             sub redraw {
338 0     0 0   my ($cw) = @_;
339            
340 0           $cw->_chartconstruction;
341 0           return;
342             }
343            
344             sub delete_balloon {
345 0     0 0   my ($cw) = @_;
346            
347 0           $cw->{RefChart}->{Balloon}{State} = 0;
348 0           $cw->_balloon();
349            
350 0           return;
351             }
352            
353             sub add_data {
354 0     0 0   my ( $cw, $ref_data, $legend ) = @_;
355            
356             # Doesn't work for Pie graph
357 0 0         if ( $cw->class eq 'Pie' ) {
358 0           $cw->_error("This method 'add_data' not allowed for Tk::Chart::Pie\n");
359 0           return;
360             }
361            
362 0           my $refdata = $cw->{RefChart}->{Data}{RefAllData};
363            
364             # Cumulate pourcent => data change
365 0           my $cumulatepercent = $cw->cget( -cumulatepercent );
366 0 0 0       if ( defined $cumulatepercent and $cumulatepercent == 1 ) {
367 0           $refdata = $cw->{RefChart}->{Data}{RefAllDataBeforePercent};
368             }
369            
370 0           push @{$refdata}, $ref_data;
  0            
371 0 0         if ( $cw->{RefChart}->{Legend}{NbrLegend} > 0 ) {
372 0           push @{ $cw->{RefChart}->{Legend}{DataLegend} }, $legend;
  0            
373             }
374            
375 0           $cw->plot($refdata);
376            
377 0           return;
378             }
379            
380             sub set_balloon {
381 0     0 0   my ( $cw, %options ) = @_;
382            
383 0           $cw->{RefChart}->{Balloon}{State} = 1;
384            
385 0 0         if ( defined $options{-colordatamouse} ) {
386 0 0         if ( scalar @{ $options{-colordatamouse} } < 2 ) {
  0            
387 0           $cw->_error(
388             "Can't set -colordatamouse, you have to set 2 colors\nEx : -colordatamouse => ['red','green'],", 1 );
389             }
390             else {
391 0           $cw->{RefChart}->{Balloon}{ColorData} = $options{-colordatamouse};
392             }
393             }
394 0 0         if ( defined $options{-morepixelselected} ) {
395 0           $cw->{RefChart}->{Balloon}{MorePixelSelected} = $options{-morepixelselected};
396             }
397 0 0         if ( defined $options{-background} ) {
398 0           $cw->{RefChart}->{Balloon}{Background} = $options{-background};
399             }
400            
401 0           $cw->_balloon();
402            
403 0           return;
404             }
405            
406             sub zoom {
407 0     0 0   my ( $cw, $zoom ) = @_;
408            
409 0           my ( $new_width, $new_height ) = $cw->_zoomcalcul( $zoom, $zoom );
410 0           $cw->configure( -width => $new_width, -height => $new_height );
411 0           $cw->toplevel->geometry($EMPTY);
412            
413 0           return 1;
414             }
415            
416             sub zoomx {
417 0     0 0   my ( $cw, $zoom ) = @_;
418            
419 0           my ( $new_width, $new_height ) = $cw->_zoomcalcul( $zoom, undef );
420 0           $cw->configure( -width => $new_width );
421 0           $cw->toplevel->geometry($EMPTY);
422            
423 0           return 1;
424             }
425            
426             sub zoomy {
427 0     0 0   my ( $cw, $zoom ) = @_;
428            
429 0           my ( $new_width, $new_height ) = $cw->_zoomcalcul( undef, $zoom );
430 0           $cw->configure( -height => $new_height );
431 0           $cw->toplevel->geometry($EMPTY);
432            
433 0           return 1;
434             }
435            
436             # Clear the Canvas Widget
437             sub clearchart {
438 0     0 0   my ($cw) = @_;
439            
440 0           $cw->update;
441 0           $cw->delete( $cw->{RefChart}->{TAGS}{AllTagsChart} );
442            
443 0           return;
444             }
445            
446             sub display_values {
447 0     0 0   my ( $cw, $ref_data, %options ) = @_;
448            
449             # Doesn't work for Pie graph
450 0 0         if ( $cw->class eq 'Pie' ) {
    0          
451 0           $cw->_error("This method 'display_values' not allowed for Tk::Chart::Pie\n");
452 0           return;
453             }
454             elsif ( $cw->class eq 'Bars' ) {
455 0           $cw->_error("This method 'display_values' not allowed for Tk::Chart::Bars\n");
456 0           return;
457             }
458            
459 0 0 0       if ( !( defined $ref_data and ref $ref_data eq 'ARRAY' ) ) {
460 0           $cw->_error( 'data not defined', 1 );
461 0           return;
462             }
463 0           $cw->{RefChart}->{Data}{RefDataToDisplay} = $ref_data;
464 0           $cw->{RefChart}->{Data}{RefOptionDataToDisplay} = \%options;
465            
466 0 0         if ( $cw->class eq 'Areas' ) {
467 0           foreach my $ref_value ( @{$ref_data} ) {
  0            
468 0           unshift @{$ref_value}, undef;
  0            
469             }
470             }
471            
472 0 0         if ( defined $cw->{RefChart}->{Data}{PlotDefined} ) {
473 0           $cw->redraw;
474             }
475            
476 0           return;
477             }
478            
479             sub enabled_automatic_redraw {
480 0     0 0   my ($cw) = @_;
481            
482 0           my $class = $cw->class;
483 0           foreach my $key (qw{ Down End Home Left Next Prior Right Up }) {
484 0           $cw->Tk::bind( "Tk::Chart::$class", "", undef );
485 0           $cw->Tk::bind( "Tk::Chart::$class", "", undef );
486             }
487            
488             # recreate graph after widget resize
489 0     0     $cw->Tk::bind( '' => sub { $cw->_chartconstruction; } );
  0            
490 0           return;
491             }
492            
493             sub disabled_automatic_redraw {
494 0     0 0   my ($cw) = @_;
495            
496 0           my $class = $cw->class;
497 0           foreach my $key (qw{ Down End Home Left Next Prior Right Up }) {
498 0           $cw->Tk::bind( "Tk::Chart::$class", "", undef );
499 0           $cw->Tk::bind( "Tk::Chart::$class", "", undef );
500             }
501            
502             # recreate graph after widget resize
503 0           $cw->Tk::bind( '' => undef );
504 0           return;
505             }
506            
507             1;
508            
509             __END__