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.75';
9              
10             #############################################################################
11             #############################################################################
12              
13             package Graph::Easy::Node;
14              
15 49     49   316 use strict;
  49         120  
  49         4132  
16 49     49   290 use warnings;
  49         109  
  49         2416  
17              
18 49         114048 use Graph::Easy::Edge::Cell qw/
19             EDGE_END_E EDGE_END_N EDGE_END_S EDGE_END_W
20 49     49   302 /;
  49         113  
21              
22             sub _shuffle_dir
23             {
24             # take a list with four entries and shuffle them around according to $dir
25 3674     3674   6682 my ($self, $e, $dir) = @_;
26              
27             # $dir: 0 => north, 90 => east, 180 => south, 270 => west
28              
29 3674 100       8749 $dir = 90 unless defined $dir; # default is east
30              
31 3674 100       11337 return [ @$e ] if $dir == 90; # default is no shuffling
32              
33 1603         4038 my @shuffle = (0,1,2,3); # the default
34 1603 100       5333 @shuffle = (1,2,0,3) if $dir == 180; # south
35 1603 100       3477 @shuffle = (2,3,1,0) if $dir == 270; # west
36 1603 100       3320 @shuffle = (3,0,2,1) if $dir == 0; # north
37              
38             [
39 1603         5936 $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   443 my ($self, $turn) = @_;
50              
51 229         846 my $dir = $self->flow();
52              
53 229         511 $dir += $turn;
54 229 100       671 $dir += 360 if $dir < 0;
55 229 50       688 $dir -= 360 if $dir > 360;
56 229         691 $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   9015 my ($n, $cells, $d, $type, $loose, $dir) = @_;
73              
74 3669   100     11893 my $cx = $n->{cx} || 1;
75 3669   100     9427 my $cy = $n->{cy} || 1;
76            
77 3669 100       7782 $d = 2 unless defined $d; # default is distance = 2
78              
79 3669         4977 my $flags = $type;
80              
81 3669 100       10068 if (ref($flags) ne 'ARRAY')
82             {
83 3148         11901 $flags = [
84             EDGE_END_W,
85             EDGE_END_N,
86             EDGE_END_E,
87             EDGE_END_S,
88             ];
89             }
90 3669 100       22312 $dir = $n->flow() unless defined $dir;
91              
92 3669         15430 my $index = $n->_shuffle_dir( [ 0,3,6,9], $dir);
93              
94 3669         8847 my @places = ();
95              
96             # single-celled node
97 3669 100       7953 if ($cx + $cy == 2)
98             {
99 3490         26032 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             $n->{x}, $n->{y} - $d, $flags->[3], # up
104             );
105              
106 3490         7441 for my $i (0..3)
107             {
108 13960         24994 my $idx = $index->[$i];
109 13960         26467 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     48282 next if !$loose && exists $cells->{"$x,$y"};
116 13458         23270 push @places, $x, $y;
117 13458 100       50790 push @places, $t if defined $type;
118             }
119 3490         30854 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         464 my $nx = $n->{x};
130 179         283 my $ny = $n->{y};
131 179         233 my ($px,$py);
132              
133 179         216 my $idx = 0;
134 179         511 my @results = ( [], [], [], [] );
135            
136 179         302 $cy--; $cx--;
  179         205  
137 179         333 my $t = $flags->[$idx++];
138             # right
139 179         240 $px = $nx + $cx + $d;
140 179         394 for my $y (0 .. $cy)
141             {
142 377         509 $py = $y + $ny;
143 377 100 100     1538 next if exists $cells->{"$px,$py"} && !$loose;
144 356         425 push @{$results[0]}, $px, $py;
  356         1328  
145 356 100       875 push @{$results[0]}, $t if defined $type;
  192         550  
146             }
147              
148             # below
149 179         320 $py = $ny + $cy + $d;
150 179         306 $t = $flags->[$idx++];
151 179         401 for my $x (0 .. $cx)
152             {
153 556         642 $px = $x + $nx;
154 556 100 100     1828 next if exists $cells->{"$px,$py"} && !$loose;
155 522         2114 push @{$results[1]}, $px, $py;
  522         1169  
156 522 100       1216 push @{$results[1]}, $t if defined $type;
  279         650  
157             }
158              
159             # left
160 179         369 $px = $nx - $d;
161 179         433 $t = $flags->[$idx++];
162 179         340 for my $y (0 .. $cy)
163             {
164 377         442 $py = $y + $ny;
165 377 100 100     1633 next if exists $cells->{"$px,$py"} && !$loose;
166 362         415 push @{$results[2]}, $px, $py;
  362         926  
167 362 100       835 push @{$results[2]}, $t if defined $type;
  191         498  
168             }
169              
170             # top
171 179         280 $py = $ny - $d;
172 179         279 $t = $flags->[$idx];
173 179         394 for my $x (0 .. $cx)
174             {
175 556         600 $px = $x + $nx;
176 556 100 100     1679 next if exists $cells->{"$px,$py"} && !$loose;
177 555         811 push @{$results[3]}, $px, $py;
  555         1543  
178 555 100       1214 push @{$results[3]}, $t if defined $type;
  278         712  
179             }
180              
181             # accumulate the results in the requested, shuffled order
182 179         351 for my $i (0..3)
183             {
184 716         1170 my $idx = $index->[$i] / 3;
185 716         713 push @places, @{$results[$idx]};
  716         2791  
186             }
187              
188 179         3179 @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   2072 my ($self, $places, $allowed, $step) = @_;
196              
197 56 50       211 print STDERR
198             "# calculating allowed places for $self->{name} from " . @$places .
199             " positions and " . scalar @$allowed . " allowed ones:\n"
200             if $self->{graph}->{debug};
201              
202 56   100     194 $step ||= 2; # default: "x,y"
203              
204 56         70 my @good;
205 56         75 my $i = 0;
206 56         158 while ($i < @$places)
207             {
208 480         881 my ($x,$y) = ($places->[$i], $places->[$i+1]);
209 480         503 my $allow = 0;
210 480         464 my $j = 0;
211 480         960 while ($j < @$allowed)
212             {
213 4260         4941 my ($m,$n) = ($allowed->[$j], $allowed->[$j+1]);
214 4260 100 50     8728 $allow++ and last if ($m == $x && $n == $y);
      100        
215 4260         7144 } continue { $j += 2; }
216 480 100       836 next unless $allow;
217 183         845 push @good, $places->[$i + $_ -1] for (1..$step);
218 480         973 } continue { $i += $step; }
219              
220 56 50       192 print STDERR "# left with " . ((scalar @good) / $step) . " position(s)\n" if $self->{graph}->{debug};
221 56         678 @good;
222             }
223              
224             sub _allow
225             {
226             # return a list of places, depending on the start/end atribute:
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   12268 my ($self, $dir, @pos) = @_;
235              
236             # for relative direction, get the absolute flow from the node
237 72 50       323 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         1594 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         179 my $p = $place->{$dir};
255              
256 72 50       177 return [] unless defined $p;
257              
258             # start pos
259 72         225 my $x = $p->[0] + $self->{x} + $p->[2] * $self->{cx};
260 72         157 my $y = $p->[1] + $self->{y} + $p->[3] * $self->{cy};
261              
262 72         90 my @allowed;
263 72 100       192 push @pos, '' if @pos == 0;
264              
265 72         117 my $c = $p->[4];
266 72 100 66     482 if (@pos == 1 && $pos[0] eq '')
267             {
268             # allow all of them
269 39         116 for (1 .. $self->{$c})
270             {
271 173         248 push @allowed, $x, $y;
272 173         201 $x += $p->[5];
273 173         252 $y += $p->[6];
274             }
275             }
276             else
277             {
278             # allow only the given position
279 33         68 my $ps = $pos[0];
280             # limit to 0..$self->{cx}-1
281 33 100       113 $ps = $self->{$c} + $ps if $ps < 0;
282 33 100       77 $ps = 0 if $ps < 0;
283 33 100       104 $ps = $self->{$c} - 1 if $ps >= $self->{$c};
284 33         59 $x += $p->[5] * $ps;
285 33         253 $y += $p->[6] * $ps;
286 33         82 push @allowed, $x, $y;
287             }
288              
289 72         633 \@allowed;
290             }
291              
292             package Graph::Easy;
293 49     49   393 use strict;
  49         109  
  49         2985  
294 49     49   1362 use Graph::Easy::Node::Cell;
  49         126  
  49         1952  
295              
296 49         33527 use Graph::Easy::Edge::Cell qw/
297             EDGE_HOR EDGE_VER EDGE_CROSS
298             EDGE_TYPE_MASK
299             EDGE_HOLE
300 49     49   262 /;
  49         113  
301              
302             sub _clear_tries
303             {
304             # Take a list of potential positions for a node, and then remove the
305             # ones that are immidiately near any other node.
306             # Returns a list of "good" positions. Afterwards $node->{x} is undef.
307 790     790   1585 my ($self, $node, $cells, $tries) = @_;
308              
309 790         1058 my $src = 0; my @new;
  790         962  
310              
311 790 50       2401 print STDERR "# clearing ", scalar @$tries / 2, " tries for $node->{name}\n" if $self->{debug};
312              
313 790         3118 my $node_grandpa = $node->find_grandparent();
314              
315 790         2614 while ($src < scalar @$tries)
316             {
317             # check the current position
318              
319             # temporary place node here
320 2496         4066 my $x = $tries->[$src];
321 2496         3965 my $y = $tries->[$src+1];
322              
323             # print STDERR "# checking $x,$y\n" if $self->{debug};
324              
325 2496         4432 $node->{x} = $x;
326 2496         9676 $node->{y} = $y;
327              
328 2496         11071 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 2496   50     26142 push @near, $x-1, $y-1, # upperleft corner
      50        
      50        
      50        
332             $x-1, $y+($node->{cy}||1), # lowerleft corner
333             $x+($node->{cx}||1), $y+($node->{cy}||1), # lowerright corner
334             $x+($node->{cx}||1), $y-1; # upperright corner
335            
336             # check all near places to be free from nodes (except our children)
337 2496         3202 my $j = 0; my $g = 0;
  2496         6475  
338 2496         5902 while ($j < @near)
339             {
340 19372         48691 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         26020 my $cell = $cells->{$xy};
345              
346             # skip, unless we are a children of node, or the cell is our children
347 19372 100 66     61401 next unless ref($cell) && $cell->isa('Graph::Easy::Node');
348              
349 136         497 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     1185 next if $grandpa == $node || $cell == $node_grandpa || $grandpa == $node_grandpa;
      33        
355              
356 136         224 $g++; last;
  136         304  
357              
358 19236         46086 } continue { $j += 2; }
359              
360 2496 100       5499 if ($g == 0)
361             {
362 2360         6977 push @new, $tries->[$src], $tries->[$src+1];
363             }
364 2496         17412 $src += 2;
365             }
366              
367 790         1465 $node->{x} = undef;
368              
369 790         5857 @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   78 my ($self) = shift;
383              
384 39         53 my $placed;
385 39         87 for my $n (@_)
386             {
387 76 100 50     293 $placed = [$n->{x}, $n->{y}] and last if defined $n->{x};
388             }
389 39         101 $placed;
390             }
391              
392 49     49   381 use Graph::Easy::Util qw(first_kv);
  49         126  
  49         176732  
393              
394             sub _find_node_place
395             {
396             # Try to place a node (or node cluster). Return score (usually 0).
397 982     982   2718 my ($self, $node, $try, $parent, $edge) = @_;
398              
399 982   50     4865 $try ||= 0;
400              
401 982 50       3003 print STDERR "# Finding place for $node->{name}, try #$try\n" if $self->{debug};
402 982 50 33     3371 print STDERR "# Parent node is '$parent->{name}'\n" if $self->{debug} && ref $parent;
403              
404 982 50       2512 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       3328 if ($node->{rank} >= 0)
409             {
410 3         7 my $r = abs($node->{rank});
411             # print STDERR "# User-set rank for $node->{name} (rank $r)\n";
412 3         7 my $c = $self->{_rank_coord};
413             # use Data::Dumper; print STDERR "# rank_pos: \n", Dumper($self->{_rank_pos});
414 3 100       28 if (exists $self->{_rank_pos}->{ $r })
415             {
416 2         6 my $co = { x => 0, y => 0 };
417 2         8 $co->{$c} = $self->{_rank_pos}->{ $r };
418 2         3 while (1 < 3)
419             {
420             # print STDERR "# trying to force placement of '$node->{name}' at $co->{x} $co->{y}\n";
421 5 100       43 return 0 if $node->_do_place($co->{x},$co->{y},$self);
422 3         7 $co->{$c} += 2;
423             }
424             }
425             }
426              
427 980         1653 my $cells = $self->{cells};
428              
429             # local $self->{debug} = 1;
430              
431 980         1613 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       4592 $min_dist = $edge->attribute('minlen') + 1 if ref($edge);
436              
437             # if the node has outgoing edges (which might be shared)
438 980 100       2692 if (!ref($edge))
439             {
440 347 100       1269 (undef,$edge) = first_kv($node->{edges}) if keys %{$node->{edges}} > 0;
  347         2622  
441             }
442              
443 980 100       1698 my $dir = undef; $dir = $edge->flow() if ref($edge);
  980         4932  
444              
445 980         1649 my @tries;
446             # if (ref($parent) && defined $parent->{x})
447 980 100       1580 if (keys %{$node->{edges}} > 0)
  980         3766  
448             {
449 939 100 66     1277 my $src_node = $parent; $src_node = $edge->{from} if ref($edge) && !ref($parent);
  939         5525  
450 939 50       2871 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         1289 my ($s_p, @ss_p);
456 939 50       4531 ($s_p, @ss_p) = $edge->port('start') if ref($edge);
457              
458 939         1799 my ($from,$to);
459 939 50       2877 if (ref($edge))
460             {
461 939         2959 $from = $edge->{from}; $to = $edge->{to};
  939         1702  
462             }
463              
464 939         1261 my @shared_nodes;
465 939 100 100     3200 @shared_nodes = $from->nodes_sharing_start($s_p,@ss_p) if defined $s_p && @ss_p > 0;
466              
467 939 50       2704 print STDERR "# Edge from '$src_node->{name}' shares an edge start with ", scalar @shared_nodes, " other nodes\n"
468             if $self->{debug};
469              
470 939 100       2203 if (@shared_nodes > 1)
471             {
472 15 50       51 $min_dist = 3 if $min_dist < 3; # make space
473 15 50       79 $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         68 my $placed = $self->_placed_shared(@shared_nodes);
477              
478 15 100       42 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         31 my ($bx, $by) = @$placed;
483              
484 9         36 my $flow = $node->flow();
485              
486 9 50       39 print STDERR "# One of the shared nodes was already placed at ($bx,$by) with flow $flow\n"
487             if $self->{debug};
488              
489 9         15 my $ofs = 2; # start with a distance of 2
490 9 50       13 my ($mx, $my) = @{ ($flow_shift->{$flow} || [ 0, 1 ]) };
  9         53  
491              
492 9         17 while (1)
493             {
494 16         37 my $x = $bx + $mx * $ofs; my $y = $by + $my * $ofs;
  16         26  
495              
496 16 50       46 print STDERR "# Trying to place $node->{name} at ($x,$y)\n"
497             if $self->{debug};
498              
499 16 100       69 next if $self->_clear_tries($node, $cells, [ $x,$y ]) == 0;
500 9 50       53 last if $node->_do_place($x,$y,$self);
501             }
502             continue {
503 7         15 $ofs += 2;
504             }
505 9         63 return 0; # found place already
506             } # end we-are-the-first-to-be-placed
507             }
508              
509             # shared end point?
510 930 50       4498 ($s_p, @ss_p) = $edge->port('end') if ref($edge);
511              
512 930 100 100     3882 @shared_nodes = $to->nodes_sharing_end($s_p,@ss_p) if defined $s_p && @ss_p > 0;
513              
514 930 50       2598 print STDERR "# Edge from '$src_node->{name}' shares an edge end with ", scalar @shared_nodes, " other nodes\n"
515             if $self->{debug};
516              
517 930 100       9065 if (@shared_nodes > 1)
518             {
519 24 100       167 $min_dist = 3 if $min_dist < 3;
520 24 100       113 $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         89 my $placed = $self->_placed_shared(@shared_nodes);
526              
527             # print STDERR "# "; for (@shared_nodes) { print $_->{name}, " "; } print "\n";
528              
529 24 100 100     241 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         23 my ($bx, $by) = @$placed;
534              
535 7         31 my $flow = $node->flow();
536              
537 7 50       31 print STDERR "# One of the shared nodes was already placed at ($bx,$by) with flow $flow\n"
538             if $self->{debug};
539              
540 7         112 my $ofs = 2; # start with a distance of 2
541 7 50       12 my ($mx, $my) = @{ ($flow_shift->{$flow} || [ 0, 1 ]) };
  7         43  
542              
543 7         13 while (1)
544             {
545 13         26 my $x = $bx + $mx * $ofs; my $y = $by + $my * $ofs;
  13         21  
546              
547 13 50       42 print STDERR "# Trying to place $node->{name} at ($x,$y)\n"
548             if $self->{debug};
549              
550 13 100       50 next if $self->_clear_tries($node, $cells, [ $x,$y ]) == 0;
551 7 50       41 last if $node->_do_place($x,$y,$self);
552             }
553             continue {
554 6         17 $ofs += 2;
555             }
556 7         46 return 0; # found place already
557             } # end we-are-the-first-to-be-placed
558             }
559             }
560              
561 964 100 66     5303 if (ref($parent) && defined $parent->{x})
562             {
563 602         2883 @tries = $parent->_near_places($cells, $min_dist, undef, 0, $dir);
564              
565 602 50       2204 print STDERR
566             "# Trying chained placement of $node->{name} with min distance $min_dist from parent $parent->{name}\n"
567             if $self->{debug};
568              
569             # weed out positions that are unsuitable
570 602         2574 @tries = $self->_clear_tries($node, $cells, \@tries);
571              
572 602 50       2536 splice (@tries,0,$try) if $try > 0; # remove the first N tries
573 602 50       1916 print STDERR "# Left with " . scalar @tries . " tries for node $node->{name}\n" if $self->{debug};
574              
575 602         3543 while (@tries > 0)
576             {
577 603         1322 my $x = shift @tries;
578 603         1670 my $y = shift @tries;
579              
580 603 50       2334 print STDERR "# Trying to place $node->{name} at $x,$y\n" if $self->{debug};
581 603 100       2678 return 0 if $node->_do_place($x,$y,$self);
582             } # for all trial positions
583             }
584              
585 363 50 33     2278 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     4031 return 0 if $try == 0 && $node->_do_place(0,0,$self);
589              
590             # try to place node near the predecessor(s)
591 89         501 my @pre_all = $node->predecessors();
592              
593 89 50       493 print STDERR "# Predecessors of $node->{name} " . scalar @pre_all . "\n" if $self->{debug};
594              
595             # find all already placed predecessors
596 89         710 my @pre;
597 89         210 for my $p (@pre_all)
598             {
599 5 50       31 push @pre, $p if defined $p->{x};
600 5 50 33     33 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         233 @pre = sort { $b->{rank} <=> $a->{rank} } @pre;
  0         0  
605              
606 89 50       294 print STDERR "# Number of placed predecessors of $node->{name}: " . scalar @pre . "\n" if $self->{debug};
607              
608 89 100 66     561 if (@pre <= 2 && @pre > 0)
609             {
610              
611 5 50       18 if (@pre == 1)
612             {
613             # only one placed predecessor, so place $node near it
614 5 50       19 print STDERR "# placing $node->{name} near predecessor\n" if $self->{debug};
615 5         26 @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 0         0 @tries = (
630             $pre[0]->{x}, $pre[1]->{y},
631             $pre[0]->{y}, $pre[1]->{x},
632             );
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: shouldnt we also try this if we have more than 2 placed
647             # 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         623 my @suc_all = $node->successors();
659              
660             # find all already placed successors
661 89         270 my @suc;
662 89         221 for my $s (@suc_all)
663             {
664 101 100       524 push @suc, $s if defined $s->{x};
665             }
666 89 50       310 print STDERR "# Number of placed successors of $node->{name}: " . scalar @suc . "\n" if $self->{debug};
667 89         262 foreach my $s (@suc)
668             {
669             # for each successors (especially if there is only one), try to place near
670 18         86 push @tries, $s->_near_places($cells, $min_dist);
671 18         74 push @tries, $s->_near_places($cells, $min_dist + 2);
672             }
673              
674             # weed out positions that are unsuitable
675 89         489 @tries = $self->_clear_tries($node, $cells, \@tries);
676              
677 89 50       360 print STDERR "# Left with " . scalar @tries . " for node $node->{name}\n" if $self->{debug};
678              
679 89 50       272 splice (@tries,0,$try) if $try > 0; # remove the first N tries
680            
681 89         360 while (@tries > 0)
682             {
683 21         133 my $x = shift @tries;
684 21         98 my $y = shift @tries;
685              
686 21 50       97 print STDERR "# Trying to place $node->{name} at $x,$y\n" if $self->{debug};
687 21 50       103 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       206 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       119 my $col = 0; $col = $node->{rank} * 2 if @pre > 0;
  68         201  
704              
705 68 50       207 $col = $pre[0]->{x} if @pre > 0;
706            
707             # find the first free row
708 68         151 my $y = 0;
709 68         584 $y +=2 while (exists $cells->{"$col,$y"});
710 68 100       274 $y += 1 if exists $cells->{"$col," . ($y-1)}; # leave one cell spacing
711              
712             # now try to place node (or node cluster)
713 68         113 while (1)
714             {
715 70 100       524 next if $self->_clear_tries($node, $cells, [ $col,$y ]) == 0;
716 68 50       321 last if $node->_do_place($col,$y,$self);
717             }
718             continue {
719 2         4 $y += 2;
720             }
721              
722 68         182 $node->{x} = $col;
723              
724 68         363 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   1890 my ($self, $src, $dst, $edge) = @_;
731              
732 899 50       2751 print STDERR "# Finding path from '$src->{name}' to '$dst->{name}'\n" if $self->{debug};
733 899 50       2819 print STDERR "# src: $src->{x}, $src->{y} dst: $dst->{x}, $dst->{y}\n" if $self->{debug};
734              
735 899         12752 my $coords = $self->_find_path ($src, $dst, $edge);
736              
737             # found no path?
738 899 50       2767 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       3349 return 1 if scalar @$coords == 0;
746              
747             # Create all cells from the returned list and score path (lower score: better)
748 891         1860 my $i = 0;
749 891         1443 my $score = 0;
750 891         3179 while ($i < scalar @$coords)
751             {
752 2040         3802 my $type = $coords->[$i+2];
753 2040         8268 $self->_create_cell($edge,$coords->[$i],$coords->[$i+1],$type);
754 2040         2555 $score ++; # each element: one point
755 2040         2649 $type &= EDGE_TYPE_MASK; # mask flags
756             # edge bend or cross: one point extra
757 2040 100 100     8741 $score ++ if $type != EDGE_HOR && $type != EDGE_VER;
758 2040 50       5115 $score += 3 if $type == EDGE_CROSS; # crossings are doubleplusungood
759 2040         5308 $i += 3;
760             }
761              
762 891         5273 $score;
763             }
764              
765             sub _create_cell
766             {
767 2040     2040   4956 my ($self,$edge,$x,$y,$type) = @_;
768              
769 2040         3878 my $cells = $self->{cells}; my $xy = "$x,$y";
  2040         5023  
770            
771 2040 100 66     6722 if (ref($cells->{$xy}) && $cells->{$xy}->isa('Graph::Easy::Edge'))
772             {
773 19         139 $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         112 Graph::Easy::Edge::Cell->new( type => EDGE_HOLE, edge => $edge, x => $x, y => $y );
777 19         45 return;
778             }
779              
780 2021         9788 my $path = Graph::Easy::Edge::Cell->new( type => $type, edge => $edge, x => $x, y => $y );
781 2021         9265 $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   72 my ($self,$path) = @_;
789              
790 33         64 my $cells = $self->{cells};
791 33         53 my $i = 0;
792 33         112 while ($i < scalar @$path)
793             {
794 43         76 my $x = $path->[$i];
795 43         79 my $y = $path->[$i+1];
796             # my $t = $path->[$i+2];
797 43         59 $i += 3;
798              
799 43 100       215 return 0 if exists $cells->{"$x,$y"}; # obstacle hit
800             }
801 32         145 1; # path is clear
802             }
803              
804             1;
805             __END__