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.76';
11              
12             #############################################################################
13             #############################################################################
14             # for layouts with groups:
15              
16             package Graph::Easy;
17              
18 48     48   202 use strict;
  48         58  
  48         1297  
19 48     48   151 use warnings;
  48         67  
  48         1350  
20              
21 48     48   159 use Graph::Easy::Util qw(ord_values);
  48         50  
  48         92029  
22              
23             sub _edges_into_groups
24             {
25 291     291   309 my $self = shift;
26              
27             # Put all edges between two nodes with the same group in the group as well
28 291         740 for my $edge (ord_values $self->{edges})
29             {
30 911         1813 my $gf = $edge->{from}->group();
31 911         1395 my $gt = $edge->{to}->group();
32              
33 911 100 100     2105 $gf->_add_edge($edge) if defined $gf && defined $gt && $gf == $gt;
      100        
34             }
35              
36 291         529 $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   36 my ($self) = @_;
44 31         39 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         91 for my $n ($self->nodes())
49             {
50             # 1 => 1, 2 => 3, 3 => 5, 4 => 7 etc
51 110         126 $n->{cx} = $n->{cx} * 2 - 1;
52 110         129 $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         84 for my $cell (ord_values $cells)
60             {
61 291 100       667 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       24 if ($cell->{x} > $cell->{node}->{x})
65             {
66 4         6 my $x = $cell->{x} - 1; my $y = $cell->{y};
  4         3  
67              
68             # print STDERR "# inserting filler at $x,$y for $cell->{node}->{name}\n";
69             $cells->{"$x,$y"} =
70 4         11 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       18 if ($cell->{y} > $cell->{node}->{y})
76             {
77 8         10 my $x = $cell->{x}; my $y = $cell->{y} - 1;
  8         9  
78              
79             # print STDERR "# inserting filler at $x,$y for $cell->{node}->{name}\n";
80             $cells->{"$x,$y"} =
81 8         18 Graph::Easy::Node::Cell->new(node => $cell->{node}, x => $x, y => $y );
82             }
83             }
84             }
85              
86             sub _repair_cell
87             {
88 50     50   68 my ($self, $type, $edge, $x, $y, $after, $before) = @_;
89              
90             # already repaired?
91 50 50       85 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         105 $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   38 my ($self) = @_;
107              
108 31         34 my $cells = $self->{cells};
109              
110 31 50       70 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       115 for my $cell (sort { $a->{x} <=> $b->{x} || $a->{y} <=> $b->{y} } values %$cells)
  604         759  
118             {
119 241 100       576 next unless $cell->isa('Graph::Easy::Edge::Cell');
120              
121 121         110 my $edge = $cell->{edge};
122              
123             #########################################################################
124             # check for "[ JOINT ] [ empty ] [ edge ]"
125              
126 121         114 my $x = $cell->{x} + 2; my $y = $cell->{y};
  121         103  
127              
128 121         94 my $type = $cell->{type} & EDGE_TYPE_MASK;
129              
130             # left is a joint and right exists
131 121 50 33     615 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             $self->_repair_cell(EDGE_HOR(), $right->{edge},$cell->{x}+1,$y,0)
149 0 0       0 if $edge != $right->{edge};
150             }
151             }
152              
153             #########################################################################
154             # check for "[ edge ] [ empty ] [ joint ]"
155              
156 121         99 $x = $cell->{x} - 2; $y = $cell->{y};
  121         103  
157              
158             # right is a joint and left exists
159 121 50 33     553 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             $self->_repair_cell(EDGE_HOR(), $edge, $cell->{x}+1,$y,0) # $left,$cell)
173 0 0       0 if $edge != $left->{edge};
174             }
175             }
176              
177             #########################################################################
178             # check for " [ joint ]
179             # [ empty ]
180             # [ edge ]"
181              
182 121         110 $x = $cell->{x}; $y = $cell->{y} + 2;
  121         95  
183              
184             # top is a joint and down exists
185 121 50 33     529 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             $self->_repair_cell(EDGE_VER(), $bottom->{edge},$x,$cell->{y}+1,0)
199 0 0       0 if $edge != $bottom->{edge};
200             }
201              
202             #########################################################################
203             # check for "[ --- ] [ empty ] [ ---> ]"
204              
205 121         96 $x = $cell->{x} + 2; $y = $cell->{y};
  121         100  
