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