File Coverage

blib/lib/Text/BarGraph.pm
Criterion Covered Total %
statement 12 118 10.1
branch 0 56 0.0
condition 0 20 0.0
subroutine 4 12 33.3
pod 2 2 100.0
total 18 208 8.6


line stmt bran cond sub pod time code
1             package Text::BarGraph;
2              
3 1     1   24249 use strict;
  1         2  
  1         39  
4 1     1   6 use warnings;
  1         2  
  1         36  
5              
6 1     1   6 use vars qw /$AUTOLOAD $VERSION/;
  1         7  
  1         72  
7              
8 1     1   5 use Carp;
  1         2  
  1         1528  
9              
10             =head1 NAME
11              
12             Text::BarGraph - Text Bar graph generator
13              
14             =head1 SYNOPSIS
15              
16             use Text::BarGraph;
17              
18             $graph = Text::BarGraph->new();
19              
20             =head1 ABSTRACT
21              
22             A module to create text bar graphs
23              
24             =head1 DESCRIPTION
25              
26             This module takes as input a hash, where the keys are labels for bars on
27             a graph and the values are the magnitudes of those bars.
28              
29             =head1 EXAMPLE
30              
31             $graph = Text::BarGraph->new();
32              
33             %hash = (
34             alpha => 30,
35             beta => 40,
36             gamma => 25
37             );
38              
39             print $g->graph(\%hash);
40              
41             =head1 METHODS
42              
43             =over 4
44              
45             =cut
46              
47             our $VERSION = 1.1;
48             our %fields = (
49             dot => '#', # character to graph with
50             num => 1, # display data value in ()'s
51             enable_color => 0, # whether or not to color the graph
52             sortvalue => "key", # key or data
53             sorttype => "string", # string or numeric, ignored if sort is 'data'
54             zero => 0, # value to start the graph with
55             max_data => 0, # where to end the graph
56             autozero => 0, # automatically set start value
57             autosize => 1, # requires Term::ReadKey
58             columns => 80, # columns
59             );
60              
61             =item I
62              
63             $graph = Text::BarGraph->new();
64              
65             The constructor.
66              
67             =cut
68             sub new {
69 0     0 1   my $that = shift;
70 0   0       my $class = ref($that) || $that;
71              
72 0           my $self = {
73             _permitted => \%fields,
74             %fields,
75             };
76              
77 0           my %args = @_;
78              
79 0           while(my ($field, $value) = each %args) {
80 0 0         if(exists($self->{'_permitted'}{$field})) {
81 0           $self->{$field} = $value;
82             } else {
83 0           croak "Invalid field name '$field' in class $class";
84             }
85             }
86              
87 0 0         if(eval "require Term::ANSIColor") {
88 0           import Term::ANSIColor;
89 0           $self->{'colortype'} = "module";
90             } else {
91 0           $self->{'colortype'} = "raw";
92             }
93              
94 0           bless $self, $class;
95 0           return $self;
96             }
97              
98 0     0     sub DESTROY { }
99              
100             sub AUTOLOAD {
101 0     0     my $self = shift;
102 0   0       my $type = ref($self) || die "$self is not an object";
103 0           my $name = $AUTOLOAD;
104 0           $name =~ s/.*://; # strip fully qualified portion
105 0 0         unless (exists $self->{'_permitted'}{$name} ) {
106 0           croak "Invalid field name '$name' in class $type";
107             }
108              
109 0 0         if (@_) {
110 0           $self->{$name} = shift;
111             }
112 0           return $self->{$name};
113             }
114              
115             =item I
116              
117             $graph_text = $graph->graph(\%data);
118            
119             Return a graph of the data in the supplied hash. The keys in
120             the hash are labels, and the values are the magnitudes.
121              
122             =cut
123             sub graph {
124 0     0 1   my ($self, $data) = @_;
125 0           my $gtext = '';
126 0           my $label_length = 5;
127 0           my $scale = 1;
128 0           my $sep = " ";
129 0           my $barsize = 0;
130 0           my $sort_sub;
131             my $min_data;
132 0           my $max_data;
133              
134 0           my $columns = $self->{'columns'};
135              
136             # silently fail to autoresize if we are not talking to a tty
137             # OR if the Term::ReadKey module doesn't exist
138 0 0 0       if($self->{'autosize'} && -t STDOUT && eval "require Term::ReadKey") {
      0        
139 0           import Term::ReadKey;
140 0           ($columns) = GetTerminalSize('STDOUT');
141             }
142              
143             # find initial column width and scaling
144 0           foreach my $key (keys %{$data}) {
  0            
145 0 0 0       if(!defined($min_data) || $min_data > $data->{$key}) {
146 0           $min_data = $data->{$key};
147             }
148 0 0         if(length($key) > $label_length) {
149 0           $label_length = length($key);
150             }
151 0 0 0       if(!defined($max_data) || $data->{$key} > $max_data) {
152 0           $max_data = $data->{$key};
153             }
154             }
155 0 0 0       if(!defined($max_data) || $self->{'max_data'} > $max_data) {
156 0           $max_data = $self->{'max_data'};
157             }
158              
159             # determine how many columns are left for the graph after
160             # the labels
161 0           my $data_length = length($max_data);
162 0 0         if($label_length > ($columns * .25)) {
163 0           $sep = "\n";
164 0           $barsize = $columns;
165             } else {
166 0           $sep = " ";
167 0 0         if($self->{'num'}) {
168 0           $barsize = $columns - ($label_length + $data_length + 4);
169             } else {
170 0           $barsize = $columns - ($label_length + 1);
171             }
172             }
173              
174 0 0         if($self->{'autozero'}) {
175 0           $self->{'zero'} = int($min_data - (($max_data - $min_data) / ($barsize - 1)));
176             }
177            
178             # determine points to change colors
179 0           my ($p1, $p2, $p3) = 0;
180 0 0         if($self->{'enable_color'}) {
181 0           $p1 = int($barsize * .25);
182 0           $p2 = $p1*2; $p3 = $p1*3;
  0            
183             }
184              
185 0 0         if($max_data) { $scale = $barsize / ($max_data - $self->{'zero'}); }
  0            
186              
187             # create a sort subroutine based on sortvalue and sorttype
188 0 0         if($self->{'sortvalue'} eq "key") {
189 0 0         if($self->{'sorttype'} eq "string") {
190 0     0     $sort_sub = sub { return $a cmp $b; }
191 0           } else {
192 0     0     $sort_sub = sub { return $a <=> $b; }
193 0           }
194             } else {
195 0     0     $sort_sub = sub { return $data->{$a} <=> $data->{$b}; }
196 0           }
197              
198             # build the graph
199 0           foreach my $label (sort $sort_sub keys %{$data}) {
  0            
200 0           my $bar = '';
201 0           my $dots = int(($data->{$label} - $self->{'zero'}) * $scale);
202              
203 0 0         if($self->{'enable_color'}) {
204 0           $bar = $self->_colordots($p1, $p2, $p3, $dots);
205             } else {
206 0           $bar = $self->{'dot'}x$dots;
207             }
208              
209 0 0         if($self->{'num'}) {
210 0           $gtext .= sprintf "%${label_length}s (%${data_length}d)${sep}%s\n",
211             $label, $data->{$label}, $bar;
212             } else {
213 0           $gtext .= sprintf "%${label_length}s${sep}%s\n", $label, $bar;
214             }
215             }
216              
217             # add a line giving the start point if it's not zero
218 0 0         if($self->{'zero'}) {
219 0 0         if($self->{'num'}) {
220 0           $gtext .= sprintf "%${label_length}s %${data_length}d /\n", '', $self->{'zero'};
221             } else {
222 0           $gtext .= sprintf "%${label_length}s /\n", "$self->{'zero'}";
223             }
224             }
225 0           return $gtext;
226             }
227              
228             sub _colordots {
229 0     0     my ($self, $p1, $p2, $p3, $dots) = @_;
230              
231 0           my $bar = '';
232              
233 0 0         if($self->{'colortype'} eq "module") {
    0          
234 0           $bar = color('blue');
235              
236 0           for(1..$dots) {
237 0 0         if( $_ eq $p1) { $bar .= color('green'); }
  0 0          
    0          
238 0           elsif($_ eq $p2) { $bar .= color('yellow'); }
239 0           elsif($_ eq $p3) { $bar .= color('red'); }
240              
241 0           $bar .= $self->{'dot'};
242             }
243 0           $bar .= color('reset');
244              
245             } elsif($self->{'colortype'} eq "raw") {
246 0           $bar = "\e[34m"; # start blue
247              
248 0           for(1..$dots) {
249 0 0         if( $_ eq $p1) { $bar .= "\e[32m"; } # green
  0 0          
    0          
250 0           elsif($_ eq $p2) { $bar .= "\e[33m"; } # yellow
251 0           elsif($_ eq $p3) { $bar .= "\e[31m"; } # red
252 0           $bar .= $self->{'dot'};
253             }
254 0           $bar .= "\e[0m"; # turn the color off
255             }
256 0           return $bar;
257             }
258              
259             1;
260              
261             __DATA__