File Coverage

lib/Graph/Easy/Layout/Path.pm
Criterion Covered Total %
statement 343 356 96.3
branch 170 226 75.2
condition 62 89 69.6
subroutine 18 18 100.0
pod n/a
total 593 689 86.0


line stmt bran cond sub pod time code
1             #############################################################################
2             # Path and cell management for Graph::Easy.
3             #
4             #############################################################################
5              
6             package Graph::Easy::Layout::Path;
7              
8             $VERSION = '0.76';
9              
10             #############################################################################
11             #############################################################################
12              
13             package Graph::Easy::Node;
14              
15 48     48   173 use strict;
  48         57  
  48         1482  
16 48     48   155 use warnings;
  48         58  
  48         1405  
17              
18 48         49520 use Graph::Easy::Edge::Cell qw/
19             EDGE_END_E EDGE_END_N EDGE_END_S EDGE_END_W
20 48     48   157 /;
  48         46  
21              
22             sub _shuffle_dir
23             {
24             # take a list with four entries and shuffle them around according to $dir
25 3674     3674   3267 my ($self, $e, $dir) = @_;
26              
27             # $dir: 0 => north, 90 => east, 180 => south, 270 => west
28              
29 3674 100       4904 $dir = 90 unless defined $dir; # default is east
30              
31 3674 100       5969 return [ @$e ] if $dir == 90; # default is no shuffling
32              
33 1603         1671 my @shuffle = (0,1,2,3); # the default
34 1603 100       2393 @shuffle = (1,2,0,3) if $dir == 180; # south
35 1603 100       2032 @shuffle = (2,3,1,0) if $dir == 270; # west
36 1603 100       1963 @shuffle = (3,0,2,1) if $dir == 0; # north
37              
38             [
39 1603         2757 $e->[ $shuffle[0] ],
40             $e->[ $shuffle[1] ],
41             $e->[ $shuffle[2] ],
42             $e->[ $shuffle[3] ],
43             ];
44             }
45              
46             sub _shift
47             {
48             # get a flow shifted by X° to $dir
49 229     229   238 my ($self, $turn) = @_;
50              
51 229         424 my $dir = $self->flow();
52              
53 229         236 $dir += $turn;
54 229 100       400 $dir += 360 if $dir < 0;
55 229 50       325 $dir -= 360 if $dir > 360;
56 229         333 $dir;
57             }
58              
59             sub _near_places
60             {
61             # Take a node and return a list of possible placements around it and
62             # prune out already occupied cells. $d is the distance from the node
63             # border and defaults to two (for placements). Set it to one for
64             # adjacent cells.
65              
66             # If defined, $type contains four flags for each direction. If undef,
67             # two entries (x,y) will be returned for each pos, instead of (x,y,type).
68              
69             # If $loose is true, no checking whether the returned fields are free
70             # is done.
71              
72 3669     3669   4432 my ($n, $cells, $d, $type, $loose, $dir) = @_;
73              
74 3669   100     5524 my $cx = $n->{cx} || 1;
75 3669   100     4651 my $cy = $n->{cy} || 1;
76              
77 3669 100       4568 $d = 2 unless defined $d; # default is distance = 2
78              
79 3669         2349 my $flags = $type;
80              
81 3669 100       4641 if (ref($flags) ne 'ARRAY')
82             {
83 3148         5150 $flags = [
84             EDGE_END_W,
85             EDGE_END_N,
86             EDGE_END_E,
87             EDGE_END_S,
88             ];
89             }
90 3669 100       8200 $dir = $n->flow() unless defined $dir;
91              
92 3669         6621 my $index = $n->_shuffle_dir( [ 0,3,6,9], $dir);
93              
94 3669         3978 my @places = ();
95              
96             # single-celled node
97 3669 100       4827 if ($cx + $cy == 2)
98             {
99             my @tries = (
100             $n->{x} + $d, $n->{y}, $flags->[0], # right
101             $n->{x}, $n->{y} + $d, $flags->[1], # down
102             $n->{x} - $d, $n->{y}, $flags->[2], # left
103 3490         11481 $n->{x}, $n->{y} - $d, $flags->[3], # up
104             );
105              
106 3490         4153 for my $i (0..3)
107             {
108 13960         8862 my $idx = $index->[$i];
109 13960         13184 my ($x,$y,$t) = ($tries[$idx], $tries[$idx+1], $tries[$idx+2]);
110              
111             # print STDERR "# Considering place $x, $y \n";
112              
113             # This quick check does not take node clusters or multi-celled nodes
114             # into account. These are handled in $node->_do_place() later.
115 13960 100 100     22204 next if !$loose && exists $cells->{"$x,$y"};
116 13458         11484 push @places, $x, $y;
117 13458 100       18129 push @places, $t if defined $type;
118             }
119 3490         14063 return @places;
120             }
121              
122             # Handle a multi-celled node. For a 3x2 node:
123             # A B C
124             # J [00][10][20] D
125             # I [10][11][21] E
126             # H G F
127             # we have 10 (3 * 2 + 2 * 2) places to consider
128              
129 179         165 my $nx = $n->{x};
130 179         175 my $ny = $n->{y};
131 179         162 my ($px,$py);
132              
133 179         142 my $idx = 0;
134 179         233 my @results = ( [], [], [], [] );
135              
136 179         158 $cy--; $cx--;
  179         108  
137 179         161 my $t = $flags->[$idx++];
138             # right
139 179         130 $px = $nx + $cx + $d;
140 179         255 for my $y (0 .. $cy)
141             {
142 377         221 $py = $y + $ny;
143 377 100 100     836 next if exists $cells->{"$px,$py"} && !$loose;
144 356         206 push @{$results[0]}, $px, $py;
  356         497  
145 356 100       516 push @{$results[0]}, $t if defined $type;
  192         259  
146             }
147              
148             # below
149 179         172 $py = $ny + $cy + $d;
150 179         162 $t = $flags->[$idx++];
151 179         189 for my $x (0 .. $cx)
152             {
153 556         364 $px = $x + $nx;
154 556 100 100     1067 next if exists $cells->{"$px,$py"} && !$loose;
155 522         335 push @{$results[1]}, $px, $py;
  522         605  
156 522 100       659 push @{$results[1]}, $t if defined $type;
  279         326  
157             }
158              
159             # left
160 179         156 $px = $nx - $d;
161 179         156 $t = $flags->[$idx++];
162 179         189 for my $y (0 .. $cy)
163             {
164 377         258 $py = $y + $ny;
165 377 100 100     771 next if exists $cells->{"$px,$py"} && !$loose;
166 362         228 push @{$results[2]}, $px, $py;
  362         444  
167 362 100       476 push @{$results[2]}, $t if defined $type;
  191         239  
168             }
169              
170             # top
171 179         135 $py = $ny - $d;
172 179         165 $t = $flags->[$idx];
173 179         185 for my $x (0 .. $cx)
174             {
175 556         323 $px = $x + $nx;
176 556 100 100     922 next if exists $cells->{"$px,$py"} && !$loose;
177 555         320 push @{$results[3]}, $px, $py;
  555         639  
178 555 100       710 push @{$results[3]}, $t if defined $type;
  278         299  
179             }
180              
181             # accumulate the results in the requested, shuffled order
182 179         183 for my $i (0..3)
183             {
184 716         602 my $idx = $index->[$i] / 3;
185 716         393 push @places, @{$results[$idx]};
  716         1280  
186             }
187              
188 179         1524 @places;
189             }
190              
191             sub _allowed_places
192             {
193             # given a list of potential positions, and a list of allowed positions,
194             # return the valid ones (e.g. that are in both lists)
195 56     56   1062 my ($self, $places, $allowed, $step) = @_;
196              
197             print STDERR
198             "# calculating allowed places for $self->{name} from " . @$places .
199             " positions and " . scalar @$allowed . " allowed ones:\n"
200 56 50       133 if $self->{graph}->{debug};
201              
202 56   100     114 $step ||= 2; # default: "x,y"
203              
204 56         39 my @good;
205 56         49 my $i = 0;
206 56         114 while ($i < @$places)
207             {
208 480         462 my ($x,$y) = ($places->[$i], $places->[$i+1]);
209 480         273 my $allow = 0;
210 480         286 my $j = 0;
211 480         596 while ($j < @$allowed)
212             {
213 4260         2803 my ($m,$n) = ($allowed->[$j], $allowed->[$j+1]);
214 4260 100 50     5870 $allow++ and last if ($m == $x && $n == $y);
      100        
215 4260         4776 } continue { $j += 2; }
216 480 100       576 next unless $allow;
217 183         483 push @good, $places->[$i + $_ -1] for (1..$step);
218 480         613 } continue { $i += $step; }
219              
220 56 50       111 print STDERR "# left with " . ((scalar @good) / $step) . " position(s)\n" if $self->{graph}->{debug};
221 56         323 @good;
222             }
223              
224             sub _allow
225             {
226             # return a list of places, depending on the start/end attribute:
227             # "south" - any place south
228             # "south,0" - first place south
229             # "south,-1" - last place south
230             # XXX TODO:
231             # "south,0..2" - first three places south
232             # "south,0,1,-1" - first, second and last place south
233              
234 72     72   6290 my ($self, $dir, @pos) = @_;
235              
236             # for relative direction, get the absolute flow from the node
237 72 50       234 if ($dir =~ /^(front|forward|back|left|right)\z/)
238             {
239             # get the flow at the node
240 0         0 $dir = $self->flow();
241             }
242              
243 72         766 my $place = {
244             'south' => [ 0,0, 0,1, 'cx', 1,0 ],
245             'north' => [ 0,-1, 0,0, 'cx', 1,0 ],
246             'east' => [ 0,0, 1,0, 'cy', 0,1 ],
247             'west' => [ -1,0, 0,0, 'cy', 0,1 ] ,
248             180 => [ 0,0, 0,1, 'cx', 1,0 ],
249             0 => [ 0,-1, 0,0, 'cx', 1,0 ],
250             90 => [ 0,0, 1,0, 'cy', 0,1 ],
251             270 => [ -1,0, 0,0, 'cy', 0,1 ] ,
252             };
253              
254 72         94 my $p = $place->{$dir};
255              
256 72 50       129 return [] unless defined $p;
257              
258             # start pos
259 72         153 my $x = $p->[0] + $self->{x} + $p->[2] * $self->{cx};
260 72         108 my $y = $p->[1] + $self->{y} + $p->[3] * $self->{cy};
261              
262 72         72 my @allowed;
263 72 100       131 push @pos, '' if @pos == 0;
264              
265 72         74 my $c = $p->[4];
266 72 100 66     270 if (@pos == 1 && $pos[0] eq '')
267             {
268             # allow all of them
269 39         84 for (1 .. $self->{$c})
270             {
271 173         150 push @allowed, $x, $y;
272 173         140 $x += $p->[5];
273 173         138 $y += $p->[6];
274             }
275             }
276             else
277             {
278             # allow only the given position
279 33         37 my $ps = $pos[0];
280             # limit to 0..$self->{cx}-1
281 33 100       79 $ps = $self->{$c} + $ps if $ps < 0;
282 33 100       51 $ps = 0 if $ps < 0;
283 33 100       62 $ps = $self->{$c} - 1 if $ps >= $self->{$c};
284 33         33 $x += $p->[5] * $ps;
285 33         37 $y += $p->[6] * $ps;
286 33         53 push @allowed, $x, $y;
287             }
288              
289 72         357 \@allowed;
290             }
291              
292             package Graph::Easy;
293 48     48   223 use strict;
  48         58  
  48         911  
