File Coverage

blib/lib/Data/Xtab.pm
Criterion Covered Total %
statement 3 50 6.0
branch 0 2 0.0
condition 0 2 0.0
subroutine 1 5 20.0
pod 0 4 0.0
total 4 63 6.3


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # Data::Xtab - cross-tabulate a table of data.
4             #
5             # Copyright (c) 1997, Brian C. Jepson
6             #
7             # You may distribute this under the same terms as Perl
8             # itself.
9             #
10              
11             $Data::Xtab::VERSION = '1.01';
12              
13             =head1 NAME
14              
15             Data::Xtab - Pivot (cross-tabulate) a table of data.
16              
17             =head1 DESCRIPTION
18              
19             This module allows you to feed it tables of data to be
20             pivoted in such a way that they can be easily used in a
21             report or graph. Here is an example of input data:
22              
23             'A', 'JUN', 7
24             'A', 'JAN', 4
25             'B', 'JAN', 3
26             'B', 'FEB', 39
27             'C', 'MAY', 8
28             'A', 'JUN', 100
29              
30             The output would be rendered as:
31              
32             JAN FEB MAR APR MAY JUN
33             A 4 0 0 0 0 107
34             B 3 39 0 0 0 0
35             C 0 0 0 0 8 0
36              
37             The first column in the table ends up becoming the data
38             series. The second column becomes the headers, under which
39             the third column is summed. If more than one data records
40             for the same data series and header column appear in the
41             input data, the values are totalled for that intersection.
42              
43             This module was designed to be used in conjunction with the
44             GIFGraph module, but no doubt has other uses.
45              
46             =head1 SYNOPSIS
47              
48             #!/usr/local/bin/perl
49              
50             use Data::Xtab;
51             use GIFgraph::lines;
52             use CGI;
53             $query = new CGI;
54             print $query->header("image/gif");
55              
56             my @data = ( ['A', 'FEB', 31],
57             ['A', 'FEB', 12],
58             ['A', 'MAR', 18],
59             ['A', 'MAR', 29],
60             ['A', 'APR', 142],
61             ['B', 'FEB', 217],
62             ['B', 'FEB', 14],
63             ['B', 'MAR', 121],
64             ['B', 'APR', 37],
65             ['C', 'MAR', 39],
66             ['C', 'MAR', 8],
67             ['C', 'APR', 100] );
68              
69             # The outputcols parameter is used to enumerate the
70             # columns that should be used in the output table, and
71             # more importantly, the order in which they should appear.
72             #
73             my @outputcols = ('JAN', 'FEB', 'MAR', 'APR');
74              
75             my $xtab = new Data::Xtab(\@data, \@outputcols);
76            
77             my @graph_data = $xtab->graph_data;
78              
79             $my_graph = new GIFgraph::lines();
80              
81             $my_graph->set( 'x_label' => 'Month',
82             'y_label' => 'Sales',
83             'title' => 'Monthly Sales',
84             'y_max_value' => 450,
85             'y_tick_number' => 5,
86             'y_label_skip' => 2 );
87             print $my_graph->plot( \@graph_data );
88              
89             =head1 AUTHOR
90              
91             Copyright (c) 1997, Brian Jepson
92             You may distribute this kit under the same terms as Perl itself.
93              
94             =cut
95              
96             package Data::Xtab;
97 1     1   697 use strict;
  1         2  
  1         6399  
98              
99             sub new {
100              
101 0     0 0   my $class = shift;
102              
103 0           my $self = {};
104 0           bless($self,$class);
105              
106 0           $self->{data} = shift;
107 0           $self->{cols} = shift;
108              
109 0           $self->pivot;
110              
111 0           return $self;
112              
113             }
114              
115             # Pivot the data.
116             #
117             sub pivot {
118              
119 0     0 0   my $self = shift;
120              
121 0           my %rows;
122              
123             # This is the input data.
124             #
125 0           my @data = @{ $self->{data} };
  0            
126              
127 0           my @cols;
128 0           foreach (@data) {
129              
130             # Each row in the input data is a reference to an array
131             # of the row_label, column_label, and data value. The
132             # row_label is the value that describes each data series.
133             # The column_label is the value that is used as headers
134             # for each columns, and the data value is the information
135             # that appears cross-referenced between the row_label and
136             # column_label values.
137             #
138             # In the SYNOPSIS section of the documentation, the
139             # 'A', 'B' and 'C' values are the row_label values, and
140             # the months (FEB-APR) are the column_label values.
141             #
142 0           my $row_label = $$_[0];
143 0           my $column_label = $$_[1];
144              
145             # By incrementing the row_label attribute, we ensure
146             # that each row_label gets listed in an easy-to-fetch
147             # lookup hash.
148             #
149 0           $self->{row_label}->{$row_label}++;
150              
151             # By incrementing the column_label attribute, we ensure
152             # that each pivoted column gets listed in an
153             # easy-to-fetch lookup hash.
154             #
155 0           $self->{column_label}->{$column_label}++;
156              
157             # The values are stored in a hash of hashes - keyed
158             # first by the row_label, and then by the column_label
159             # value. Note that the values can be cumulative, as you
160             # can have more than one data element that goes into a
161             # given row_label/column_label value "bucket."
162             #
163 0           $rows{$row_label}{$column_label} += $$_[2];
164            
165             }
166              
167             # If, for some reason, the user didn't pass in a list of
168             # column titles, then we'll sort the keys we have in the
169             # column_label attribute, and use that. This is a bad
170             # idea, particularly with character month names and data
171             # sets that may have gaps. It's best to always explicitly
172             # supply the columns.
173             #
174 0           @cols = sort keys %{ $self->{column_label} };
  0            
175 0   0       $self->{cols} ||= \@cols;
176              
177 0           return (%{$self->{'rows'}} = %rows);
  0            
178              
179             }
180              
181             sub row_labels {
182 0     0 0   my ($self) = shift;
183 0           keys %{$self->{row_label}};
  0            
184             }
185              
186             # massage the cross-tab into something that GIFgraph.pm can
187             # handle.
188             #
189             sub graph_data {
190 0     0 0   my $self = shift;
191              
192 0           my %rows = %{$self->{rows}};
  0            
193 0           my @graph_data;
194             my @header;
195 0           foreach my $col ( @{$self->{cols}}) {
  0            
196 0           push @header, $col;
197             }
198 0           push @graph_data, \@header;
199 0           my @total;
200 0           foreach my $row ($self->row_labels) {
201 0           my @data;
202             my $i;
203 0           foreach my $col (@{$self->{cols}}) {
  0            
204              
205 0           my $val = 0;
206 0 0         if (defined $rows{$row}{$col}) {
207 0           $val = $rows{$row}{$col} * 1;
208             }
209 0           push @data, $val;
210 0           $total[$i++] += $val;
211             }
212 0           push @graph_data, \@data;
213             }
214 0           push @graph_data, \@total;
215 0           @graph_data;
216             }
217              
218             1;