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);
90
|
0
|
|
|
|
|
0
|
print HTML qq( | Kind | Items | \n);
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
|
|
|
|
|
|
|
" | \n"
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( |
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|