294 48     48   157 use Graph::Easy::Node::Cell;
  48         60  
  48         1146  
295              
296 48         17782 use Graph::Easy::Edge::Cell qw/
297             EDGE_HOR EDGE_VER EDGE_CROSS
298             EDGE_TYPE_MASK
299             EDGE_HOLE
300 48     48   144 /;
  48         55  
301              
302             sub _clear_tries
303             {
304             # Take a list of potential positions for a node, and then remove the
305             # ones that are immediately near any other node.
306             # Returns a list of "good" positions. Afterwards $node->{x} is undef.
307 790     790   854 my ($self, $node, $cells, $tries) = @_;
308              
309 790         688 my $src = 0; my @new;
  790         547  
310              
311 790 50       1188 print STDERR "# clearing ", scalar @$tries / 2, " tries for $node->{name}\n" if $self->{debug};
312              
313 790         1550 my $node_grandpa = $node->find_grandparent();
314              
315 790         1379 while ($src < scalar @$tries)
316             {
317             # check the current position
318              
319             # temporary place node here
320 2496         2066 my $x = $tries->[$src];
321 2496         2158 my $y = $tries->[$src+1];
322              
323             # print STDERR "# checking $x,$y\n" if $self->{debug};
324              
325 2496         2118 $node->{x} = $x;
326 2496         1895 $node->{y} = $y;
327              
328 2496         3080 my @near = $node->_near_places($cells, 1, undef, 1);
329              
330             # push also the four corner cells to avoid placing nodes corner-to-corner
331             push @near, $x-1, $y-1, # upperleft corner
332             $x-1, $y+($node->{cy}||1), # lowerleft corner
333             $x+($node->{cx}||1), $y+($node->{cy}||1), # lowerright corner
334 2496   50     10967 $x+($node->{cx}||1), $y-1; # upperright corner
      50        
      50        
      50        
335              
336             # check all near places to be free from nodes (except our children)
337 2496         1703 my $j = 0; my $g = 0;
  2496         1786  
338 2496         3543 while ($j < @near)
339             {
340 19372         21225 my $xy = $near[$j]. ',' . $near[$j+1];
341              
342             # print STDERR "# checking near-place: $xy: " . ref($cells->{$xy}) . "\n" if $self->{debug};
343              
344 19372         12497 my $cell = $cells->{$xy};
345              
346             # skip, unless we are a children of node, or the cell is our children
347 19372 100 66     27327 next unless ref($cell) && $cell->isa('Graph::Easy::Node');
348              
349 136         274 my $grandpa = $cell->find_grandparent();
350              
351             # this cell is our children
352             # this cell is our grandpa
353             # has the same grandpa as node
354 136 50 33     739 next if $grandpa == $node || $cell == $node_grandpa || $grandpa == $node_grandpa;
      33        
355              
356 136         144 $g++; last;
  136         142  
357              
358 19236         23030 } continue { $j += 2; }
359              
360 2496 100       3115 if ($g == 0)
361             {
362 2360         3072 push @new, $tries->[$src], $tries->[$src+1];
363             }
364 2496         5244 $src += 2;
365             }
366              
367 790         704 $node->{x} = undef;
368              
369 790         2715 @new;
370             }
371              
372             my $flow_shift = {
373             270 => [ 0, -1 ],
374             90 => [ 0, 1 ],
375             0 => [ 1, 0 ],
376             180 => [ -1, 0 ],
377             };
378              
379             sub _placed_shared
380             {
381             # check whether one of the nodes from the list of shared was already placed
382 39     39   40 my ($self) = shift;
383              
384 39         30 my $placed;
385 39         50 for my $n (@_)
386             {
387 76 100 50     162 $placed = [$n->{x}, $n->{y}] and last if defined $n->{x};
388             }
389 39         43 $placed;
390             }
391              
392 48     48   224 use Graph::Easy::Util qw(first_kv);
  48         62  
  48         83502  
