| 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 | |||||