File Coverage

blib/lib/Text/Graph.pm
Criterion Covered Total %
statement 96 96 100.0
branch 44 44 100.0
condition 11 11 100.0
subroutine 23 23 100.0
pod 13 13 100.0
total 187 187 100.0


line stmt bran cond sub pod time code
1             package Text::Graph;
2              
3 7     7   317286 use strict;
  7         16  
  7         266  
4 7     7   35 use warnings;
  7         14  
  7         272  
5 7     7   4422 use Text::Graph::DataSet;
  7         18  
  7         12105  
6              
7             our $VERSION = '0.50';
8              
9             sub new
10             {
11 45     45 1 16334 my $class = shift;
12 45   100     138 my $style = shift || 'Bar';
13              
14 45         101 my $obj = {
15             _initialize( $style ),
16              
17             # data display
18             log => 0,
19              
20             # data limit
21             maxval => undef,
22             minval => undef,
23             maxlen => undef,
24              
25             # graph display
26             separator => ' :',
27             right => 0,
28             showval => 0,
29             @_
30             };
31 44 100       157 $obj->{fill} = $obj->{marker} unless defined $obj->{fill};
32              
33 44         153 return bless $obj, $class;
34             }
35              
36             #--------------------------------------------
37             # INTERNAL: Initialize the default parameters based on the supplied
38             # style.
39             sub _initialize
40             {
41 45     45   56 my $style = shift;
42 45         77 my $lstyle = lc $style;
43              
44 45 100       132 if( 'bar' eq $lstyle )
    100          
45             {
46 26         235 return ( style => 'Bar', marker => '*' );
47             }
48             elsif( 'line' eq $lstyle )
49             {
50 18         195 return ( style => 'Line', marker => '*', fill => ' ' );
51             }
52             else
53             {
54 1         8 die "Unknown style '$style'.\n";
55             }
56             }
57              
58             # Data Display Options
59 4     4 1 1306 sub get_marker { return $_[0]->{marker}; }
60 4     4 1 23 sub get_fill { return $_[0]->{fill}; }
61 5     5 1 32 sub is_log { return $_[0]->{log}; }
62              
63             # Data Limit Options
64 2     2 1 14 sub get_maxlen { return $_[0]->{maxlen}; }
65 2     2 1 11 sub get_maxval { return $_[0]->{maxval}; }
66 2     2 1 11 sub get_minval { return $_[0]->{minval}; }
67              
68             # Graph Display Options
69 2     2 1 15 sub get_separator { return $_[0]->{separator}; }
70 5     5 1 22 sub is_right_justified { return $_[0]->{right}; }
71 5     5 1 23 sub show_value { return $_[0]->{showval}; }
72              
73             sub make_lines
74             {
75 43     43 1 54 my $self = shift;
76 43         82 my $data = _make_graph_data( @_ );
77              
78 43         94 my @lines = _histogram( $data, $self );
79              
80 43 100       229 return wantarray ? @lines : \@lines;
81             }
82              
83             sub make_labelled_lines
84             {
85 41     41 1 160 my $self = shift;
86 41         76 my $data = _make_graph_data( @_ );
87              
88 41         136 my @labels = _fmt_labels( $self->{right}, $data->get_labels() );
89 41         113 my @lines = $self->make_lines( $data );
90 41         116 foreach my $i ( 0 .. $#lines )
91             {
92 287         621 $lines[$i] = $labels[$i] . $self->{separator} . $lines[$i];
93             }
94              
95 41 100       423 return wantarray ? @lines : \@lines;
96             }
97              
98             sub to_string
99             {
100 35     35 1 12745 my $self = shift;
101              
102 35         79 return join( "\n", $self->make_labelled_lines( @_ ) ) . "\n";
103             }
104              
105             #--------------------------------------------
106             # INTERNAL: Convert input parameters to a graph data object as needed.
107             sub _make_graph_data
108             {
109 84 100   84   195 if( 'Text::Graph::DataSet' eq ref $_[0] )
110             {
111 79         142 return shift;
112             }
113             else
114             {
115 5         25 return Text::Graph::DataSet->new( @_ );
116             }
117             }
118              
119             #--------------------------------------------
120             # INTERNAL: This routine pads the labels as needed.
121             sub _fmt_labels
122             {
123 41     41   57 my $right = shift;
124 41         651 my $len = 0;
125 41         52 my @labels;
126              
127 41         80 foreach my $label ( @_ )
128             {
129 287 100       564 $len = length $label if length $label > $len;
130             }
131              
132 41 100       83 if( $right )
133             {
134 2         5 @labels = map { ( ' ' x ( $len - length $_ ) ) . $_ } @_;
  14         32  
135             }
136             else
137             {
138 39         73 my $pad = ' ' x $len;
139              
140 39         59 @labels = map { substr( ( $_ . $pad ), 0, $len ) } @_;
  273         555  
141             }
142              
143 41         171 return @labels;
144             }
145              
146             #--------------------------------------------
147             # INTERNAL: This is the workhorse routine that actually builds the
148             # histogram bars.
149             sub _histogram
150             {
151 43     43   74 my ($dset, $args) = @_;
152 43         47 my $parms = { %{$args}, labels => [ $dset->get_labels ] };
  43         228  
153 43         113 my @values;
154              
155 43   100     121 $parms->{fill} ||= $parms->{marker};
156              
157 43         110 my @orig = $dset->get_values;
158 43 100       104 if( $parms->{log} )
159             {
160 10         18 @values = map { log } @orig;
  70         129  
161              
162 10 100 100     54 $parms->{minval} = 1 if defined $parms->{minval} and !$parms->{minval};
163              
164 10 100       51 $parms->{minval} = log $parms->{minval} if $parms->{minval};
165 10 100       28 $parms->{maxval} = log $parms->{maxval} if $parms->{maxval};
166             }
167             else
168             {
169 33         65 @values = @orig;
170             }
171              
172 43 100 100     162 unless( defined( $parms->{minval} ) and defined( $parms->{maxval} ) )
173             {
174 37         75 my ( $min, $max ) = _minmax( \@values );
175 37 100       107 $parms->{minval} = $min unless defined $parms->{minval};
176 37 100       97 $parms->{maxval} = $max unless defined $parms->{maxval};
177             }
178              
179 43 100       124 $parms->{maxlen} = $parms->{maxval} - $parms->{minval}
180             unless defined $parms->{maxlen};
181 43         123 my $scale = $parms->{maxlen} / ( $parms->{maxval} - $parms->{minval} );
182              
183 301         711 @values =
184 301         668 map { _makebar( ( $_ - $parms->{minval} ) * $scale, $parms->{marker}, $parms->{fill} ) }
185 43         63 map { _make_within( $_, $parms->{minval}, $parms->{maxval} ) } @values;
186              
187 43 100       158 if( $parms->{showval} )
188             {
189 4         11 foreach my $i ( 0 .. $#values )
190             {
191 28         82 $values[$i] .=
192             ( ' ' x ( $parms->{maxlen} - length $values[$i] ) ) . ' (' . $orig[$i] . ')';
193             }
194             }
195              
196 43         331 return @values;
197             }
198              
199             #--------------------------------------------
200             # INTERNAL: This routine finds both the minimum and maximum of
201             # an array of values.
202             sub _minmax
203             {
204 37     37   6135 my $list = shift;
205 37         41 my ( $min, $max );
206              
207 37         56 $min = $max = $list->[0];
208              
209 37         37 foreach ( @{$list} )
  37         74  
210             {
211 259 100       490 if( $_ > $max ) { $max = $_; }
  144 100       153  
212 16         30 elsif( $_ < $min ) { $min = $_; }
213             }
214              
215 37         96 return ( $min, $max );
216             }
217              
218             #--------------------------------------------
219             # INTERNAL: This routine expects a number, a minimum, and a maximum.
220             # It returns a number with the range.
221             sub _make_within
222             {
223 301 100   301   11971 return ( $_[0] < $_[1] ) ? $_[1] : ( $_[0] > $_[2] ? $_[2] : $_[0] );
    100          
224             }
225              
226             #--------------------------------------------
227             # INTERNAL: This routine builds the actual histogram bar.
228             sub _makebar
229             {
230 301     301   445 my ( $val, $m, $f, $s ) = @_;
231              
232 301         342 $val = int( $val + 0.5 );
233              
234 301 100       7219 return $val > 0 ? ( ( $f x ( $val - 1 ) ) . $m ) : '';
235             }
236              
237             1;
238              
239             __END__