393              
394             sub _find_node_place
395             {
396             # Try to place a node (or node cluster). Return score (usually 0).
397 982     982   1046 my ($self, $node, $try, $parent, $edge) = @_;
398              
399 982   50     2355 $try ||= 0;
400              
401 982 50       1667 print STDERR "# Finding place for $node->{name}, try #$try\n" if $self->{debug};
402 982 50 33     1903 print STDERR "# Parent node is '$parent->{name}'\n" if $self->{debug} && ref $parent;
403              
404 982 50       1435 print STDERR "# called from ". join (" ", caller) . "\n" if $self->{debug};
405              
406             # If the node has a user-set rank, see if we already placed another node in that
407             # row/column
408 982 100       1671 if ($node->{rank} >= 0)
409             {
410 3         4 my $r = abs($node->{rank});
411             # print STDERR "# User-set rank for $node->{name} (rank $r)\n";
412 3         5 my $c = $self->{_rank_coord};
413             # use Data::Dumper; print STDERR "# rank_pos: \n", Dumper($self->{_rank_pos});
414 3 100       8 if (exists $self->{_rank_pos}->{ $r })
415             {
416 2         4 my $co = { x => 0, y => 0 };
417 2         5 $co->{$c} = $self->{_rank_pos}->{ $r };
418 2         1 while (1 < 3)
419             {
420             # print STDERR "# trying to force placement of '$node->{name}' at $co->{x} $co->{y}\n";
421 5 100       8 return 0 if $node->_do_place($co->{x},$co->{y},$self);
422 3         5 $co->{$c} += 2;
423             }
424             }
425             }
426              
427 980         851 my $cells = $self->{cells};
428              
429             # local $self->{debug} = 1;
430              
431 980         752 my $min_dist = 2;
432             # minlen = 0 => min_dist = 2,
433             # minlen = 1 => min_dist = 2,
434             # minlen = 2 => min_dist = 3, etc
435 980 100       2471 $min_dist = $edge->attribute('minlen') + 1 if ref($edge);
436              
437             # if the node has outgoing edges (which might be shared)
438 980 100       1538 if (!ref($edge))
439             {
440 347 100       310 (undef,$edge) = first_kv($node->{edges}) if keys %{$node->{edges}} > 0;
  347         1426  
441             }
442              
443 980 100       811 my $dir = undef; $dir = $edge->flow() if ref($edge);
  980         2642  
444              
445 980         863 my @tries;
446             # if (ref($parent) && defined $parent->{x})
447 980 100       777 if (keys %{$node->{edges}} > 0)
  980         2307  
448             {
449 939 100 66     728 my $src_node = $parent; $src_node = $edge->{from} if ref($edge) && !ref($parent);
  939         3229  
450 939 50       1432 print STDERR "# from $src_node->{name} to $node->{name}: edge $edge dir $dir\n" if $self->{debug};
451              
452             # if there are more than one edge to this node, and they share a start point,
453             # move the node at least 3 cells away to create space for the joints
454              
455 939         894 my ($s_p, @ss_p);
456 939 50       2518 ($s_p, @ss_p) = $edge->port('start') if ref($edge);
457              
458 939         872 my ($from,$to);
459 939 50       1497 if (ref($edge))
460             {
461 939         1029 $from = $edge->{from}; $to = $edge->{to};
  939         854  
462             }
463              
464 939         681 my @shared_nodes;
465 939 100 100     1768 @shared_nodes = $from->nodes_sharing_start($s_p,@ss_p) if defined $s_p && @ss_p > 0;
466              
467             print STDERR "# Edge from '$src_node->{name}' shares an edge start with ", scalar @shared_nodes, " other nodes\n"
468 939 50       1333 if $self->{debug};
469              
470 939 100       1481 if (@shared_nodes > 1)
471             {
472 15 50       29 $min_dist = 3 if $min_dist < 3; # make space
473 15 50       37 $min_dist++ if $edge->label() ne ''; # make more space for the label
474              
475             # if we are the first shared node to be placed
476 15         34 my $placed = $self->_placed_shared(@shared_nodes);
477              
478 15 100       24 if (defined $placed)
479             {
480             # we are not the first, so skip the placement below
481             # instead place on the same column/row as already placed node(s)
482 9         10 my ($bx, $by) = @$placed;
483              
484 9         22 my $flow = $node->flow();
485              
486             print STDERR "# One of the shared nodes was already placed at ($bx,$by) with flow $flow\n"
487 9 50       21 if $self->{debug};
488              
489 9         9 my $ofs = 2; # start with a distance of 2
490 9 50       7 my ($mx, $my) = @{ ($flow_shift->{$flow} || [ 0, 1 ]) };
  9         25  
491              
492 9         8 while (1)
493             {
494 16         18 my $x = $bx + $mx * $ofs; my $y = $by + $my * $ofs;
  16         17  
495              
496             print STDERR "# Trying to place $node->{name} at ($x,$y)\n"
497 16 50       22 if $self->{debug};
498              
499 16 100       36 next if $self->_clear_tries($node, $cells, [ $x,$y ]) == 0;
500 9 50       27 last if $node->_do_place($x,$y,$self);
501             }
502             continue {
503 7         8 $ofs += 2;
504             }
505 9         37 return 0; # found place already
506             } # end we-are-the-first-to-be-placed
507             }
508              
509             # shared end point?
510 930 50       2561 ($s_p, @ss_p) = $edge->port('end') if ref($edge);
511              
512 930 100 100     1969 @shared_nodes = $to->nodes_sharing_end($s_p,@ss_p) if defined $s_p && @ss_p > 0;
513              
514             print STDERR "# Edge from '$src_node->{name}' shares an edge end with ", scalar @shared_nodes, " other nodes\n"
515 930 50       1499 if $self->{debug};
516              
517 930 100       1784 if (@shared_nodes > 1)
518             {
519 24 100       50 $min_dist = 3 if $min_dist < 3;
520 24 100       61 $min_dist++ if $edge->label() ne ''; # make more space for the label
521              
522             # if the node to be placed is not in the list to be placed, it is the end-point
523              
524             # see if we are the first shared node to be placed
525 24         46 my $placed = $self->_placed_shared(@shared_nodes);
526              
527             # print STDERR "# "; for (@shared_nodes) { print $_->{name}, " "; } print "\n";
528              
529 24 100 100     133 if ((grep( $_ == $node, @shared_nodes)) && defined $placed)
530             {
531             # we are not the first, so skip the placement below
532             # instead place on the same column/row as already placed node(s)
533 7         12 my ($bx, $by) = @$placed;
534              
535 7         16 my $flow = $node->flow();
536              
537             print STDERR "# One of the shared nodes was already placed at ($bx,$by) with flow $flow\n"
538 7 50       15 if $self->{debug};
539              
540 7         9 my $ofs = 2; # start with a distance of 2
541 7 50       6 my ($mx, $my) = @{ ($flow_shift->{$flow} || [ 0, 1 ]) };
  7         21  
542              
543 7         7 while (1)
544             {
545 13         15 my $x = $bx + $mx * $ofs; my $y = $by + $my * $ofs;
  13         12  
546              
547             print STDERR "# Trying to place $node->{name} at ($x,$y)\n"
548 13 50       20 if $self->{debug};
549              
550 13 100       27 next if $self->_clear_tries($node, $cells, [ $x,$y ]) == 0;
551 7 50       19 last if $node->_do_place($x,$y,$self);
552             }
553             continue {
554 6         9 $ofs += 2;
555             }
556 7         29 return 0; # found place already
557             } # end we-are-the-first-to-be-placed
558             }
559             }
560              
561 964 100 66     2833 if (ref($parent) && defined $parent->{x})
562             {
563 602         1272 @tries = $parent->_near_places($cells, $min_dist, undef, 0, $dir);
564              
565             print STDERR
566             "# Trying chained placement of $node->{name} with min distance $min_dist from parent $parent->{name}\n"
567 602 50       1166 if $self->{debug};
568              
569             # weed out positions that are unsuitable
570 602         1275 @tries = $self->_clear_tries($node, $cells, \@tries);
571              
572 602 50       1235 splice (@tries,0,$try) if $try > 0; # remove the first N tries
573 602 50       990 print STDERR "# Left with " . scalar @tries . " tries for node $node->{name}\n" if $self->{debug};
574              
575 602         1349 while (@tries > 0)
576             {
577 603         703 my $x = shift @tries;
578 603         603 my $y = shift @tries;
579              
580 603 50       966 print STDERR "# Trying to place $node->{name} at $x,$y\n" if $self->{debug};
581 603 100       1407 return 0 if $node->_do_place($x,$y,$self);
582             } # for all trial positions
583             }
584              
585 363 50 33     1245 print STDERR "# Trying to place $node->{name} at 0,0\n" if $try == 0 && $self->{debug};
586             # Try to place node at upper left corner (the very first node to be
587             # placed will usually end up there).
588 363 100 66     1390 return 0 if $try == 0 && $node->_do_place(0,0,$self);
589              
590             # try to place node near the predecessor(s)
591 89         231 my @pre_all = $node->predecessors();
592              
593 89 50       217 print STDERR "# Predecessors of $node->{name} " . scalar @pre_all . "\n" if $self->{debug};
594              
595             # find all already placed predecessors
596 89         89 my @pre;
597 89         141 for my $p (@pre_all)
598             {
599 5 50       16 push @pre, $p if defined $p->{x};
600 5 50 33     16 print STDERR "# Placed predecessors of $node->{name}: $p->{name} at $p->{x},$p->{y}\n" if $self->{debug} && defined $p->{x};
601             }
602              
603             # sort predecessors on their rank (to try first the higher ranking ones on placement)
604 89         115 @pre = sort { $b->{rank} <=> $a->{rank} } @pre;
  0         0  
605              
606 89 50       182 print STDERR "# Number of placed predecessors of $node->{name}: " . scalar @pre . "\n" if $self->{debug};
607              
608 89 100 66     351 if (@pre <= 2 && @pre > 0)
609             {
610              
611 5 50       8 if (@pre == 1)
612             {
613             # only one placed predecessor, so place $node near it
614 5 50       12 print STDERR "# placing $node->{name} near predecessor\n" if $self->{debug};
615 5         13 @tries = ( $pre[0]->_near_places($cells, $min_dist), $pre[0]->_near_places($cells,$min_dist+2) );
616             }
617             else
618             {
619             # two placed predecessors, so place at crossing point of both of them
620             # compute difference between the two nodes
621              
622 0         0 my $dx = ($pre[0]->{x} - $pre[1]->{x});
623 0         0 my $dy = ($pre[0]->{y} - $pre[1]->{y});
624              
625             # are both nodes NOT on a straight line?
626 0 0 0     0 if ($dx != 0 && $dy != 0)
627             {
628             # ok, so try to place at the crossing point
629             @tries = (
630             $pre[0]->{x}, $pre[1]->{y},
631             $pre[0]->{y}, $pre[1]->{x},
632 0         0 );
633             }
634             else
635             {
636             # two nodes on a line, try to place node in the middle
637 0 0       0 if ($dx == 0)
638             {
639 0         0 @tries = ( $pre[1]->{x}, $pre[1]->{y} + int($dy / 2) );
640             }
641             else
642             {
643 0         0 @tries = ( $pre[1]->{x} + int($dx / 2), $pre[1]->{y} );
644             }
645             }
646             # XXX TODO BUG: shouldn't we also try this if we have more than 2
647             # placed predecessors?
648              
649             # In addition, we can also try to place the node around the
650             # different nodes:
651 0         0 foreach my $n (@pre)
652             {
653 0         0 push @tries, $n->_near_places($cells, $min_dist);
654             }
655             }
656             }
657              
658 89         217 my @suc_all = $node->successors();
659              
660             # find all already placed successors
661 89         140 my @suc;
662 89         122 for my $s (@suc_all)
663             {
664 101 100       211 push @suc, $s if defined $s->{x};
665             }
666 89 50       184 print STDERR "# Number of placed successors of $node->{name}: " . scalar @suc . "\n" if $self->{debug};
667 89         139 foreach my $s (@suc)
668             {
669             # for each successors (especially if there is only one), try to place near
670 18         42 push @tries, $s->_near_places($cells, $min_dist);
671 18         39 push @tries, $s->_near_places($cells, $min_dist + 2);
672             }
673              
674             # weed out positions that are unsuitable
675 89         205 @tries = $self->_clear_tries($node, $cells, \@tries);
676              
677 89 50       196 print STDERR "# Left with " . scalar @tries . " for node $node->{name}\n" if $self->{debug};
678              
679 89 50       191 splice (@tries,0,$try) if $try > 0; # remove the first N tries
680              
681 89         175 while (@tries > 0)
682             {
683 21         31 my $x = shift @tries;
684 21         27 my $y = shift @tries;
685              
686 21 50       43 print STDERR "# Trying to place $node->{name} at $x,$y\n" if $self->{debug};
687 21 50       58 return 0 if $node->_do_place($x,$y,$self);
688              
689             } # for all trial positions
690              
691             ##############################################################################
692             # all simple possibilities exhausted, try a generic approach
693              
694 68 50       135 print STDERR "# No more simple possibilities for node $node->{name}\n" if $self->{debug};
695              
696             # XXX TODO:
697             # find out which sides of the node predecessor node(s) still have free
698             # ports/slots. With increasing distances, try to place the node around these.
699              
700             # If no predecessors/incoming edges, try to place in column 0, otherwise
701             # considered the node's rank, too
702              
703 68 50       73 my $col = 0; $col = $node->{rank} * 2 if @pre > 0;
  68         127  
704              
705 68 50       129 $col = $pre[0]->{x} if @pre > 0;
706              
707             # find the first free row
708 68         73 my $y = 0;
709 68         379 $y +=2 while (exists $cells->{"$col,$y"});
710 68 100       180 $y += 1 if exists $cells->{"$col," . ($y-1)}; # leave one cell spacing
711              
712             # now try to place node (or node cluster)
713 68         52 while (1)
714             {
715 70 100       192 next if $self->_clear_tries($node, $cells, [ $col,$y ]) == 0;
716 68 50       181 last if $node->_do_place($col,$y,$self);
717             }
718             continue {
719 2         5 $y += 2;
720             }
721              
722 68         91 $node->{x} = $col;
723              
724 68         182 0; # success, score 0
725             }
726              
727             sub _trace_path
728             {
729             # find a free way from $src to $dst (both need to be placed beforehand)
730 899     899   863 my ($self, $src, $dst, $edge) = @_;
731              
732 899 50       1388 print STDERR "# Finding path from '$src->{name}' to '$dst->{name}'\n" if $self->{debug};
733 899 50       1339 print STDERR "# src: $src->{x}, $src->{y} dst: $dst->{x}, $dst->{y}\n" if $self->{debug};
734              
735 899         2089 my $coords = $self->_find_path ($src, $dst, $edge);
736              
737             # found no path?
738 899 50       1506 if (!defined $coords)
739             {
740 0 0       0 print STDERR "# Unable to find path from $src->{name} ($src->{x},$src->{y}) to $dst->{name} ($dst->{x},$dst->{y})\n" if $self->{debug};
741 0         0 return undef;
742             }
743              
744             # path is empty, happens for sharing edges with only a joint
745 899 100       1576 return 1 if scalar @$coords == 0;
746              
747             # Create all cells from the returned list and score path (lower score: better)
748 891         706 my $i = 0;
749 891         684 my $score = 0;
750 891         1473 while ($i < scalar @$coords)
751             {
752 2040         2043 my $type = $coords->[$i+2];
753 2040         3566 $self->_create_cell($edge,$coords->[$i],$coords->[$i+1],$type);
754 2040         1468 $score ++; # each element: one point
755 2040         1786 $type &= EDGE_TYPE_MASK; # mask flags
756             # edge bend or cross: one point extra
757 2040 100 100     4868 $score ++ if $type != EDGE_HOR && $type != EDGE_VER;
758 2040 50       2661 $score += 3 if $type == EDGE_CROSS; # crossings are doubleplusungood
759 2040         3412 $i += 3;
760             }
761              
762 891         2332 $score;
763             }
764              
765             sub _create_cell
766             {
767 2040     2040   2189 my ($self,$edge,$x,$y,$type) = @_;
768              
769 2040         1791 my $cells = $self->{cells}; my $xy = "$x,$y";
  2040         2296  
770              
771 2040 100 66     3608 if (ref($cells->{$xy}) && $cells->{$xy}->isa('Graph::Easy::Edge'))
772             {
773 19         87 $cells->{$xy}->_make_cross($edge,$type & EDGE_FLAG_MASK);
774             # insert a EDGE_HOLE into the cells of the edge (but not into the list of
775             # to-be-rendered cells). This cell will be removed by the optimizer later on.
776 19         62 Graph::Easy::Edge::Cell->new( type => EDGE_HOLE, edge => $edge, x => $x, y => $y );
777 19         25 return;
778             }
779              
780 2021         5048 my $path = Graph::Easy::Edge::Cell->new( type => $type, edge => $edge, x => $x, y => $y );
781 2021         3579 $cells->{$xy} = $path; # store in cells
782             }
783              
784             sub _path_is_clear
785             {
786             # For all points (x,y pairs) in the path, check that the cell is still free
787             # $path points to a list of [ x,y,type, x,y,type, ...]
788 33     33   41 my ($self,$path) = @_;
789              
790 33         40 my $cells = $self->{cells};
791 33         34 my $i = 0;
792 33         83 while ($i < scalar @$path)
793             {
794 43         49 my $x = $path->[$i];
795 43         55 my $y = $path->[$i+1];
796             # my $t = $path->[$i+2];
797 43         41 $i += 3;
798              
799 43 100       142 return 0 if exists $cells->{"$x,$y"}; # obstacle hit
800             }
801 32         74 1; # path is clear
802             }
803              
804             1;
805             __END__