File Coverage

blib/lib/SVG/Sparkline/Whisker.pm
Criterion Covered Total %
statement 103 103 100.0
branch 32 34 94.1
condition 5 8 62.5
subroutine 14 14 100.0
pod 2 2 100.0
total 156 161 96.8


line stmt bran cond sub pod time code
1             package SVG::Sparkline::Whisker;
2              
3 13     13   35973 use warnings;
  13         27  
  13         489  
4 13     13   79 use strict;
  13         26  
  13         444  
5 13     13   71 use Carp;
  13         27  
  13         1166  
6 13     13   1413 use SVG;
  13         25174  
  13         118  
7 13     13   19469 use SVG::Sparkline::Utils;
  13         60  
  13         484  
8              
9 13     13   596 use 5.008000;
  13         58  
  13         21061  
10             our $VERSION = 1.10;
11              
12             # alias to make calling shorter.
13             *_f = *SVG::Sparkline::Utils::format_f;
14              
15             sub valid_param {
16 5     5 1 12 return scalar grep { $_[1] eq $_ } qw/gap thick/;
  10         55  
17             }
18              
19             sub make
20             {
21 36     36 1 110 my ($class, $args) = @_;
22             # validate parameters
23 36         47 my @values;
24 36 100       225 croak "Missing required 'values'\n" unless exists $args->{values};
25 35 100       147 if( 'ARRAY' eq ref $args->{values} )
    100          
26             {
27 11         17 @values = @{$args->{values}};
  11         42  
28             }
29             elsif( !ref $args->{values} )
30             {
31 23         36 my $valstr = $args->{values};
32             # Convert 1/0 string to a +/- string.
33 23 100       70 $valstr =~ tr/10/+-/ if $valstr =~ /1/;
34              
35 23         113 @values = split //, $valstr;
36             }
37             else
38             {
39 1         15 croak "Unrecognized type of 'values' data.\n";
40             }
41 34         69 @values = map { _val( $_ ) } @values;
  213         337  
42 33 100       115 croak "No values specified for 'values'.\n" unless @values;
43              
44             # Figure out the width I want and define the viewBox
45 31   100     176 my $thick = $args->{thick} || 1;
46 31   66     177 my $gap = $args->{gap} || 2 * $thick;
47 31         53 my $space = $thick + $gap;
48 31         35 my $dwidth;
49 31 100       75 if($args->{width})
50             {
51 4         8 $dwidth = $args->{width} - 2*$args->{padx};
52 4         16 $thick = _f( $dwidth / (3*@values) );
53 4         18 $gap = _f( 2* $thick );
54 4         7 $space = 3*$thick;
55             }
56             else
57             {
58 27         48 $dwidth = @values * $space;
59 27         66 $args->{width} = $dwidth + 2*$args->{padx};
60             }
61 31 50       96 ++$space if $space =~s/\.9\d$//;
62 31         85 my $height = $args->{height} - 2*$args->{pady};
63 31         67 my $wheight = $args->{height}/2;
64 31         61 $args->{yoff} = -$wheight;
65 31         52 $wheight -= $args->{pady};
66 31         101 my $svg = SVG::Sparkline::Utils::make_svg( $args );
67              
68 31         156 my $off = _f( $gap/2 );
69 31         73 my $path = "M$off,0";
70 31         123 foreach my $v (@values[0..$#values-1])
71             {
72 179 100       305 if( $v )
73             {
74 122         183 my ($u,$d) = ( -$v*$wheight, $v*$wheight );
75 122         290 $path .= "v${u}m$space,${d}";
76             }
77             else
78             {
79 57         104 $path .= "m$space,0";
80             }
81             }
82 31         102 $path .= 'v' . (-$values[-1]*$wheight);
83 31         87 $path = _clean_path( $path );
84 31         281 $svg->path( 'stroke-width'=>$thick, stroke=>$args->{color}, d=>$path );
85              
86 31 100       2013 if( exists $args->{mark} )
87             {
88 9         38 _make_marks( $svg,
89             thick=>$thick, off=>$off, space=>$space, wheight=>-$wheight,
90             values=>\@values, mark=>$args->{mark}
91             );
92             }
93 29         181 return $svg;
94             }
95              
96             sub _make_marks
97             {
98 9     9   37 my ($svg, %args) = @_;
99            
100 9         37 my @marks = @{$args{mark}};
  9         24  
101 9         24 while(@marks)
102             {
103 11         22 my ($index,$color) = splice( @marks, 0, 2 );
104 11         22 $index = _check_index( $index, $args{values} );
105 9         29 _make_mark( $svg, %args, index=>$index, color=>$color );
106             }
107 7         20 return;
108             }
109              
110             sub _make_mark
111             {
112 9     9   72 my ($svg, %args) = @_;
113 9         16 my $index = $args{index};
114 9 100       25 return unless $args{values}->[$index];
115 8         23 my $x = $index * $args{space}+$args{off};
116 8         48 $svg->line( x1=>$x, x2=>$x, y1=>0, y2=>$args{wheight} * $args{values}->[$index],
117             'stroke-width'=>$args{thick}, stroke=>$args{color}
118             );
119 8         518 return;
120             }
121              
122             sub _check_index
123             {
124 11     11   17 my ($index, $values) = @_;
125 11 100       44 return 0 if $index eq 'first';
126 10 100       20 return $#{$values} if $index eq 'last';
  1         3  
127 9 100       36 return $index unless $index =~ /\D/;
128              
129 2         52 die "'$index' is not a valid mark for Whisker sparkline";
130             }
131              
132             sub _val
133             {
134 213     213   237 my $val = shift;
135              
136 213 100       657 return $val <=> 0 if $val =~ /\d/;
137 114 100       364 return $val eq '+' ? 1 : ( $val eq '-' ? -1 : die "Unrecognized character '$val'\n" );
    100          
138             }
139              
140             sub _clean_path
141             {
142 37     37   67 my ($path) = @_;
143 37         530 $path =~ s/((?:m[-.\d]+,[-.\d+]+){2,})/_consolidate_moves( $1 )/eg;
  47         106  
144             # Consolidate initial M with m
145 37         154 $path =~ s/^M([-.\d]+),([-.\d]+)m([-.\d]+),([-.\d]+)/'M'. _f($1+$3) .','. _f($2+$4)/e;
  8         35  
146 37         158 $path =~ s/m[-.\d]+,[-.\d]+$//; # remove trailing move.
147 37         68 $path =~ s/m0,0(?![.\d])//;
148 37         109 return $path;
149             }
150              
151             sub _consolidate_moves
152             {
153 47     47   115 my ($moves) = @_;
154 47         309 my @coords = split /[m,]/, $moves;
155 47         72 shift @coords; # dump empty initial string.
156 47         63 my ($x,$y);
157 47         109 while(@coords)
158             {
159 100         190 my ($lx, $ly) = splice @coords, 0, 2;
160 100         154 $x += $lx;
161 100         235 $y += $ly;
162             }
163              
164 47 50 33     224 return ($x||$y) ? 'm' . _f($x).',' . _f($y) : '';
165             }
166              
167             1;
168              
169             __END__