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 48     48   22747 use Graph::Easy::Node;
  48         69  
  48         1970  
9             @ISA = qw/Graph::Easy::Node/; # an edge is just a special node
10             $VERSION = '0.76';
11              
12 48     48   543 use strict;
  48         42  
  48         768  
13 48     48   133 use warnings;
  48         36  
  48         1038  
14              
15 48     48   126 use constant isa_cell => 1;
  48         49  
  48         80835  
16              
17             #############################################################################
18              
19             sub _init
20             {
21             # generic init, override in subclasses
22 1173     1173   1106 my ($self,$args) = @_;
23              
24 1173         1603 $self->{class} = 'edge';
25              
26             # leave this unitialized until we need it
27             # $self->{cells} = [ ];
28              
29 1173         3093 foreach my $k (sort keys %$args)
30             {
31 2040 50       5144 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       1586 my $n = $k; $n = 'label' if $k eq 'name';
  2040         2926  
37              
38 2040         3637 $self->{att}->{$n} = $args->{$k};
39             }
40              
41 1173         2131 $self;
42             }
43              
44             #############################################################################
45             # accessor methods
46              
47             sub bidirectional
48             {
49 605     605 1 1144 my $self = shift;
50              
51 605 100       1051 if (@_ > 0)
52             {
53 36   100     122 my $old = $self->{bidirectional} || 0;
54 36 100       90 $self->{bidirectional} = $_[0] ? 1 : 0;
55              
56             # invalidate layout?
57 36 100 100     188 $self->{graph}->{score} = undef if $old != $self->{bidirectional} && ref($self->{graph});
58             }
59              
60 605         1810 $self->{bidirectional};
61             }
62              
63             sub undirected
64             {
65 653     653 1 1205 my $self = shift;
66              
67 653 100       1032 if (@_ > 0)
68             {
69 30   100     106 my $old = $self->{undirected} || 0;
70 30 100       65 $self->{undirected} = $_[0] ? 1 : 0;
71              
72             # invalidate layout?
73 30 100 66     148 $self->{graph}->{score} = undef if $old != $self->{undirected} && ref($self->{graph});
74             }
75              
76 653         1255 $self->{undirected};
77             }
78              
79             sub has_ports
80             {
81 1782     1782 1 1452 my $self = shift;
82              
83 1782   100     5209 my $s_port = $self->{att}->{start} || $self->attribute('start');
84              
85 1782 100       3007 return 1 if $s_port ne '';
86              
87 1682   66     4491 my $e_port = $self->{att}->{end} || $self->attribute('end');
88              
89 1682 100       2722 return 1 if $e_port ne '';
90              
91 1655         4284 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 2477 my $self = shift;
127              
128 2812 100       8163 $self->{att}->{style} || $self->attribute('style');
129             }
130              
131             sub name
132             {
133             # returns actually the label
134 539     539 1 710 my $self = shift;
135              
136 539 100       1340 $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   9 my $self = shift;
146              
147 6 100       13 $self->{cells} = [] unless defined $self->{cells};
148              
149 6         5 @{$self->{cells}};
  6         20  
150             }
151              
152             sub _clear_cells
153             {
154             # remove all belonging cells
155 902     902   1145 my $self = shift;
156              
157 902         1246 $self->{cells} = [];
158              
159 902         948 $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   3189 my ($self, $cell, $after, $before) = @_;
245              
246 2135 100       3290 $self->{cells} = [] unless defined $self->{cells};
247 2135         1605 my $cells = $self->{cells};
248              
249             # if both are defined, but belong to different edges, just ignore $before:
250 2135 100 100     3816 $before = undef if ref($before) && $before->{edge} != $self;
251 2135 50 66     3315 $after = undef if ref($after) && $after->{edge} != $self;
252 2135 50 66     6630 if (!defined $after && ref($before))
253             {
254 0         0 $after = $before; $before = undef;
  0         0  
255             }
256              
257 2135 100       2349 if (defined $after)
258             {
259             # insert the new cell right after $after
260 81         62 my $ofs = $after;
261 81 100 100     356 if (ref($after) && !ref($before))
    100 66        
262             {
263             # insert after $after
264 5         7 $ofs = 1;
265 5         7 for my $cell (@$cells)
266             {
267 9 100       20 last if $cell == $after;
268 4         4 $ofs++;
269             }
270             }
271             elsif (ref($after) && ref($before))
272             {
273             # insert between after and before (or before/after for "reversed edges)
274 46         41 $ofs = 0;
275 46         33 my $found = 0;
276 46         80 while ($ofs < scalar @$cells - 1) # 0,1,2,3 => 0 .. 2
277             {
278 178         125 my $c1 = $cells->[$ofs];
279 178         136 my $c2 = $cells->[$ofs+1];
280 178         96 $ofs++;
281 178 100 100     620 $found++, last if (($c1 == $after && $c2 == $before) ||
      66        
      66        
282             ($c1 == $before && $c2 == $after));
283             }
284 46 100       62 if (!$found)
285             {
286             # XXX TODO: last effort
287              
288             # insert after $after
289 1         2 $ofs = 1;
290 1         3 for my $cell (@$cells)
291             {
292 3 100       5 last if $cell == $after;
293 2         2 $ofs++;
294             }
295 1         2 $found++;
296             }
297 46 50       66 $self->_croak("Could not find $after and $before") unless $found;
298             }
299 81         122 splice (@$cells, $ofs, 0, $cell);
300             }
301             else
302             {
303             # insert new cell at the end
304 2054         2483 push @$cells, $cell;
305             }
306              
307 2135         4136 $cell->_update_boundaries();
308              
309 2135         2484 $self;
310             }
311              
312             #############################################################################
313              
314             sub from
315             {
316 2     2 1 3 my $self = shift;
317              
318 2         6 $self->{from};
319             }
320              
321             sub to
322             {
323 2     2 1 5 my $self = shift;
324              
325 2         5 $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 14 my ($self, $node) = @_;
339              
340             # if not a node yet, or not part of this graph, make into one proper node
341 11         19 $node = $self->{graph}->add_node($node);
342              
343 11 50 33     53 $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       20 return $node if $self->{from} == $node;
348              
349             # delete self at A
350 11         19 delete $self->{from}->{edges}->{ $self->{id} };
351              
352             # set "from" to B
353 11         10 $self->{from} = $node;
354              
355             # add to B
356 11         18 $self->{from}->{edges}->{ $self->{id} } = $self;
357              
358             # invalidate layout
359 11 50       23 $self->{graph}->{score} = undef if ref($self->{graph});
360              
361             # return new start point
362 11         21 $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 12 my ($self, $node) = @_;
369              
370             # if not a node yet, or not part of this graph, make into one proper node
371 11         20 $node = $self->{graph}->add_node($node);
372              
373 11 50 33     57 $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       19 return $node if $self->{to} == $node;
378              
379             # delete self at A
380 11         16 delete $self->{to}->{edges}->{ $self->{id} };
381              
382             # set "to" to B
383 11         11 $self->{to} = $node;
384              
385             # add to node B
386 11         18 $self->{to}->{edges}->{ $self->{id} } = $self;
387              
388             # invalidate layout
389 11 50       20 $self->{graph}->{score} = undef if ref($self->{graph});
390              
391             # return new end point
392 11         16 $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 721 my $self = shift;
399              
400             # our flow comes from ourselves
401 886         851 my $flow = $self->{att}->{flow};
402 886 100       2280 $flow = $self->raw_attribute('flow') unless defined $flow;
403              
404 886         1882 $flow;
405             }
406              
407             sub flow
408             {
409             # return the flow at this edge (including inheriting flow from node)
410 1333     1333 1 1315 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         1363 my $flow = $self->{att}->{flow};
416             # or maybe our class
417 1333 100       3448 $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       2727 $flow = $self->{from}->{att}->{flow} if !defined $flow;
421              
422             # if that didn't work out either, use the parents flows
423 1333 100       3417 $flow = $self->parent()->attribute('flow') if !defined $flow;
424             # or finally, the default "east":
425 1333 50       2063 $flow = 90 if !defined $flow;
426              
427             # absolute flow does not depend on the in-flow, so can return early
428 1333 100       3267 return $flow if $flow =~ /^(0|90|180|270)\z/;
429              
430             # in-flow comes from our "from" node
431 1316         3002 my $in = $self->{from}->flow();
432              
433             # print STDERR "# in: $self->{from}->{name} = $in\n";
434              
435 1316         2861 my $out = $self->{graph}->_flow_as_direction($in,$flow);
436 1316         1984 $out;
437             }
438              
439             sub port
440             {
441 4438     4438 1 4279 my ($self, $which) = @_;
442              
443 4438 50       12018 $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         6995 my $sp = $self->attribute($which);
447              
448 4438 100 66     17567 return (undef,undef) unless defined $sp && $sp ne '';
449              
450 512         2122 my ($side, $port) = split /\s*,\s*/, $sp;
451              
452             # if absolut direction, return as is
453 512         1111 my $s = Graph::Easy->_direction_as_side($side);
454              
455 512 100       1025 if (defined $s)
456             {
457 311 100       315 my @rc = ($s); push @rc, $port if defined $port;
  311         464  
458 311         732 return @rc;
459             }
460              
461             # in_flow comes from our "from" node
462 201 50       127 my $in = 90; $in = $self->{from}->flow() if ref($self->{from});
  201         542  
463              
464             # turn left in "south" etc:
465 201         386 $s = Graph::Easy->_flow_as_side($in,$side);
466              
467 201 100       250 my @rc = ($s); push @rc, $port if defined $port;
  201         333  
468 201         462 @rc;
469             }
470              
471             sub flip
472             {
473             # swap from and to for this edge
474 2     2 1 5 my ($self) = @_;
475              
476 2         6 ($self->{from}, $self->{to}) = ($self->{to}, $self->{from});
477              
478             # invalidate layout
479 2 50       7 $self->{graph}->{score} = undef if ref($self->{graph});
480              
481 2         3 $self;
482             }
483              
484             sub as_ascii
485             {
486 1594     1594 1 2056 my ($self, $x,$y) = @_;
487              
488             # invisible nodes, or very small ones
489 1594 100 100     5505 return '' if $self->{w} == 0 || $self->{h} == 0;
490              
491 1477         2648 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         2938 $self->_draw_label($fb, $x, $y, '');
496              
497 1477         6444 join ("\n", @$fb);
498             }
499              
500             sub as_txt
501             {
502 622     622 1 3960 require Graph::Easy::As_ascii;
503              
504 622         1329 _as_txt(@_);
505             }
506              
507             1;
508             __END__