File: | blib/lib/SVG/Sparkline/Bar.pm |
Coverage: | 100.0% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package SVG::Sparkline::Bar; | |||||
2 | ||||||
3 | 10 10 10 | 10010 13 258 | use warnings; | |||
4 | 10 10 10 | 27 9 155 | use strict; | |||
5 | 10 10 10 | 23 8 450 | use Carp; | |||
6 | 10 10 10 | 165 8327 148 | use SVG; | |||
7 | 10 10 10 | 11891 8 119 | use List::Util (); | |||
8 | 10 10 10 | 1163 14 192 | use SVG::Sparkline::Utils; | |||
9 | ||||||
10 | 10 10 10 | 295 24 7460 | use 5.008000; | |||
11 | our $VERSION = 0.35; | |||||
12 | ||||||
13 | # alias to make calling shorter. | |||||
14 | *_f = *SVG::Sparkline::Utils::format_f; | |||||
15 | ||||||
16 | sub valid_param { | |||||
17 | 5 10 | 5 72 | return scalar grep { $_[1] eq $_ } qw/gap thick/; | |||
18 | } | |||||
19 | ||||||
20 | sub make | |||||
21 | { | |||||
22 | 41 | 45 | my ($class, $args) = @_; | |||
23 | # validate parameters | |||||
24 | 41 | 526 | SVG::Sparkline::Utils::validate_array_param( $args, 'values' ); | |||
25 | 37 | 433 | my $vals = SVG::Sparkline::Utils::summarize_values( $args->{values} ); | |||
26 | ||||||
27 | 37 | 52 | my $height = $args->{height} - 2*$args->{pady}; | |||
28 | 37 | 46 | my $yscale = -$height / $vals->{range}; | |||
29 | 37 | 467 | my $baseline = _f(-$yscale*$vals->{min}); | |||
30 | ||||||
31 | # Figure out the width I want and define the viewBox | |||||
32 | 37 | 25 | my $dwidth; | |||
33 | 37 | 137 | my $gap = $args->{gap} || 0; | |||
34 | 37 | 54 | $args->{thick} ||= 3; | |||
35 | 37 | 38 | my $space = $args->{thick}+$gap; | |||
36 | 37 | 49 | if($args->{width}) | |||
37 | { | |||||
38 | 1 | 1 | $dwidth = $args->{width} - $args->{padx}*2; | |||
39 | 1 1 | 0 14 | $space = _f( $dwidth / @{$args->{values}} ); | |||
40 | 1 | 2 | $args->{thick} = $space - $gap; | |||
41 | } | |||||
42 | else | |||||
43 | { | |||||
44 | 36 36 | 20 44 | $dwidth = @{$args->{values}} * $space; | |||
45 | 36 | 39 | $args->{width} = $dwidth + 2*$args->{padx}; | |||
46 | } | |||||
47 | 37 | 55 | $args->{yoff} = -($baseline+$height+$args->{pady}); | |||
48 | 37 | 31 | $args->{xscale} = $space; | |||
49 | 37 | 458 | my $svg = SVG::Sparkline::Utils::make_svg( $args ); | |||
50 | ||||||
51 | 37 | 425 | my $off = _f( $gap/2 ); | |||
52 | 37 | 33 | my $prev = 0; | |||
53 | 37 | 23 | my @pieces; | |||
54 | 37 37 | 23 46 | foreach my $v (@{$args->{values}}) | |||
55 | { | |||||
56 | 287 | 3237 | my $curr = _f( $yscale*($v-$prev) ); | |||
57 | 287 | 383 | my $subpath = $curr ? "v${curr}h$args->{thick}" : "h$args->{thick}"; | |||
58 | 287 | 133 | $prev = $v; | |||
59 | 287 | 373 | if($gap && $curr) | |||
60 | { | |||||
61 | 16 | 196 | $subpath .= 'v' . _f(-$curr); | |||
62 | 16 | 12 | $prev = 0; | |||
63 | } | |||||
64 | 287 | 252 | push @pieces, $subpath; | |||
65 | } | |||||
66 | 37 | 307 | push @pieces, 'v' . _f( $yscale*(-$prev) ) if $prev; | |||
67 | 37 | 44 | my $spacer = $gap ? "h$gap" : ''; | |||
68 | 37 | 70 | my $path = "M$off,0" . join( $spacer, @pieces ) . 'z'; | |||
69 | 37 | 40 | $path = _clean_path( $path ); | |||
70 | 37 | 343 | $svg->path( stroke=>'none', fill=>$args->{color}, d=>$path ); | |||
71 | ||||||
72 | 37 | 1572 | if( exists $args->{mark} ) | |||
73 | { | |||||
74 | 20 | 33 | _make_marks( $svg, | |||
75 | thick=>$args->{thick}, off=>$off, | |||||
76 | space=>$space, yscale=>$yscale, | |||||
77 | values=>$args->{values}, mark=>$args->{mark} | |||||
78 | ); | |||||
79 | } | |||||
80 | 37 | 456 | return $svg; | |||
81 | } | |||||
82 | ||||||
83 | sub _make_marks | |||||
84 | { | |||||
85 | 20 | 46 | my ($svg, %args) = @_; | |||
86 | ||||||
87 | 20 20 | 12 25 | my @marks = @{$args{mark}}; | |||
88 | 20 | 26 | while(@marks) | |||
89 | { | |||||
90 | 20 | 20 | my ($index,$color) = splice( @marks, 0, 2 ); | |||
91 | 20 | 20 | $index = _check_index( $index, $args{values} ); | |||
92 | 20 | 40 | _make_mark( $svg, %args, index=>$index, color=>$color ); | |||
93 | } | |||||
94 | 20 | 29 | return; | |||
95 | } | |||||
96 | ||||||
97 | sub _make_mark | |||||
98 | { | |||||
99 | 20 | 43 | my ($svg, %args) = @_; | |||
100 | 20 | 15 | my $index = $args{index}; | |||
101 | 20 | 242 | my $h = _f($args{values}->[$index] * $args{yscale}); | |||
102 | 20 | 21 | if($h) | |||
103 | { | |||||
104 | 17 | 202 | my $x = _f($index * $args{space} + $args{off}); | |||
105 | 17 | 22 | my $y = $h > 0 ? 0 : $h; | |||
106 | 17 | 144 | $svg->rect( x=>$x, y=>$y, | |||
107 | width=>$args{thick}, height=>abs( $h ), | |||||
108 | stroke=>'none', fill=>$args{color} | |||||
109 | ); | |||||
110 | } | |||||
111 | else | |||||
112 | { | |||||
113 | 3 | 38 | my $x = _f(($index+0.5) * $args{space} +$args{off}); | |||
114 | 3 | 28 | $svg->ellipse( cx=>$x, cy=>0, ry=>0.5, rx=>$args{thick}/2, | |||
115 | stroke=>'none', fill=>$args{color} | |||||
116 | ); | |||||
117 | } | |||||
118 | 20 | 921 | return; | |||
119 | } | |||||
120 | ||||||
121 | sub _check_index | |||||
122 | { | |||||
123 | 20 | 248 | return SVG::Sparkline::Utils::mark_to_index( 'Bar', @_ ); | |||
124 | } | |||||
125 | ||||||
126 | sub _clean_path | |||||
127 | { | |||||
128 | 41 | 67293 | my ($path) = @_; | |||
129 | 41 8 | 166 10 | $path =~ s!((?:h[\d.]+){2,})!_consolidate_moves( $1 )!eg; | |||
130 | 41 | 43 | $path =~ s/h0(?![.\d])//g; | |||
131 | 41 | 75 | return $path; | |||
132 | } | |||||
133 | ||||||
134 | sub _consolidate_moves | |||||
135 | { | |||||
136 | 8 | 15 | my ($moves) = @_; | |||
137 | 8 | 28 | my @steps = split /h/, $moves; | |||
138 | 8 | 8 | shift @steps; # discard empty initial string | |||
139 | 8 | 137 | return 'h' . _f( List::Util::sum( @steps ) ); | |||
140 | } | |||||
141 | ||||||
142 | 1; # Magic true value required at end of module |