File Coverage

blib/lib/SVG/Sparkline/RangeBar.pm
Criterion Covered Total %
statement 103 103 100.0
branch 20 20 100.0
condition 4 4 100.0
subroutine 13 13 100.0
pod 2 2 100.0
total 142 142 100.0


line stmt bran cond sub pod time code
1             package SVG::Sparkline::RangeBar;
2              
3 3     3   14 use warnings;
  3         3  
  3         108  
4 3     3   13 use strict;
  3         4  
  3         119  
5 3     3   13 use Carp;
  3         3  
  3         186  
6 3     3   12 use SVG;
  3         4  
  3         23  
7 3     3   1523 use List::Util ();
  3         6  
  3         96  
8 3     3   1250 use SVG::Sparkline::Utils;
  3         5  
  3         76  
9              
10 3     3   60 use 5.008000;
  3         9  
  3         2758  
11             our $VERSION = 1.11;
12              
13             # alias to make calling shorter.
14             *_f = *SVG::Sparkline::Utils::format_f;
15              
16             sub valid_param {
17 10     10 1 15 return scalar grep { $_[1] eq $_ } qw/gap thick/;
  20         64  
18             }
19              
20             sub make
21             {
22 32     32 1 34 my ($class, $args) = @_;
23             # validate parameters
24 32         79 SVG::Sparkline::Utils::validate_array_param( $args, 'values' );
25 136         321 croak "'values' must be an array of pairs.\n"
26 28 100       26 if grep { 'ARRAY' ne ref $_ || 2 != @{$_} } @{$args->{values}};
  140 100       225  
  28         54  
27 136         216 my $vals = SVG::Sparkline::Utils::summarize_values(
28 27         49 [ map { @{$_} } @{$args->{values}} ]
  136         86  
  27         33  
29             );
30              
31 27         56 my $height = $args->{height} - 2*$args->{pady};
32 27         43 my $yscale = -$height / $vals->{range};
33 27         66 my $baseline = _f(-$yscale*$vals->{min});
34              
35             # Figure out the width I want and define the viewBox
36 27         25 my $dwidth;
37 27   100     79 my $gap = $args->{gap} || 0;
38 27   100     70 $args->{thick} ||= 3;
39 27         30 my $space = $args->{thick}+$gap;
40 27 100       47 if($args->{width})
41             {
42 1         2 $dwidth = $args->{width} - $args->{padx}*2;
43 1         1 $space = _f( $dwidth / @{$args->{values}} );
  1         4  
44 1         2 $args->{thick} = $space - $gap;
45             }
46             else
47             {
48 26         22 $dwidth = @{$args->{values}} * $space;
  26         54  
49 26         42 $args->{width} = $dwidth + 2*$args->{padx};
50             }
51 27         51 $args->{yoff} = -($baseline+$height+$args->{pady});
52 27         37 $args->{xscale} = $space;
53 27         55 my $svg = SVG::Sparkline::Utils::make_svg( $args );
54              
55 27         70 my $off = _f( $gap/2 );
56 27         31 my $prev = 0;
57 27         89 my $path = "M". _f(-$args->{thick}-$off).",0";
58 27         29 foreach my $v (@{$args->{values}})
  27         50  
59             {
60             # Move from previous x,y to low value
61 136         253 $path .= 'm'. _f($args->{thick}+$gap) .','. _f($yscale*($v->[0]-$prev));
62 136         295 my $vert = _f( $yscale * ($v->[1]-$v->[0]) );
63 136 100       173 if($vert)
64             {
65 130         329 $path .= "v${vert}h$args->{thick}v". _f(-$vert)."h-$args->{thick}";
66             }
67             else
68             {
69 6         16 $path .= _zero_height_path( $args->{thick} );
70             }
71 136         185 $prev = $v->[0];
72             }
73 27         50 $path = _clean_path( $path );
74 27         88 $svg->path( stroke=>'none', fill=>$args->{color}, d=>$path );
75              
76 27 100       1228 if( exists $args->{mark} )
77             {
78 9         23 _make_marks( $svg,
79             thick=>$args->{thick}, off=>$off,
80             space=>$space, yscale=>$yscale,
81             values=>$args->{values}, mark=>$args->{mark}
82             );
83             }
84 27         118 return $svg;
85             }
86              
87             sub _zero_height_path
88             {
89 7     7   11 my ($thick) = @_;
90 7         14 my $path = 'v-0.5';
91 7         7 my $step = 1;
92 7 100       17 $step = $thick/4 if $thick <= 2;
93 7 100       22 $step = 2 if $thick >= 8;
94 7         14 my $num_steps = int( $thick/$step ) - 1;
95 7         11 my $leftover = $thick-($num_steps*$step);
96 7         13 foreach my $i (1 .. $num_steps)
97             {
98 19 100       53 $path .= "h${step}v" . ($i%2? 1 :-1);
99             }
100 7 100       49 $path .= "h${leftover}v". ($thick%2?0.5: -0.5) . "h-$thick";
101 7         21 return $path;
102             }
103              
104             sub _make_marks
105             {
106 9     9   28 my ($svg, %args) = @_;
107            
108 9         9 my @marks = @{$args{mark}};
  9         18  
109 9         16 while(@marks)
110             {
111 9         15 my ($index,$color) = splice( @marks, 0, 2 );
112 9         21 $index = SVG::Sparkline::Utils::range_mark_to_index( 'RangeBar', $index, $args{values} );
113 9         23 _make_mark( $svg, %args, index=>$index, color=>$color );
114             }
115 9         18 return;
116             }
117              
118             sub _make_mark
119             {
120 9     9   28 my ($svg, %args) = @_;
121 9         10 my $index = $args{index};
122 9         9 my ($lo, $hi) = @{$args{values}->[$index]};
  9         13  
123 9         24 my $y = _f( $hi * $args{yscale} );
124 9         21 my $h = _f( ($hi-$lo) * $args{yscale});
125 9 100       14 if($h)
126             {
127 8         26 my $x = _f($index * $args{space} + $args{off});
128 8         33 $svg->rect( x=>$x, y=>$y,
129             width=>$args{thick}, height=>abs($h),
130             stroke=>'none', fill=>$args{color}
131             );
132             }
133             else
134             {
135 1         7 my $x = _f($index * $args{space} +$args{off});
136 1         7 $svg->path(
137             d=>"M$x,$y". _zero_height_path( $args{thick} ),
138             stroke=>'none', fill=>$args{color}
139             );
140             }
141 9         479 return;
142             }
143              
144             sub _clean_path
145             {
146 27     27   29 my ($path) = @_;
147 27         134 $path =~ s/^M([-.\d]+),([-.\d]+)m([-.\d]+),([-.\d]+)/'M'. _f($1+$3) .','. _f($2+$4)/e;
  27         99  
148 27         65 $path =~ s/h0(?![.\d])//g;
149 27         46 return $path;
150             }
151              
152             1;
153              
154             __END__