| lib/HTML/TagCloud/Centred.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 169 | 183 | 92.3 |
| branch | 40 | 64 | 62.5 |
| condition | 15 | 23 | 65.2 |
| subroutine | 26 | 26 | 100.0 |
| pod | 0 | 5 | 0.0 |
| total | 250 | 301 | 83.0 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 22477 | use strict; | |||
| 1 | 3 | ||||||
| 1 | 43 | ||||||
| 2 | 1 | 1 | 5 | use warnings; | |||
| 1 | 2 | ||||||
| 1 | 205 | ||||||
| 3 | |||||||
| 4 | =head1 NAME | ||||||
| 5 | |||||||
| 6 | HTML::TagCloud::Centred - Biggest tags in the centre | ||||||
| 7 | |||||||
| 8 | =head1 SYNOPSIS | ||||||
| 9 | |||||||
| 10 | use HTML::TagCloud::Centred; | ||||||
| 11 | my $cloud = HTML::TagCloud::Centred->new( | ||||||
| 12 | # size_min_pc => 50, | ||||||
| 13 | # size_max_pc => 200, | ||||||
| 14 | # scale_code => sub { ... }, | ||||||
| 15 | # html_esc_code => sub { ... }, | ||||||
| 16 | # clr_max => '#FF0000', | ||||||
| 17 | # clr_min => '#550000', | ||||||
| 18 | ); | ||||||
| 19 | $cloud->add( 'FirstWord', 'http://www.google.co.uk' ); | ||||||
| 20 | foreach my $w ( | ||||||
| 21 | ('Biggest')x7, ('Medium')x5, ('Small')x5, ('Smallest')x10 | ||||||
| 22 | ){ | ||||||
| 23 | $cloud->add( $w ); | ||||||
| 24 | } | ||||||
| 25 | open my $OUT, '>cloud.html'; | ||||||
| 26 | # print $OUT $cloud->css; | ||||||
| 27 | # print $OUT $cloud->html; | ||||||
| 28 | print $OUT $cloud->html_and_css; | ||||||
| 29 | close $OUT; | ||||||
| 30 | warn 'Tags: ',Dumper $cloud->tags; | ||||||
| 31 | exit; | ||||||
| 32 | |||||||
| 33 | =head1 DESCRIPTION | ||||||
| 34 | |||||||
| 35 | This modules produces a tag cloud with the heaviest words in the centre, | ||||||
| 36 | and the lightest on the outside, to make it appear a bit like the clouds | ||||||
| 37 | seen in the sky. | ||||||
| 38 | |||||||
| 39 | Words are accepted through L |
||||||
| 40 | add the heaviest word first, the lightest last. When the C or C |
||||||
| 41 | methods are called, the words are added to a grid in a simple spiral: this may | ||||||
| 42 | change to produce a prettier cloud, but it works well enough as it is. | ||||||
| 43 | |||||||
| 44 | Otherwise, it is API-compatible with L |
||||||
| 45 | that module is not required. For further details of this modules methods, | ||||||
| 46 | please see L |
||||||
| 47 | |||||||
| 48 | =head2 OUTPUT | ||||||
| 49 | |||||||
| 50 | Output is HTML and/or CSS. The HTML contains a C of class C |
||||||
| 51 | that contains one or more C of class C |
||||||
| 52 | C elements for each linked word. If words were supplied without links, | ||||||
| 53 | they are contained in C elements. | ||||||
| 54 | |||||||
| 55 | Colouring and font-sizing is contained in the C and C C\n"; | ||||||
| 170 | } | ||||||
| 171 | |||||||
| 172 | sub html { | ||||||
| 173 | 3 | 3 | 0 | 376 | my $self = shift; | ||
| 174 | 3 | 50 | 8 | $self->{limit} = $_[0] if $_[0]; | |||
| 175 | |||||||
| 176 | 3 | 3 | my $out = "\n "; |
||||
| 177 | 3 | 5 | my $blank = quotemeta BLANK; | ||||
| 178 | 3 | 38 | my $re = qr/^\s*$blank+\s*$/; | ||||
| 179 | |||||||
| 180 | 3 | 10 | $self->_build; | ||||
| 181 | |||||||
| 182 | 3 | 6 | for my $y (1..$self->{size_y} ){ | ||||
| 183 | 18 | 20 | my $row = ''; | ||||
| 184 | 18 | 26 | for my $x (1..$self->{size_x} ){ | ||||
| 185 | 108 | 100 | 66 | 563 | next if not defined $self->{grid}->[$x-1]->[$y-1] | ||
| 186 | or $self->{grid}->[$x-1]->[$y-1] eq BLANK; | ||||||
| 187 | 90 | 176 | $row .= "\t" . $self->{grid}->[$x-1]->[$y-1]->html ."\n"; | ||||
| 188 | } | ||||||
| 189 | 18 | 100 | 66 | 126 | $out .= "\n \n" . $row . " \n" |
||
| 190 | unless $row eq '' or $row =~ /$re/s; | ||||||
| 191 | } | ||||||
| 192 | |||||||
| 193 | 3 | 4 | $out .= "\n"; | ||||
| 194 | 3 | 33 | return $out; | ||||
| 195 | } | ||||||
| 196 | |||||||
| 197 | # Move into sub html | ||||||
| 198 | sub tags { | ||||||
| 199 | 1 | 1 | 0 | 1 | my $self = shift; | ||
| 200 | 1 | 50 | 5 | $self->{limit} = $_[0] if $_[0]; | |||
| 201 | 1 | 50 | 3 | $self->_build unless $self->{inputs}; | |||
| 202 | 1 | 4 | my $c = 0; | ||||
| 203 | |||||||
| 204 | 1 | 3 | my $t = scalar( @{ $self->{words} } ); | ||||
| 1 | 2 | ||||||
| 205 | 1 | 2 | my @rv; | ||||
| 206 | 1 | 2 | my $blank = quotemeta BLANK; | ||||
| 207 | 1 | 19 | my $re = qr/^$blank+$/; | ||||
| 208 | 1 | 3 | for my $y (1..$self->{size_y} ){ | ||||
| 209 | 6 | 11 | for my $x (1..$self->{size_x} ){ | ||||
| 210 | 36 | 100 | 66 | 162 | next if not defined $self->{grid}->[$x-1]->[$y-1] | ||
| 211 | or $self->{grid}->[$x-1]->[$y-1] eq BLANK; | ||||||
| 212 | 30 | 43 | my $w = $self->{grid}->[$x-1]->[$y-1]; | ||||
| 213 | 30 | 175 | push @rv, { | ||||
| 214 | %$w, | ||||||
| 215 | count => $t - $c, | ||||||
| 216 | level => $c, | ||||||
| 217 | }; | ||||||
| 218 | 30 | 55 | $c ++; | ||||
| 219 | } | ||||||
| 220 | } | ||||||
| 221 | |||||||
| 222 | 1 | 9 | return @rv; | ||||
| 223 | } | ||||||
| 224 | |||||||
| 225 | |||||||
| 226 | sub _prepare { | ||||||
| 227 | 3 | 3 | 5 | my $self = shift; | |||
| 228 | 3 | 8 | die "No words from which to create a cloud - see add(...)." | ||||
| 229 | 3 | 50 | 50 | 10 | unless $self->{words} and scalar @{ $self->{words} }; | ||
| 230 | |||||||
| 231 | # Custom size does not work yet | ||||||
| 232 | #if (not $self->{size_x} and not $self->{size_y}){ | ||||||
| 233 | 3 | 3 | $self->{size_y} = $self->{size_x} = int( sqrt(scalar @{$self->{words}})) +1; | ||||
| 3 | 17 | ||||||
| 234 | #} | ||||||
| 235 | |||||||
| 236 | 3 | 3 | $self->{inputs} = [@{ $self->{words} }]; | ||||
| 3 | 34 | ||||||
| 237 | 3 | 7 | $self->{grid} = []; | ||||
| 238 | 3 | 10 | $self->{tags} = []; # HTML::TagCloud API | ||||
| 239 | |||||||
| 240 | 3 | 100 | 12 | $self->{size_max_pc} ||= 120; | |||
| 241 | 3 | 66 | 11 | $self->{size_min_pc} ||= $self->{size_max_pc} / 2; | |||
| 242 | |||||||
| 243 | $self->{scale_code} ||= sub { | ||||||
| 244 | 3 | 3 | 5 | ($self->{size_max_pc} - $self->{size_min_pc}) / scalar @{$self->{words}}; | |||
| 3 | 8 | ||||||
| 245 | 3 | 100 | 12 | }; | |||
| 246 | |||||||
| 247 | 3 | 6 | $self->{scale_f} = $self->{scale_code}->($self); | ||||
| 248 | |||||||
| 249 | 3 | 7 | for my $y (1..$self->{size_y}){ | ||||
| 250 | 18 | 23 | $self->{grid}->[$y-1] = []; | ||||
| 251 | 18 | 21 | for my $x (1..$self->{size_x}){ | ||||
| 252 | 108 | 148 | $self->{grid}->[$y-1]->[$x-1] = BLANK; | ||||
| 253 | } | ||||||
| 254 | } | ||||||
| 255 | |||||||
| 256 | # If inputs supplied as words: | ||||||
| 257 | 3 | 3 | foreach my $w (@{ $self->{inputs} } ){ | ||||
| 3 | 6 | ||||||
| 258 | 90 | 50 | 145 | if (not ref $w){ | |||
| 259 | 0 | 0 | $w = new HTML::TagCloud::Centred::Word( %$w ); | ||||
| 260 | 0 | 0 | 0 | $w->{html_esc_code} = $self->{html_esc_code} if $self->{html_esc_code}; | |||
| 261 | } | ||||||
| 262 | } | ||||||
| 263 | |||||||
| 264 | # For API of HTML::TagCloud | ||||||
| 265 | 3 | 50 | 8 | if (exists $self->{limit}){ | |||
| 266 | 0 | 0 | $self->{inputs} = [ | ||||
| 267 | 0 | 0 | @{ $self->{inputs} } [ 0 .. $self->{limit} -1 ] | ||||
| 268 | ]; | ||||||
| 269 | } | ||||||
| 270 | |||||||
| 271 | 3 | 5 | return $self; | ||||
| 272 | } | ||||||
| 273 | |||||||
| 274 | |||||||
| 275 | # Naive spiral - 1,1,2,2,3,3,..N,N. Replace! | ||||||
| 276 | sub _build { | ||||||
| 277 | 3 | 3 | 4 | my $self = shift; | |||
| 278 | 3 | 7 | $self->_prepare; | ||||
| 279 | 3 | 5 | my $x = int ($self->{size_x} / 2); # Centre starting position | ||||
| 280 | 3 | 5 | my $y = int ($self->{size_y} / 2); # Centre starting position | ||||
| 281 | 3 | 14 | my @d = ( # Direction of turns | ||||
| 282 | [1, 0], | ||||||
| 283 | [0, 1], | ||||||
| 284 | [-1, 0], | ||||||
| 285 | [0, -1] | ||||||
| 286 | ); | ||||||
| 287 | 3 | 4 | my $tside = 0; # Total sides so far | ||||
| 288 | 3 | 4 | my $cside = 0; # Current side, index to @d | ||||
| 289 | 3 | 5 | my $length = 1; # Length of current side | ||||
| 290 | |||||||
| 291 | 3 | 3 | my @clrs; # Color palette if requested | ||||
| 292 | 3 | 50 | 6 | if ($Color::Spectrum::VERSION){ | |||
| 293 | 0 | 0 | @clrs = Color::Spectrum::generate( | ||||
| 294 | 0 | 0 | scalar( @{ $self->{inputs} } ), | ||||
| 295 | $self->{clr_max}, | ||||||
| 296 | $self->{clr_min} | ||||||
| 297 | ); | ||||||
| 298 | } | ||||||
| 299 | |||||||
| 300 | 3 | 9 | while (@{ $self->{inputs} } ){ | ||||
| 33 | 81 | ||||||
| 301 | 30 | 41 | my $add_x = ($length * $d[ $cside ]->[0] ); | ||||
| 302 | 30 | 33 | my $add_y = ($length * $d[ $cside ]->[1] ); | ||||
| 303 | |||||||
| 304 | 30 | 50 | 83 | $self->_create_side( | |||
| 305 | from_x => $x, | ||||||
| 306 | from_y => $y, | ||||||
| 307 | to_x => $x + $add_x, | ||||||
| 308 | to_y => $y + $add_y, | ||||||
| 309 | (@clrs? (clrs => \@clrs) : ()), | ||||||
| 310 | ); | ||||||
| 311 | |||||||
| 312 | 30 | 41 | $x += $add_x; | ||||
| 313 | 30 | 26 | $y += $add_y; | ||||
| 314 | |||||||
| 315 | 30 | 83 | DEBUG "For $tside $cside, X $x, Y $y \n\tadd to x $add_x; add to y $add_y \n"; | ||||
| 316 | |||||||
| 317 | # Increase length every second side | ||||||
| 318 | 30 | 100 | 74 | $length += 1 if $cside % 2; | |||
| 319 | |||||||
| 320 | # Next side | ||||||
| 321 | 30 | 100 | 48 | if (++$cside == 4){ | |||
| 322 | 6 | 9 | $cside = 0; | ||||
| 323 | } | ||||||
| 324 | |||||||
| 325 | 30 | 30 | $tside++; | ||||
| 326 | } | ||||||
| 327 | } | ||||||
| 328 | |||||||
| 329 | sub _create_side { | ||||||
| 330 | 30 | 50 | 30 | 108 | my ($self, $args) = (shift, ref($_[0])? shift : {@_}); | ||
| 331 | 30 | 42 | my ($from_x, $from_y, $to_x, $to_y); | ||||
| 332 | |||||||
| 333 | 30 | 100 | 52 | if ($args->{from_x} > $args->{to_x}){ | |||
| 334 | 6 | 7 | $from_x = $args->{to_x}; | ||||
| 335 | 6 | 7 | $to_x = $args->{from_x}; | ||||
| 336 | } else { | ||||||
| 337 | 24 | 27 | $from_x = $args->{from_x}; | ||||
| 338 | 24 | 28 | $to_x = $args->{to_x}; | ||||
| 339 | } | ||||||
| 340 | |||||||
| 341 | 30 | 100 | 45 | if ($args->{from_y} > $args->{to_y}){ | |||
| 342 | 6 | 6 | $from_y = $args->{to_y}; | ||||
| 343 | 6 | 7 | $to_y = $args->{from_y}; | ||||
| 344 | } else { | ||||||
| 345 | 24 | 26 | $from_y = $args->{from_y}; | ||||
| 346 | 24 | 27 | $to_y = $args->{to_y}; | ||||
| 347 | } | ||||||
| 348 | |||||||
| 349 | 30 | 78 | DEBUG "From X $from_x -> $to_x;From Y $from_y -> $to_y"; | ||||
| 350 | WORDS: | ||||||
| 351 | 30 | 50 | for my $x ($from_x .. $to_x){ | ||||
| 352 | 75 | 87 | for my $y ($from_y .. $to_y){ | ||||
| 353 | # TRACE $x-1, ', ', $y-1; | ||||||
| 354 | 120 | 50 | 234 | next if not $self->{grid}->[ $x-1 ]->[ $y-1 ]; | |||
| 355 | 120 | 100 | 280 | next if $self->{grid}->[ $x-1 ]->[ $y-1 ] ne BLANK; | |||
| 356 | 93 | 100 | 82 | last WORDS if not @{ $self->{inputs} }; | |||
| 93 | 186 | ||||||
| 357 | 90 | 78 | my $word = shift @{ $self->{inputs} }; | ||||
| 90 | 116 | ||||||
| 358 | 90 | 237 | DEBUG " set $x $y = $word->{name}"; | ||||
| 359 | 90 | 50 | 169 | $word->{clr} = $args->{clr} if $args->{clr}; | |||
| 360 | 90 | 102 | $word->{x} = $x-1; | ||||
| 361 | 90 | 101 | $word->{y} = $y-1; | ||||
| 362 | 90 | 100 | $word->{size} = int $self->{size_min_pc} + ( $self->{scale_f} * (1 + scalar @{ $self->{inputs} })); | ||||
| 90 | 152 | ||||||
| 363 | 90 | 50 | 155 | $word->{clr} = shift( @{$args->{clrs}}) if $args->{clrs}; | |||
| 0 | 0 | ||||||
| 364 | 90 | 219 | $self->{grid}->[ $x-1 ]->[ $y-1 ] = $word; | ||||
| 365 | } | ||||||
| 366 | } | ||||||
| 367 | } | ||||||
| 368 | |||||||
| 369 | |||||||
| 370 | package HTML::TagCloud::Centred::Word; | ||||||
| 371 | 1 | 1 | 6 | use base 'HTML::TagCloud::Centred::Base'; | |||
| 1 | 2 | ||||||
| 1 | 624 | ||||||
| 372 | |||||||
| 373 | sub _init { | ||||||
| 374 | 30 | 30 | 29 | my $self = shift; | |||
| 375 | $self->{html_esc_code} ||= sub { | ||||||
| 376 | 90 | 50 | 90 | 1220 | if (require CGI::Util){ return CGI::Util::escape(shift)} | ||
| 90 | 4608 | ||||||
| 377 | 0 | 0 | return shift; | ||||
| 378 | 30 | 50 | 148 | }; | |||
| 379 | 30 | 50 | 74 | die "No 'name'?" if not defined $self->{name}; | |||
| 380 | } | ||||||
| 381 | |||||||
| 382 | sub html { | ||||||
| 383 | 90 | 90 | 83 | my $self = shift; | |||
| 384 | 90 | 86 | my $ctag = 'span'; | ||||
| 385 | 90 | 77 | my $otag = $ctag; | ||||
| 386 | 90 | 234 | my $name = $self->{html_esc_code}->( $self->{name} ); | ||||
| 387 | 90 | 100 | 703 | if (defined $self->{url}){ | |||
| 388 | 6 | 6 | $ctag = 'a'; | ||||
| 389 | 6 | 14 | $otag = "a href='$self->{url}' title='$name'"; | ||||
| 390 | } | ||||||
| 391 | 90 | 50 | 142 | my $clr = defined($self->{clr})? 'color:'.$self->{clr} : ''; | |||
| 392 | 90 | 332 | return "<$otag style='$clr; font-size:$self->{size}%'>$name$ctag>"; | ||||
| 393 | } | ||||||
| 394 | |||||||
| 395 | 1; | ||||||
| 396 | |||||||
| 397 | =head1 SEE ALSO | ||||||
| 398 | |||||||
| 399 | L |
||||||
| 400 | |||||||
| 401 | =head1 AUTHOR AND COPYRIGHT | ||||||
| 402 | |||||||
| 403 | Copyright (C) Lee Goddard, 2010-2011. All Rights Reserved. | ||||||
| 404 | |||||||
| 405 | This distribution is made available under the same terms as Perl. | ||||||
| 406 | |||||||
| 407 |