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   175 use warnings;
  30         63  
  30         796  
4 30     30   139 use strict;
  30         58  
  30         456  
5 30     30   134 use Carp;
  30         62  
  30         1278  
6 30     30   165 use List::Util;
  30         61  
  30         1573  
7 30     30   179 use SVG;
  30         64  
  30         155  
8              
9             our $VERSION = 1.12;
10              
11             sub format_f
12             {
13 3033     3033 1 9934 my $val = sprintf '%.02f', $_[0];
14 3033         8151 $val =~ s/0$//;
15 3033         6696 $val =~ s/\.0$//;
16 3033 100       6814 $val = 0 if $val eq '-0';
17 3033         7946 return $val;
18             }
19              
20             sub calculate_xscale
21             {
22 76     76 1 145 my ($args, $xrange) = @_;
23              
24 76 100       189 if( $args->{width} )
25             {
26 8         16 my $dwidth = $args->{width} - 2*$args->{padx};
27 8         24 $args->{xscale} = ($dwidth-1) / $xrange;
28             }
29             else
30             {
31 68   100     321 $args->{xscale} ||= 2;
32 68         107 my $dwidth = @{$args->{values}} * $args->{xscale} - 1;
  68         147  
33 68         167 $args->{width} = $dwidth + 2*$args->{padx};
34             }
35 76         170 return;
36             }
37              
38             sub calculate_yscale_and_offset
39             {
40 76     76 1 156 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     190 $yrange ||= 1;
45 76         146 my $height = $args->{height} - 2*$args->{pady};
46 76         198 $args->{yscale} = -$height / $yrange;
47 76         218 my $baseline = format_f( -$args->{yscale} * $offset );
48              
49 76         251 $args->{yoff} = -($baseline+$height+$args->{pady});
50              
51 76         174 return;
52             }
53              
54             sub xypairs_to_points_str
55             {
56 76     76 1 159 my ($vals, $xscale, $yscale) = @_;
57             return join( ' ',
58 785         1556 map { format_f($xscale * $_->[0]) .','. format_f($yscale * $_->[1]) }
59 76         115 @{$vals}
  76         146  
60             );
61             }
62              
63             sub summarize_values
64             {
65 65     65 1 126 my ($array) = @_;
66             my $desc = {
67 65         227 min => List::Util::min( @{$array} ),
68 65         105 max => List::Util::max( @{$array} ),
  65         242  
69             };
70            
71 65 100       187 $desc->{min} = 0 if $desc->{min} > 0;
72 65 100       163 $desc->{max} = 0 if $desc->{max} < 0;
73              
74 65         139 $desc->{range} = $desc->{max}-$desc->{min};
75 65         150 return $desc;
76             }
77              
78             sub summarize_xy_values
79             {
80 80     80 1 150 my ($array) = @_;
81 80 100       230 return _summarize_xy_pairs( $array ) if 'ARRAY' eq ref $array->[0];
82             my $desc = {
83 74         256 ymin => List::Util::min( @{$array} ),
84 74         211 ymax => List::Util::max( @{$array} ),
85             xmin => 0,
86 74         147 xmax => $#{$array},
87 74         120 xrange => $#{$array},
  74         228  
88             };
89 74         166 $desc->{base} = 0;
90 74 100       183 $desc->{base} = $desc->{ymin} if $desc->{ymin} > 0;
91 74 100       172 $desc->{base} = $desc->{ymax} if $desc->{ymax} < 0;
92 74         152 $desc->{offset} = $desc->{ymin} - $desc->{base};
93              
94 74         199 $desc->{yrange} = $desc->{ymax}-$desc->{ymin};
95 74         115 my $i = 0;
96 74         132 $desc->{vals} = [map { [$i++,$_-$desc->{base}] } @{$array}];
  705         1389  
  74         140  
97 74         203 return $desc;
98             }
99              
100             sub _summarize_xy_pairs
101             {
102 6     6   10 my ($array) = @_;
103 6         20 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         11 foreach my $p ( @{$array} )
  6         11  
111             {
112             die "Array element is not a pair.\n"
113 34 100 100     108 unless 'ARRAY' eq ref $p && 2 == @{$p};
  32         106  
114 30 50       65 $desc->{xmin} = $p->[0] if $p->[0] < $desc->{xmin};
115 30 50       65 $desc->{xmax} = $p->[0] if $p->[0] > $desc->{xmax};
116 30 100       64 $desc->{ymin} = $p->[1] if $p->[1] < $desc->{ymin};
117 30 100       70 $desc->{ymax} = $p->[1] if $p->[1] > $desc->{ymax};
118             }
119 2         5 $desc->{base} = 0;
120 2 50       7 $desc->{base} = $desc->{ymin} if $desc->{ymin} > 0;
121 2 50       5 $desc->{base} = $desc->{ymax} if $desc->{ymax} < 0;
122 2         5 $desc->{offset} = $desc->{ymin} - $desc->{base};
123              
124 2         5 $desc->{xrange} = $desc->{xmax}-$desc->{xmin};
125 2         4 $desc->{yrange} = $desc->{ymax}-$desc->{ymin};
126             $desc->{vals} =
127 2         5 [map { [$_->[0]-$desc->{xmin},$_->[1]-$desc->{base}] } @{$array}];
  22         59  
  2         5  
128 2         7 return $desc;
129             }
130              
131             sub make_svg
132             {
133 172     172 1 332 my ($args) = @_;
134             my $svg = SVG->new(
135             -inline=>1, -nocredits=>1, -raiseerror=>1, -indent=>'', -elsep=>'',
136             width=>$args->{width}, height=>$args->{height},
137 172         577 viewBox=> join( ' ', @{$args}{qw/xoff yoff width height/} )
  172         1263  
138             );
139              
140 172 100       46298 if( exists $args->{bgcolor} )
141             {
142             $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 8         45 );
147             }
148 172         1045 return $svg;
149             }
150              
151             sub validate_array_param
152             {
153 165     165 1 319 my ($args, $name) = @_;
154 165         278 local $Carp::CarpLevel = 2;
155 165 100       502 croak "Missing required '$name' parameter.\n" if !exists $args->{$name};
156 160 100       536 croak "'$name' must be an array reference.\n" unless 'ARRAY' eq ref $args->{$name};
157 152 100       253 croak "No values for '$name' specified.\n" unless @{$args->{$name}};
  152         519  
158 147         342 return;
159             }
160              
161             sub range_mark_to_index
162             {
163 14     14 1 30 my ($type, $index, $values) = @_;
164 14 100       40 return 0 if $index eq 'first';
165 11 100       27 return $#{$values} if $index eq 'last';
  2         7  
166 9 100 66     37 return $index if $index !~ /\D/ && $index < @{$values};
  3         20  
167 6 100       24 if( 'high' eq $index )
    50          
168             {
169 4         10 my $high = $values->[0]->[1];
170 4         7 my $ndx = 0;
171 4         9 foreach my $i ( 1 .. $#{$values} )
  4         12  
172             {
173 16 100       44 ($high,$ndx) = ($values->[$i]->[1],$i) if $values->[$i]->[1] > $high;
174             }
175 4         13 return $ndx;
176             }
177             elsif( 'low' eq $index )
178             {
179 2         5 my $low = $values->[0]->[0];
180 2         5 my $ndx = 0;
181 2         4 foreach my $i ( 1 .. $#{$values} )
  2         6  
182             {
183 8 100       28 ($low,$ndx) = ($values->[$i]->[0],$i) if $values->[$i]->[0] < $low;
184             }
185 2         8 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 106 my ($type, $index, $values) = @_;
194 51 100       169 return 0 if $index eq 'first';
195 42 100       106 return $#{$values} if $index eq 'last';
  9         29  
196 33 100 66     139 return $index if $index !~ /\D/ && $index < @{$values};
  15         91  
197 18 100       66 if( 'high' eq $index )
    50          
198             {
199 9         17 my $high = $values->[0];
200 9         17 my $ndx = 0;
201 9         14 foreach my $i ( 1 .. $#{$values} )
  9         27  
202             {
203 54 100       130 ($high,$ndx) = ($values->[$i],$i) if $values->[$i] > $high;
204             }
205 9         29 return $ndx;
206             }
207             elsif( 'low' eq $index )
208             {
209 9         19 my $low = $values->[0];
210 9         16 my $ndx = 0;
211 9         16 foreach my $i ( 1 .. $#{$values} )
  9         25  
212             {
213 54 100       126 ($low,$ndx) = ($values->[$i],$i) if $values->[$i] < $low;
214             }
215 9         28 return $ndx;
216             }
217              
218 0           croak "'$index' is not a valid mark for $type sparkline";
219             }
220              
221              
222             1;
223              
224             __END__