File: | blib/lib/SVG/Sparkline/RangeBar.pm |
Coverage: | 100.0% |
line | stmt | bran | cond | sub | time | code |
---|---|---|---|---|---|---|
1 | package SVG::Sparkline::RangeBar; | |||||
2 | ||||||
3 | 3 3 3 | 8 3 84 | use warnings; | |||
4 | 3 3 3 | 8 1 42 | use strict; | |||
5 | 3 3 3 | 5 3 133 | use Carp; | |||
6 | 3 3 3 | 18 2 33 | use SVG; | |||
7 | 3 3 3 | 2586 3 24 | use List::Util (); | |||
8 | 3 3 3 | 463 4 52 | use SVG::Sparkline::Utils; | |||
9 | ||||||
10 | 3 3 3 | 111 7 1900 | 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 | 10 20 | 8 138 | return scalar grep { $_[1] eq $_ } qw/gap thick/; | |||
18 | } | |||||
19 | ||||||
20 | sub make | |||||
21 | { | |||||
22 | 32 | 27 | my ($class, $args) = @_; | |||
23 | # validate parameters | |||||
24 | 32 | 368 | SVG::Sparkline::Utils::validate_array_param( $args, 'values' ); | |||
25 | 136 | 266 | croak "'values' must be an array of pairs.\n" | |||
26 | 28 140 28 | 20 290 31 | if grep { 'ARRAY' ne ref $_ || 2 != @{$_} } @{$args->{values}}; | |||
27 | 136 | 442 | my $vals = SVG::Sparkline::Utils::summarize_values( | |||
28 | 27 136 27 | 22 55 27 | [ map { @{$_} } @{$args->{values}} ] | |||
29 | ); | |||||
30 | ||||||
31 | 27 | 47 | my $height = $args->{height} - 2*$args->{pady}; | |||
32 | 27 | 36 | my $yscale = -$height / $vals->{range}; | |||
33 | 27 | 319 | my $baseline = _f(-$yscale*$vals->{min}); | |||
34 | ||||||
35 | # Figure out the width I want and define the viewBox | |||||
36 | 27 | 14 | my $dwidth; | |||
37 | 27 | 78 | my $gap = $args->{gap} || 0; | |||
38 | 27 | 41 | $args->{thick} ||= 3; | |||
39 | 27 | 28 | my $space = $args->{thick}+$gap; | |||
40 | 27 | 30 | if($args->{width}) | |||
41 | { | |||||
42 | 1 | 1 | $dwidth = $args->{width} - $args->{padx}*2; | |||
43 | 1 1 | 1 13 | $space = _f( $dwidth / @{$args->{values}} ); | |||
44 | 1 | 2 | $args->{thick} = $space - $gap; | |||
45 | } | |||||
46 | else | |||||
47 | { | |||||
48 | 26 26 | 16 32 | $dwidth = @{$args->{values}} * $space; | |||
49 | 26 | 27 | $args->{width} = $dwidth + 2*$args->{padx}; | |||
50 | } | |||||
51 | 27 | 34 | $args->{yoff} = -($baseline+$height+$args->{pady}); | |||
52 | 27 | 23 | $args->{xscale} = $space; | |||
53 | 27 | 330 | my $svg = SVG::Sparkline::Utils::make_svg( $args ); | |||
54 | ||||||
55 | 27 | 298 | my $off = _f( $gap/2 ); | |||
56 | 27 | 21 | my $prev = 0; | |||
57 | 27 | 307 | my $path = "M". _f(-$args->{thick}-$off).",0"; | |||
58 | 27 27 | 15 33 | foreach my $v (@{$args->{values}}) | |||
59 | { | |||||
60 | # Move from previous x,y to low value | |||||
61 | 136 | 1511 | $path .= 'm'. _f($args->{thick}+$gap) .','. _f($yscale*($v->[0]-$prev)); | |||
62 | 136 | 1497 | my $vert = _f( $yscale * ($v->[1]-$v->[0]) ); | |||
63 | 136 | 117 | if($vert) | |||
64 | { | |||||
65 | 130 | 1506 | $path .= "v${vert}h$args->{thick}v". _f(-$vert)."h-$args->{thick}"; | |||
66 | } | |||||
67 | else | |||||
68 | { | |||||
69 | 6 | 8 | $path .= _zero_height_path( $args->{thick} ); | |||
70 | } | |||||
71 | 136 | 127 | $prev = $v->[0]; | |||
72 | } | |||||
73 | 27 | 30 | $path = _clean_path( $path ); | |||
74 | 27 | 224 | $svg->path( stroke=>'none', fill=>$args->{color}, d=>$path ); | |||
75 | ||||||
76 | 27 | 1123 | if( exists $args->{mark} ) | |||
77 | { | |||||
78 | 9 | 16 | _make_marks( $svg, | |||
79 | thick=>$args->{thick}, off=>$off, | |||||
80 | space=>$space, yscale=>$yscale, | |||||
81 | values=>$args->{values}, mark=>$args->{mark} | |||||
82 | ); | |||||
83 | } | |||||
84 | 27 | 305 | return $svg; | |||
85 | } | |||||
86 | ||||||
87 | sub _zero_height_path | |||||
88 | { | |||||
89 | 7 | 6 | my ($thick) = @_; | |||
90 | 7 | 10 | my $path = 'v-0.5'; | |||
91 | 7 | 4 | my $step = 1; | |||
92 | 7 | 8 | $step = $thick/4 if $thick <= 2; | |||
93 | 7 | 10 | $step = 2 if $thick >= 8; | |||
94 | 7 | 9 | my $num_steps = int( $thick/$step ) - 1; | |||
95 | 7 | 5 | my $leftover = $thick-($num_steps*$step); | |||
96 | 7 | 8 | foreach my $i (1 .. $num_steps) | |||
97 | { | |||||
98 | 19 | 28 | $path .= "h${step}v" . ($i%2? 1 :-1); | |||
99 | } | |||||
100 | 7 | 18 | $path .= "h${leftover}v". ($thick%2?0.5: -0.5) . "h-$thick"; | |||
101 | 7 | 36 | return $path; | |||
102 | } | |||||
103 | ||||||
104 | sub _make_marks | |||||
105 | { | |||||
106 | 9 | 18 | my ($svg, %args) = @_; | |||
107 | ||||||
108 | 9 9 | 6 12 | my @marks = @{$args{mark}}; | |||
109 | 9 | 11 | while(@marks) | |||
110 | { | |||||
111 | 9 | 11 | my ($index,$color) = splice( @marks, 0, 2 ); | |||
112 | 9 | 103 | $index = SVG::Sparkline::Utils::range_mark_to_index( 'RangeBar', $index, $args{values} ); | |||
113 | 9 | 17 | _make_mark( $svg, %args, index=>$index, color=>$color ); | |||
114 | } | |||||
115 | 9 | 12 | return; | |||
116 | } | |||||
117 | ||||||
118 | sub _make_mark | |||||
119 | { | |||||
120 | 9 | 19 | my ($svg, %args) = @_; | |||
121 | 9 | 6 | my $index = $args{index}; | |||
122 | 9 9 | 6 9 | my ($lo, $hi) = @{$args{values}->[$index]}; | |||
123 | 9 | 103 | my $y = _f( $hi * $args{yscale} ); | |||
124 | 9 | 107 | my $h = _f( ($hi-$lo) * $args{yscale}); | |||
125 | 9 | 10 | if($h) | |||
126 | { | |||||
127 | 8 | 90 | my $x = _f($index * $args{space} + $args{off}); | |||
128 | 8 | 67 | $svg->rect( x=>$x, y=>$y, | |||
129 | width=>$args{thick}, height=>abs($h), | |||||
130 | stroke=>'none', fill=>$args{color} | |||||
131 | ); | |||||
132 | } | |||||
133 | else | |||||
134 | { | |||||
135 | 1 | 11 | my $x = _f($index * $args{space} +$args{off}); | |||
136 | 1 | 2 | $svg->path( | |||
137 | d=>"M$x,$y". _zero_height_path( $args{thick} ), | |||||
138 | stroke=>'none', fill=>$args{color} | |||||
139 | ); | |||||
140 | } | |||||
141 | 9 | 412 | return; | |||
142 | } | |||||
143 | ||||||
144 | sub _clean_path | |||||
145 | { | |||||
146 | 27 | 24 | my ($path) = @_; | |||
147 | 27 27 | 85 372 | $path =~ s/^M([-.\d]+),([-.\d]+)m([-.\d]+),([-.\d]+)/'M'. _f($1+$3) .','. _f($2+$4)/e; | |||
148 | 27 | 28 | $path =~ s/h0(?![.\d])//g; | |||
149 | 27 | 36 | return $path; | |||
150 | } | |||||
151 | ||||||
152 | 1; # Magic true value required at end of module |