| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package URI::GoogleChart; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 2 |  |  | 2 |  | 13842 | use strict; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 108 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | our $VERSION = "1.02"; | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 2 |  |  | 2 |  | 1946 | use URI; | 
|  | 2 |  |  |  |  | 23009 |  | 
|  | 2 |  |  |  |  | 80 |  | 
| 8 | 2 |  |  | 2 |  | 21 | use Carp qw(croak carp); | 
|  | 2 |  |  |  |  | 9 |  | 
|  | 2 |  |  |  |  | 13388 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | my $BASE = "http://chart.apis.google.com/chart"; | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | our %TYPE_ALIAS = ( | 
| 13 |  |  |  |  |  |  | "lines" => "lc", | 
| 14 |  |  |  |  |  |  | "sparklines" => "ls", | 
| 15 |  |  |  |  |  |  | "xy-lines" => "lxy", | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | "horizontal-stacked-bars" => "bhs", | 
| 18 |  |  |  |  |  |  | "vertical-stacked-bars" => "bvs", | 
| 19 |  |  |  |  |  |  | "horizontal-grouped-bars" => "bhg", | 
| 20 |  |  |  |  |  |  | "vertical-grouped-bars" => "bvg", | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | "pie" => "p", | 
| 23 |  |  |  |  |  |  | "pie-3d" => "p3", | 
| 24 |  |  |  |  |  |  | "3d-pie" => "p3", | 
| 25 |  |  |  |  |  |  | "concentric-pie" => "pc", | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | "venn" => "v", | 
| 28 |  |  |  |  |  |  | "scatter-plot" => "s", | 
| 29 |  |  |  |  |  |  | "radar" => "r", | 
| 30 |  |  |  |  |  |  | "radar-splines" => "rs", | 
| 31 |  |  |  |  |  |  | "google-o-meter" => "gom", | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | "africa" => "t", | 
| 34 |  |  |  |  |  |  | "asia" => "t", | 
| 35 |  |  |  |  |  |  | "europe" => "t", | 
| 36 |  |  |  |  |  |  | "middle_east" => "t", | 
| 37 |  |  |  |  |  |  | "south_america" => "t", | 
| 38 |  |  |  |  |  |  | "usa" => "t", | 
| 39 |  |  |  |  |  |  | "world" => "t", | 
| 40 |  |  |  |  |  |  | ); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | our %COLOR_ALIAS = ( | 
| 43 |  |  |  |  |  |  | "red"     => "FF0000", | 
| 44 |  |  |  |  |  |  | "lime"    => "00FF00", | 
| 45 |  |  |  |  |  |  | "blue"    => "0000FF", | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | "green"   => "008000", | 
| 48 |  |  |  |  |  |  | "navy"    => "000080", | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | "yellow"  => "FFFF00", | 
| 51 |  |  |  |  |  |  | "aqua"    => "00FFFF", | 
| 52 |  |  |  |  |  |  | "fuchsia" => "FF00FF", | 
| 53 |  |  |  |  |  |  | "maroon"  => "800000", | 
| 54 |  |  |  |  |  |  | "purple"  => "800080", | 
| 55 |  |  |  |  |  |  | "olive"   => "808000", | 
| 56 |  |  |  |  |  |  | "teal"    => "008080", | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | "white"   => "FFFFFF", | 
| 59 |  |  |  |  |  |  | "silver"  => "C0C0C0", | 
| 60 |  |  |  |  |  |  | "gray"    => "808080", | 
| 61 |  |  |  |  |  |  | "black"   => "000000", | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | "transparent" => "00000000", | 
| 64 |  |  |  |  |  |  | ); | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | our %AXIS_ALIAS = ( | 
| 67 |  |  |  |  |  |  | "left"   => "y", | 
| 68 |  |  |  |  |  |  | "right"  => "r", | 
| 69 |  |  |  |  |  |  | "top"    => "t", | 
| 70 |  |  |  |  |  |  | "bottom" => "x", | 
| 71 |  |  |  |  |  |  | ); | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | our %ENCODING_ALIAS = ( | 
| 74 |  |  |  |  |  |  | "text"     => "t", | 
| 75 |  |  |  |  |  |  | "simple"   => "s", | 
| 76 |  |  |  |  |  |  | "extended" => "e", | 
| 77 |  |  |  |  |  |  | ); | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | # constants for data encoding | 
| 80 |  |  |  |  |  |  | my @C = ("A" .. "Z", "a" .. "z", 0 .. 9, "-", "."); | 
| 81 |  |  |  |  |  |  | my $STR_s = join("", @C[0 .. 61]); | 
| 82 |  |  |  |  |  |  | my $STR_e = do { | 
| 83 |  |  |  |  |  |  | my @v; | 
| 84 |  |  |  |  |  |  | for my $x (@C) { | 
| 85 |  |  |  |  |  |  | for my $y (@C) { | 
| 86 |  |  |  |  |  |  | push(@v, "$x$y"); | 
| 87 |  |  |  |  |  |  | } | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  | join("", @v); | 
| 90 |  |  |  |  |  |  | }; | 
| 91 |  |  |  |  |  |  | die unless length($STR_s) == 62; | 
| 92 |  |  |  |  |  |  | die unless length($STR_e) == 4096 * 2; | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | sub new { | 
| 96 | 22 |  |  | 22 | 1 | 4542 | my($class, $type, $width, $height, %opt) = @_; | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 22 | 50 |  |  |  | 67 | croak("Chart type not provided") unless $type; | 
| 99 | 22 | 50 | 33 |  |  | 108 | croak("Chart size not provided") unless $width && $height; | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 22 |  | 66 |  |  | 136 | my %param = ( | 
| 102 |  |  |  |  |  |  | cht => $TYPE_ALIAS{$type} || $type, | 
| 103 |  |  |  |  |  |  | chs => join("x", $width, $height), | 
| 104 |  |  |  |  |  |  | ); | 
| 105 | 22 | 100 | 66 |  |  | 87 | $param{chtm} = $type if $param{cht} eq "t" && $type ne "t";  # maps | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | my %handle = ( | 
| 108 |  |  |  |  |  |  | data => \&_data, | 
| 109 |  |  |  |  |  |  | range => 1, | 
| 110 |  |  |  |  |  |  | min => 1, | 
| 111 |  |  |  |  |  |  | max => 1, | 
| 112 |  |  |  |  |  |  | range_round => 1, | 
| 113 |  |  |  |  |  |  | range_show => 1, | 
| 114 |  |  |  |  |  |  | encoding => 1, | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | color => sub { | 
| 117 | 8 |  |  | 8 |  | 11 | my $v = shift; | 
| 118 | 8 | 100 |  |  |  | 22 | $v = [$v] unless ref($v); | 
| 119 | 8 |  |  |  |  | 21 | $param{chco} = join(",", map _color($_), @$v); | 
| 120 |  |  |  |  |  |  | }, | 
| 121 |  |  |  |  |  |  | background => sub { | 
| 122 | 3 |  |  | 3 |  | 7 | $param{chf} = "bg,s," . _color(shift); | 
| 123 |  |  |  |  |  |  | }, | 
| 124 |  |  |  |  |  |  | title => sub { | 
| 125 | 4 |  |  | 4 |  | 7 | my $title = shift; | 
| 126 | 4 | 50 |  |  |  | 87 | ($title, my($color, $size)) = @$title if ref($title) eq "ARRAY"; | 
| 127 | 4 |  |  |  |  | 46 | $title =~ s/\n+\z//; | 
| 128 | 4 |  |  |  |  | 9 | $title =~ s/\n/|/g; | 
| 129 | 4 |  |  |  |  | 8 | $param{chtt} = $title; | 
| 130 | 4 | 50 | 33 |  |  | 30 | if (defined($color) || defined($size)) { | 
| 131 | 0 | 0 |  |  |  | 0 | $color = defined($color) ? _color($color) : ""; | 
| 132 | 0 | 0 |  |  |  | 0 | $size = "" unless defined $size; | 
| 133 | 0 |  |  |  |  | 0 | $param{chts} = "$color,$size"; | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  | }, | 
| 136 |  |  |  |  |  |  | label => sub { | 
| 137 | 7 |  |  | 7 |  | 11 | my $lab = shift; | 
| 138 | 7 | 100 |  |  |  | 21 | $lab = [$lab] unless ref($lab) eq "ARRAY"; | 
| 139 | 7 | 100 |  |  |  | 32 | my $k = $param{cht} =~ /^p|^gom$/ ? "chl" : "chdl"; | 
| 140 | 7 |  |  |  |  | 35 | $param{$k} = join("|", @$lab); | 
| 141 |  |  |  |  |  |  | }, | 
| 142 |  |  |  |  |  |  | rotate => sub { | 
| 143 | 2 |  |  | 2 |  | 3 | my $p = shift; | 
| 144 | 2 |  |  |  |  | 8 | $p += 360 while $p < 0; | 
| 145 | 2 |  |  |  |  | 6 | $p /= 180 / 3.1416;  # convert to radians | 
| 146 | 2 |  |  |  |  | 34 | $param{chp} = sprintf "%.2f", $p; | 
| 147 |  |  |  |  |  |  | }, | 
| 148 |  |  |  |  |  |  | margin => sub { | 
| 149 | 6 |  |  | 6 |  | 8 | my $m = shift; | 
| 150 | 6 | 100 |  |  |  | 23 | $m = [($m) x 4] unless ref($m); | 
| 151 | 6 |  |  |  |  | 37 | $param{chma} = join(",", @$m); | 
| 152 |  |  |  |  |  |  | } | 
| 153 | 22 |  |  |  |  | 472 | ); | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 22 |  |  |  |  | 60 | my $data = delete $opt{data};  # need to be processed last | 
| 156 | 22 |  |  |  |  | 68 | for my $k (keys %opt) { | 
| 157 | 52 | 100 |  |  |  | 111 | if (my $h = $handle{$k}) { | 
| 158 | 45 | 100 |  |  |  | 166 | $h->($opt{$k}, \%param, \%opt) if ref($h) eq "CODE"; | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | else { | 
| 161 | 7 |  |  |  |  | 16 | $param{$k} = $opt{$k}; | 
| 162 | 7 | 50 |  |  |  | 33 | carp("Unrecognized parameter '$k' embedded in GoogleChart URI") | 
| 163 |  |  |  |  |  |  | unless $k =~ /^ch/; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  | } | 
| 166 | 22 | 100 |  |  |  | 86 | _data($data, \%param, \%opt) if $data; | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | # generate URI | 
| 169 | 22 |  |  |  |  | 107 | my $uri = URI->new($BASE); | 
| 170 | 22 |  |  |  |  | 27558 | $uri->query_form(map { $_ => $param{$_} } _sort_chart_keys(keys %param)); | 
|  | 116 |  |  |  |  | 290 |  | 
| 171 | 22 |  |  |  |  | 4103 | for ($uri->query) { | 
| 172 | 22 |  |  |  |  | 289 | s/%3A/:/g; | 
| 173 | 22 |  |  |  |  | 89 | s/%2C/,/g; | 
| 174 | 22 |  |  |  |  | 55 | s/%7C/|/g; # XXX doesn't work (it ends up encoded anyways) | 
| 175 | 22 |  |  |  |  | 66 | $uri->query($_); | 
| 176 |  |  |  |  |  |  | } | 
| 177 | 22 |  |  |  |  | 1132 | return $uri; | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | sub _color { | 
| 181 | 19 |  |  | 19 |  | 31 | local $_ = shift; | 
| 182 | 19 |  | 66 |  |  | 144 | return $COLOR_ALIAS{$_} || | 
| 183 |  |  |  |  |  |  | (/^[\da-fA-F]{3}\z/ ? join("", map "$_$_", split(//, $_)) : $_); | 
| 184 |  |  |  |  |  |  | } | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | sub _sort_chart_keys { | 
| 187 | 22 |  |  | 22 |  | 81 | my %o = ( cht => 1, chtm => 2, chs => 3, chd => 100 ); | 
| 188 | 22 | 50 | 100 |  |  | 71 | return sort { ($o{$a}||=99) <=> ($o{$b}||=99) || $a cmp $b } @_; | 
|  | 198 |  | 100 |  |  | 877 |  | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | sub _default_minmax { | 
| 192 | 25 |  |  | 25 |  | 27 | my $param = shift; | 
| 193 | 25 |  |  |  |  | 38 | my $t = $param->{cht}; | 
| 194 | 25 | 100 |  |  |  | 65 | return 0, undef if $t =~ /^p/;  # pie chart | 
| 195 | 22 | 100 |  |  |  | 46 | return 0, undef if $t eq "v";   # venn | 
| 196 | 21 | 50 |  |  |  | 57 | return 0, undef if $t =~ /^r/;  # radar chart | 
| 197 | 21 | 100 |  |  |  | 60 | return 0, undef if $t =~ /^b/;  # bar chart | 
| 198 | 15 | 100 |  |  |  | 31 | return 0, 100   if $t eq "gom"; # meter | 
| 199 | 14 |  |  |  |  | 29 | return; | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | sub _data { | 
| 203 | 19 |  |  | 19 |  | 31 | my($data, $param, $opt) = @_; | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | # various shortcuts | 
| 206 | 19 |  |  |  |  | 39 | $data = _deep_copy($data);  # want to modify it | 
| 207 | 19 | 100 |  |  |  | 53 | if (ref($data) eq "ARRAY") { | 
|  |  | 50 |  |  |  |  |  | 
| 208 | 18 | 100 |  |  |  | 59 | $data = [$data] unless ref($data->[0]); | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  | elsif (ref($data) eq "HASH") { | 
| 211 | 0 |  |  |  |  | 0 | $data = [$data]; | 
| 212 |  |  |  |  |  |  | } | 
| 213 |  |  |  |  |  |  | else { | 
| 214 | 1 |  |  |  |  | 3 | $data = [[$data]]; | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  |  | 
| 217 | 19 |  |  |  |  | 83 | my $range = _deep_copy($opt->{range}); | 
| 218 | 19 |  |  |  |  | 49 | for (qw(min max range_round range_show)) { | 
| 219 | 76 |  |  |  |  | 172 | (my $r = $_) =~ s/^range_//; | 
| 220 | 76 | 100 |  |  |  | 234 | $range->{""}{$r} = $opt->{$_} if exists $opt->{$_}; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 19 |  |  |  |  | 28 | my $vcount = 0; | 
| 224 | 19 |  |  |  |  | 36 | for my $set (@$data) { | 
| 225 | 25 | 100 |  |  |  | 90 | $set = { v => $set } if ref($set) eq "ARRAY"; | 
| 226 | 25 |  |  |  |  | 65 | my $v = $set->{v}; | 
| 227 | 25 |  | 100 |  |  | 110 | my $r = $set->{range} ||= ""; | 
| 228 | 25 |  | 100 |  |  | 99 | my $rh = $range->{$r} ||= {}; | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 25 |  |  |  |  | 49 | my($min, $max) = _default_minmax($param); | 
| 231 | 25 |  |  |  |  | 35 | my $i = 0; | 
| 232 | 25 |  |  |  |  | 46 | for (@$v) { | 
| 233 | 136 | 100 |  |  |  | 253 | next unless defined; | 
| 234 | 135 | 100 | 100 |  |  | 469 | $min = $_ if !defined($min) || $_ < $min; | 
| 235 | 135 | 100 | 100 |  |  | 449 | $max = $_ if !defined($max) || $_ > $max; | 
| 236 | 135 | 100 |  |  |  | 338 | if ($param->{cht} =~ /^b.s\z/) { | 
| 237 |  |  |  |  |  |  | # stacked stuff | 
| 238 | 22 |  | 50 |  |  | 93 | $rh->{stacked}{min}[$i] ||= 0; | 
| 239 | 22 |  | 100 |  |  | 70 | $rh->{stacked}{max}[$i] ||= 0; | 
| 240 | 22 | 100 |  |  |  | 60 | $rh->{stacked}{$_ < 0 ? "min" : "max"}[$i] += $_; | 
| 241 |  |  |  |  |  |  | } | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  | continue { | 
| 244 | 136 |  |  |  |  | 191 | $i++; | 
| 245 |  |  |  |  |  |  | } | 
| 246 | 25 |  |  |  |  | 45 | $vcount += @$v; | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 25 | 100 |  |  |  | 59 | if ($rh->{stacked}) { | 
| 249 |  |  |  |  |  |  | # XXX we really only need to this after we have processed | 
| 250 |  |  |  |  |  |  | # the last dataset, the other rounds it's wasted effort | 
| 251 | 4 |  |  |  |  | 6 | ($min, $max) = (0, 0); | 
| 252 | 4 |  |  |  |  | 8 | for (qw(min max)) { | 
| 253 | 8 |  |  |  |  | 9 | for my $v (@{$rh->{stacked}{$_}}) { | 
|  | 8 |  |  |  |  | 18 |  | 
| 254 | 44 | 50 |  |  |  | 75 | next unless defined $v; | 
| 255 | 44 | 100 |  |  |  | 67 | if ($_ eq "min") { | 
| 256 | 22 | 100 |  |  |  | 53 | $min = $v if $v < $min; | 
| 257 |  |  |  |  |  |  | } | 
| 258 |  |  |  |  |  |  | else { | 
| 259 | 22 | 100 |  |  |  | 53 | $max = $v if $v > $max; | 
| 260 |  |  |  |  |  |  | } | 
| 261 |  |  |  |  |  |  | } | 
| 262 |  |  |  |  |  |  | } | 
| 263 |  |  |  |  |  |  | } | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 25 | 100 |  |  |  | 51 | if (defined $min) { | 
| 266 | 24 |  |  |  |  | 66 | my %h = (min => $min, max => $max); | 
| 267 | 24 |  |  |  |  | 53 | for my $k (keys %h) { | 
| 268 | 48 | 50 |  |  |  | 99 | if (defined $set->{$k}) { | 
| 269 | 0 |  |  |  |  | 0 | $h{$k} = $set->{$k}; | 
| 270 |  |  |  |  |  |  | } | 
| 271 |  |  |  |  |  |  | else { | 
| 272 | 48 |  |  |  |  | 83 | $set->{$k} = $h{$k}; | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  |  | 
| 275 | 48 |  |  |  |  | 62 | my $rv = $rh->{$k}; | 
| 276 | 48 | 100 | 100 |  |  | 181 | if (!defined($rv) || | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 277 |  |  |  |  |  |  | ($k eq "min" && $h{$k} < $rv) || | 
| 278 |  |  |  |  |  |  | ($k eq "max" && $h{$k} > $rv) | 
| 279 |  |  |  |  |  |  | ) | 
| 280 |  |  |  |  |  |  | { | 
| 281 | 40 |  |  |  |  | 152 | $rh->{$k} = $h{$k}; | 
| 282 |  |  |  |  |  |  | } | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  | } | 
| 285 |  |  |  |  |  |  | } | 
| 286 |  |  |  |  |  |  |  | 
| 287 |  |  |  |  |  |  | # should we round any of the ranges | 
| 288 | 19 |  |  |  |  | 50 | for my $r (values %$range) { | 
| 289 | 20 | 100 |  |  |  | 61 | next unless $r->{round}; | 
| 290 |  |  |  |  |  |  |  | 
| 291 | 2 |  |  | 2 |  | 2393 | use POSIX qw(floor ceil); | 
|  | 2 |  |  |  |  | 43726 |  | 
|  | 2 |  |  |  |  | 17 |  | 
| 292 | 4 |  |  | 4 | 0 | 26 | sub log10 { log(shift) / log(10) } | 
| 293 |  |  |  |  |  |  |  | 
| 294 | 4 |  |  |  |  | 11 | my($min, $max) = @$r{"min", "max"}; | 
| 295 | 4 |  |  |  |  | 6 | my $range = $max - $min; | 
| 296 | 4 | 50 |  |  |  | 10 | next if $range == 0; | 
| 297 | 4 | 50 |  |  |  | 34 | die "Assert" if $range < 0; | 
| 298 |  |  |  |  |  |  |  | 
| 299 | 4 |  |  |  |  | 12 | my $step = 10 ** int(log10($range)); | 
| 300 | 4 | 50 |  |  |  | 14 | $step /= 10 if $step / $range >= 0.1; | 
| 301 | 4 | 100 |  |  |  | 11 | $step *= 5 if $step / $range < 0.05; | 
| 302 |  |  |  |  |  |  |  | 
| 303 | 4 |  |  |  |  | 24 | $min = floor($min / $step - 0.2) * $step; | 
| 304 | 4 |  |  |  |  | 12 | $max = ceil($max / $step + 0.2) * $step; | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | # zero based minimum is usually a good thing so make it more likely | 
| 307 | 4 | 100 | 66 |  |  | 19 | $min = 0 if $min > 0 && $min/$range < 0.4; | 
| 308 |  |  |  |  |  |  |  | 
| 309 | 4 |  |  |  |  | 16 | @$r{"min", "max"} = ($min, $max); | 
| 310 |  |  |  |  |  |  | } | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | #use Data::Dump; dd $data; | 
| 313 |  |  |  |  |  |  | #use Data::Dump; dd $range; | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | # encode data | 
| 316 | 19 |  | 66 |  |  | 150 | my $e = $ENCODING_ALIAS{$opt->{encoding} || ""} || $opt->{encoding}; | 
| 317 | 19 | 100 |  |  |  | 39 | unless ($e) { | 
| 318 |  |  |  |  |  |  | # try to me a little smart about selecting a suitable encoding based | 
| 319 |  |  |  |  |  |  | # on the number of data points we're plotting and the resolution of | 
| 320 |  |  |  |  |  |  | # the generated image | 
| 321 | 15 |  |  |  |  | 98 | my @s = ($param->{chs} =~ /(\d+)/g); | 
| 322 | 15 |  |  |  |  | 40 | my $res = $s[0] * $s[1]; | 
| 323 | 15 | 100 | 33 |  |  | 43 | if ($vcount < 20) { | 
|  |  | 50 |  |  |  |  |  | 
| 324 | 13 |  |  |  |  | 27 | $e = "t"; | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  | elsif ($vcount > 256 || $res < 300*200) { | 
| 327 | 2 |  |  |  |  | 6 | $e = "s"; | 
| 328 |  |  |  |  |  |  | } | 
| 329 |  |  |  |  |  |  | else { | 
| 330 | 0 |  |  |  |  | 0 | $e = "e"; | 
| 331 |  |  |  |  |  |  | } | 
| 332 |  |  |  |  |  |  | } | 
| 333 |  |  |  |  |  |  |  | 
| 334 |  |  |  |  |  |  | my %enc = ( | 
| 335 |  |  |  |  |  |  | t => { | 
| 336 |  |  |  |  |  |  | null => -1, | 
| 337 |  |  |  |  |  |  | sep1 => ",", | 
| 338 |  |  |  |  |  |  | sep2 => "|", | 
| 339 |  |  |  |  |  |  | fmt => sub { | 
| 340 | 78 |  |  | 78 |  | 111 | my $v = 100 * shift; | 
| 341 | 78 | 100 |  |  |  | 372 | $v = sprintf "%.1f", $v if $v ne int($v); | 
| 342 | 78 |  |  |  |  | 209 | $v; | 
| 343 |  |  |  |  |  |  | }, | 
| 344 |  |  |  |  |  |  | }, | 
| 345 |  |  |  |  |  |  | s => { | 
| 346 |  |  |  |  |  |  | null => "_", | 
| 347 |  |  |  |  |  |  | sep1 => "", | 
| 348 |  |  |  |  |  |  | sep2 => ",", | 
| 349 |  |  |  |  |  |  | fmt => sub { | 
| 350 | 54 |  |  | 54 |  | 172 | return substr($STR_s, $_[0] * length($STR_s) - 0.5, 1); | 
| 351 |  |  |  |  |  |  | }, | 
| 352 |  |  |  |  |  |  | }, | 
| 353 |  |  |  |  |  |  | e => { | 
| 354 |  |  |  |  |  |  | null => "__", | 
| 355 |  |  |  |  |  |  | sep1 => "", | 
| 356 |  |  |  |  |  |  | sep2 => ",", | 
| 357 |  |  |  |  |  |  | fmt => sub { | 
| 358 | 3 |  |  | 3 |  | 15 | return substr($STR_e, int($_[0] * length($STR_e) / 2 - 0.5) * 2, 2); | 
| 359 |  |  |  |  |  |  | }, | 
| 360 |  |  |  |  |  |  | } | 
| 361 | 19 |  |  |  |  | 315 | ); | 
| 362 | 19 |  | 33 |  |  | 71 | my $enc = $enc{$e} || croak("unsupported encoding $e"); | 
| 363 | 19 |  |  |  |  | 22 | my @res; | 
| 364 | 19 |  |  |  |  | 31 | for my $set (@$data) { | 
| 365 | 25 |  |  |  |  | 30 | my($min, $max) = @{$range->{$set->{range}}}{"min", "max"}; | 
|  | 25 |  |  |  |  | 66 |  | 
| 366 | 25 |  |  |  |  | 40 | my $v = $set->{v}; | 
| 367 | 25 |  |  |  |  | 39 | for (@$v) { | 
| 368 | 136 | 100 | 66 |  |  | 956 | if (defined($_) && $_ >= $min && $_ <= $max && $min != $max) { | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 369 | 135 |  |  |  |  | 336 | $_ = $enc->{fmt}(($_ - $min) / ($max - $min)); | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  | else { | 
| 372 | 1 |  |  |  |  | 4 | $_ = $enc->{null}; | 
| 373 |  |  |  |  |  |  | } | 
| 374 |  |  |  |  |  |  | } | 
| 375 | 25 |  |  |  |  | 110 | push(@res, join($enc->{sep1}, @$v)); | 
| 376 |  |  |  |  |  |  | } | 
| 377 | 19 |  |  |  |  | 74 | $param->{chd} = "$e:" . join($enc->{sep2}, @res); | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | # handle bar chart zero line if we charted negative data | 
| 380 | 19 | 100 |  |  |  | 58 | if ($param->{cht} =~ /^b/) { | 
| 381 | 4 |  |  |  |  | 6 | my($min, $max) = @{$range->{""}}{"min", "max"}; | 
|  | 4 |  |  |  |  | 10 |  | 
| 382 | 4 | 100 |  |  |  | 10 | if ($min < 0) { | 
| 383 | 2 | 50 |  |  |  | 16 | $param->{chp} = $max < 0 ? 1 : sprintf "%.2f", -$min / ($max - $min); | 
| 384 |  |  |  |  |  |  | } | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | # enable axis labels? | 
| 388 | 19 |  |  |  |  | 62 | for (sort keys %$range) { | 
| 389 | 20 |  |  |  |  | 34 | my $r = $range->{$_}; | 
| 390 | 20 |  | 100 |  |  | 98 | my @chxt = split(/,/, $param->{chxt} || ""); | 
| 391 | 20 |  | 100 |  |  | 76 | my @chxr = split(/\|/, $param->{chxr} || ""); | 
| 392 | 20 | 100 |  |  |  | 55 | if (my $rshow = $r->{show}) { | 
| 393 | 7 |  |  |  |  | 16 | my($min, $max) = @$r{"min", "max"}; | 
| 394 | 7 |  |  |  |  | 26 | for ($min, $max) { | 
| 395 | 14 |  |  |  |  | 55 | $_ = sprintf "%.2g", $_; | 
| 396 |  |  |  |  |  |  | } | 
| 397 | 7 |  | 33 |  |  | 26 | push(@chxt, $AXIS_ALIAS{$rshow} || $rshow); | 
| 398 | 7 |  |  |  |  | 10 | my $i = $#chxt; | 
| 399 | 7 |  |  |  |  | 31 | push(@chxr, "$i,$min,$max"); | 
| 400 |  |  |  |  |  |  | } | 
| 401 | 20 | 100 |  |  |  | 218 | if (@chxt) { | 
| 402 | 7 |  |  |  |  | 17 | $param->{chxt} = join(",", @chxt); | 
| 403 | 7 |  |  |  |  | 106 | $param->{chxr} = join("|", @chxr); | 
| 404 |  |  |  |  |  |  | } | 
| 405 |  |  |  |  |  |  | } | 
| 406 |  |  |  |  |  |  | } | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | sub _deep_copy { | 
| 409 | 193 |  |  | 193 |  | 222 | my $o = shift; | 
| 410 | 193 | 100 |  |  |  | 547 | return $o unless ref($o); | 
| 411 | 33 | 100 |  |  |  | 115 | return [map _deep_copy($_), @$o] if ref($o) eq "ARRAY"; | 
| 412 | 5 | 50 |  |  |  | 20 | return {map { $_ => _deep_copy($o->{$_}) } keys %$o} if ref($o) eq "HASH"; | 
|  | 10 |  |  |  |  | 19 |  | 
| 413 | 0 |  |  |  |  |  | die "Can't copy " . ref($o); | 
| 414 |  |  |  |  |  |  | } | 
| 415 |  |  |  |  |  |  |  | 
| 416 |  |  |  |  |  |  | 1; | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | __END__ |