File Coverage

blib/lib/SVG/Sparkline/Utils.pm
Criterion Covered Total %
statement 136 138 98.5
branch 58 64 90.6
condition 11 13 84.6
subroutine 16 16 100.0
pod 10 10 100.0
total 231 241 95.8


line stmt bran cond sub pod time code
1             package SVG::Sparkline::Utils;
2              
3 30     30   136 use warnings;
  30         46  
  30         951  
4 30     30   135 use strict;
  30         50  
  30         769  
5 30     30   134 use Carp;
  30         61  
  30         1678  
6 30     30   167 use List::Util;
  30         42  
  30         1844  
7 30     30   145 use SVG;
  30         50  
  30         145  
8              
9             our $VERSION = 1.11;
10              
11             sub format_f
12             {
13 3033     3033 1 9267 my $val = sprintf '%.02f', $_[0];
14 3033         6454 $val =~ s/0$//;
15 3033         4469 $val =~ s/\.0$//;
16 3033 100       4635 $val = 0 if $val eq '-0';
17 3033         6438 return $val;
18             }
19              
20             sub calculate_xscale
21             {
22 76     76 1 96 my ($args, $xrange) = @_;
23              
24 76 100       175 if( $args->{width} )
25             {
26 8         27 my $dwidth = $args->{width} - 2*$args->{padx};
27 8         27 $args->{xscale} = ($dwidth-1) / $xrange;
28             }
29             else
30             {
31 68   100     251 $args->{xscale} ||= 2;
32 68         56 my $dwidth = @{$args->{values}} * $args->{xscale} - 1;
  68         145  
33 68         148 $args->{width} = $dwidth + 2*$args->{padx};
34             }
35 76         142 return;
36             }
37              
38             sub calculate_yscale_and_offset
39             {
40 76     76 1 84 my ($args, $yrange, $offset) = @_;
41              
42             # If the data values are all 0, default the range. Any value would be
43             # usable. So, I just pick 1 arbitrarily.
44 76   100     143 $yrange ||= 1;
45 76         133 my $height = $args->{height} - 2*$args->{pady};
46 76         147 $args->{yscale} = -$height / $yrange;
47 76         191 my $baseline = format_f( -$args->{yscale} * $offset );
48              
49 76         272 $args->{yoff} = -($baseline+$height+$args->{pady});
50              
51 76         129 return;
52             }
53              
54             sub xypairs_to_points_str
55             {
56 76     76 1 110 my ($vals, $xscale, $yscale) = @_;
57 785         1232 return join( ' ',
58 76         121 map { format_f($xscale * $_->[0]) .','. format_f($yscale * $_->[1]) }
59 76         85 @{$vals}
60             );
61             }
62              
63             sub summarize_values
64             {
65 65     65 1 101 my ($array) = @_;
66 65         198 my $desc = {
67 65         182 min => List::Util::min( @{$array} ),
68 65         73 max => List::Util::max( @{$array} ),
69             };
70            
71 65 100       182 $desc->{min} = 0 if $desc->{min} > 0;
72 65 100       161 $desc->{max} = 0 if $desc->{max} < 0;
73              
74 65         105 $desc->{range} = $desc->{max}-$desc->{min};
75 65         121 return $desc;
76             }
77              
78             sub summarize_xy_values
79             {
80 80     80 1 87 my ($array) = @_;
81 80 100       217 return _summarize_xy_pairs( $array ) if 'ARRAY' eq ref $array->[0];
82 74         264 my $desc = {
83 74         151 ymin => List::Util::min( @{$array} ),
84 74         89 ymax => List::Util::max( @{$array} ),
85             xmin => 0,
86 74         225 xmax => $#{$array},
87 74         79 xrange => $#{$array},
88             };
89 74         122 $desc->{base} = 0;
90 74 100       170 $desc->{base} = $desc->{ymin} if $desc->{ymin} > 0;
91 74 100       145 $desc->{base} = $desc->{ymax} if $desc->{ymax} < 0;
92 74         139 $desc->{offset} = $desc->{ymin} - $desc->{base};
93              
94 74         170 $desc->{yrange} = $desc->{ymax}-$desc->{ymin};
95 74         76 my $i = 0;
96 74         67 $desc->{vals} = [map { [$i++,$_-$desc->{base}] } @{$array}];
  705         1043  
  74         110  
97 74         180 return $desc;
98             }
99              
100             sub _summarize_xy_pairs
101             {
102 6     6   57 my ($array) = @_;
103 6         39 my $desc = {
104             xmin => $array->[0]->[0],
105             xmax => $array->[-1]->[0],
106             ymin => $array->[0]->[1],
107             ymax => $array->[0]->[1],
108             };
109              
110 6         9 foreach my $p ( @{$array} )
  6         19  
111             {
112 32         171 die "Array element is not a pair.\n"
113 34 100 100     109 unless 'ARRAY' eq ref $p && 2 == @{$p};
114 30 50       73 $desc->{xmin} = $p->[0] if $p->[0] < $desc->{xmin};
115 30 50       63 $desc->{xmax} = $p->[0] if $p->[0] > $desc->{xmax};
116 30 100       85 $desc->{ymin} = $p->[1] if $p->[1] < $desc->{ymin};
117 30 100       80 $desc->{ymax} = $p->[1] if $p->[1] > $desc->{ymax};
118             }
119 2         8 $desc->{base} = 0;
120 2 50       11 $desc->{base} = $desc->{ymin} if $desc->{ymin} > 0;
121 2 50       11 $desc->{base} = $desc->{ymax} if $desc->{ymax} < 0;
122 2         9 $desc->{offset} = $desc->{ymin} - $desc->{base};
123              
124 2         8 $desc->{xrange} = $desc->{xmax}-$desc->{xmin};
125 2         9 $desc->{yrange} = $desc->{ymax}-$desc->{ymin};
126 22         70 $desc->{vals} =
127 2         4 [map { [$_->[0]-$desc->{xmin},$_->[1]-$desc->{base}] } @{$array}];
  2         6  
128 2         12 return $desc;
129             }
130              
131             sub make_svg
132             {
133 172     172 1 194 my ($args) = @_;
134 172         1256 my $svg = SVG->new(
135             -inline=>1, -nocredits=>1, -raiseerror=>1, -indent=>'', -elsep=>'',
136             width=>$args->{width}, height=>$args->{height},
137 172         569 viewBox=> join( ' ', @{$args}{qw/xoff yoff width height/} )
138             );
139              
140 172 100       36342 if( exists $args->{bgcolor} )
141             {
142 8         51 $svg->rect(
143             x => $args->{xoff}-1, y => $args->{yoff}-1,
144             width => $args->{width}+2, height => $args->{height}+2,
145             stroke => 'none', fill => $args->{bgcolor}
146             );
147             }
148 172         741 return $svg;
149             }
150              
151             sub validate_array_param
152             {
153 165     165 1 257 my ($args, $name) = @_;
154 165         192 local $Carp::CarpLevel = 2;
155 165 100       524 croak "Missing required '$name' parameter.\n" if !exists $args->{$name};
156 160 100       490 croak "'$name' must be an array reference.\n" unless 'ARRAY' eq ref $args->{$name};
157 152 100       136 croak "No values for '$name' specified.\n" unless @{$args->{$name}};
  152         429  
158 147         241 return;
159             }
160              
161             sub range_mark_to_index
162             {
163 14     14 1 15 my ($type, $index, $values) = @_;
164 14 100       28 return 0 if $index eq 'first';
165 11 100       20 return $#{$values} if $index eq 'last';
  2         6  
166 9 100 66     67 return $index if $index !~ /\D/ && $index < @{$values};
  3         17  
167 6 100       19 if( 'high' eq $index )
    50          
168             {
169 4         8 my $high = $values->[0]->[1];
170 4         4 my $ndx = 0;
171 4         5 foreach my $i ( 1 .. $#{$values} )
  4         6  
172             {
173 16 100       36 ($high,$ndx) = ($values->[$i]->[1],$i) if $values->[$i]->[1] > $high;
174             }
175 4         10 return $ndx;
176             }
177             elsif( 'low' eq $index )
178             {
179 2         4 my $low = $values->[0]->[0];
180 2         2 my $ndx = 0;
181 2         3 foreach my $i ( 1 .. $#{$values} )
  2         4  
182             {
183 8 100       20 ($low,$ndx) = ($values->[$i]->[0],$i) if $values->[$i]->[0] < $low;
184             }
185 2         6 return $ndx;
186             }
187              
188 0         0 croak "'$index' is not a valid mark for $type sparkline";
189             }
190              
191             sub mark_to_index
192             {
193 51     51 1 58 my ($type, $index, $values) = @_;
194 51 100       106 return 0 if $index eq 'first';
195 42 100       71 return $#{$values} if $index eq 'last';
  9         25  
196 33 100 66     125 return $index if $index !~ /\D/ && $index < @{$values};
  15         69  
197 18 100       50 if( 'high' eq $index )
    50          
198             {
199 9         15 my $high = $values->[0];
200 9         14 my $ndx = 0;
201 9         14 foreach my $i ( 1 .. $#{$values} )
  9         21  
202             {
203 54 100       108 ($high,$ndx) = ($values->[$i],$i) if $values->[$i] > $high;
204             }
205 9         30 return $ndx;
206             }
207             elsif( 'low' eq $index )
208             {
209 9         14 my $low = $values->[0];
210 9         9 my $ndx = 0;
211 9         13 foreach my $i ( 1 .. $#{$values} )
  9         20  
212             {
213 54 100       103 ($low,$ndx) = ($values->[$i],$i) if $values->[$i] < $low;
214             }
215 9         26 return $ndx;
216             }
217              
218 0           croak "'$index' is not a valid mark for $type sparkline";
219             }
220              
221              
222             1;
223              
224             __END__