File Coverage

blib/lib/GD/Graph/histogram.pm
Criterion Covered Total %
statement 15 17 88.2
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 22 90.9


line stmt bran cond sub pod time code
1             ##########################################################################################################
2             # GD::Graph::histogram
3             #
4             # Copyright 2007, Snehanshu Shah
5             #
6             ##########################################################################################################
7             package GD::Graph::histogram;
8 1     1   23984 use strict;
  1         2  
  1         41  
9              
10             BEGIN {
11 1     1   5 use Exporter ();
  1         2  
  1         20  
12 1     1   4 use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         7  
  1         125  
13 1     1   2 $VERSION = 1.10;
14 1         13 @ISA = qw (Exporter);
15             #Give a hoot don't pollute, do not export more than needed by default
16 1         3 @EXPORT = qw ();
17 1         12 @EXPORT_OK = qw ();
18 1         27 %EXPORT_TAGS = ();
19             }
20              
21 1     1   560 use GD::Graph::bars;
  0            
  0            
22             use GD::Graph::Data;
23              
24             @GD::Graph::histogram::ISA = qw(GD::Graph::bars);
25              
26             my %Defaults = (
27             histogram_bins => undef,
28             histogram_type => 'count'
29             );
30              
31             sub plot
32             {
33             my $self = shift;
34             my $dataArrRef = shift;
35              
36             my $histogram_bins = $self->get('histogram_bins');
37              
38             my $cp = _histogram_bins( $dataArrRef, $histogram_bins );
39              
40             my $binArrRef = _histogram_frequency( $dataArrRef, $cp );
41              
42             if ($self->get('histogram_type') eq 'percentage')
43             {
44             my $total = 0;
45             grep($total += $_ , @$binArrRef);
46              
47             if ($total > 0)
48             {
49             for (my $i = 0; $i < scalar(@$binArrRef); $i++)
50             {
51             $binArrRef->[$i] = 100 * $binArrRef->[$i] / $total;
52             }
53             }
54             }
55              
56             my @labelArr;
57             for my $bin (@$cp)
58             {
59             push(@labelArr, _numformat( $bin->[0] + ($bin->[1] - $bin->[0])/2 ) );
60             }
61              
62             my $data = GD::Graph::Data->new([ \@labelArr, $binArrRef ]) or die GD::Graph::Data->error;
63              
64             # Display the labels veritcally for histogram
65             $self->set( x_labels_vertical => 1 );
66              
67             return $self->SUPER::plot($data);
68             }
69              
70             ###################################################################
71             # _histogram_bins - calculates the bins usings Scott's algorithm
72             #
73             # Arguements:
74             #
75             # $data (Vector) - Data values
76             #
77             # $nbins (Integer) - Number of bins to create. If $nbins is undef
78             # the number of bins is calculated using Scott's
79             # algorithm
80             #
81             ###################################################################
82             sub _histogram_bins {
83             my ( $data, $nbins ) = @_;
84              
85             if( !defined $data ) { return; }
86              
87             my $calcBins = ( defined $nbins )? 0 : 1;
88             my $cnt = 0;
89             my $mean= 0;
90             my $max = my $min = $data->[0];
91             foreach (@$data) {
92             $mean += $_;
93             $min = ( $_ < $min )? $_ : $min;
94             $max = ( $_ > $max )? $_ : $max;
95             $cnt++;
96             }
97             $mean /= $cnt if( $cnt > 1 );
98              
99             my $sumsq = 0;
100             $nbins = 1 if( $calcBins );
101             my $s = 0;
102             if( $cnt > 1 ) {
103             foreach (@$data) {
104             $sumsq += ( $_ - $mean )**2;
105             }
106             $s = sqrt( $sumsq / ($cnt - 1));
107             $nbins = 3.49 * $s / $cnt**0.33 if( $s > 0 && $calcBins );
108             }
109              
110             my $binwidth = ( $max - $min ) / $nbins;
111              
112             my $lower = $min;
113             my $upper = $lower;
114              
115             my $bins;
116             my @cutPoints;
117             my $cntr = 0;
118             while ( $upper <= $max && $cntr < $nbins) {
119             $upper = $lower + $binwidth;
120             push( @cutPoints, [$lower, $upper] );
121             $lower = $upper;
122             $cntr++;
123             }
124              
125             return \@cutPoints;
126             }
127              
128             ###################################################################
129             # _histogram_frequency - bins the data
130             #
131             # Lower Boundry <= data value < Upper Boundry
132             #
133             # Arguements:
134             #
135             # $data (Vector) - Data values
136             #
137             # $nbins (Integer) - Vector containing the cutpoints to bin the data
138             #
139             ###################################################################
140             sub _histogram_frequency {
141             my ( $data, $cutPoints ) = @_;
142              
143             if( !defined $data || !defined $cutPoints ) { return; }
144              
145             my @freqs;
146             foreach (@$cutPoints) {
147             push( @freqs, 0 );
148             }
149              
150             foreach (@$data)
151             {
152             for( my $i = 0; $i < scalar( @$cutPoints ); $i++ )
153             {
154             if( ($_ >= $cutPoints->[$i]->[0] && $_ < $cutPoints->[$i]->[1])
155             ||
156             ($i == (scalar (@$cutPoints) - 1) && $_ >= $cutPoints->[$i]->[1]) )
157             {
158              
159             $freqs[$i]++;
160             }
161             }
162             }
163             return \@freqs;
164             }
165              
166             sub _numformat {
167             my ($v, $f1, $f2) = @_;
168              
169             unless(defined $v) { return undef; }
170              
171             unless(defined $f1) { $f1 = "%.4e"; }
172              
173             unless(defined $f2) {
174             if ($v < 1) {
175             $f2 = "%.5f";
176             } else {
177             $f2 = "%.3f";
178             }
179             }
180              
181              
182             ## To display no for eg. 22.50 as 22.5
183             if ($v =~ /^([+-]?\d+)\.(\d+)$/) {
184             my $no = $1;
185             my $fraction = $2;
186             $fraction =~ s/0+$//;
187             $v = (length($fraction) == 0) ? $no : "$no.$fraction";
188             }
189              
190             if ($v =~ /\./){
191             if ($v == 0) {
192             $v = 0;
193             } elsif (($v > -0.001) and ($v < 0.001)) {
194             $v = sprintf($f1, $v);
195             } else {
196             $v = sprintf($f2, $v);
197             }
198             }
199              
200             return $v;
201             }
202              
203             sub _has_default {
204             my $self = shift;
205             my $attr = shift || return;
206             exists $Defaults{$attr} || $self->SUPER::_has_default($attr);
207             }
208              
209              
210             ########################################### main pod documentation begin ##
211             # Below is the stub of documentation for your module. You better edit it!
212              
213              
214             =head1 NAME
215              
216             GD::Graph::histogram - Histogram plotting module for Perl5
217              
218             =head1 SYNOPSIS
219              
220             use GD::Graph::histogram;
221              
222             =head1 DESCRIPTION
223              
224             GD::Graph::histogram extends the GD::Graph module to create histograms.
225             The module allow creation of count or percentage histograms.
226              
227             =head1 USAGE
228              
229             Fill an array with all the data values that are to be plotted. Note that
230             GD::Graph::histogram unlike the other GD::Graph modules can only plot one
231             data set at a time.
232              
233             $data = [1,5,7,8,9,10,11,3,3,5,5,5,7,2,2];
234              
235             Create the graph
236              
237             my $graph = new GD::Graph::histogram(400,600);
238              
239             Set graph options
240              
241             $graph->set(
242             x_label => 'X Label',
243             y_label => 'Count',
244             title => 'A Simple Count Histogram Chart',
245             x_labels_vertical => 1,
246             bar_spacing => 0,
247             shadow_depth => 1,
248             shadowclr => 'dred',
249             transparent => 0,
250             )
251             or warn $graph->error;
252              
253             plot the graph
254              
255             my $gd = $graph->plot($data) or die $graph->error;
256              
257             save the graph to a file
258              
259             open(IMG, '>histogram.png') or die $!;
260             binmode IMG;
261             print IMG $gd->png;
262              
263             =head1 METHODS
264              
265             GD::Graph::histogram supports all the methods support by GD::Graph.
266             Please refer to the GD::Graph documentation for more information.
267              
268             The only method that behaves differently is I
269              
270             The I method provided by GD::Graph::histogram expects a
271             reference to an array of numbers.
272              
273             Based on the input data, GD::Graph::histogram will generate the
274             appropriate labels for the X axis. The X axis label represent the center
275             point of the range of each histogram bin.
276              
277             =head1 OPTIONS
278              
279             GD::Graph::histogram supports all the options supported by GD::Graph::bars.
280             Please refer to the GD::Graph documentation for more information.
281              
282             The two additional options that are specific to GD::Graph::histogram are:
283              
284             histogram_bins
285             Specify the number of histogram bins to bucket the data into.
286             The default is for the module to automatically computed the
287             histogram bins based on the data.
288              
289             histogram_type
290             Can be set to either 'percentage' or 'count'. By default the module
291             will create 'count' histograms.
292              
293             =head1 NOTES
294              
295             As with all Modules for Perl: Please stick to using the interface. If
296             you try to fiddle too much with knowledge of the internals of this
297             module, you could get burned. I may change them at any time.
298              
299             =head1 AUTHOR
300              
301             Snehanshu Shah
302             perl@whizdog.com
303             http://www.whizdog.com
304              
305             =head1 ACKNOWLEDGEMENTS
306              
307             Thanks for all the feedback, bug reports and bug fixes
308              
309             Martin Corley
310             Jonathan Barber
311             William Miller
312              
313              
314             =head1 COPYRIGHT
315              
316             This program is free software; you can redistribute
317             it and/or modify it under the same terms as Perl itself.
318              
319             The full text of the license can be found in the
320             LICENSE file included with this module.
321              
322             =head1 SEE ALSO
323              
324             perl(1), GD::Graph
325              
326             =cut
327              
328             __END__