206              
207 121 100       235 if (exists $cells->{"$x,$y"})
208             {
209 83         101 my $right = $cells->{"$x,$y"};
210              
211             $self->_repair_cell(EDGE_HOR(), $edge, $cell->{x}+1,$y,$cell,$right)
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 83 100 66     526 $cell->{type} == EDGE_CROSS );
      66        
      100        
      33        
220             }
221              
222             #########################################################################
223             # check for [ | ]
224             # [ empty ]
225             # [ | ]
226 121         102 $x = $cell->{x}; $y = $cell->{y}+2;
  121         95  
227              
228 121 100       243 if (exists $cells->{"$x,$y"})
229             {
230 67         69 my $below = $cells->{"$x,$y"};
231              
232             $self->_repair_cell(EDGE_VER(),$edge,$x,$cell->{y}+1,$cell,$below)
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 67 100 100     300 $cell->{type} == EDGE_CROSS );
      66        
240             }
241              
242             } # end for all cells
243              
244 31         34 $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   37 my ($self, $cells, $group, $edge, $x, $y, $after, $type) = @_;
251              
252 29 100       53 $type += EDGE_SHORT_CELL() if defined $group;
253              
254 29         72 my $e_cell = Graph::Easy::Edge::Cell->new(
255             type => $type, edge => $edge, x => $x, y => $y, after => $after);
256 29 100       92 $group->_del_cell($e_cell) if defined $group;
257 29         62 $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   146 my ($self, $cell, $x, $y, $flag, $type, $match, $check, $where) = @_;
264              
265 106         108 my $edge = $cell->{edge};
266 106 100       173 if (grep { exists $_->{cell_class} && $_->{cell_class} =~ $match } ord_values ($check))
  759 100       2714  
