File Coverage

lib/Graph/Easy/Layout/Repair.pm
Criterion Covered Total %
statement 165 179 92.1
branch 75 104 72.1
condition 38 60 63.3
subroutine 13 13 100.0
pod n/a
total 291 356 81.7


line stmt bran cond sub pod time code
1             #############################################################################
2             # Layout directed graphs on a flat plane. Part of Graph::Easy.
3             #
4             # Code to repair spliced layouts (after group cells have been inserted).
5             #
6             #############################################################################
7              
8             package Graph::Easy::Layout::Repair;
9              
10             $VERSION = '0.75';
11              
12             #############################################################################
13             #############################################################################
14             # for layouts with groups:
15              
16             package Graph::Easy;
17              
18 49     49   300 use strict;
  49         131  
  49         2351  
19 49     49   336 use warnings;
  49         117  
  49         2325  
20              
21 49     49   308 use Graph::Easy::Util qw(ord_values);
  49         130  
  49         212372  
22              
23             sub _edges_into_groups
24             {
25 291     291   756 my $self = shift;
26              
27             # Put all edges between two nodes with the same group in the group as well
28 291         1811 for my $edge (ord_values $self->{edges})
29             {
30 911         4004 my $gf = $edge->{from}->group();
31 911         3195 my $gt = $edge->{to}->group();
32              
33 911 100 100     4689 $gf->_add_edge($edge) if defined $gf && defined $gt && $gf == $gt;
      100        
34             }
35              
36 291         1088 $self;
37             }
38              
39             sub _repair_nodes
40             {
41             # Splicing the rows/columns to add filler cells will have torn holes into
42             # multi-edges nodes, so we insert additional filler cells.
43 31     31   65 my ($self) = @_;
44 31         89 my $cells = $self->{cells};
45              
46             # Make multi-celled nodes occupy the proper double space due to splicing
47             # in group cell has doubled the layout in each direction:
48 31         202 for my $n ($self->nodes())
49             {
50             # 1 => 1, 2 => 3, 3 => 5, 4 => 7 etc
51 110         254 $n->{cx} = $n->{cx} * 2 - 1;
52 110         442 $n->{cy} = $n->{cy} * 2 - 1;
53             }
54              
55             # We might get away with not inserting filler cells if we just mark the
56             # cells as used (e.g. use only one global filler cell) since filler cells
57             # aren't actually rendered, anyway.
58              
59 31         161 for my $cell (ord_values $cells)
60             {
61 291 100       1966 next unless $cell->isa('Graph::Easy::Node::Cell');
62              
63             # we have "[ empty ] [ filler ]" (unless cell is on the same column as node)
64 10 100       41 if ($cell->{x} > $cell->{node}->{x})
65             {
66 4         11 my $x = $cell->{x} - 1; my $y = $cell->{y};
  4         7  
67              
68             # print STDERR "# inserting filler at $x,$y for $cell->{node}->{name}\n";
69 4         18 $cells->{"$x,$y"} =
70             Graph::Easy::Node::Cell->new(node => $cell->{node}, x => $x, y => $y );
71             }
72              
73             # we have " [ empty ] "
74             # " [ filler ] " (unless cell is on the same row as node)
75 10 100       39 if ($cell->{y} > $cell->{node}->{y})
76             {
77 8         15 my $x = $cell->{x}; my $y = $cell->{y} - 1;
  8         16  
78              
79             # print STDERR "# inserting filler at $x,$y for $cell->{node}->{name}\n";
80 8         33 $cells->{"$x,$y"} =
81             Graph::Easy::Node::Cell->new(node => $cell->{node}, x => $x, y => $y );
82             }
83             }
84             }
85              
86             sub _repair_cell
87             {
88 50     50   120 my ($self, $type, $edge, $x, $y, $after, $before) = @_;
89              
90             # already repaired?
91 50 50       368 return if exists $self->{cells}->{"$x,$y"};
92              
93             # print STDERR "# Insert edge cell at $x,$y (type $type) for edge $edge->{from}->{name} --> $edge->{to}->{name}\n";
94              
95 50         191 $self->{cells}->{"$x,$y"} =
96             Graph::Easy::Edge::Cell->new(
97             type => $type,
98             edge => $edge, x => $x, y => $y, before => $before, after => $after );
99              
100             }
101              
102             sub _splice_edges
103             {
104             # Splicing the rows/columns to add filler cells might have torn holes into
105             # edges, so we splice these together again.
106 31     31   62 my ($self) = @_;
107              
108 31         69 my $cells = $self->{cells};
109              
110 31 50       112 print STDERR "# Reparing spliced layout\n" if $self->{debug};
111              
112             # Edge end/start points inside groups are not handled here, but in
113             # _repair_group_edge()
114              
115             # go over the old layout, because the new cells were inserted into odd
116             # rows/columns and we do not care for these:
117 31 50       224 for my $cell (sort { $a->{x} <=> $b->{x} || $a->{y} <=> $b->{y} } values %$cells)
  612         1485  
118             {
119 241 100       1486 next unless $cell->isa('Graph::Easy::Edge::Cell');
120            
121 121         366 my $edge = $cell->{edge};
122              
123             #########################################################################
124             # check for "[ JOINT ] [ empty ] [ edge ]"
125            
126 121         216 my $x = $cell->{x} + 2; my $y = $cell->{y};
  121         183  
127              
128 121         221 my $type = $cell->{type} & EDGE_TYPE_MASK;
129              
130             # left is a joint and right exists
131 121 50 33     1004 if ( ($type == EDGE_S_E_W || $type == EDGE_N_E_W || $type == EDGE_E_N_S)
      33        
132             && exists $cells->{"$x,$y"})
133             {
134 0         0 my $right = $cells->{"$x,$y"};
135              
136             # print STDERR "# at $x,$y\n";
137              
138             # |-> [ empty ] [ node ]
139 0 0       0 if ($right->isa('Graph::Easy::Edge::Cell'))
140             {
141             # when the left one is a joint, the right one must be an edge
142 0 0       0 $self->error("Found non-edge piece ($right->{type} $right) right to a joint ($type)")
143             unless $right->isa('Graph::Easy::Edge::Cell');
144              
145             # print STDERR "splicing in HOR piece to the right of joint at $x, $y ($edge $right $right->{edge})\n";
146              
147             # insert the new piece before the first part of the edge after the joint
148 0 0       0 $self->_repair_cell(EDGE_HOR(), $right->{edge},$cell->{x}+1,$y,0)
149             if $edge != $right->{edge};
150             }
151             }
152              
153             #########################################################################
154             # check for "[ edge ] [ empty ] [ joint ]"
155            
156 121         205 $x = $cell->{x} - 2; $y = $cell->{y};
  121         196  
157              
158             # right is a joint and left exists
159 121 50 33     856 if ( ($type == EDGE_S_E_W || $type == EDGE_N_E_W || $type == EDGE_W_N_S)
      33        
160             && exists $cells->{"$x,$y"})
161             {
162 0         0 my $left = $cells->{"$x,$y"};
163              
164             # [ node ] [ empty ] [ <-| ]
165 0 0       0 if (!$left->isa('Graph::Easy::Node'))
166             {
167             # when the left one is a joint, the right one must be an edge
168 0 0       0 $self->error('Found non-edge piece right to a joint')
169             unless $left->isa('Graph::Easy::Edge::Cell');
170              
171             # insert the new piece before the joint
172 0 0       0 $self->_repair_cell(EDGE_HOR(), $edge, $cell->{x}+1,$y,0) # $left,$cell)
173             if $edge != $left->{edge};
174             }
175             }
176              
177             #########################################################################
178             # check for " [ joint ]
179             # [ empty ]
180             # [ edge ]"
181            
182 121         176 $x = $cell->{x}; $y = $cell->{y} + 2;
  121         182  
183              
184             # top is a joint and down exists
185 121 50 33     917 if ( ($type == EDGE_S_E_W || $type == EDGE_E_N_S || $type == EDGE_W_N_S)
      33        
186             && exists $cells->{"$x,$y"})
187             {
188 0         0 my $bottom = $cells->{"$x,$y"};
189              
190             # when top is a joint, the bottom one must be an edge
191 0 0       0 $self->error('Found non-edge piece below a joint')
192             unless $bottom->isa('Graph::Easy::Edge::Cell');
193              
194             # print STDERR "splicing in VER piece below joint at $x, $y\n";
195              
196             # XXX TODO
197             # insert the new piece after the joint
198 0 0       0 $self->_repair_cell(EDGE_VER(), $bottom->{edge},$x,$cell->{y}+1,0)
199             if $edge != $bottom->{edge};
200             }
201              
202             #########################################################################
203             # check for "[ --- ] [ empty ] [ ---> ]"
204              
205 121         187 $x = $cell->{x} + 2; $y = $cell->{y};
  121         221  
206              
207 121 100       498 if (exists $cells->{"$x,$y"})
208             {
209 83         305 my $right = $cells->{"$x,$y"};
210              
211 83 100 66     1069 $self->_repair_cell(EDGE_HOR(), $edge, $cell->{x}+1,$y,$cell,$right)
      66        
      100        
      33        
212             if $right->isa('Graph::Easy::Edge::Cell') &&
213             defined $right->{edge} && defined $right->{type} &&
214             # check that both cells belong to the same edge
215             ( $edge == $right->{edge} ||
216             # or the right part is a cross
217             $right->{type} == EDGE_CROSS ||
218             # or the left part is a cross
219             $cell->{type} == EDGE_CROSS );
220             }
221            
222             #########################################################################
223             # check for [ | ]
224             # [ empty ]
225             # [ | ]
226 121         204 $x = $cell->{x}; $y = $cell->{y}+2;
  121         190  
227              
228 121 100       425 if (exists $cells->{"$x,$y"})
229             {
230 67         116 my $below = $cells->{"$x,$y"};
231              
232 67 100 100     553 $self->_repair_cell(EDGE_VER(),$edge,$x,$cell->{y}+1,$cell,$below)
      66        
233             if $below->isa('Graph::Easy::Edge::Cell') &&
234             # check that both cells belong to the same edge
235             ( $edge == $below->{edge} ||
236             # or the lower part is a cross
237             $below->{type} == EDGE_CROSS ||
238             # or the upper part is a cross
239             $cell->{type} == EDGE_CROSS );
240             }
241              
242             } # end for all cells
243              
244 31         84 $self;
245             }
246              
247             sub _new_edge_cell
248             {
249             # create a new edge cell to be spliced into the layout for repairs
250 29     29   73 my ($self, $cells, $group, $edge, $x, $y, $after, $type) = @_;
251              
252 29 100       74 $type += EDGE_SHORT_CELL() if defined $group;
253              
254 29         145 my $e_cell = Graph::Easy::Edge::Cell->new(
255             type => $type, edge => $edge, x => $x, y => $y, after => $after);
256 29 100       641 $group->_del_cell($e_cell) if defined $group;
257 29         119 $cells->{"$x,$y"} = $e_cell;
258             }
259              
260             sub _check_edge_cell
261             {
262             # check a start/end edge cell and if nec. repair it
263 106     106   294 my ($self, $cell, $x, $y, $flag, $type, $match, $check, $where) = @_;
264              
265 106         302 my $edge = $cell->{edge};
266 106 100       336 if (grep { exists $_->{cell_class} && $_->{cell_class} =~ $match } ord_values ($check))
  759 100       12077  
267             {
268 27         59 $cell->{type} &= ~ $flag; # delete the flag
269              
270 27         128 $self->_new_edge_cell(
271             $self->{cells}, $edge->{group}, $edge, $x, $y, $where, $type + $flag);
272             }
273             }
274              
275             sub _repair_group_edge
276             {
277             # repair an edges inside a group
278 73     73   153 my ($self, $cell, $rows, $cols, $group) = @_;
279              
280 73         125 my $cells = $self->{cells};
281 73         105 my ($x,$y,$doit);
282              
283 73         159 my $type = $cell->{type};
284              
285             #########################################################################
286             # check for " [ empty ] [ |---> ]"
287 73         124 $x = $cell->{x} - 1; $y = $cell->{y};
  73         128  
288              
289 73 100       448 $self->_check_edge_cell($cell, $x, $y, EDGE_START_W, EDGE_HOR, qr/g[rl]/, $cols->{$x}, 0)
290             if (($type & EDGE_START_MASK) == EDGE_START_W);
291              
292             #########################################################################
293             # check for " [ <--- ] [ empty ]"
294 73         201 $x = $cell->{x} + 1;
295              
296 73 100       220 $self->_check_edge_cell($cell, $x, $y, EDGE_START_E, EDGE_HOR, qr/g[rl]/, $cols->{$x}, 0)
297             if (($type & EDGE_START_MASK) == EDGE_START_E);
298              
299             #########################################################################
300             # check for " [ --> ] [ empty ]"
301 73         160 $x = $cell->{x} + 1;
302              
303 73 100       548 $self->_check_edge_cell($cell, $x, $y, EDGE_END_E, EDGE_HOR, qr/g[rl]/, $cols->{$x}, -1)
304             if (($type & EDGE_END_MASK) == EDGE_END_E);
305              
306             # $self->_check_edge_cell($cell, $x, $y, EDGE_END_E, EDGE_E_N_S, qr/g[rl]/, $cols->{$x}, -1)
307             # if (($type & EDGE_END_MASK) == EDGE_END_E);
308              
309             #########################################################################
310             # check for " [ empty ] [ <-- ]"
311 73         287 $x = $cell->{x} - 1;
312              
313 73 100       218 $self->_check_edge_cell($cell, $x, $y, EDGE_END_W, EDGE_HOR, qr/g[rl]/, $cols->{$x}, -1)
314             if (($type & EDGE_END_MASK) == EDGE_END_W);
315              
316             #########################################################################
317             #########################################################################
318             # vertical cases
319              
320             #########################################################################
321             # check for [empty]
322             # [ | ]
323 73         442 $x = $cell->{x}; $y = $cell->{y} - 1;
  73         141  
324              
325 73 100       280 $self->_check_edge_cell($cell, $x, $y, EDGE_START_N, EDGE_VER, qr/g[tb]/, $rows->{$y}, 0)
326             if (($type & EDGE_START_MASK) == EDGE_START_N);
327              
328             #########################################################################
329             # check for [ |]
330             # [ empty ]
331 73         154 $y = $cell->{y} + 1;
332              
333 73 50       323 $self->_check_edge_cell($cell, $x, $y, EDGE_START_S, EDGE_VER, qr/g[tb]/, $rows->{$y}, 0)
334             if (($type & EDGE_START_MASK) == EDGE_START_S);
335              
336             #########################################################################
337             # check for [ v ]
338             # [empty]
339 73         114 $y = $cell->{y} + 1;
340              
341 73 100       287 $self->_check_edge_cell($cell, $x, $y, EDGE_END_S, EDGE_VER, qr/g[tb]/, $rows->{$y}, -1)
342             if (($type & EDGE_END_MASK) == EDGE_END_S);
343              
344             #########################################################################
345             # check for [ empty ]
346             # [ ^ ]
347 73         142 $y = $cell->{y} - 1;
348              
349 73 100       366 $self->_check_edge_cell($cell, $x, $y, EDGE_END_N, EDGE_VER, qr/g[tb]/, $rows->{$y}, -1)
350             if (($type & EDGE_END_MASK) == EDGE_END_N);
351             }
352              
353             sub _repair_edge
354             {
355             # repair an edge outside a group
356 48     48   92 my ($self, $cell, $rows, $cols) = @_;
357              
358 48         183 my $cells = $self->{cells};
359              
360             #########################################################################
361             # check for [ |\n|\nv ]
362             # [empty] ... [non-empty]
363             # [node]
364              
365 48         267 my $x = $cell->{x}; my $y = $cell->{y} + 1;
  48         183  
366              
367 48         118 my $below = $cells->{"$x,$y"}; # must be empty
368              
369 48 100 100     247 if (!ref($below) && (($cell->{type} & EDGE_END_MASK) == EDGE_END_S))
370             {
371 6 100       41 if (grep { exists $_->{cell_class} && $_->{cell_class} =~ /g[tb]/ } ord_values $rows->{$y})
  18 100       128  
372             {
373             # delete the start flag
374 2         4 $cell->{type} &= ~ EDGE_END_S;
375              
376 2         12 $self->_new_edge_cell($cells, undef, $cell->{edge}, $x, $y, -1,
377             EDGE_VER() + EDGE_END_S() );
378             }
379             }
380             # XXX TODO: do the other ends (END_N, END_W, END_E), too
381              
382             }
383              
384             sub _repair_edges
385             {
386             # fix edge end/start cells to be closer to the node cell they point at
387 31     31   85 my ($self, $rows, $cols) = @_;
388              
389 31         316 my $cells = $self->{cells};
390              
391             # go over all existing cells
392 31 50       264 for my $cell (sort { $a->{x} <=> $b->{x} || $a->{y} <=> $b->{y} } values %$cells)
  5687         12245  
393             {
394 1243 100       8271 next unless $cell->isa('Graph::Easy::Edge::Cell');
395              
396             # skip odd positions
397 185 100 100     1791 next unless ($cell->{x} & 1) == 0 && ($cell->{y} & 1) == 0;
398              
399 121         396 my $group = $cell->group();
400              
401 121 100       442 $self->_repair_edge($cell,$rows,$cols) unless $group;
402 121 100       432 $self->_repair_group_edge($cell,$rows,$cols,$group) if $group;
403              
404             } # end for all cells
405             }
406              
407             sub _fill_group_cells
408             {
409             # after doing a layout(), we need to add the group to each cell based on
410             # what group the nearest node is in.
411 31     31   62 my ($self, $cells_layout) = @_;
412              
413 31 50       104 print STDERR "\n# Padding with fill cells, have ",
414             scalar $self->groups(), " groups.\n" if $self->{debug};
415              
416             # take a shortcut if we do not have groups
417 31 50       177 return $self if $self->groups == 0;
418              
419 31         87 $self->{padding_cells} = 1; # set to true
420              
421             # We need to insert "filler" cells around each node/edge/cell:
422              
423             # To "insert" the filler cells, we simple multiply each X and Y by 2, this
424             # is O(N) where N is the number of actually existing cells. Otherwise we
425             # would have to create the full table-layout, and then insert rows/columns.
426 31         72 my $cells = {};
427 31         267 for my $key (sort keys %$cells_layout)
428             {
429 241         643 my ($x,$y) = split /,/, $key;
430 241         802 my $cell = $cells_layout->{$key};
431              
432 241         331 $x *= 2;
433 241         287 $y *= 2;
434 241         385 $cell->{x} = $x;
435 241         358 $cell->{y} = $y;
436              
437 241         890 $cells->{"$x,$y"} = $cell;
438             }
439              
440 31         91 $self->{cells} = $cells; # override with new cell layout
441              
442 31         144 $self->_splice_edges(); # repair edges
443 31         144 $self->_repair_nodes(); # repair multi-celled nodes
444              
445 31         152 my $c = 'Graph::Easy::Group::Cell';
446 31         134 for my $cell (ord_values $self->{cells})
447             {
448             # DO NOT MODIFY $cell IN THE LOOP BODY!
449              
450 303         767 my ($x,$y) = ($cell->{x},$cell->{y});
451              
452             # find the primary node for node cells, for group check
453 303         972 my $group = $cell->group();
454              
455             # not part of group, so no group-cells nec.
456 303 100       821 next unless $group;
457              
458             # now insert up to 8 filler cells around this cell
459 209         799 my $ofs = [ -1, 0,
460             0, -1,
461             +1, 0,
462             +1, 0,
463             0, +1,
464             0, +1,
465             -1, 0,
466             -1, 0, ];
467 209         509 while (@$ofs > 0)
468             {
469 1672         3001 $x += shift @$ofs;
470 1672         2577 $y += shift @$ofs;
471              
472 1672 100       8415 $cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y )
473             unless exists $cells->{"$x,$y"};
474             }
475             }
476              
477             # Nodes positioned two cols/rows apart (f.i. y == 0 and y == 2) will be
478             # three cells apart (y == 0 and y == 4) after the splicing, the step above
479             # will not be able to close that hole - it will create fillers at y == 1 and
480             # y == 3. So we close these holes now with an extra step.
481 31         336 for my $cell (ord_values ( $self->{cells} ))
482             {
483             # only for filler cells
484 1231 100       5211 next unless $cell->isa('Graph::Easy::Group::Cell');
485              
486 928         2105 my ($sx,$sy) = ($cell->{x},$cell->{y});
487 928         1339 my $group = $cell->{group};
488              
489 928         1212 my $x = $sx; my $y2 = $sy + 2; my $y = $sy + 1;
  928         1414  
  928         1272  
490             # look for:
491             # [ group ]
492             # [ empty ]
493             # [ group ]
494 928 100 100     5661 if (exists $cells->{"$x,$y2"} && !exists $cells->{"$x,$y"})
495             {
496 64         125 my $down = $cells->{"$x,$y2"};
497 64 100 66     1947 if ($down->isa('Graph::Easy::Group::Cell') && $down->{group} == $group)
498             {
499 12         48 $cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y );
500             }
501             }
502 928         1070 $x = $sx+1; my $x2 = $sx + 2; $y = $sy;
  928         1068  
  928         1138  
