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