File Coverage

lib/Graph/Easy/Group.pm
Criterion Covered Total %
statement 191 211 90.5
branch 74 100 74.0
condition 40 57 70.1
subroutine 29 33 87.8
pod 14 14 100.0
total 348 415 83.8


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 49     49   78829 use Graph::Easy::Group::Cell;
  49         139  
  49         1586  
9 49     49   1613 use Graph::Easy;
  49         100  
  49         3296  
10 49     49   301 use Scalar::Util qw/weaken/;
  49         96  
  49         6706  
11              
12             @ISA = qw/Graph::Easy::Node Graph::Easy/;
13             $VERSION = '0.75';
14              
15 49     49   392 use strict;
  49         96  
  49         1656  
16 49     49   264 use warnings;
  49         105  
  49         59580  
17              
18 49     49   315 use Graph::Easy::Util qw(ord_values);
  49         102  
  49         24458  
19              
20             #############################################################################
21              
22             sub _init
23             {
24             # generic init, override in subclasses
25 97     97   234 my ($self,$args) = @_;
26            
27 97         616 $self->{name} = 'Group #'. $self->{id};
28 97         275 $self->{class} = 'group';
29 97         301 $self->{_cells} = {}; # the Group::Cell objects
30             # $self->{cx} = 1;
31             # $self->{cy} = 1;
32              
33 97         417 foreach my $k (sort keys %$args)
34             {
35 79 50       577 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         302 $self->{$k} = $args->{$k};
41             }
42            
43 97         359 $self->{nodes} = {};
44 97         268 $self->{groups} = {};
45 97         242 $self->{att} = {};
46              
47 97         433 $self;
48             }
49              
50             #############################################################################
51             # accessor methods
52              
53             sub nodes
54             {
55 13     13 1 39 my $self = shift;
56              
57 13 50       39 wantarray ? ( ord_values ( $self->{nodes} ) ) : scalar keys %{$self->{nodes}};
  13         73  
58             }
59              
60             sub edges
61             {
62             # edges leading from/to this group
63 8     8 1 19 my $self = shift;
64              
65 8 50       114 wantarray ? ( ord_values ( $self->{edges} ) ) : scalar keys %{$self->{edges}};
  8         49  
66             }
67              
68             sub edges_within
69             {
70             # edges between nodes inside this group
71 5     5 1 14 my $self = shift;
72              
73 5         29 wantarray ? ( ord_values ( $self->{edges_within} ) ) :
74 5 50       14 scalar keys %{$self->{edges_within}};
75             }
76              
77             sub _groups_within
78             {
79 8     8   16 my ($self, $level, $max_level, $cur) = @_;
80              
81 49     49   330 no warnings 'recursion';
  49         159  
  49         107641  
82              
83 8         29 push @$cur, ord_values ( $self->{groups} );
84              
85 8 100       33 return if $level >= $max_level;
86              
87 3         14 for my $g (ord_values ( $self->{groups} ))
88             {
89 6 100       8 $g->_groups_within($level+1,$max_level, $cur) if scalar keys %{$g->{groups}} > 0;
  6         36  
90             }
91             }
92              
93             #############################################################################
94              
95             sub set_attribute
96             {
97 36     36 1 122 my ($self, $name, $val, $class) = @_;
98              
99 36         452 $self->SUPER::set_attribute($name, $val, $class);
100              
101             # if defined attribute "nodeclass", put our nodes into that class
102 36 100       256 if ($name eq 'nodeclass')
103             {
104 2         7 my $class = $self->{att}->{nodeclass};
105 2         15 for my $node (ord_values ( $self->{nodes} ) )
106             {
107 4         14 $node->sub_class($class);
108             }
109             }
110 36         133 $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 252 my ($self,$n) = @_;
128              
129 144 100 66     1149 if (!ref($n) || !$n->isa("Graph::Easy::Node"))
130             {
131 1 50       7 if (!ref($self->{graph}))
132             {
133 0         0 return $self->error("Cannot add non node-object $n to group '$self->{name}'");
134             }
135 1         8 $n = $self->{graph}->add_node($n);
136             }
137 144         2011 $self->{nodes}->{ $n->{name} } = $n;
138              
139             # if defined attribute "nodeclass", put our nodes into that class
140 144 100       427 $n->sub_class($self->{att}->{nodeclass}) if exists $self->{att}->{nodeclass};
141              
142             # register ourselves with the member
143 144         360 $n->{group} = $self;
144              
145             # set the proper attribute (for layout)
146 144         347 $n->{att}->{group} = $self->{name};
147              
148             # Register the nodes and the edge with our graph object
149             # and weaken the references. Be carefull 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 144 100 100     1260 weaken($n->{graph} = $self->{graph}) unless
      66        
156             $n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
157              
158 144         254 $n;
159             }
160              
161             sub add_member
162             {
163             # add a node or group to this group
164 128     128 1 289 my ($self,$n) = @_;
165            
166 128 50 33     1423 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       953 return $self->_add_edge($n) if $n->isa("Graph::Easy::Edge");
175 125 100       798 return $self->add_group($n) if $n->isa('Graph::Easy::Group');
176              
177 117         1906 $self->{nodes}->{ $n->{name} } = $n;
178              
179             # if defined attribute "nodeclass", put our nodes into that class
180 117         575 my $cl = $self->attribute('nodeclass');
181 117 50       383 $n->sub_class($cl) if $cl ne '';
182              
183             # register ourselves with the member
184 117         319 $n->{group} = $self;
185              
186             # set the proper attribute (for layout)
187 117         412 $n->{att}->{group} = $self->{name};
188              
189             # Register the nodes and the edge with our graph object
190             # and weaken the references. Be carefull 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 117 100 100     1361 weaken($n->{graph} = $self->{graph}) unless
      66        
197             $n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
198              
199 117         5540 $n;
200             }
201              
202             sub del_member
203             {
204             # delete a node or group from this group
205 2     2 1 6 my ($self,$n) = @_;
206              
207             # XXX TOOD: groups vs. nodes
208 2         6 my $class = 'nodes'; my $key = 'name';
  2         4  
209 2 50       25 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         11 delete $self->{$class}->{ $n->{$key} };
215 2         4 delete $n->{group}; # unregister us
216              
217 2 50       13 if ($n->isa('Graph::Easy::Node'))
218             {
219             # find all edges that mention this node and drop them from the group
220 2         6 my $edges = $self->{edges_within};
221 2         13 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         7 $self;
228             }
229              
230             sub del_node
231             {
232             # delete a node from this group
233 2     2 1 4 my ($self,$n) = @_;
234              
235 2         10 delete $self->{nodes}->{ $n->{name} };
236 2         8 delete $n->{group}; # unregister us
237 2         7 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         7 my $edges = $self->{edges_within};
241 2         10 for my $e (ord_values ( $edges))
242             {
243 2 50 66     22 delete $edges->{ $e->{id} } if $e->{from} == $n || $e->{to} == $n;
244             }
245              
246 2         13 $self;
247             }
248              
249             sub add_nodes
250             {
251 10     10 1 857 my $self = shift;
252              
253             # make a copy in case of scalars
254 10         34 my @arg = @_;
255 10         29 foreach my $n (@arg)
256             {
257 18 50 66     77 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       140 return $self->error("Cannot add group-object $n to group '$self->{name}'")
262             if $n->isa('Graph::Easy::Group');
263              
264 18 100       65 $n = $self->{graph}->add_node($n) unless ref($n);
265              
266 18         68 $self->{nodes}->{ $n->{name} } = $n;
267              
268             # set the proper attribute (for layout)
269 18         52 $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         32 $n->{group} = $self;
277              
278             # Register the nodes and the edge with our graph object
279             # and weaken the references. Be carefull 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 18 50 66     221 weaken($n->{graph} = $self->{graph}) unless
      66        
286             $n->{graph} && $self->{graph} && $n->{graph} == $self->{graph};
287              
288             }
289              
290 10         39 @arg;
291             }
292              
293             #############################################################################
294              
295             sub _del_edge
296             {
297             # delete an edge from this group
298 2     2   5 my ($self,$e) = @_;
299              
300 2         8 delete $self->{edges_within}->{ $e->{id} };
301 2         109 delete $e->{group}; # unregister us
302              
303 2         14 $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   135 my ($self,$e) = @_;
311              
312 66 50 33     602 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         301 $self->{edges_within}->{ $e->{id} } = $e;
317              
318             # if defined attribute "edgeclass", put our edges into that class
319 66         309 my $edge_class = $self->attribute('edgeclass');
320 66 50       396 $e->sub_class($edge_class) if $edge_class ne '';
321              
322             # XXX TODO: inline
323 66         309 $self->add_node($e->{from});
324 66         211 $self->add_node($e->{to});
325              
326             # register us, but don't do weaken() if the ref was already set
327 66 100 66     498 weaken($e->{group} = $self) unless defined $e->{group} && $e->{group} == $self;
328              
329 66         254 $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 556 my ($self,$from,$to) = @_;
336              
337 2         6 my $g = $self->{graph};
338 2 50       11 return $self->error("Cannot add edge to group '$self->{name}' without graph")
339             unless defined $g;
340              
341 2         12 my $edge = $g->add_edge($from,$to);
342              
343 2         9 $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 635 my ($self,$from,$to) = @_;
350              
351 1         2 my $g = $self->{graph};
352 1 50       4 return $self->error("Cannot non edge to group '$self->{name}' without graph")
353             unless defined $g;
354              
355 1         6 my $edge = $g->add_edge_once($from,$to);
356             # edge already exists => so fetch it
357 1 50       7 $edge = $g->edge($from,$to) unless defined $edge;
358              
359 1         4 $self->_add_edge($edge);
360             }
361              
362             #############################################################################
363              
364             sub add_group
365             {
366             # add a group to us
367 9     9 1 18 my ($self,$group) = @_;
368              
369             # group with that name already exists?
370 9         20 my $name = $group;
371 9 100       30 $group = $self->{groups}->{ $group } unless ref $group;
372              
373             # group with that name doesn't exist, so create new one
374 9 100       48 $group = $self->{graph}->add_group($name) unless ref $group;
375              
376             # index under the group name for easier lookup
377 9         31 $self->{groups}->{ $group->{name} } = $group;
378              
379             # make attribute->('group') work
380 9         28 $group->{att}->{group} = $self->{name};
381              
382             # register group with the graph and ourself
383 9         24 $group->{graph} = $self->{graph};
384 9         18 $group->{group} = $self;
385             {
386 49     49   420 no warnings; # dont warn on already weak references
  49         110  
  49         51564  
  9         18  
387 9         201 weaken($group->{graph});
388 9         37 weaken($group->{group});
389             }
390 9         23 $self->{graph}->{score} = undef; # invalidate last layout
391              
392 9         34 $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   1769 my ($self,$cell) = @_;
419              
420 944         2727 $cell->_update_boundaries();
421 944         3893 $self->{_cells}->{"$cell->{x},$cell->{y}"} = $cell;
422 944         2069 $cell;
423             }
424              
425             sub _del_cell
426             {
427             # delete a cell from the list of cells this group covers
428 28     28   51 my ($self,$cell) = @_;
429              
430 28         124 delete $self->{_cells}->{"$cell->{x},$cell->{y}"};
431 28         58 delete $cell->{group};
432              
433 28         77 $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   89 my $self = shift;
440              
441 42         117 my $g = $self->{graph};
442              
443 42         241 my $align = $self->attribute('align');
444 42         171 my $loc = $self->attribute('labelpos');
445              
446             # depending on whether the label should be on top or bottom:
447 42         199 my $match = qr/^\s*gt\s*\z/;
448 42 100       149 $match = qr/^\s*gb\s*\z/ if $loc eq 'bottom';
449              
450 42         71 my $lc; # the label cell
451              
452 42         188 for my $c (ord_values ( $self->{_cells} ))
453             {
454             # find a cell where to put the label
455 913 100       4007 next unless $c->{cell_class} =~ $match;
456              
457 180 100       364 if (defined $lc)
458             {
459 143 100       303 if ($align eq 'left')
    100          
    50          
460             {
461             # find top-most, left-most cell
462 113 100 100     500 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       54 next if $lc->{y} < $c->{y};
468             }
469             elsif ($align eq 'right')
470             {
471             # find top-most, right-most cell
472 12 50 33     76 next if $lc->{x} > $c->{x} || $lc->{y} < $c->{y};
473             }
474             }
475 78         987 $lc = $c;
476             }
477              
478             # find the cell mostly near the center in the found top-row
479 42 100 100     376 if (ref($lc) && $align eq 'center')
480             {
481 7         17 my ($left, $right);
482             # find left/right most coordinates
483 7         31 for my $c (ord_values ( $self->{_cells} ))
484             {
485 171 100       630 next if $c->{y} != $lc->{y};
486 39 100 100     184 $left = $c->{x} if !defined $left || $left > $c->{x};
487 39 100 100     269 $right = $c->{x} if !defined $right || $right < $c->{x};
488             }
489 7         56 my $center = int(($right - $left) / 2 + $left);
490 7         16 my $min_dist;
491             # find the cell mostly near the center in the found top-row
492 7         31 for my $c (ord_values ( $self->{_cells} ))
493             {
494 171 100       419 next if $c->{y} != $lc->{y};
495             # squared to get rid of sign
496 39         59 my $dist = ($center - $c->{x}); $dist *= $dist;
  39         50  
497 39 100 100     225 next if defined $min_dist && $dist > $min_dist;
498 21         22 $min_dist = $dist; $lc = $c;
  21         34  
499             }
500             }
501              
502 42 50       174 print STDERR "# Setting label for group '$self->{name}' at $lc->{x},$lc->{y}\n"
503             if $self->{debug};
504              
505 42 100       276 $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   7 my $self = shift;
518              
519             ###########################################################################
520             # set local {debug} for groups
521 1         4 local $self->{debug} = $self->{graph}->{debug};
522              
523 1         11 $self->SUPER::_layout();
524             }
525              
526             sub _set_cell_types
527             {
528 42     42   148 my ($self, $cells) = @_;
529              
530             # Set the right cell class for all of our cells:
531 42         209 for my $cell (ord_values ( $self->{_cells} ))
532             {
533 940         7800 $cell->_set_type($cells);
534             }
535            
536 42         337 $self;
537             }
538              
539             1;
540             __END__