File Coverage

lib/Module/Dependency/Grapher.pm
Criterion Covered Total %
statement 101 366 27.6
branch 32 128 25.0
condition 22 62 35.4
subroutine 7 22 31.8
pod 6 8 75.0
total 168 586 28.6


line stmt bran cond sub pod time code
1             package Module::Dependency::Grapher;
2 2     2   38759 use strict;
  2         5  
  2         76  
3 2     2   480 use Module::Dependency::Info;
  2         4  
  2         110  
4              
5 2         41459 use vars qw/$VERSION @TIERS %LOOKUP %COLOURS
6             @numElements $colWidth $rowHeight
7             $nOffset $eOffset $sOffset $wOffset
8 2     2   11 /;
  2         12  
9              
10             $VERSION = (q$Revision: 6632 $ =~ /(\d+)/g)[0];
11              
12             %COLOURS = (
13             type => [ 0, 0, 0 ],
14             links => [ 164, 192, 255 ],
15             blob_to => [ 192, 0, 0 ],
16             blob_from => [ 0, 192, 0 ],
17             border => [ 192, 192, 192 ],
18             title1 => [ 64, 0, 0 ],
19             test => [ 255, 0, 0 ],
20             black => [ 0, 0, 0 ],
21             white => [ 255, 255, 255 ],
22             );
23              
24             ### PUBLIC INTERFACE FUNCTIONS
25              
26             sub setIndex {
27 1     1 1 7205 Module::Dependency::Info::setIndex(@_);
28             }
29              
30             sub makeText {
31 5     5 1 2415 my ( $kind, $seeds, $filename, $options ) = @_;
32 5         40 my ( $maxitems, $pushed ) =
33             _makeCols( $kind, $seeds, $options->{IncludeRegex}, $options->{ExcludeRegex} );
34 5   50     41 my $imgtitle = $options->{'Title'} || 'Dependency Tree';
35              
36             # print the text out
37 5         15 TRACE("Printing text to $filename");
38 5         17 local *TXT;
39 5 50       523 open( TXT, "> $filename" ) or die("Can't open $filename for text write: $!");
40 5         124 print TXT $imgtitle, "\n", ( '-' x length($imgtitle) ) . "\n\n";
41 5 100       114 print TXT q[Key: Parent> indicates parent dependencies
42             Child> are child dependencies
43             ****> indicates the item(s) from which the relationships are drawn
44              
45             ] unless $options->{'NoLegend'};
46 5 100       235 print( TXT "Grapher.pm $VERSION - " . localtime() . "\n\n" ) unless $options->{'NoVersion'};
47              
48 5         14 my $pref = 'Parent>';
49 5         14 for ( 0 .. $#TIERS ) {
50 21 100       52 if ( $_ == $pushed ) { $pref = '****>'; }
  5 100       10  
51 4         7 elsif ( $_ == $pushed + 1 ) { $pref = 'Child>'; }
52 21         27 printf( TXT "%8s %s %s\n", $pref, '+-', join( ', ', sort { $a cmp $b } @{ $TIERS[$_] } ) );
  40         106  
  21         81  
53 21 100       73 print( TXT " |\n" ) unless ( $_ == $#TIERS );
54             }
55 5         228 close TXT;
56             }
57              
58             sub makeHtml {
59 0     0 1 0 my ( $kind, $seeds, $filename, $options ) = @_;
60 0         0 my ( $maxitems, $pushed ) =
61             _makeCols( $kind, $seeds, $options->{IncludeRegex}, $options->{ExcludeRegex} );
62              
63 0         0 my %rowclasses = (
64             parent => 'MDGraphParent',
65             seed => 'MDGraphSeed',
66             child => 'MDGraphChild',
67             );
68              
69 0         0 my %notes = (
70             parent => 'Parent',
71             seed => '****',
72             child => 'Child',
73             );
74              
75 0   0     0 my $imgtitle = $options->{'Title'} || 'Dependency Tree';
76              
77             # print the HTML out
78 0         0 TRACE("Printing HTML to $filename");
79 0         0 local *HTML;
80 0 0       0 open( HTML, "> $filename" ) or die("Can't open $filename for HTML write: $!");
81 0         0 print HTML qq(

$imgtitle

\n);
82 0 0       0 print( HTML "

Grapher.pm $VERSION - " . localtime() . "

\n" )
83             unless $options->{'NoVersion'};
84 0 0       0 print HTML qq[Key:
$notes{'parent'} indicates parent dependencies
85             $notes{'seed'} indicates the item(s) from which the relationships are drawn
86             $notes{'child'} are child dependencies
\n\n] unless $options->{'NoLegend'};
87              
88 0         0 my $type = 'parent';
89 0         0 print HTML qq(\n); \n); \n"
90 0         0 print HTML qq(
KindItems
91 0         0 for ( 0 .. $#TIERS ) {
92 0 0       0 if ( $_ == $pushed ) { $type = 'seed'; }
  0 0       0  
93 0         0 elsif ( $_ == $pushed + 1 ) { $type = 'child'; }
94 0         0 print( HTML
95             qq(
$notes{$type}),
96 0         0 join( ', ', sort { $a cmp $b } @{ $TIERS[$_] } ),
  0         0  
97             "
98             );
99             }
100 0         0 print HTML "
\n\n";
101              
102             # create the imagemap
103 0         0 my $rv = 1;
104 0 0       0 if ( $options->{ImageMap} ) {
105 0   0     0 my $code = $options->{ImageMapCode} || \&_imgmapdefault;
106 0   0     0 my $frmt = $options->{HrefFormat} || '';
107 0         0 _imageDimsSet();
108 0 0       0 if ( $maxitems < 8 ) {
    0          
109 0         0 $rowHeight = 8 * $rowHeight * 1.5 / $maxitems;
110             }
111             elsif ( $maxitems < 16 ) {
112 0         0 $rowHeight = 16 * $rowHeight / $maxitems;
113             }
114 0         0 _packObjects( $rowHeight * $maxitems, 5 );
115 0         0 my $str = qq(\n);
116 0         0 foreach my $v ( values %LOOKUP ) { $str .= $code->( $v, $frmt ); }
  0         0  
117 0         0 $str .= qq(\n);
118              
119 0 0       0 if ( lc( $options->{ImageMap} ) eq 'print' ) {
120 0         0 print HTML $str;
121             }
122             else {
123 0         0 $rv = $str;
124             }
125             }
126              
127 0         0 close HTML;
128              
129 0         0 return $rv;
130             }
131              
132             sub _imgmapdefault {
133 0     0   0 my ( $v, $frmt ) = @_;
134 0         0 my $pack = $v->{'package'};
135 0         0 my $alt = "Root the dependency tree on '$pack'";
136 0         0 return qq( 137             . sprintf( $frmt, $pack )
138             . q(" shape="rect" coords=")
139             . int( $v->{'x'} - 3 ) . ','
140             . int( $v->{'y'} - 1 ) . ','
141             . int( $v->{'x2'} + 3 ) . ','
142             . int( $v->{'y'} + 9 )
143             . qq(" alt="$alt" title="$alt" />\n);
144             }
145              
146             sub makeImage {
147 0     0 1 0 require GD;
148 0         0 import GD;
149              
150 0         0 my ( $kind, $seeds, $filename, $options ) = @_;
151 0   0     0 my $type = uc( $options->{'Format'} ) || 'PNG';
152 0   0     0 my $imgtitle = $options->{'Title'} || 'Dependency Chart';
153              
154 0         0 my ( $maxitems, $pushed ) =
155             _makeCols( $kind, $seeds, $options->{IncludeRegex}, $options->{ExcludeRegex} );
156 0         0 _imageDimsSet();
157              
158 0         0 LOG("Making image to $filename");
159              
160 0 0       0 if ( $maxitems < 8 ) {
    0          
161 0         0 $rowHeight = 8 * $rowHeight * 1.5 / $maxitems;
162             }
163             elsif ( $maxitems < 16 ) {
164 0         0 $rowHeight = 16 * $rowHeight / $maxitems;
165             }
166              
167 0 0       0 my $imgWidth = $colWidth * ( scalar(@TIERS) < 3 ? 3 : scalar(@TIERS) );
168 0         0 my $imgHeight = $rowHeight * $maxitems;
169              
170 0         0 my $realImgWidth = $imgWidth + $wOffset + $eOffset;
171 0         0 my $realImgHeight = $imgHeight + $nOffset + $sOffset;
172 0         0 LOG("Rows are $rowHeight px, maxitems is $maxitems, image is $realImgWidth * $realImgWidth");
173              
174             # set up image object
175 0   0     0 my $im = new GD::Image( $imgWidth + $wOffset + $eOffset, $imgHeight + $nOffset + $sOffset )
176             || die("Couldn't build GD object: $!");
177 0         0 my $colours;
178 0         0 $im->colorAllocate( 255, 255, 255 );
179 0         0 while ( my ( $k, $v ) = each %COLOURS ) { $colours->{$k} = $im->colorAllocate(@$v); }
  0         0  
180              
181 0         0 _packObjects( $imgHeight, 5 ); # gdTinyFont has 5 pixel wide characters
182 0         0 _linkObjects( $im, $colours );
183 0         0 _labelObjects( $im, $colours );
184              
185             # add legend and prettiness
186 0         0 TRACE("Drawing legend etc");
187 0         0 $im->string( gdMediumBoldFont(), 5, 3, $imgtitle, $colours->{'title1'} );
188 0 0       0 $im->string( gdSmallFont(), 5, 17, "Grapher.pm $VERSION - " . localtime(),
189             $colours->{'title1'} )
190             unless $options->{'NoVersion'};
191              
192 0 0       0 _drawLegend( $im, $colours, $realImgWidth - 160 - $eOffset, 3 ) unless $options->{'NoLegend'};
193              
194 0         0 TRACE("Printing image");
195 0         0 local *IMG;
196 0 0       0 open( IMG, "> $filename" ) or die("Can't open $filename for image write: $!");
197 0         0 binmode(IMG);
198 0 0       0 if ( $type eq 'GIF' ) {
    0          
    0          
    0          
199 0         0 print IMG $im->gif;
200             }
201             elsif ( $type eq 'PNG' ) {
202 0         0 print IMG $im->png;
203             }
204             elsif ( $type eq 'JPG' ) {
205 0         0 print IMG $im->jpg;
206             }
207             elsif ( $type eq 'GD' ) {
208 0         0 print IMG $im->gd;
209             }
210 0         0 else { die("Unrecognized image type $type"); }
211 0         0 close IMG;
212             }
213              
214             # SVG has an origin at the top-left, like GD, and an SVG image can use unitless coordinates: so we can borrow a lot from makeImage()
215             sub makeSvg {
216 0     0 1 0 require SVG;
217 0         0 import SVG;
218              
219 0         0 my ( $kind, $seeds, $filename, $options ) = @_;
220 0   0     0 my $imgtitle = $options->{'Title'} || 'Dependency Chart';
221              
222 0         0 my ( $maxitems, $pushed ) =
223             _makeCols( $kind, $seeds, $options->{IncludeRegex}, $options->{ExcludeRegex} );
224 0         0 _imageDimsSet();
225              
226 0         0 LOG("Making SVG to $filename");
227              
228 0 0       0 if ( $maxitems < 8 ) {
    0          
229 0         0 $rowHeight = 8 * $rowHeight * 1.5 / $maxitems;
230             }
231             elsif ( $maxitems < 16 ) {
232 0         0 $rowHeight = 16 * $rowHeight / $maxitems;
233             }
234              
235 0 0       0 my $imgWidth = $colWidth * ( scalar(@TIERS) < 3 ? 3 : scalar(@TIERS) );
236 0         0 my $imgHeight = $rowHeight * $maxitems;
237              
238 0         0 my $realImgWidth = $imgWidth + $wOffset + $eOffset;
239 0         0 my $realImgHeight = $imgHeight + $nOffset + $sOffset;
240 0         0 LOG("Rows are $rowHeight px, maxitems is $maxitems, image is $realImgWidth * $realImgWidth");
241              
242 0         0 my $im = new SVG(
243             'viewBox' => (
244             '0 0 '
245             . ( $imgWidth + $wOffset + $eOffset ) . ' '
246             . ( $imgHeight + $nOffset + $sOffset )
247             ),
248             'preserveAspectRatio' => 'xMidYMid',
249             '-indent' => "\t"
250             );
251              
252             # set up image object
253 0         0 my $colours;
254 0         0 while ( my ( $k, $v ) = each %COLOURS ) {
255 0         0 $colours->{$k} = sprintf( '#%2.2x%2.2x%2.2x', @$v );
256             }
257              
258             $im->rectangle(
259 0         0 'x' => 0,
260             'y' => 0,
261             'width' => ( $imgWidth + $wOffset + $eOffset ),
262             'height' => ( $imgHeight + $nOffset + $sOffset ),
263             stroke => $colours->{'black'},
264             fill => 'none'
265             );
266 0         0 _packObjects( $imgHeight, 5 );
267 0         0 _linkObjects( $im, $colours );
268              
269             # are things clickable? Bit of a kludge, this
270 0         0 $colours->{'_HREF_FORMAT'} = $options->{'HrefFormat'};
271 0         0 _labelObjects( $im, $colours );
272 0         0 delete $colours->{'_HREF_FORMAT'};
273              
274             # add legend and prettiness
275 0         0 TRACE("Drawing legend etc");
276              
277 0         0 $im->text(
278             'x' => 5,
279             'y' => 12,
280             'fill' => $colours->{'title1'},
281             'style' => { 'font-size' => '12px' }
282             )->cdata($imgtitle);
283 0 0       0 $im->text(
284             'x' => 5,
285             'y' => 23,
286             'fill' => $colours->{'title1'},
287             'style' => { 'font-size' => '9px' }
288             )->cdata( "Grapher.pm $VERSION - " . localtime() )
289             unless $options->{'NoVersion'};
290 0 0       0 _drawLegend( $im, $colours, $realImgWidth - 160 - $eOffset, 3 ) unless $options->{'NoLegend'};
291              
292 0         0 $im->title( id => 'document-title' )->cdata($imgtitle);
293 0         0 $im->desc( id => 'document-desc' )
294             ->cdata('This image shows dependency relationships between perl programs and modules');
295              
296 0         0 TRACE("Printing SVG");
297 0         0 local *IMG;
298 0 0       0 open( IMG, "> $filename" ) or die("Can't open $filename for image write: $!");
299 0         0 print IMG $im->xmlify;
300 0         0 close IMG;
301             }
302              
303             sub makePs {
304 0     0 1 0 require PostScript::Simple;
305              
306 0         0 my ( $kind, $seeds, $filename, $options ) = @_;
307 0   0     0 my $imgtitle = $options->{'Title'} || 'Dependency Chart';
308 0 0       0 my $eps = ( uc( $options->{'Format'} ) eq 'PS' ) ? 0 : 1;
309 0 0       0 my $colour = exists( $options->{'Colour'} ) ? $options->{'Colour'} : 1;
310 0   0     0 my $font = $options->{'Font'} || 'Helvetica';
311              
312 0         0 my ( $maxitems, $pushed ) =
313             _makeCols( $kind, $seeds, $options->{IncludeRegex}, $options->{ExcludeRegex} );
314 0         0 _psDimsSet();
315              
316 0         0 LOG("Making postscript to $filename");
317              
318 0 0       0 if ( $maxitems < 8 ) {
    0          
319 0         0 $rowHeight = 8 * $rowHeight * 1.5 / $maxitems;
320             }
321             elsif ( $maxitems < 16 ) {
322 0         0 $rowHeight = 16 * $rowHeight / $maxitems;
323             }
324              
325 0 0       0 my $imgWidth = $colWidth * ( scalar(@TIERS) < 3 ? 3 : scalar(@TIERS) );
326 0         0 my $imgHeight = $rowHeight * $maxitems;
327              
328 0         0 my $realImgWidth = $imgWidth + $wOffset + $eOffset;
329 0         0 my $realImgHeight = $imgHeight + $nOffset + $sOffset;
330 0         0 LOG("Rows are $rowHeight px, maxitems is $maxitems, image is $realImgWidth * $realImgWidth");
331              
332 0   0     0 my $p = new PostScript::Simple(
333             eps => $eps,
334             colour => $colour,
335             clip => 1,
336             landscape => ( !$eps ),
337             xsize => $realImgWidth,
338             ysize => $realImgHeight,
339             units => 'bp'
340             ) # we use points because they're close to pixels, as used in GD
341             || die("Can't build Postscript object: $!");
342 0         0 $p->setlinewidth(0.5);
343 0         0 $p->setfont( $font, 9 );
344              
345 0         0 _packObjects( $imgHeight, 5.5 );
346 0         0 _linkObjects($p);
347 0         0 $p->setcolour( @{ $COLOURS{'type'} } );
  0         0  
348 0         0 _labelObjects($p);
349              
350             # add legend and prettiness
351 0         0 TRACE("Drawing legend etc");
352 0 0       0 _drawPsLegend( $p, $realImgWidth - 160 - $eOffset, 16 ) unless $options->{'NoLegend'};
353              
354 0         0 $p->setfont( $font, 16 );
355 0         0 $p->setcolour( @{ $COLOURS{'title1'} } );
  0         0  
356 0         0 $p->text( 15, 18, $imgtitle );
357              
358 0         0 $p->setfont( $font, 12 );
359 0         0 $p->setcolour( @{ $COLOURS{'title1'} } );
  0         0  
360 0 0       0 $p->text( 15, 35, "Grapher.pm $VERSION - " . localtime() ) unless $options->{'NoVersion'};
361              
362             # $p->setcolour( @{$COLOURS{'test'}} ); $p->line( 0, $nOffset, $realImgWidth, $nOffset); $p->line( 0, $realImgHeight-$sOffset, $realImgWidth, $realImgHeight-$sOffset); $p->line( $wOffset, 0, $wOffset, $realImgHeight); $p->line( $realImgWidth-$eOffset, 0, $realImgWidth-$eOffset, $realImgHeight);
363              
364 0         0 TRACE("Printing image");
365 0         0 $p->output($filename);
366             }
367              
368             ### PRIVATE INTERNAL ROUTINES
369              
370             # algorithm which sorts dependencies into a series of generations (the @TIERS array)
371             sub _makeCols {
372 5     5   9 my $kind = shift();
373 5         7 my $seeds = shift();
374 5   50     215 my $re = shift() || '';
375 5   50     31 my $xre = shift() || '';
376              
377 5         14 $kind = uc($kind);
378 5         33 TRACE("makeCols: kind <$kind> re <$re> xre <$xre>");
379 5 50       22 unless ( ref($seeds) ) { $seeds = [$seeds]; }
  5         13  
380 5 50 100     44 unless ( $kind eq 'CHILD' || $kind eq 'PARENT' || $kind eq 'BOTH' ) {
      66        
381 0         0 die("unrecognized sort of tree required: $kind - should be 'child', 'parent' or 'both'");
382             }
383              
384 5         18 @TIERS = ();
385 5         25 my %seen = ();
386              
387             # this entry is where we start the tree discovery off from
388 5         12 my $seedrow = [@$seeds];
389 5         8 push( @TIERS, $seedrow );
390              
391 5         7 my $found = 0;
392 5         47 my $ptr = 0;
393              
394             # get child dependencies
395 5 100 100     28 if ( $kind eq 'CHILD' || $kind eq 'BOTH' ) {
396 4         11 TRACE("makeCols: child dependencies");
397 4         5 do {
398 12         14 $found = 0;
399 12         20 my $temp = [];
400 12         13 foreach ( @{ $TIERS[$ptr] } ) {
  12         25  
401 28         66 my $obj = Module::Dependency::Info::getItem($_);
402 28 100       79 next unless $obj->{filename};
403 16         28 $LOOKUP{$_} = $obj;
404 16         23 $seen{$_} = 1;
405 16         37 TRACE("...for $obj->{'package'}");
406              
407 16         21 foreach my $dep ( @{ $obj->{'depends_on'} } ) {
  16         35  
408 28 100       65 next if $seen{$dep};
409 24 50 33     118 if ( ( $re && $dep !~ m/$re/ ) || ( $xre && $dep =~ m/$xre/ ) )
      33        
      33        
410             { # if given regexps then apply filter
411 0         0 TRACE(" !..$dep skipped by regex");
412 0         0 $seen{$dep} = 1;
413 0         0 next;
414             }
415 24         57 TRACE(" ...found $dep");
416             $LOOKUP{$dep} = Module::Dependency::Info::getItem($dep)
417 24   33     50 || do { $seen{$dep} = 1; next; };
418 24         46 push( @$temp, $dep );
419 24         38 $seen{$dep} = 1;
420 24         59 $found = 1;
421             }
422             }
423 12 100       32 push( @TIERS, $temp ) if $found;
424 12         32 $ptr++;
425             } while ( $found == 1 );
426             }
427              
428 5         6 my $pushed = 0;
429              
430             # get parent dependencies
431 5 100 100     28 if ( $kind eq 'PARENT' || $kind eq 'BOTH' ) {
432 4         9 TRACE("makeCols: parent dependencies");
433 4         4 do {
434 12         15 $found = 0;
435 12         20 my $temp = [];
436 12         16 foreach ( @{ $TIERS[0] } ) {
  12         23  
437 24         51 my $obj = Module::Dependency::Info::getItem($_);
438 24 50       56 next unless $obj->{filename};
439 24         39 $LOOKUP{$_} = $obj;
440 24         31 $seen{$_} = 1;
441 24         59 TRACE("...for $obj->{'package'}");
442              
443 24         30 foreach my $dep ( @{ $obj->{'depended_upon_by'} } ) {
  24         59  
444 32 100       82 next if $seen{$dep};
445 20 50 33     96 if ( ( $re && $dep !~ m/$re/ ) || ( $xre && $dep =~ m/$xre/ ) )
      33        
      33        
446             { # if given regexps then apply filter
447 0         0 TRACE(" !..$dep skipped by regex");
448 0         0 $seen{$dep} = 1;
449 0         0 next;
450             }
451 20         50 TRACE(" ...found $dep");
452             $LOOKUP{$dep} = Module::Dependency::Info::getItem($dep)
453 20   33     44 || do { $seen{$dep} = 1; next; };
454 20         32 push( @$temp, $dep );
455 20         37 $seen{$dep} = 1;
456 20         40 $found = 1;
457             }
458             }
459 12 100       33 if ($found) {
460 8         13 unshift( @TIERS, $temp );
461 8         22 $pushed += 1;
462             }
463             } while ( $found == 1 );
464             }
465              
466             # extract sizes of each column
467 5         9 @numElements = ();
468 5         8 my $maxitems = 1;
469 5         9 foreach (@TIERS) {
470 21         23 my $num = $#{$_} + 1;
  21         32  
471 21 100       47 $maxitems = $num if $num > $maxitems;
472 21         34 push( @numElements, $num );
473             }
474 5         24 return ( $maxitems, $pushed );
475             }
476              
477             # work out _where_ we're going to put the items
478             sub _packObjects {
479 0     0   0 my ( $imgHeight, $charwidth ) = @_;
480 0         0 TRACE("Packing objects");
481 0         0 for my $x ( 0 .. $#TIERS ) {
482 0         0 my $y = 0;
483 0         0 foreach ( sort { $a cmp $b } @{ $TIERS[$x] } ) {
  0         0  
  0         0  
484 0         0 my $obj = $LOOKUP{$_};
485 0         0 my $cx = ( $colWidth * $x ) + $wOffset;
486 0         0 my $cy = ( ( $imgHeight * ( $y + 1 ) ) / ( $numElements[$x] + 1 ) ) + $nOffset;
487              
488             # TRACE( "Putting text $obj->{'package'} at $cx, $cy" );
489             # use the first, i.e. highest up the food chain, coordinates only
490 0 0       0 unless ( exists $obj->{'x'} ) {
491 0         0 $obj->{'x'} = $cx;
492 0         0 $obj->{'y'} = $cy;
493 0         0 $obj->{'x2'} =
494             $cx + 1 + $charwidth * length( $obj->{'package'} )
495             ; # gdTinyFont has characters 5 pixels wide
496             }
497 0         0 $y++;
498             }
499             }
500             }
501              
502             sub _linkObjects {
503 0     0   0 my ( $im, $colours ) = @_;
504              
505             # draw a load of lines...
506 0         0 TRACE("Drawing links between items");
507 0         0 foreach my $x (@TIERS) {
508              
509             #...for every object
510 0         0 foreach (@$x) {
511 0         0 my $obj = $LOOKUP{$_};
512              
513             #...link to all its dependencies
514 0         0 foreach my $dep ( @{ $obj->{'depends_on'} } ) {
  0         0  
515 0 0       0 next unless ( exists $LOOKUP{$dep} );
516 0         0 my $depObj = $LOOKUP{$dep};
517 0         0 TRACE( $obj->{'package'} . ' -> ' . $depObj->{'package'} );
518 0         0 _drawLink( $im, $colours, $obj->{'x2'}, $obj->{'y'}, $depObj->{'x'},
519             $depObj->{'y'} );
520             }
521             }
522             }
523             }
524              
525             sub _labelObjects {
526 0     0   0 my ( $p, $colours ) = @_;
527 0         0 TRACE("Drawing the text");
528 0         0 foreach my $x (@TIERS) {
529 0         0 foreach (@$x) {
530 0         0 my $obj = $LOOKUP{$_};
531 0         0 _drawText( $p, $colours, $obj->{'x'}, $obj->{'y'}, $obj->{'package'} );
532             }
533             }
534             }
535              
536             # ! behaves differently for each image type
537             sub _drawLegend {
538 0     0   0 my ( $im, $colours, $x, $y ) = @_;
539 0         0 my $type = ref($im);
540              
541 0 0       0 if ( $type =~ m/^GD/ ) {
    0          
542 0         0 $im->rectangle( $x, $y, $x + 138, $y + 37, $colours->{'border'} );
543             }
544             elsif ( $type =~ m/SVG/ ) {
545 0         0 $im->rectangle(
546             'x' => $x,
547             'y' => $y,
548             'width' => 138,
549             'height' => 37,
550             stroke => 'none',
551             stroke => $colours->{'border'},
552             fill => 'none'
553             );
554             }
555 0         0 $x += 4;
556 0         0 $y += 3;
557              
558 0         0 _drawText( $im, $colours, $x, $y, 'Legend' );
559 0 0       0 if ( $type =~ m/^GD/ ) {
    0          
560 0         0 $im->line( $x, $y + 8, $x + 30, $y + 8, $colours->{'type'} );
561             }
562             elsif ( $type =~ m/SVG/ ) {
563 0         0 $im->line(
564             x1 => $x,
565             y1 => $y + 8,
566             x2 => $x + 30,
567             y2 => $y + 8,
568             stroke => $colours->{'type'}
569             );
570             }
571 0         0 $y += 12;
572 0         0 _drawLink( $im, $colours, $x + 31, $y, 100 + $x, $y );
573 0         0 _drawText( $im, $colours, $x, $y, 'Foo.pl' );
574 0         0 _drawText( $im, $colours, 100 + $x, $y, 'Bar' );
575 0         0 $y += 12;
576 0         0 _drawText( $im, $colours, $x, $y, 'Foo.pl depends upon Bar.pm' );
577             }
578              
579             sub _drawPsLegend {
580 0     0   0 my ( $p, $x, $y ) = @_;
581              
582 0         0 _drawText( $p, undef, $x + 2, $y + 26, 'Legend' );
583 0         0 $p->setlinewidth(0.4);
584 0         0 $p->line( $x + 2, $y + 25, $x + 32, $y + 25 );
585 0         0 _drawText( $p, undef, $x + 2, $y + 14, 'Foo.pl' );
586 0         0 _drawText( $p, undef, $x + 102, $y + 14, 'Bar' );
587 0         0 _drawText( $p, undef, $x + 2, $y + 2, 'Foo.pl depends upon Bar.pm' );
588 0         0 _drawLink( $p, undef, $x + 29, $y + 14, $x + 102, $y + 14 );
589              
590 0         0 $p->setlinewidth(0.25);
591 0         0 $p->setcolour( @{ $COLOURS{'black'} } );
  0         0  
592 0         0 $p->box( $x, $y - 1, $x + 120, $y + 34 );
593             }
594              
595             # ! behaves differently for each image type
596             sub _drawText {
597 0     0   0 my ( $im, $colours, $x, $y, $text ) = @_;
598 0         0 my $type = ref($im);
599              
600             # TRACE("_drawText for $type");
601              
602 0 0       0 if ( $type =~ m/^GD/ ) {
    0          
    0          
603 0         0 $im->string( gdTinyFont(), $x, $y, $text, $colours->{'type'} );
604             }
605             elsif ( $type =~ m/^PostScript/ ) {
606 0         0 $im->text( $x, $y, $text );
607             }
608             elsif ( $type =~ m/^SVG/ ) {
609 0 0       0 if ( $colours->{'_HREF_FORMAT'} ) {
610 0         0 $im->anchor( -href => sprintf( $colours->{'_HREF_FORMAT'}, $text ) )->text(
611             'x' => $x,
612             'y' => $y + 5.5,
613             'fill' => $colours->{'type'},
614             'style' => { 'font-size' => '8px', 'font-family' => 'Courier, Monaco, monospaced' }
615             )->cdata($text);
616             }
617             else {
618 0         0 $im->text(
619             'x' => $x,
620             'y' => $y + 5.5,
621             'fill' => $colours->{'type'},
622             'style' => { 'font-size' => '8px', 'font-family' => 'Courier, Monaco, monospaced' }
623             )->cdata($text);
624             }
625             }
626             }
627              
628             # ! behaves differently for each image type
629             sub _drawLink {
630 0     0   0 my ( $im, $colours, $xa, $ya, $xb, $yb ) = @_;
631 0         0 my $type = ref($im);
632              
633             # TRACE("_drawLink for $type");
634              
635 0 0       0 if ( $type =~ m/^GD/ ) {
    0          
    0          
636 0         0 $im->line( $xa, $ya + 3, $xb - 3, $yb + 3, $colours->{'links'} );
637 0         0 $im->rectangle( $xa, $ya + 2, $xa + 1, $ya + 4, $colours->{'blob_from'} );
638 0         0 $im->rectangle( $xb - 3, $yb + 2, $xb - 4, $yb + 4, $colours->{'blob_to'} );
639             }
640             elsif ( $type =~ m/^PostScript/ ) {
641 0         0 $im->setlinewidth(0.22);
642 0         0 $im->line( $xa, $ya + 3, $xb - 3, $yb + 3, @{ $COLOURS{'black'} } );
  0         0  
643 0         0 $im->setcolour( @{ $COLOURS{'white'} } );
  0         0  
644 0         0 $im->circle( $xb - 3, $yb + 3, 1, 1 );
645 0         0 $im->setcolour( @{ $COLOURS{'black'} } );
  0         0  
646 0         0 $im->circle( $xa, $ya + 3, 1, 1 );
647 0         0 $im->circle( $xb - 3, $yb + 3, 1, 0 );
648             }
649             elsif ( $type =~ m/^SVG/ ) {
650 0         0 $im->line(
651             x1 => $xa,
652             y1 => $ya + 3,
653             x2 => $xb - 3,
654             y2 => $yb + 3,
655             stroke => $colours->{'links'}
656             );
657 0         0 $im->rectangle(
658             'x' => $xa,
659             'y' => $ya + 2,
660             'width' => 2,
661             'height' => 2,
662             stroke => 'none',
663             fill => $colours->{'blob_from'}
664             );
665 0         0 $im->rectangle(
666             'x' => $xb - 4,
667             'y' => $yb + 2,
668             'width' => 2,
669             'height' => 2,
670             stroke => 'none',
671             fill => $colours->{'blob_to'}
672             );
673             }
674             else {
675 0         0 die 'This indicates that the object model has changed somewhere. Should not happen.';
676             }
677             }
678              
679             sub _imageDimsSet {
680 0     0   0 $colWidth = 200;
681 0         0 $rowHeight = 12;
682              
683 0         0 $nOffset = 40;
684 0         0 $sOffset = 10;
685 0         0 $wOffset = 20;
686 0         0 $eOffset = 1;
687             }
688              
689             sub _psDimsSet {
690 0     0   0 $colWidth = 150;
691 0         0 $rowHeight = 12;
692              
693 0         0 $nOffset = 60;
694 0         0 $sOffset = 40;
695 0         0 $wOffset = 40;
696 0         0 $eOffset = 30;
697             }
698              
699 102     102 0 128 sub TRACE { }
700 0     0 0   sub LOG { }
701              
702             1;
703              
704             =head1 NAME
705              
706             Module::Dependency::Grapher - creates visual dependency charts and accessible text versions
707              
708             =head1 SYNOPSIS
709              
710             use Module::Dependency::Grapher;
711             Module::Dependency::Grapher::setIndex( '/var/tmp/dependence/unified.dat' );
712             Module::Dependency::Grapher::makeImage( 'both', ['Foo::Bar', 'Foo::Baz'], '/home/www/foodep.png', {Format => 'png'} );
713             Module::Dependency::Grapher::makePs( 'both', ['Foo::Bar', 'Foo::Baz'], '/home/www/foodep.eps' );
714             Module::Dependency::Grapher::makeText( 'both', ['Foo::Bar', 'Foo::Baz'], '/home/www/foodep.txt', {NoLegend => 1} );
715             Module::Dependency::Grapher::makeHtml( 'both', ['Foo::Bar', 'Foo::Baz'], '/home/www/foodep.ssi', {NoLegend => 1} );
716              
717             =head1 DESCRIPTION
718              
719             =over 4
720              
721             =item Module::Dependency::Grapher::setIndex( $filename );
722              
723             This tells the module where the database is. It doesn't affect the other
724             modules - they have their own setIndex routines. The default is /var/tmp/dependence/unified.dat
725              
726             =item Module::Dependency::Grapher::makeImage( $kind, $seeds, $filename, $options );
727              
728             Draws an image showing the dependency links between a set of items. The 'tree' of dependencies is
729             started at the item or items named in the $seeds array reference. The code then links to all
730             the parent and/or child dependencies of those seeds. And repeat for those items, etc.
731              
732             $kind is 'parent', 'child' or 'both'. This parameter tells the code whether to plot (respectively)
733             things that depend upon the seed items, things that the seed items depend upon, or both directions.
734              
735             $seeds is a reference to an array of item names
736              
737             $filename is the file to which the output should go. Use '-' for STDOUT. Clobbers existing files.
738              
739             See below for the options. See README.EXAMPLES too.
740              
741             =item Module::Dependency::Grapher::makePs( $kind, $seeds, $filename, $options );
742              
743             As makeImage() but does it in PostScript or EPS. EPS is the default. See below for the options. See README.EXAMPLES too.
744              
745             =item Module::Dependency::Grapher::makeSvg( $kind, $seeds, $filename, $options );
746              
747             As makeImage() but does it in SVG. See below for the options. See README.EXAMPLES too.
748              
749             =item Module::Dependency::Grapher::makeText( $kind, $seeds, $filename, $options );
750              
751             Creates a plain-text rendition of the dependency heirarchy. As it's only ASCII it can't plot
752             the individual links between items, so it simplifies and presents only each level of the
753             tree as a whole.
754              
755             Parameters are as for makeImage()
756              
757             See below for options. See README.EXAMPLES too.
758              
759             =item Module::Dependency::Grapher::makeHtml( $kind, $seeds, $filename, $options );
760              
761             Creates an HTML fragment rendition of the dependency heirarchy. As it's only text it can't plot
762             the individual links between items, so it simplifies and presents only each level of the
763             tree. Information comes out in a table, and the whole fragment uses CLASS attributes so that you
764             can apply CSS to it. Typical fragment is:
765              
766            

Dependencies for all scripts

767            

Grapher.pm 1.7 - Fri Jan 11 00:00:56 2002

768             Key:
Parent indicates parent dependencies
769             **** indicates the item(s) from which the relationships are drawn
770             Child are child dependencies
771              
772            
773            
KindItems
774            
****x.pl, y.pl
775            
Childa, b, c
776            
777            
778              
779             Parameters are as for makeImage().
780              
781             See below for options - especially the ImageMap (and related) options, which allows this method to return an HTML client-side
782             imagemap. See README.EXAMPLES too.
783              
784             =back
785              
786             =head2 OPTIONS
787              
788             Options are case-sensitive, and you pass them in as a hash reference, e.g.
789              
790             Module::Dependency::Grapher::makeImage( $kind, $objlist, $IMGFILE, {Title => $title, Format => 'GIF'} );
791              
792             These are the recognized options:
793              
794             =over 4
795              
796             =item Title
797              
798             Sets the title of the output to whatever string you want. Displayed at the top.
799              
800             =item Format
801              
802             The output image format - can be (case-insensitive) GIF, PNG, GD, or JPG - but some may not be available
803             depending on how your local copy of libgd was compiled. You'll need to examine you local GD setup (PNG is
804             pretty standard thesedays though) Default is PNG.
805              
806             The makePs() method recognizes only 'EPS' or 'PS' as format options. Default is 'EPS'.
807              
808             =item IncludeRegex
809              
810             A regular expression use to filter the items displayed. If this is '::' for example then the output will only
811             show dependencies that contain those characters.
812              
813             =item ExcludeRegex
814              
815             A regular expression use to filter the items displayed. If this is '::' for example then the output will B
816             show dependencies that contain those characters.
817              
818             =item NoLegend
819              
820             If true, don't print the 'legend' box/text
821              
822             =item NoVersion
823              
824             If true, don't print the version/date line.
825              
826             =item Colour
827              
828             Used by makePs() only - if 1 it makes a colour image, if 0 it makes a greyscale image. Default is 1.
829              
830             =item Font
831              
832             sed by makePs() only. Set the font used in the drawing. Default is 'Helvetica'.
833              
834             =item ImageMap
835              
836             Used by makeHtml() only - if set to 'print' it will print a skeleton imagemap to the output file; if set to 'return' then the imagemap text
837             is the return value of makeHtml() so that the caller can process the string further.
838              
839             An imagemap looks like this example, but you can change the href attributes using the HrefFormat option (see below) so that they match what your CGI
840             program is expecting.
841              
842            
843             Root dependency tree on a
844             Root dependency tree on x.pl
845            
846              
847             If you want to totally change the format of each 'area' element see the ImageMapCode option below.
848              
849             Note that the href attributes are deliberately left empty, for users of the 'return' method to easily post-process the string. The PACK comment
850             at the start of each line is provided to tell you what the package or scriptname is. The imagemap corresponds to the image that _would_
851             be produced by makeImage() if it were given the same arguments.
852              
853             See the bundled 'cgidepend.plx' CGI program to see a use for this imagemap.
854              
855             =item ImageMapCode
856              
857             Used by makeHtml() only - must be a code reference. Called once for each 'area' required. The first argument is the package name
858             that the 'area' corresponds to, 'Foo::Bar' or 'baz.pl' for example. The second argument is the current HrefFormat setting, but you
859             may ignore that, seeing as you're going to be writing the entire element. The default coderef creates the 'area' elements as shown above
860             and respects the HrefFormat option.
861              
862             =item HrefFormat
863              
864             Used by makeHtml() and makeSvg() only - default is ''. A sprintf() formatting string used to format the 'href'
865             attribute in EACH 'area' element of the imagemap, or the href of the anchors in SVG output.
866             E.g. '?myparam=%s' would create an href of '?myparam=Foo'.
867              
868             If empty (as is the default) then you get no clickable links in the SVG output.
869              
870             =back
871              
872             =head1 PREREQUISITES
873              
874             If you want to use the makePs() method you'll need PostScript::Simple installed.
875             If you want to use the makeImage() method you'll need GD installed.
876             If you want to use the makeSvg() method you'll need the SVG module.
877             However, these modules are 'require'd as needed so you can quite happily use the makeText and makeHtml routines.
878              
879             =head1 SEE ALSO
880              
881             Module::Dependency and the README files.
882              
883             =head1 VERSION
884              
885             $Id: Grapher.pm 6632 2006-07-11 14:00:38Z timbo $
886              
887             =cut
888