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   15733 use warnings;
  10         15  
  10         309  
4 10     10   40 use strict;
  10         11  
  10         281  
5 10     10   35 use Carp;
  10         11  
  10         600  
6 10     10   737 use SVG;
  10         12152  
  10         52  
7 10     10   4911 use List::Util ();
  10         16  
  10         146  
8 10     10   1584 use SVG::Sparkline::Utils;
  10         18  
  10         200  
9              
10 10     10   195 use 5.008000;
  10         26  
  10         8217  
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 5     5 1 8 return scalar grep { $_[1] eq $_ } qw/gap thick/;
  10         34  
18             }
19              
20             sub make
21             {
22 42     42 1 46 my ($class, $args) = @_;
23             # validate parameters
24 42         92 SVG::Sparkline::Utils::validate_array_param( $args, 'values' );
25 38         93 my $vals = SVG::Sparkline::Utils::summarize_values( $args->{values} );
26              
27 38         65 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     95 my $yscale = -$height / ($vals->{range} || 1);
31 38         100 my $baseline = _f(-$yscale*$vals->{min});
32              
33             # Figure out the width I want and define the viewBox
34 38         31 my $dwidth;
35 38   100     143 my $gap = $args->{gap} || 0;
36 38   100     117 $args->{thick} ||= 3;
37 38         46 my $space = $args->{thick}+$gap;
38 38 100       79 if($args->{width})
39             {
40 1         2 $dwidth = $args->{width} - $args->{padx}*2;
41 1         2 $space = _f( $dwidth / @{$args->{values}} );
  1         4  
42 1         36 $args->{thick} = $space - $gap;
43             }
44             else
45             {
46 37         34 $dwidth = @{$args->{values}} * $space;
  37         54  
47 37         57 $args->{width} = $dwidth + 2*$args->{padx};
48             }
49 38         79 $args->{yoff} = -($baseline+$height+$args->{pady});
50 38         44 $args->{xscale} = $space;
51 38         83 my $svg = SVG::Sparkline::Utils::make_svg( $args );
52              
53 38         95 my $off = _f( $gap/2 );
54 38         47 my $prev = 0;
55 38         38 my @pieces;
56 38         41 foreach my $v (@{$args->{values}})
  38         70  
57             {
58 294         517 my $curr = _f( $yscale*($v-$prev) );
59 294 100       681 my $subpath = $curr ? "v${curr}h$args->{thick}" : "h$args->{thick}";
60 294         213 $prev = $v;
61 294 100 100     445 if($gap && $curr)
62             {
63 16         38 $subpath .= 'v' . _f(-$curr);
64 16         17 $prev = 0;
65             }
66 294         416 push @pieces, $subpath;
67             }
68 38 100       108 push @pieces, 'v' . _f( $yscale*(-$prev) ) if $prev;
69 38 100       66 my $spacer = $gap ? "h$gap" : '';
70 38         101 my $path = "M$off,0" . join( $spacer, @pieces ) . 'z';
71 38         68 $path = _clean_path( $path );
72 38         137 $svg->path( stroke=>'none', fill=>$args->{color}, d=>$path );
73              
74 38 100       1689 if( exists $args->{mark} )
75             {
76 20         52 _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         153 return $svg;
83             }
84              
85             sub _make_marks
86             {
87 20     20   78 my ($svg, %args) = @_;
88            
89 20         18 my @marks = @{$args{mark}};
  20         37  
90 20         35 while(@marks)
91             {
92 20         30 my ($index,$color) = splice( @marks, 0, 2 );
93 20         31 $index = _check_index( $index, $args{values} );
94 20         47 _make_mark( $svg, %args, index=>$index, color=>$color );
95             }
96 20         40 return;
97             }
98              
99             sub _make_mark
100             {
101 20     20   59 my ($svg, %args) = @_;
102 20         21 my $index = $args{index};
103 20         50 my $h = _f($args{values}->[$index] * $args{yscale});
104 20 100       29 if($h)
105             {
106 17         52 my $x = _f($index * $args{space} + $args{off});
107 17 100       37 my $y = $h > 0 ? 0 : $h;
108 17         59 $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         14 my $x = _f(($index+0.5) * $args{space} +$args{off});
116 3         16 $svg->ellipse( cx=>$x, cy=>0, ry=>0.5, rx=>$args{thick}/2,
117             stroke=>'none', fill=>$args{color}
118             );
119             }
120 20         1093 return;
121             }
122              
123             sub _check_index
124             {
125 20     20   43 return SVG::Sparkline::Utils::mark_to_index( 'Bar', @_ );
126             }
127              
128             sub _clean_path
129             {
130 42     42   54 my ($path) = @_;
131 42         222 $path =~ s!((?:h[\d.]+){2,})!_consolidate_moves( $1 )!eg;
  9         16  
132 42         58 $path =~ s/h0(?![.\d])//g;
133 42         72 return $path;
134             }
135              
136             sub _consolidate_moves
137             {
138 9     9   22 my ($moves) = @_;
139 9         33 my @steps = split /h/, $moves;
140 9         11 shift @steps; # discard empty initial string
141 9         71 return 'h' . _f( List::Util::sum( @steps ) );
142             }
143              
144             1;
145              
146             __END__