File Coverage

blib/lib/SVG/Sparkline/Bar.pm
Criterion Covered Total %
statement 91 91 100.0
branch 16 16 100.0
condition 9 9 100.0
subroutine 14 14 100.0
pod 2 2 100.0
total 132 132 100.0


line stmt bran cond sub pod time code
1             package SVG::Sparkline::Bar;
2              
3 10     10   14228 use warnings;
  10         21  
  10         348  
4 10     10   53 use strict;
  10         33  
  10         183  
5 10     10   46 use Carp;
  10         18  
  10         510  
6 10     10   488 use SVG;
  10         11551  
  10         59  
7 10     10   7752 use List::Util ();
  10         39  
  10         170  
8 10     10   1394 use SVG::Sparkline::Utils;
  10         22  
  10         222  
9              
10 10     10   180 use 5.008000;
  10         34  
11             our $VERSION = 1.12;
12              
13             # alias to make calling shorter.
14             *_f = *SVG::Sparkline::Utils::format_f;
15              
16             sub valid_param {
17 5     5 1 13 return scalar grep { $_[1] eq $_ } qw/gap thick/;
  10         45  
18             }
19              
20             sub make
21             {
22 42     42 1 83 my ($class, $args) = @_;
23             # validate parameters
24 42         144 SVG::Sparkline::Utils::validate_array_param( $args, 'values' );
25 38         111 my $vals = SVG::Sparkline::Utils::summarize_values( $args->{values} );
26              
27 38         83 my $height = $args->{height} - 2*$args->{pady};
28             # If we get all zeros for data, the range will be 0, and the division will
29             # fail. Almost anything will be a reasonable range, so arbitrarily choose 1.
30 38   100     154 my $yscale = -$height / ($vals->{range} || 1);
31 38         154 my $baseline = _f(-$yscale*$vals->{min});
32              
33             # Figure out the width I want and define the viewBox
34 38         68 my $dwidth;
35 38   100     192 my $gap = $args->{gap} || 0;
36 38   100     184 $args->{thick} ||= 3;
37 38         69 my $space = $args->{thick}+$gap;
38 38 100       107 if($args->{width})
39             {
40 1         3 $dwidth = $args->{width} - $args->{padx}*2;
41 1         2 $space = _f( $dwidth / @{$args->{values}} );
  1         18  
42 1         4 $args->{thick} = $space - $gap;
43             }
44             else
45             {
46 37         59 $dwidth = @{$args->{values}} * $space;
  37         68  
47 37         82 $args->{width} = $dwidth + 2*$args->{padx};
48             }
49 38         125 $args->{yoff} = -($baseline+$height+$args->{pady});
50 38         72 $args->{xscale} = $space;
51 38         111 my $svg = SVG::Sparkline::Utils::make_svg( $args );
52              
53 38         121 my $off = _f( $gap/2 );
54 38         67 my $prev = 0;
55 38         60 my @pieces;
56 38         62 foreach my $v (@{$args->{values}})
  38         86  
57             {
58 294         695 my $curr = _f( $yscale*($v-$prev) );
59 294 100       747 my $subpath = $curr ? "v${curr}h$args->{thick}" : "h$args->{thick}";
60 294         423 $prev = $v;
61 294 100 100     693 if($gap && $curr)
62             {
63 16         57 $subpath .= 'v' . _f(-$curr);
64 16         32 $prev = 0;
65             }
66 294         566 push @pieces, $subpath;
67             }
68 38 100       140 push @pieces, 'v' . _f( $yscale*(-$prev) ) if $prev;
69 38 100       102 my $spacer = $gap ? "h$gap" : '';
70 38         116 my $path = "M$off,0" . join( $spacer, @pieces ) . 'z';
71 38         94 $path = _clean_path( $path );
72 38         173 $svg->path( stroke=>'none', fill=>$args->{color}, d=>$path );
73              
74 38 100       2529 if( exists $args->{mark} )
75             {
76             _make_marks( $svg,
77             thick=>$args->{thick}, off=>$off,
78             space=>$space, yscale=>$yscale,
79             values=>$args->{values}, mark=>$args->{mark}
80 20         74 );
81             }
82 38         171 return $svg;
83             }
84              
85             sub _make_marks
86             {
87 20     20   83 my ($svg, %args) = @_;
88            
89 20         35 my @marks = @{$args{mark}};
  20         51  
90 20         64 while(@marks)
91             {
92 20         49 my ($index,$color) = splice( @marks, 0, 2 );
93 20         61 $index = _check_index( $index, $args{values} );
94 20         68 _make_mark( $svg, %args, index=>$index, color=>$color );
95             }
96 20         47 return;
97             }
98              
99             sub _make_mark
100             {
101 20     20   120 my ($svg, %args) = @_;
102 20         44 my $index = $args{index};
103 20         62 my $h = _f($args{values}->[$index] * $args{yscale});
104 20 100       50 if($h)
105             {
106 17         60 my $x = _f($index * $args{space} + $args{off});
107 17 100       57 my $y = $h > 0 ? 0 : $h;
108             $svg->rect( x=>$x, y=>$y,
109             width=>$args{thick}, height=>abs( $h ),
110             stroke=>'none', fill=>$args{color}
111 17         84 );
112             }
113             else
114             {
115 3         13 my $x = _f(($index+0.5) * $args{space} +$args{off});
116             $svg->ellipse( cx=>$x, cy=>0, ry=>0.5, rx=>$args{thick}/2,
117             stroke=>'none', fill=>$args{color}
118 3         19 );
119             }
120 20         1516 return;
121             }
122              
123             sub _check_index
124             {
125 20     20   59 return SVG::Sparkline::Utils::mark_to_index( 'Bar', @_ );
126             }
127              
128             sub _clean_path
129             {
130 42     42   100 my ($path) = @_;
131 42         242 $path =~ s!((?:h[\d.]+){2,})!_consolidate_moves( $1 )!eg;
  9         27  
132 42         94 $path =~ s/h0(?![.\d])//g;
133 42         116 return $path;
134             }
135              
136             sub _consolidate_moves
137             {
138 9     9   30 my ($moves) = @_;
139 9         35 my @steps = split /h/, $moves;
140 9         18 shift @steps; # discard empty initial string
141 9         79 return 'h' . _f( List::Util::sum( @steps ) );
142             }
143              
144             1;
145              
146             __END__