| 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__ |