File Coverage

lib/Graph/Easy/Edge.pm
Criterion Covered Total %
statement 143 191 74.8
branch 76 110 69.0
condition 40 73 54.7
subroutine 23 28 82.1
pod 18 18 100.0
total 300 420 71.4


line stmt bran cond sub pod time code
1             #############################################################################
2             # An edge connecting two nodes in Graph::Easy.
3             #
4             #############################################################################
5              
6             package Graph::Easy::Edge;
7              
8 49     49   45724 use Graph::Easy::Node;
  49         197  
  49         3418  
9             @ISA = qw/Graph::Easy::Node/; # an edge is just a special node
10             $VERSION = '0.75';
11              
12 49     49   532 use strict;
  49         95  
  49         2268  
13 49     49   308 use warnings;
  49         86  
  49         1866  
14              
15 49     49   269 use constant isa_cell => 1;
  49         87  
  49         147020  
16              
17             #############################################################################
18              
19             sub _init
20             {
21             # generic init, override in subclasses
22 1173     1173   2585 my ($self,$args) = @_;
23            
24 1173         3628 $self->{class} = 'edge';
25              
26             # leave this unitialized until we need it
27             # $self->{cells} = [ ];
28              
29 1173         5775 foreach my $k (sort keys %$args)
30             {
31 2040 50       9946 if ($k !~ /^(label|name|style)\z/)
32             {
33 0         0 require Carp;
34 0         0 Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Node->new()");
35             }
36 2040 100       3769 my $n = $k; $n = 'label' if $k eq 'name';
  2040         5443  
37              
38 2040         8750 $self->{att}->{$n} = $args->{$k};
39             }
40              
41 1173         4601 $self;
42             }
43              
44             #############################################################################
45             # accessor methods
46              
47             sub bidirectional
48             {
49 605     605 1 2948 my $self = shift;
50            
51 605 100       7883 if (@_ > 0)
52             {
53 36   100     199 my $old = $self->{bidirectional} || 0;
54 36 100       156 $self->{bidirectional} = $_[0] ? 1 : 0;
55              
56             # invalidate layout?
57 36 100 100     296 $self->{graph}->{score} = undef if $old != $self->{bidirectional} && ref($self->{graph});
58             }
59              
60 605         3643 $self->{bidirectional};
61             }
62              
63             sub undirected
64             {
65 653     653 1 2214 my $self = shift;
66              
67 653 100       3169 if (@_ > 0)
68             {
69 30   100     191 my $old = $self->{undirected} || 0;
70 30 100       130 $self->{undirected} = $_[0] ? 1 : 0;
71              
72             # invalidate layout?
73 30 100 66     275 $self->{graph}->{score} = undef if $old != $self->{undirected} && ref($self->{graph});
74             }
75              
76 653         2709 $self->{undirected};
77             }
78              
79             sub has_ports
80             {
81 1782     1782 1 3020 my $self = shift;
82              
83 1782   100     31654 my $s_port = $self->{att}->{start} || $self->attribute('start');
84              
85 1782 100       5690 return 1 if $s_port ne '';
86              
87 1682   66     14660 my $e_port = $self->{att}->{end} || $self->attribute('end');
88              
89 1682 100       4753 return 1 if $e_port ne '';
90              
91 1655         7355 0;
92             }
93              
94             sub start_port
95             {
96             # return the side and portnumber if the edge has a shared source port
97             # undef for none
98 0     0 1 0 my $self = shift;
99              
100 0   0     0 my $s = $self->{att}->{start} || $self->attribute('start');
101 0 0 0     0 return undef if !defined $s || $s !~ /,/; # "south, 0" => ok, "south" => no
102              
103 0 0       0 return (split /\s*,\s*/, $s) if wantarray;
104              
105 0         0 $s =~ s/\s+//g; # remove spaces to normalize "south, 0" to "south,0"
106 0         0 $s;
107             }
108              
109             sub end_port
110             {
111             # return the side and portnumber if the edge has a shared source port
112             # undef for none
113 0     0 1 0 my $self = shift;
114              
115 0   0     0 my $s = $self->{att}->{end} || $self->attribute('end');
116 0 0 0     0 return undef if !defined $s || $s !~ /,/; # "south, 0" => ok, "south" => no
117              
118 0 0       0 return split /\s*,\s*/, $s if wantarray;
119              
120 0         0 $s =~ s/\s+//g; # remove spaces to normalize "south, 0" to "south,0"
121 0         0 $s;
122             }
123              
124             sub style
125             {
126 2812     2812 1 4701 my $self = shift;
127              
128 2812 100       16210 $self->{att}->{style} || $self->attribute('style');
129             }
130              
131             sub name
132             {
133             # returns actually the label
134 539     539 1 1945 my $self = shift;
135              
136 539 100       3202 $self->{att}->{label} || '';
137             }
138              
139             #############################################################################
140             # cell management - used by the cell-based layouter
141              
142             sub _cells
143             {
144             # return all the cells this edge currently occupies
145 6     6   17 my $self = shift;
146              
147 6 100       23 $self->{cells} = [] unless defined $self->{cells};
148              
149 6         7 @{$self->{cells}};
  6         33  
150             }
151              
152             sub _clear_cells
153             {
154             # remove all belonging cells
155 902     902   2452 my $self = shift;
156              
157 902         2715 $self->{cells} = [];
158              
159 902         1890 $self;
160             }
161              
162             sub _unplace
163             {
164             # Take an edge, and remove all the cells it covers from the cells area
165 0     0   0 my ($self, $cells) = @_;
166              
167 0 0       0 print STDERR "# clearing path from $self->{from}->{name} to $self->{to}->{name}\n" if $self->{debug};
168              
169 0         0 for my $key (@{$self->{cells}})
  0         0  
170             {
171             # XXX TODO: handle crossed edges differently (from CROSS => HOR or VER)
172             # free in our cells area
173 0         0 delete $cells->{$key};
174             }
175              
176 0         0 $self->clear_cells();
177              
178 0         0 $self;
179             }
180              
181             sub _distance
182             {
183             # estimate the distance from SRC to DST node
184 0     0   0 my ($self) = @_;
185              
186 0         0 my $src = $self->{from};
187 0         0 my $dst = $self->{to};
188              
189             # one of them not yet placed?
190 0 0 0     0 return 100000 unless defined $src->{x} && defined $dst->{x};
191              
192 0         0 my $cells = $self->{graph}->{cells};
193              
194             # get all the starting positions
195             # distance = 1: slots, generate starting types, the direction is shifted
196             # by 90° counter-clockwise
197              
198 0         0 my @start = $src->_near_places($cells, 1, undef, undef, $src->_shift(-90) );
199              
200             # potential stop positions
201 0         0 my @stop = $dst->_near_places($cells, 1); # distance = 1: slots
202              
203 0         0 my ($s_p,@ss_p) = $self->port('start');
204 0         0 my ($e_p,@ee_p) = $self->port('end');
205              
206             # the edge has a port description, limiting the start places
207 0 0       0 @start = $src->_allowed_places( \@start, $src->_allow( $s_p, @ss_p ), 3)
208             if defined $s_p;
209              
210             # the edge has a port description, limiting the stop places
211 0 0       0 @stop = $dst->_allowed_places( \@stop, $dst->_allow( $e_p, @ee_p ), 3)
212             if defined $e_p;
213              
214 0         0 my $stop = scalar @stop;
215              
216 0 0 0     0 return 0 unless @stop > 0 && @start > 0; # no free slots on one node?
217              
218 0         0 my $lowest;
219              
220 0         0 my $i = 0;
221 0         0 while ($i < scalar @start)
222             {
223 0         0 my $sx = $start[$i]; my $sy = $start[$i+1]; $i += 2;
  0         0  
  0         0  
224              
225             # for each start point, calculate the distance to each stop point, then use
226             # the smallest as value
227              
228 0         0 for (my $u = 0; $u < $stop; $u += 2)
229             {
230 0         0 my $dist = Graph::Easy::_astar_distance($sx,$sy, $stop[$u], $stop[$u+1]);
231 0 0 0     0 $lowest = $dist if !defined $lowest || $dist < $lowest;
232             }
233             }
234              
235 0         0 $lowest;
236             }
237              
238             sub _add_cell
239             {
240             # add a cell to the list of cells this edge covers. If $after is a ref
241             # to a cell, then the new cell will be inserted right after this cell.
242             # if after is defined, but not a ref, the new cell will be inserted
243             # at the specified position.
244 2135     2135   6621 my ($self, $cell, $after, $before) = @_;
245            
246 2135 100       6224 $self->{cells} = [] unless defined $self->{cells};
247 2135         3634 my $cells = $self->{cells};
248              
249             # if both are defined, but belong to different edges, just ignore $before:
250 2135 100 100     6007 $before = undef if ref($before) && $before->{edge} != $self;
251 2135 50 66     11474 $after = undef if ref($after) && $after->{edge} != $self;
252 2135 50 66     9686 if (!defined $after && ref($before))
253             {
254 0         0 $after = $before; $before = undef;
  0         0  
255             }
256              
257 2135 100       4506 if (defined $after)
258             {
259             # insert the new cell right after $after
260 81         119 my $ofs = $after;
261 81 100 100     540 if (ref($after) && !ref($before))
    100 66        
262             {
263             # insert after $after
264 5         13 $ofs = 1;
265 5         12 for my $cell (@$cells)
266             {
267 9 100       28 last if $cell == $after;
268 4         10 $ofs++;
269             }
270             }
271             elsif (ref($after) && ref($before))
272             {
273             # insert between after and before (or before/after for "reversed edges)
274 46         129 $ofs = 0;
275 46         52 my $found = 0;
276 46         155 while ($ofs < scalar @$cells - 1) # 0,1,2,3 => 0 .. 2
277             {
278 178         229 my $c1 = $cells->[$ofs];
279 178         264 my $c2 = $cells->[$ofs+1];
280 178         176 $ofs++;
281 178 100 100     1331 $found++, last if (($c1 == $after && $c2 == $before) ||
      66        
      66        
282             ($c1 == $before && $c2 == $after));
283             }
284 46 100       105 if (!$found)
285             {
286             # XXX TODO: last effort
287              
288             # insert after $after
289 1         3 $ofs = 1;
290 1         3 for my $cell (@$cells)
291             {
292 3 100       10 last if $cell == $after;
293 2         4 $ofs++;
294             }
295 1         3 $found++;
296             }
297 46 50       88 $self->_croak("Could not find $after and $before") unless $found;
298             }
299 81         200 splice (@$cells, $ofs, 0, $cell);
300             }
301             else
302             {
303             # insert new cell at the end
304 2054         4389 push @$cells, $cell;
305             }
306              
307 2135         7842 $cell->_update_boundaries();
308              
309 2135         5441 $self;
310             }
311              
312             #############################################################################
313              
314             sub from
315             {
316 2     2 1 5 my $self = shift;
317              
318 2         9 $self->{from};
319             }
320              
321             sub to
322             {
323 2     2 1 9 my $self = shift;
324              
325 2         9 $self->{to};
326             }
327              
328             sub nodes
329             {
330 0     0 1 0 my $self = shift;
331              
332 0         0 ($self->{from}, $self->{to});
333             }
334              
335             sub start_at
336             {
337             # move the edge's start point from the current node to the given node
338 11     11 1 19 my ($self, $node) = @_;
339              
340             # if not a node yet, or not part of this graph, make into one proper node
341 11         49 $node = $self->{graph}->add_node($node);
342              
343 11 50 33     113 $self->_croak("start_at() needs a node object, but got $node")
344             unless ref($node) && $node->isa('Graph::Easy::Node');
345              
346             # A => A => nothing to do
347 11 50       39 return $node if $self->{from} == $node;
348              
349             # delete self at A
350 11         45 delete $self->{from}->{edges}->{ $self->{id} };
351              
352             # set "from" to B
353 11         27 $self->{from} = $node;
354              
355             # add to B
356 11         44 $self->{from}->{edges}->{ $self->{id} } = $self;
357              
358             # invalidate layout
359 11 50       39 $self->{graph}->{score} = undef if ref($self->{graph});
360              
361             # return new start point
362 11         29 $node;
363             }
364              
365             sub end_at
366             {
367             # move the edge's end point from the current node to the given node
368 11     11 1 16 my ($self, $node) = @_;
369              
370             # if not a node yet, or not part of this graph, make into one proper node
371 11         42 $node = $self->{graph}->add_node($node);
372              
373 11 50 33     74 $self->_croak("start_at() needs a node object, but got $node")
374             unless ref($node) && $node->isa('Graph::Easy::Node');
375              
376             # A => A => nothing to do
377 11 50       33 return $node if $self->{to} == $node;
378              
379             # delete self at A
380 11         35 delete $self->{to}->{edges}->{ $self->{id} };
381              
382             # set "to" to B
383 11         19 $self->{to} = $node;
384              
385             # add to node B
386 11         39 $self->{to}->{edges}->{ $self->{id} } = $self;
387              
388             # invalidate layout
389 11 50       36 $self->{graph}->{score} = undef if ref($self->{graph});
390              
391             # return new end point
392 11         27 $node;
393             }
394              
395             sub edge_flow
396             {
397             # return the flow at this edge or '' if the edge itself doesn't have a flow
398 886     886 1 2442 my $self = shift;
399              
400             # our flow comes from ourselves
401 886         1984 my $flow = $self->{att}->{flow};
402 886 100       4165 $flow = $self->raw_attribute('flow') unless defined $flow;
403              
404 886         3801 $flow;
405             }
406              
407             sub flow
408             {
409             # return the flow at this edge (including inheriting flow from node)
410 1333     1333 1 2539 my ($self) = @_;
411              
412             # print STDERR "# flow from $self->{from}->{name} to $self->{to}->{name}\n";
413              
414             # our flow comes from ourselves
415 1333         3704 my $flow = $self->{att}->{flow};
416             # or maybe our class
417 1333 100       7649 $flow = $self->raw_attribute('flow') unless defined $flow;
418              
419             # if the edge doesn't have a flow, maybe the node has a default out flow
420 1333 100       10993 $flow = $self->{from}->{att}->{flow} if !defined $flow;
421              
422             # if that didn't work out either, use the parents flows
423 1333 100       6147 $flow = $self->parent()->attribute('flow') if !defined $flow;
424             # or finally, the default "east":
425 1333 50       3946 $flow = 90 if !defined $flow;
426              
427             # absolute flow does not depend on the in-flow, so can return early
428 1333 100       5139 return $flow if $flow =~ /^(0|90|180|270)\z/;
429              
430             # in-flow comes from our "from" node
431 1316         6018 my $in = $self->{from}->flow();
432              
433             # print STDERR "# in: $self->{from}->{name} = $in\n";
434              
435 1316         12289 my $out = $self->{graph}->_flow_as_direction($in,$flow);
436 1316         3891 $out;
437             }
438              
439             sub port
440             {
441 4438     4438 1 8385 my ($self, $which) = @_;
442              
443 4438 50       26635 $self->_croak("'$which' must be one of 'start' or 'end' in port()") unless $which =~ /^(start|end)/;
444              
445             # our flow comes from ourselves
446 4438         13437 my $sp = $self->attribute($which);
447              
448 4438 100 66     33039 return (undef,undef) unless defined $sp && $sp ne '';
449              
450 512         3358 my ($side, $port) = split /\s*,\s*/, $sp;
451              
452             # if absolut direction, return as is
453 512         2093 my $s = Graph::Easy->_direction_as_side($side);
454              
455 512 100       1436 if (defined $s)
456             {
457 311 100       892 my @rc = ($s); push @rc, $port if defined $port;
  311         777  
458 311         1620 return @rc;
459             }
460              
461             # in_flow comes from our "from" node
462 201 50       283 my $in = 90; $in = $self->{from}->flow() if ref($self->{from});
  201         965  
463              
464             # turn left in "south" etc:
465 201         809 $s = Graph::Easy->_flow_as_side($in,$side);
466              
467 201 100       380 my @rc = ($s); push @rc, $port if defined $port;
  201         503  
468 201         797 @rc;
469             }
470              
471             sub flip
472             {
473             # swap from and to for this edge
474 2     2 1 11 my ($self) = @_;
475              
476 2         10 ($self->{from}, $self->{to}) = ($self->{to}, $self->{from});
477              
478             # invalidate layout
479 2 50       14 $self->{graph}->{score} = undef if ref($self->{graph});
480              
481 2         7 $self;
482             }
483              
484             sub as_ascii
485             {
486 1594     1594 1 4003 my ($self, $x,$y) = @_;
487              
488             # invisible nodes, or very small ones
489 1594 100 100     13204 return '' if $self->{w} == 0 || $self->{h} == 0;
490              
491 1477         5915 my $fb = $self->_framebuffer($self->{w}, $self->{h});
492              
493             ###########################################################################
494             # "draw" the label into the framebuffer (e.g. the edge and the text)
495 1477         5137 $self->_draw_label($fb, $x, $y, '');
496              
497 1477         13521 join ("\n", @$fb);
498             }
499              
500             sub as_txt
501             {
502 622     622 1 10059 require Graph::Easy::As_ascii;
503              
504 622         2650 _as_txt(@_);
505             }
506              
507             1;
508             __END__