267             {
268 27         37 $cell->{type} &= ~ $flag; # delete the flag
269              
270             $self->_new_edge_cell(
271 27         62 $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   90 my ($self, $cell, $rows, $cols, $group) = @_;
279              
280 73         69 my $cells = $self->{cells};
281 73         60 my ($x,$y,$doit);
282              
283 73         88 my $type = $cell->{type};
284              
285             #########################################################################
286             # check for " [ empty ] [ |---> ]"
287 73         59 $x = $cell->{x} - 1; $y = $cell->{y};
  73         118  
288              
289 73 100       245 $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         111 $x = $cell->{x} + 1;
295              
296 73 100       147 $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         85 $x = $cell->{x} + 1;
302              
303 73 100       223 $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         89 $x = $cell->{x} - 1;
312              
313 73 100       117 $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         74 $x = $cell->{x}; $y = $cell->{y} - 1;
  73         66  
324              
325 73 100       145 $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         82 $y = $cell->{y} + 1;
332              
333 73 50       114 $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         68 $y = $cell->{y} + 1;
340              
341 73 100       150 $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         83 $y = $cell->{y} - 1;
348              
349 73 100       157 $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   46 my ($self, $cell, $rows, $cols) = @_;
357              
358 48         43 my $cells = $self->{cells};
359              
360             #########################################################################
361             # check for [ |\n|\nv ]
362             # [empty] ... [non-empty]
363             # [node]
364              
365 48         42 my $x = $cell->{x}; my $y = $cell->{y} + 1;
  48         43  
366              
367 48         61 my $below = $cells->{"$x,$y"}; # must be empty
368              
369 48 100 100     139 if (!ref($below) && (($cell->{type} & EDGE_END_MASK) == EDGE_END_S))
370             {
371 6 100       20 if (grep { exists $_->{cell_class} && $_->{cell_class} =~ /g[tb]/ } ord_values $rows->{$y})
  18 100       64  
372             {
373             # delete the start flag
374 2         3 $cell->{type} &= ~ EDGE_END_S;
375              
376 2         5 $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   40 my ($self, $rows, $cols) = @_;
388              
389 31         43 my $cells = $self->{cells};
390              
391             # go over all existing cells
392 31 50       126 for my $cell (sort { $a->{x} <=> $b->{x} || $a->{y} <=> $b->{y} } values %$cells)
  5686         6212  
393             {
394 1243 100       2421 next unless $cell->isa('Graph::Easy::Edge::Cell');
395              
396             # skip odd positions
397 185 100 100     556 next unless ($cell->{x} & 1) == 0 && ($cell->{y} & 1) == 0;
398              
399 121         195 my $group = $cell->group();
400              
401 121 100       214 $self->_repair_edge($cell,$rows,$cols) unless $group;
402 121 100       238 $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   44 my ($self, $cells_layout) = @_;
412              
413             print STDERR "\n# Padding with fill cells, have ",
414 31 50       63 scalar $self->groups(), " groups.\n" if $self->{debug};
415              
416             # take a shortcut if we do not have groups
417 31 50       85 return $self if $self->groups == 0;
418              
419 31         63 $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         42 my $cells = {};
427 31         161 for my $key (sort keys %$cells_layout)
428             {
429 241         359 my ($x,$y) = split /,/, $key;
430 241         215 my $cell = $cells_layout->{$key};
431              
432 241         193 $x *= 2;
433 241         143 $y *= 2;
434 241         184 $cell->{x} = $x;
435 241         175 $cell->{y} = $y;
436              
437 241         384 $cells->{"$x,$y"} = $cell;
438             }
439              
440 31         51 $self->{cells} = $cells; # override with new cell layout
441              
442 31         82 $self->_splice_edges(); # repair edges
443 31         82 $self->_repair_nodes(); # repair multi-celled nodes
444              
445 31         64 my $c = 'Graph::Easy::Group::Cell';
446 31         67 for my $cell (ord_values $self->{cells})
447             {
448             # DO NOT MODIFY $cell IN THE LOOP BODY!
449              
450 303         357 my ($x,$y) = ($cell->{x},$cell->{y});
451              
452             # find the primary node for node cells, for group check
453 303         504 my $group = $cell->group();
454              
455             # not part of group, so no group-cells nec.
456 303 100       416 next unless $group;
457              
458             # now insert up to 8 filler cells around this cell
459 209         389 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         301 while (@$ofs > 0)
468             {
469 1672         1222 $x += shift @$ofs;
470 1672         1066 $y += shift @$ofs;
471              
472             $cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y )
473 1672 100       3971 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         108 for my $cell (ord_values ( $self->{cells} ))
482             {
483             # only for filler cells
484 1231 100       2255 next unless $cell->isa('Graph::Easy::Group::Cell');
485              
486 928         810 my ($sx,$sy) = ($cell->{x},$cell->{y});
487 928         570 my $group = $cell->{group};
488              
489 928         595 my $x = $sx; my $y2 = $sy + 2; my $y = $sy + 1;
  928         593  
  928         506  
490             # look for:
491             # [ group ]
492             # [ empty ]
493             # [ group ]
494 928 100 100     2281 if (exists $cells->{"$x,$y2"} && !exists $cells->{"$x,$y"})
495             {
496 64         59 my $down = $cells->{"$x,$y2"};
497 64 100 66     630 if ($down->isa('Graph::Easy::Group::Cell') && $down->{group} == $group)
498             {
499 12         24 $cells->{"$x,$y"} = $c->new ( graph => $self, group => $group, x => $x, y => $y );
500             }
501             }
502 928         546 $x = $sx+1; my $x2 = $sx + 2; $y = $sy;
  928         572  
  928         560  
503             # look for:
504             # [ group ] [ empty ] [ group ]
505 928 50 66     2498 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         155 for my $group (ord_values ( $self->{groups} ))
519             {
520 42         121 $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         69 my $rows = {};
525 31         52 my $cols = {};
526 31         72 for my $cell (ord_values ($cells))
527             {
528 1243         1467 $rows->{$cell->{y}}->{$cell->{x}} = $cell;
529 1243         1399 $cols->{$cell->{x}}->{$cell->{y}} = $cell;
530             }
531 31         167 $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         68 for my $group (ord_values ( $self->{groups} ))
536             {
537 42         119 $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         308 $self;
556             }
557              
558             1;
559             __END__