File Coverage

lib/Graph/Easy/Group.pm
Criterion Covered Total %
statement 191 211 90.5
branch 74 100 74.0
condition 37 57 64.9
subroutine 29 33 87.8
pod 14 14 100.0
total 345 415 83.1


line stmt bran cond sub pod time code
1             #############################################################################
2             # A group of nodes. Part of Graph::Easy.
3             #
4             #############################################################################
5              
6             package Graph::Easy::Group;
7              
8 48     48   37986 use Graph::Easy::Group::Cell;
  48         65  
  48         1048  
9 48     48   1089 use Graph::Easy;
  48         57  
  48         947  
10 48     48   155 use Scalar::Util qw/weaken/;
  48         50  
  48         3792  
11              
12             @ISA = qw/Graph::Easy::Node Graph::Easy/;
13             $VERSION = '0.76';
14              
15 48     48   174 use strict;
  48         47  
  48         833  
16 48     48   142 use warnings;
  48         42  
  48         1014  
17              
18 48     48   139 use Graph::Easy::Util qw(ord_values);
  48         45  
  48         12574  
19              
20             #############################################################################
21              
22             sub _init
23             {
24             # generic init, override in subclasses
25 97     97   113 my ($self,$args) = @_;
26              
27 97         287 $self->{name} = 'Group #'. $self->{id};
28 97         132 $self->{class} = 'group';
29 97         142 $self->{_cells} = {}; # the Group::Cell objects
30             # $self->{cx} = 1;
31             # $self->{cy} = 1;
32              
33 97         267 foreach my $k (sort keys %$args)
34             {
35 79 50       331 if ($k !~ /^(graph|name)\z/)
36             {
37 0         0 require Carp;
38 0         0 Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Group->new()");
39             }
40 79         147 $self->{$k} = $args->{$k};
41             }
42              
43 97         138 $self->{nodes} = {};
44 97         109 $self->{groups} = {};
45 97         160 $self->{att} = {};
46              
47 97         222 $self;
48             }
49              
50             #############################################################################
51             # accessor methods
52              
53             sub nodes
54             {
55 13     13 1 23 my $self = shift;
56              
57 13 50       17 wantarray ? ( ord_values ( $self->{nodes} ) ) : scalar keys %{$self->{nodes}};
  13         46  
58             }
59              
60             sub edges
61             {
62             # edges leading from/to this group
63 8     8 1 8 my $self = shift;
64              
65 8 50       17 wantarray ? ( ord_values ( $self->{edges} ) ) : scalar keys %{$self->{edges}};
  8         28  
66             }
67              
68             sub edges_within
69             {
70             # edges between nodes inside this group
71 5     5 1 8 my $self = shift;
72              
73             wantarray ? ( ord_values ( $self->{edges_within} ) ) :
74 5 50       11 scalar keys %{$self->{edges_within}};
  5         18  
75             }
76              
77             sub _groups_within
78             {
79 8     8   8 my ($self, $level, $max_level, $cur) = @_;
80              
81 48     48   194 no warnings 'recursion';
  48         51  
  48         47748  
82              
83 8         16 push @$cur, ord_values ( $self->{groups} );
84              
85 8 100       21 return if $level >= $max_level;
86              
87 3         6 for my $g (ord_values ( $self->{groups} ))
88             {
89 6 100       1 $g->_groups_within($level+1,$max_level, $cur) if scalar keys %{$g->{groups}} > 0;
  6         22  
90             }
91             }
92              
93             #############################################################################
94              
95             sub set_attribute
96             {
97 36     36 1 53 my ($self, $name, $val, $class) = @_;
98              
99 36         105 $self->SUPER::set_attribute($name, $val, $class);
100              
101             # if defined attribute "nodeclass", put our nodes into that class
102 36 100       65 if ($name eq 'nodeclass')
103             {
104 2         3 my $class = $self->{att}->{nodeclass};
105 2         9 for my $node (ord_values ( $self->{nodes} ) )
106             {
107 4         8 $node->sub_class($class);
108             }
109             }
110 36         55 $self;
111             }
112              
113             sub shape
114             {
115 0     0 1 0 my ($self) = @_;
116              
117             # $self->{att}->{shape} || $self->attribute('shape');
118 0         0 '';
119             }
120              
121             #############################################################################
122             # node handling
123              
124             sub add_node
125             {
126             # add a node to this group
127 144     144 1 138 my ($self,$n) = @_;
128              
129 144 100 66     472 if (!ref($n) || !$n->isa("Graph::Easy::Node"))
130             {
131 1 50       2 if (!ref($self->{graph}))
132             {
133 0         0 return $self->error("Cannot add non node-object $n to group '$self->{name}'");
134             }
135 1         4 $n = $self->{graph}->add_node($n);
136             }
137 144         210 $self->{nodes}->{ $n->{name} } = $n;
138              
139             # if defined attribute "nodeclass", put our nodes into that class
140 144 100       260 $n->sub_class($self->{att}->{nodeclass}) if exists $self->{att}->{nodeclass};
141              
142             # register ourselves with the member
143 144         121 $n->{group} = $self;
144              
145             # set the proper attribute (for layout)
146 144         152 $n->{att}->{group} = $self->{name};
147              
148             # Register the nodes and the edge with our graph object
149             # and weaken the references. Be careful to not needlessly
150             # override and weaken again an already existing reference, this
151             # is an O(N) operation in most Perl versions, and thus very slow.
152              
153             # If the node does not belong to a graph yet or belongs to another
154             # graph, add it to our own graph:
155             weaken($n->{graph} = $self->{graph}) unless
156 144 100 66     600 $n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
      66        
157              
158 144         139 $n;
159             }
160              
161             sub add_member
162             {
163             # add a node or group to this group
164 128     128 1 160 my ($self,$n) = @_;
165              
166 128 50 33     561 if (!ref($n) || !$n->isa("Graph::Easy::Node"))
167             {
168 0 0       0 if (!ref($self->{graph}))
169             {
170 0         0 return $self->error("Cannot add non node-object $n to group '$self->{name}'");
171             }
172 0         0 $n = $self->{graph}->add_node($n);
173             }
174 128 100       442 return $self->_add_edge($n) if $n->isa("Graph::Easy::Edge");
175 125 100       310 return $self->add_group($n) if $n->isa('Graph::Easy::Group');
176              
177 117         217 $self->{nodes}->{ $n->{name} } = $n;
178              
179             # if defined attribute "nodeclass", put our nodes into that class
180 117         254 my $cl = $self->attribute('nodeclass');
181 117 50       291 $n->sub_class($cl) if $cl ne '';
182              
183             # register ourselves with the member
184 117         135 $n->{group} = $self;
185              
186             # set the proper attribute (for layout)
187 117         140 $n->{att}->{group} = $self->{name};
188              
189             # Register the nodes and the edge with our graph object
190             # and weaken the references. Be careful to not needlessly
191             # override and weaken again an already existing reference, this
192             # is an O(N) operation in most Perl versions, and thus very slow.
193              
194             # If the node does not belong to a graph yet or belongs to another
195             # graph, add it to our own graph:
196             weaken($n->{graph} = $self->{graph}) unless
197 117 100 66     542 $n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
      66        
198              
199 117         173 $n;
200             }
201              
202             sub del_member
203             {
204             # delete a node or group from this group
205 2     2 1 1 my ($self,$n) = @_;
206              
207             # XXX TOOD: groups vs. nodes
208 2         3 my $class = 'nodes'; my $key = 'name';
  2         2  
209 2 50       8 if ($n->isa('Graph::Easy::Group'))
210             {
211             # XXX TOOD: groups vs. nodes
212 0         0 $class = 'groups'; $key = 'id';
  0         0  
213             }
214 2         5 delete $self->{$class}->{ $n->{$key} };
215 2         2 delete $n->{group}; # unregister us
216              
217 2 50       5 if ($n->isa('Graph::Easy::Node'))
218             {
219             # find all edges that mention this node and drop them from the group
220 2         3 my $edges = $self->{edges_within};
221 2         4 for my $e (ord_values ( $edges))
222             {
223 0 0 0     0 delete $edges->{ $e->{id} } if $e->{from} == $n || $e->{to} == $n;
224             }
225             }
226              
227 2         2 $self;
228             }
229              
230             sub del_node
231             {
232             # delete a node from this group
233 2     2 1 3 my ($self,$n) = @_;
234              
235 2         5 delete $self->{nodes}->{ $n->{name} };
236 2         2 delete $n->{group}; # unregister us
237 2         3 delete $n->{att}->{group}; # delete the group attribute
238              
239             # find all edges that mention this node and drop them from the group
240 2         3 my $edges = $self->{edges_within};
241 2         6 for my $e (ord_values ( $edges))
242             {
243 2 50 66     13 delete $edges->{ $e->{id} } if $e->{from} == $n || $e->{to} == $n;
244             }
245              
246 2         3 $self;
247             }
248              
249             sub add_nodes
250             {
251 10     10 1 367 my $self = shift;
252              
253             # make a copy in case of scalars
254 10         17 my @arg = @_;
255 10         16 foreach my $n (@arg)
256             {
257 18 50 66     39 if (!ref($n) && !ref($self->{graph}))
258             {
259 0         0 return $self->error("Cannot add non node-object $n to group '$self->{name}'");
260             }
261 18 50       61 return $self->error("Cannot add group-object $n to group '$self->{name}'")
262             if $n->isa('Graph::Easy::Group');
263              
264 18 100       31 $n = $self->{graph}->add_node($n) unless ref($n);
265              
266 18         27 $self->{nodes}->{ $n->{name} } = $n;
267              
268             # set the proper attribute (for layout)
269 18         27 $n->{att}->{group} = $self->{name};
270              
271             # XXX TODO TEST!
272             # # if defined attribute "nodeclass", put our nodes into that class
273             # $n->sub_class($self->{att}->{nodeclass}) if exists $self->{att}->{nodeclass};
274              
275             # register ourselves with the member
276 18         20 $n->{group} = $self;
277              
278             # Register the nodes and the edge with our graph object
279             # and weaken the references. Be careful to not needlessly
280             # override and weaken again an already existing reference, this
281             # is an O(N) operation in most Perl versions, and thus very slow.
282              
283             # If the node does not belong to a graph yet or belongs to another
284             # graph, add it to our own graph:
285             weaken($n->{graph} = $self->{graph}) unless
286 18 50 66     94 $n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
      33        
287              
288             }
289              
290 10         18 @arg;
291             }
292              
293             #############################################################################
294              
295             sub _del_edge
296             {
297             # delete an edge from this group
298 2     2   3 my ($self,$e) = @_;
299              
300 2         6 delete $self->{edges_within}->{ $e->{id} };
301 2         3 delete $e->{group}; # unregister us
302              
303 2         3 $self;
304             }
305              
306             sub _add_edge
307             {
308             # add an edge to this group (e.g. when both from/to of this edge belong
309             # to this group)
310 66     66   68 my ($self,$e) = @_;
311              
312 66 50 33     289 if (!ref($e) || !$e->isa("Graph::Easy::Edge"))
313             {
314 0         0 return $self->error("Cannot add non edge-object $e to group '$self->{name}'");
315             }
316 66         154 $self->{edges_within}->{ $e->{id} } = $e;
317              
318             # if defined attribute "edgeclass", put our edges into that class
319 66         150 my $edge_class = $self->attribute('edgeclass');
320 66 50       117 $e->sub_class($edge_class) if $edge_class ne '';
321              
322             # XXX TODO: inline
323 66         162 $self->add_node($e->{from});
324 66         95 $self->add_node($e->{to});
325              
326             # register us, but don't do weaken() if the ref was already set
327 66 100 66     268 weaken($e->{group} = $self) unless defined $e->{group} && $e->{group} == $self;
328              
329 66         132 $e;
330             }
331              
332             sub add_edge
333             {
334             # Add an edge to the graph of this group, then register it with this group.
335 2     2 1 386 my ($self,$from,$to) = @_;
336              
337 2         3 my $g = $self->{graph};
338 2 50       5 return $self->error("Cannot add edge to group '$self->{name}' without graph")
339             unless defined $g;
340              
341 2         5 my $edge = $g->add_edge($from,$to);
342              
343 2         4 $self->_add_edge($edge);
344             }
345              
346             sub add_edge_once
347             {
348             # Add an edge to the graph of this group, then register it with this group.
349 1     1 1 339 my ($self,$from,$to) = @_;
350              
351 1         2 my $g = $self->{graph};
352 1 50       3 return $self->error("Cannot non edge to group '$self->{name}' without graph")
353             unless defined $g;
354              
355 1         3 my $edge = $g->add_edge_once($from,$to);
356             # edge already exists => so fetch it
357 1 50       4 $edge = $g->edge($from,$to) unless defined $edge;
358              
359 1         2 $self->_add_edge($edge);
360             }
361              
362             #############################################################################
363              
364             sub add_group
365             {
366             # add a group to us
367 9     9 1 11 my ($self,$group) = @_;
368              
369             # group with that name already exists?
370 9         9 my $name = $group;
371 9 100       16 $group = $self->{groups}->{ $group } unless ref $group;
372              
373             # group with that name doesn't exist, so create new one
374 9 100       19 $group = $self->{graph}->add_group($name) unless ref $group;
375              
376             # index under the group name for easier lookup
377 9         17 $self->{groups}->{ $group->{name} } = $group;
378              
379             # make attribute->('group') work
380 9         13 $group->{att}->{group} = $self->{name};
381              
382             # register group with the graph and ourself
383 9         11 $group->{graph} = $self->{graph};
384 9         13 $group->{group} = $self;
385             {
386 48     48   217 no warnings; # don't warn on already weak references
  48         55  
  48         26307  
  9         4  
387 9         13 weaken($group->{graph});
388 9         21 weaken($group->{group});
389             }
390 9         10 $self->{graph}->{score} = undef; # invalidate last layout
391              
392 9         14 $group;
393             }
394              
395             # cell management - used by the layouter
396              
397             sub _cells
398             {
399             # return all the cells this group currently occupies
400 0     0   0 my $self = shift;
401              
402 0         0 $self->{_cells};
403             }
404              
405             sub _clear_cells
406             {
407             # remove all belonging cells
408 0     0   0 my $self = shift;
409              
410 0         0 $self->{_cells} = {};
411              
412 0         0 $self;
413             }
414              
415             sub _add_cell
416             {
417             # add a cell to the list of cells this group covers
418 944     944   691 my ($self,$cell) = @_;
419              
420 944         1235 $cell->_update_boundaries();
421 944         1690 $self->{_cells}->{"$cell->{x},$cell->{y}"} = $cell;
422 944         998 $cell;
423             }
424              
425             sub _del_cell
426             {
427             # delete a cell from the list of cells this group covers
428 28     28   28 my ($self,$cell) = @_;
429              
430 28         77 delete $self->{_cells}->{"$cell->{x},$cell->{y}"};
431 28         30 delete $cell->{group};
432              
433 28         43 $self;
434             }
435              
436             sub _find_label_cell
437             {
438             # go through all cells of this group and find one where to attach the label
439 42     42   57 my $self = shift;
440              
441 42         54 my $g = $self->{graph};
442              
443 42         106 my $align = $self->attribute('align');
444 42         104 my $loc = $self->attribute('labelpos');
445              
446             # depending on whether the label should be on top or bottom:
447 42         114 my $match = qr/^\s*gt\s*\z/;
448 42 100       71 $match = qr/^\s*gb\s*\z/ if $loc eq 'bottom';
449              
450 42         58 my $lc; # the label cell
451              
452 42         100 for my $c (ord_values ( $self->{_cells} ))
453             {
454             # find a cell where to put the label
455 913 100       1976 next unless $c->{cell_class} =~ $match;
456              
457 180 100       239 if (defined $lc)
458             {
459 143 100       201 if ($align eq 'left')
    100          
    50          
460             {
461             # find top-most, left-most cell
462 113 100 100     248 next if $lc->{x} < $c->{x} || $lc->{y} < $c->{y};
463             }
464             elsif ($align eq 'center')
465             {
466             # just find any top-most cell
467 18 50       33 next if $lc->{y} < $c->{y};
468             }
469             elsif ($align eq 'right')
470             {
471             # find top-most, right-most cell
472 12 50 33     47 next if $lc->{x} > $c->{x} || $lc->{y} < $c->{y};
473             }
474             }
475 78         71 $lc = $c;
476             }
477              
478             # find the cell mostly near the center in the found top-row
479 42 100 100     249 if (ref($lc) && $align eq 'center')
480             {
481 7         10 my ($left, $right);
482             # find left/right most coordinates
483 7         17 for my $c (ord_values ( $self->{_cells} ))
484             {
485 171 100       234 next if $c->{y} != $lc->{y};
486 39 100 100     100 $left = $c->{x} if !defined $left || $left > $c->{x};
487 39 100 100     104 $right = $c->{x} if !defined $right || $right < $c->{x};
488             }
489 7         30 my $center = int(($right - $left) / 2 + $left);
490 7         6 my $min_dist;
491             # find the cell mostly near the center in the found top-row
492 7         17 for my $c (ord_values ( $self->{_cells} ))
493             {
494 171 100       216 next if $c->{y} != $lc->{y};
495             # squared to get rid of sign
496 39         31 my $dist = ($center - $c->{x}); $dist *= $dist;
  39         27  
497 39 100 100     99 next if defined $min_dist && $dist > $min_dist;
498 21         15 $min_dist = $dist; $lc = $c;
  21         19  
499             }
500             }
501              
502             print STDERR "# Setting label for group '$self->{name}' at $lc->{x},$lc->{y}\n"
503 42 50       94 if $self->{debug};
504              
505 42 100       166 $lc->_set_label() if ref($lc);
506             }
507              
508             sub layout
509             {
510 0     0 1 0 my $self = shift;
511              
512 0         0 $self->_croak('Cannot call layout() on a Graph::Easy::Group directly.');
513             }
514              
515             sub _layout
516             {
517 1     1   3 my $self = shift;
518              
519             ###########################################################################
520             # set local {debug} for groups
521 1         3 local $self->{debug} = $self->{graph}->{debug};
522              
523 1         7 $self->SUPER::_layout();
524             }
525              
526             sub _set_cell_types
527             {
528 42     42   44 my ($self, $cells) = @_;
529              
530             # Set the right cell class for all of our cells:
531 42         84 for my $cell (ord_values ( $self->{_cells} ))
532             {
533 940         1244 $cell->_set_type($cells);
534             }
535              
536 42         128 $self;
537             }
538              
539             1;
540             __END__