503             # look for:
504             # [ group ] [ empty ] [ group ]
505 928 50 66     9499 if (exists $cells->{"$x2,$y"} && !exists $cells->{"$x,$y"})
506             {
507 0         0 my $right = $cells->{"$x2,$y"};
508 0 0 0     0 if ($right->isa('Graph::Easy::Group::Cell') && $right->{group} == $group)
509             {
510 0         0 $cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y );
511             }
512             }
513             }
514              
515             # XXX TODO
516             # we should "grow" the group area to close holes
517              
518 31         315 for my $group (ord_values ( $self->{groups} ))
519             {
520 42         264 $group->_set_cell_types($cells);
521             }
522              
523             # create a mapping for each row/column so that we can repair edge starts/ends
524 31         94 my $rows = {};
525 31         74 my $cols = {};
526 31         166 for my $cell (ord_values ($cells))
527             {
528 1243         3398 $rows->{$cell->{y}}->{$cell->{x}} = $cell;
529 1243         3439 $cols->{$cell->{x}}->{$cell->{y}} = $cell;
530             }
531 31         295 $self->_repair_edges($rows,$cols); # insert short edge cells on group
532             # border rows/columns
533              
534             # for all groups, set the cell carrying the label (top-left-most cell)
535 31         173 for my $group (ord_values ( $self->{groups} ))
536             {
537 42         290 $group->_find_label_cell();
538             }
539              
540             # DEBUG:
541             # for my $cell (ord_values $cells)
542             # {
543             # $cell->_correct_size();
544             # }
545             #
546             # my $y = 0;
547             # for my $cell (sort { $a->{y} <=> $b->{y} || $a->{x} <=> $b->{x} } values %$cells)
548             # {
549             # print STDERR "\n" if $y != $cell->{y};
550             # print STDERR "$cell->{x},$cell->{y}, $cell->{w},$cell->{h}, ", $cell->{group}->{name} || 'none', "\t";
551             # $y = $cell->{y};
552             # }
553             # print STDERR "\n";
554              
555 31         710 $self;
556             }
557              
558             1;
559             __END__