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