File Coverage

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


line stmt bran cond sub pod time code
1             package SVG::Sparkline::Bar;
2              
3 10     10   31743 use warnings;
  10         21  
  10         371  
4 10     10   55 use strict;
  10         19  
  10         383  
5 10     10   52 use Carp;
  10         21  
  10         13090  
6 10     10   1104 use SVG;
  10         20983  
  10         79  
7 10     10   10502 use List::Util ();
  10         48  
  10         250  
8 10     10   3098 use SVG::Sparkline::Utils;
  10         24  
  10         283  
9              
10 10     10   287 use 5.008000;
  10         149  
  10         12902  
11             our $VERSION = 1.10;
12              
13             # alias to make calling shorter.
14             *_f = *SVG::Sparkline::Utils::format_f;
15              
16             sub valid_param {
17 5     5 1 104 return scalar grep { $_[1] eq $_ } qw/gap thick/;
  10         134  
18             }
19              
20             sub make
21             {
22 42     42 1 68 my ($class, $args) = @_;
23             # validate parameters
24 42         184 SVG::Sparkline::Utils::validate_array_param( $args, 'values' );
25 38         142 my $vals = SVG::Sparkline::Utils::summarize_values( $args->{values} );
26              
27 38         97 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     153 my $yscale = -$height / ($vals->{range} || 1);
31 38         171 my $baseline = _f(-$yscale*$vals->{min});
32              
33             # Figure out the width I want and define the viewBox
34 38         60 my $dwidth;
35 38   100     184 my $gap = $args->{gap} || 0;
36 38   100     169 $args->{thick} ||= 3;
37 38         70 my $space = $args->{thick}+$gap;
38 38 100       117 if($args->{width})
39             {
40 1         4 $dwidth = $args->{width} - $args->{padx}*2;
41 1         3 $space = _f( $dwidth / @{$args->{values}} );
  1         6  
42 1         42 $args->{thick} = $space - $gap;
43             }
44             else
45             {
46 37         44 $dwidth = @{$args->{values}} * $space;
  37         152  
47 37         84 $args->{width} = $dwidth + 2*$args->{padx};
48             }
49 38         109 $args->{yoff} = -($baseline+$height+$args->{pady});
50 38         83 $args->{xscale} = $space;
51 38         122 my $svg = SVG::Sparkline::Utils::make_svg( $args );
52              
53 38         145 my $off = _f( $gap/2 );
54 38         66 my $prev = 0;
55 38         196 my @pieces;
56 38         56 foreach my $v (@{$args->{values}})
  38         106  
57             {
58 294         1136 my $curr = _f( $yscale*($v-$prev) );
59 294 100       1082 my $subpath = $curr ? "v${curr}h$args->{thick}" : "h$args->{thick}";
60 294         351 $prev = $v;
61 294 100 100     1027 if($gap && $curr)
62             {
63 16         51 $subpath .= 'v' . _f(-$curr);
64 16         21 $prev = 0;
65             }
66 294         789 push @pieces, $subpath;
67             }
68 38 100       153 push @pieces, 'v' . _f( $yscale*(-$prev) ) if $prev;
69 38 100       101 my $spacer = $gap ? "h$gap" : '';
70 38         165 my $path = "M$off,0" . join( $spacer, @pieces ) . 'z';
71 38         96 $path = _clean_path( $path );
72 38         198 $svg->path( stroke=>'none', fill=>$args->{color}, d=>$path );
73              
74 38 100       2734 if( exists $args->{mark} )
75             {
76 20         68 _make_marks( $svg,
77             thick=>$args->{thick}, off=>$off,
78             space=>$space, yscale=>$yscale,
79             values=>$args->{values}, mark=>$args->{mark}
80             );
81             }
82 38         238 return $svg;
83             }
84              
85             sub _make_marks
86             {
87 20     20   118 my ($svg, %args) = @_;
88            
89 20         25 my @marks = @{$args{mark}};
  20         50  
90 20         45 while(@marks)
91             {
92 20         46 my ($index,$color) = splice( @marks, 0, 2 );
93 20         48 $index = _check_index( $index, $args{values} );
94 20         82 _make_mark( $svg, %args, index=>$index, color=>$color );
95             }
96 20         58 return;
97             }
98              
99             sub _make_mark
100             {
101 20     20   162 my ($svg, %args) = @_;
102 20         32 my $index = $args{index};
103 20         66 my $h = _f($args{values}->[$index] * $args{yscale});
104 20 100       37 if($h)
105             {
106 17         147 my $x = _f($index * $args{space} + $args{off});
107 17 100       44 my $y = $h > 0 ? 0 : $h;
108 17         81 $svg->rect( x=>$x, y=>$y,
109             width=>$args{thick}, height=>abs( $h ),
110             stroke=>'none', fill=>$args{color}
111             );
112             }
113             else
114             {
115 3         16 my $x = _f(($index+0.5) * $args{space} +$args{off});
116 3         33 $svg->ellipse( cx=>$x, cy=>0, ry=>0.5, rx=>$args{thick}/2,
117             stroke=>'none', fill=>$args{color}
118             );
119             }
120 20         1388 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   73 my ($path) = @_;
131 42         348 $path =~ s!((?:h[\d.]+){2,})!_consolidate_moves( $1 )!eg;
  9         25  
132 42         99 $path =~ s/h0(?![.\d])//g;
133 42         111 return $path;
134             }
135              
136             sub _consolidate_moves
137             {
138 9     9   25 my ($moves) = @_;
139 9         55 my @steps = split /h/, $moves;
140 9         18 shift @steps; # discard empty initial string
141 9         110 return 'h' . _f( List::Util::sum( @steps ) );
142             }
143              
144             1;
145              
146             __END__