File Coverage

lib/Graph/Easy/Node.pm
Criterion Covered Total %
statement 832 932 89.2
branch 465 568 81.8
condition 158 192 82.2
subroutine 74 77 96.1
pod 45 45 100.0
total 1574 1814 86.7


line stmt bran cond sub pod time code
1             #############################################################################
2             # Represents one node in a Graph::Easy graph.
3             #
4             # (c) by Tels 2004-2008. Part of Graph::Easy.
5             #############################################################################
6              
7             package Graph::Easy::Node;
8              
9             $VERSION = '0.75';
10              
11 49     49   100511 use Graph::Easy::Base;
  49         130  
  49         1691  
12 49     49   6897 use Graph::Easy::Attributes;
  49         119  
  49         4000  
13             @ISA = qw/Graph::Easy::Base/;
14              
15 49     49   36128 use Graph::Easy::Util qw(ord_values);
  49         192  
  49         3151  
16              
17             # to map "arrow-shape" to "arrowshape"
18             my $att_aliases;
19              
20 49     49   419 use strict;
  49         95  
  49         1446  
21 49     49   239 use warnings;
  49         88  
  49         1274  
22 49     49   311 use constant isa_cell => 0;
  49         92  
  49         94460  
23              
24             sub _init
25             {
26             # Generic init routine, to be overridden in subclasses.
27 1669     1669   4916 my ($self,$args) = @_;
28            
29 1669         6737 $self->{name} = 'Node #' . $self->{id};
30            
31 1669         4216 $self->{att} = { };
32 1669         3801 $self->{class} = 'node'; # default class
33              
34 1669         7170 foreach my $k (sort keys %$args)
35             {
36 1642 50       8878 if ($k !~ /^(label|name)\z/)
37             {
38 0         0 require Carp;
39 0         0 Carp::confess ("Invalid argument '$k' passed to Graph::Easy::Node->new()");
40             }
41 1642 100       7910 $self->{$k} = $args->{$k} if $k eq 'name';
42 1642 100       7572 $self->{att}->{$k} = $args->{$k} if $k eq 'label';
43             }
44              
45             # These are undef (to save memory) until needed:
46             # $self->{children} = {};
47             # $self->{dx} = 0; # relative to no other node
48             # $self->{dy} = 0;
49             # $self->{origin} = undef; # parent node (for relative placement)
50             # $self->{group} = undef;
51             # $self->{parent} = $graph or $group;
52             # Mark as not yet laid out:
53             # $self->{x} = 0;
54             # $self->{y} = 0;
55            
56 1669         15933 $self;
57             }
58              
59             my $merged_borders =
60             {
61             'dotteddashed' => 'dot-dash',
62             'dasheddotted' => 'dot-dash',
63             'double-dashdouble' => 'double',
64             'doubledouble-dash' => 'double',
65             'doublesolid' => 'double',
66             'soliddouble' => 'double',
67             'dotteddot-dash' => 'dot-dash',
68             'dot-dashdotted' => 'dot-dash',
69             };
70              
71             sub _collapse_borders
72             {
73             # Given a right border from node one, and the left border of node two,
74             # return what border we need to draw on node two:
75 86     86   197 my ($self, $one, $two, $swapem) = @_;
76              
77 86 50       197 ($one,$two) = ($two,$one) if $swapem;
78              
79 86 50       179 $one = 'none' unless $one;
80 86 50       206 $two = 'none' unless $two;
81              
82             # If the border of the left/top node is defined, we don't draw the
83             # border of the right/bottom node.
84 86 50 66     391 return 'none' if $one ne 'none' || $two ne 'none';
85              
86             # otherwise, we draw simple the right border
87 0         0 $two;
88             }
89              
90             sub _merge_borders
91             {
92 123     123   270 my ($self, $one, $two) = @_;
93              
94 123 50       287 $one = 'none' unless $one;
95 123 50       252 $two = 'none' unless $two;
96            
97             # "nonenone" => "none" or "dotteddotted" => "dotted"
98 123 100       865 return $one if $one eq $two;
99              
100             # none + solid == solid + none == solid
101 5 100       19 return $one if $two eq 'none';
102 4 100       16 return $two if $one eq 'none';
103              
104 3         6 for my $b (qw/broad wide bold double solid/)
105             {
106             # the stronger one overrides the weaker one
107 13 100 100     55 return $b if $one eq $b || $two eq $b;
108             }
109              
110 0         0 my $both = $one . $two;
111 0 0       0 return $merged_borders->{$both} if exists $merged_borders->{$both};
112              
113             # fallback
114 0         0 $two;
115             }
116              
117             sub _border_to_draw
118             {
119             # Return the border style we need to draw, taking the shape (none) into
120             # account
121 1271     1271   2265 my ($self, $shape) = @_;
122              
123 1271         2322 my $cache = $self->{cache};
124              
125 1271 100       4189 return $cache->{border_style} if defined $cache->{border_style};
126              
127 1062 100       2526 $shape = $self->{att}->{shape} unless defined $shape;
128 1062 100       2389 $shape = $self->attribute('shape') unless defined $shape;
129              
130 1062         2637 $cache->{border_style} = $self->{att}->{borderstyle};
131 1062 100       4828 $cache->{border_style} = $self->attribute('borderstyle') unless defined $cache->{border_style};
132 1062 100       4311 $cache->{border_style} = 'none' if $shape =~ /^(none|invisible)\z/;
133 1062         2828 $cache->{border_style};
134             }
135              
136             sub _border_styles
137             {
138             # Return the four border styles (right, bottom, left, top). This takes
139             # into account the neighbouring nodes and their borders, so that on
140             # ASCII output the borders can be properly collapsed.
141 1062     1062   2054 my ($self, $border, $collapse) = @_;
142              
143 1062         2170 my $cache = $self->{cache};
144              
145             # already computed values?
146 1062 50       3172 return if defined $cache->{left_border};
147              
148 1062         3489 $cache->{left_border} = $border;
149 1062         2100 $cache->{top_border} = $border;
150 1062         2200 $cache->{right_border} = $border;
151 1062         3138 $cache->{bottom_border} = $border;
152              
153 1062 50       2630 return unless $collapse;
154              
155             # print STDERR " border_styles: $self->{name} border=$border\n";
156              
157 1062         1560 my $EM = 14;
158 1062         4210 my $border_width = Graph::Easy::_border_width_in_pixels($self,$EM);
159              
160             # convert overly broad borders to the correct style
161 1062 100       3590 $border = 'bold' if $border_width > 2;
162 1062 100 66     4503 $border = 'broad' if $border_width > $EM * 0.2 && $border_width < $EM * 0.75;
163 1062 50       2599 $border = 'wide' if $border_width >= $EM * 0.75;
164              
165             # XXX TODO
166             # handle different colors, too:
167             # my $color = $self->color_attribute('bordercolor');
168              
169             # Draw border on A (left), and C (left):
170             #
171             # +---+
172             # B | A | C
173             # +---+
174              
175             # Ditto, plus C's border:
176             #
177             # +---+---+
178             # B | A | C |
179             # +---+---+
180             #
181              
182             # If no left neighbour, draw border normally
183              
184             # XXX TODO: ->{parent} ?
185 1062   66     5491 my $parent = $self->{parent} || $self->{graph};
186 1062 100       2248 return unless ref $parent;
187              
188 1060         1803 my $cells = $parent->{cells};
189 1060 50       2751 return unless ref $cells;
190              
191 1060         2011 my $x = $self->{x}; my $y = $self->{y};
  1060         1680  
192              
193 1060         1607 $x -= 1; my $left = $cells->{"$x,$y"};
  1060         2782  
194 1060         1501 $x += 1; $y-= 1; my $top = $cells->{"$x,$y"};
  1060         1561  
  1060         2526  
195 1060         1618 $x += 1; $y += 1; my $right = $cells->{"$x,$y"};
  1060         1922  
  1060         3076  
196 1060         1994 $x -= 1; $y += 1; my $bottom = $cells->{"$x,$y"};
  1060         1306  
  1060         2578  
197              
198             # where to store the result
199 1060         3665 my @where = ('left', 'top', 'right', 'bottom');
200             # need to swap arguments to _collapse_borders()?
201 1060         2765 my @swapem = (0, 0, 1, 1);
202            
203 1060         2683 for my $other ($left, $top, $right, $bottom)
204             {
205 4240         5466 my $side = shift @where;
206 4240         7757 my $swap = shift @swapem;
207            
208             # see if we have a (visible) neighbour on the left side
209 4240 100 100     38754 if (ref($other) &&
      100        
      100        
210             !$other->isa('Graph::Easy::Edge') &&
211             !$other->isa_cell() &&
212             !$other->isa('Graph::Easy::Node::Empty'))
213             {
214 209 100       735 $other = $other->{node} if ref($other->{node});
215              
216             # print STDERR "$side node $other ", $other->_border_to_draw(), " vs. $border (swap $swap)\n";
217              
218 209 50       645 if ($other->attribute('shape') ne 'invisible')
219             {
220             # yes, so take its border style
221 209         307 my $result;
222 209 100       1635 if ($swap)
223             {
224 123         411 $result = $self->_merge_borders($other->_border_to_draw(), $border);
225             }
226             else
227             {
228 86         421 $result = $self->_collapse_borders($border, $other->_border_to_draw());
229             }
230 209         856 $cache->{$side . '_border'} = $result;
231              
232             # print STDERR "# result: $result\n";
233             }
234             }
235             }
236             }
237              
238             sub _correct_size
239             {
240             # Correct {w} and {h} after parsing. This is a fallback in case
241             # the output specific routines (_correct_site_ascii() etc) do
242             # not exist.
243 1078     1078   1907 my $self = shift;
244              
245 1078 100       2998 return if defined $self->{w};
246              
247 1063         3605 my $shape = $self->attribute('shape');
248              
249 1063 100       3762 if ($shape eq 'point')
    100          
250             {
251 8         14 $self->{w} = 5;
252 8         16 $self->{h} = 3;
253 8         26 my $style = $self->attribute('pointstyle');
254 8         22 my $shape = $self->attribute('pointshape');
255 8 100 66     46 if ($style eq 'invisible' || $shape eq 'invisible')
256             {
257 1         3 $self->{w} = 0; $self->{h} = 0; return;
  1         3  
  1         4  
258             }
259             }
260             elsif ($shape eq 'invisible')
261             {
262 3         9 $self->{w} = 3;
263 3         8 $self->{h} = 3;
264             }
265             else
266             {
267 1052         3542 my ($w,$h) = $self->dimensions();
268 1052         2682 $self->{h} = $h;
269 1052         2627 $self->{w} = $w + 2;
270             }
271              
272 1062         3732 my $border = $self->_border_to_draw($shape);
273              
274 1062         3640 $self->_border_styles($border, 'collapse');
275              
276             # print STDERR "# $self->{name} $self->{w} $self->{h} $shape\n";
277             # use Data::Dumper; print Dumper($self->{cache});
278              
279 1062 100       6781 if ($shape !~ /^(invisible|point)/)
280             {
281 1052 100       3597 $self->{w} ++ if $self->{cache}->{right_border} ne 'none';
282 1052 100       14742 $self->{w} ++ if $self->{cache}->{left_border} ne 'none';
283 1052 100       5532 $self->{h} ++ if $self->{cache}->{top_border} ne 'none';
284 1052 100       3296 $self->{h} ++ if $self->{cache}->{bottom_border} ne 'none';
285              
286 1052 100 66     3726 $self->{h} += 2 if $border eq 'none' && $shape !~ /^(invisible|point)/;
287             }
288              
289 1062         3519 $self;
290             }
291              
292             sub _unplace
293             {
294             # free the cells this node occupies from $cells
295 0     0   0 my ($self,$cells) = @_;
296              
297 0         0 my $x = $self->{x}; my $y = $self->{y};
  0         0  
298 0         0 delete $cells->{"$x,$y"};
299 0         0 $self->{x} = undef;
300 0         0 $self->{y} = undef;
301 0         0 $self->{cache} = {};
302              
303 0 0       0 $self->_calc_size() unless defined $self->{cx};
304              
305 0 0       0 if ($self->{cx} + $self->{cy} > 2) # one of them > 1!
306             {
307 0         0 for my $ax (1..$self->{cx})
308             {
309 0         0 my $sx = $x + $ax - 1;
310 0         0 for my $ay (1..$self->{cy})
311             {
312 0         0 my $sy = $y + $ay - 1;
313             # free cell
314 0         0 delete $cells->{"$sx,$sy"};
315             }
316             }
317             } # end handling multi-celled node
318              
319             # unplace all edges leading to/from this node, too:
320 0         0 for my $e (ord_values ( $self->{edges} ))
321             {
322 0         0 $e->_unplace($cells);
323             }
324              
325 0         0 $self;
326             }
327              
328             sub _mark_as_placed
329             {
330             # for creating an action on the action stack we also need to recursively
331             # mark all our children as already placed:
332 583     583   1199 my ($self) = @_;
333              
334 49     49   526 no warnings 'recursion';
  49         735  
  49         6025  
335              
336 583         1031 delete $self->{_todo};
337              
338 583         2398 for my $child (ord_values ( $self->{children} ))
339             {
340 382         812 $child->_mark_as_placed();
341             }
342 583         1892 $self;
343             }
344              
345             sub _place_children
346             {
347             # recursively place node and its children
348 227     227   377 my ($self, $x, $y, $parent) = @_;
349              
350 49     49   310 no warnings 'recursion';
  49         108  
  49         275986  
351              
352 227 100       685 return 0 unless $self->_check_place($x,$y,$parent);
353              
354 224 50       540 print STDERR "# placing children of $self->{name} based on $x,$y\n" if $self->{debug};
355              
356 224         945 for my $child (ord_values ( $self->{children} ))
357             {
358             # compute place of children (depending on whether we are multicelled or not)
359              
360 153 100       517 my $dx = $child->{dx} > 0 ? $self->{cx} - 1 : 0;
361 153 100       450 my $dy = $child->{dy} > 0 ? $self->{cy} - 1 : 0;
362              
363 153         776 my $rc = $child->_place_children($x + $dx + $child->{dx},$y + $dy + $child->{dy},$parent);
364 153 100       528 return $rc if $rc == 0;
365             }
366 223         837 $self->_place($x,$y,$parent);
367             }
368              
369             sub _place
370             {
371             # place this node at the requested position (without checking)
372 1137     1137   2494 my ($self, $x, $y, $parent) = @_;
373              
374 1137         2078 my $cells = $parent->{cells};
375 1137         2177 $self->{x} = $x;
376 1137         2164 $self->{y} = $y;
377 1137         3911 $cells->{"$x,$y"} = $self;
378              
379             # store our position if we are the first node in that rank
380 1137   100     4233 my $r = abs($self->{rank} || 0);
381 1137   100     10104 my $what = $parent->{_rank_coord} || 'x'; # 'x' or 'y'
382 1137 100       5434 $parent->{_rank_pos}->{ $r } = $self->{$what}
383             unless defined $parent->{_rank_pos}->{ $r };
384              
385             # a multi-celled node will be stored like this:
386             # [ node ] [ filler ]
387             # [ filler ] [ filler ]
388             # [ filler ] [ filler ] etc.
389              
390             # $self->_calc_size() unless defined $self->{cx};
391              
392 1137 100       4574 if ($self->{cx} + $self->{cy} > 2) # one of them > 1!
393             {
394 30         88 for my $ax (1..$self->{cx})
395             {
396 74         211 my $sx = $x + $ax - 1;
397 74         150 for my $ay (1..$self->{cy})
398             {
399 124 100 100     459 next if $ax == 1 && $ay == 1; # skip left-upper most cell
400 94         204 my $sy = $y + $ay - 1;
401              
402             # We might even get away with creating only one filler cell
403             # although then its "x" and "y" values would be "wrong".
404              
405 94         619 my $filler =
406             Graph::Easy::Node::Cell->new ( node => $self, x => $sx, y => $sy );
407 94         427 $cells->{"$sx,$sy"} = $filler;
408             }
409             }
410             } # end handling of multi-celled node
411              
412 1137         3783 $self->_update_boundaries($parent);
413              
414 1137         9024 1; # did place us
415             }
416              
417             sub _check_place
418             {
419             # chack that a node can be placed at $x,$y (w/o checking its children)
420 227     227   383 my ($self,$x,$y,$parent) = @_;
421              
422 227         360 my $cells = $parent->{cells};
423              
424             # node cannot be placed here
425 227 100       1341 return 0 if exists $cells->{"$x,$y"};
426              
427 224 100       537 $self->_calc_size() unless defined $self->{cx};
428              
429 224 100       844 if ($self->{cx} + $self->{cy} > 2) # one of them > 1!
430             {
431 10         32 for my $ax (1..$self->{cx})
432             {
433 26         44 my $sx = $x + $ax - 1;
434 26         47 for my $ay (1..$self->{cy})
435             {
436 38         51 my $sy = $y + $ay - 1;
437             # node cannot be placed here
438 38 50       213 return 0 if exists $cells->{"$sx,$sy"};
439             }
440             }
441             }
442 224         709 1; # can place it here
443             }
444              
445             sub _do_place
446             {
447             # Tries to place the node at position ($x,$y) by checking that
448             # $cells->{"$x,$y"} is still free. If the node belongs to a cluster,
449             # checks all nodes of the cluster (and when all of them can be
450             # placed simultanously, does so).
451             # Returns true if the operation succeeded, otherwise false.
452 1084     1084   4010 my ($self,$x,$y,$parent) = @_;
453              
454 1084         2101 my $cells = $parent->{cells};
455              
456             # inlined from _check() for speed reasons:
457              
458             # node cannot be placed here
459 1084 100       4514 return 0 if exists $cells->{"$x,$y"};
460              
461 988 100       3285 $self->_calc_size() unless defined $self->{cx};
462              
463 988 100       3298 if ($self->{cx} + $self->{cy} > 2) # one of them > 1!
464             {
465 27         82 for my $ax (1..$self->{cx})
466             {
467 68         103 my $sx = $x + $ax - 1;
468 68         133 for my $ay (1..$self->{cy})
469             {
470 114         201 my $sy = $y + $ay - 1;
471             # node cannot be placed here
472 114 50       763 return 0 if exists $cells->{"$sx,$sy"};
473             }
474             }
475             }
476              
477 988         1662 my $children = 0;
478 988 100       3427 $children = scalar keys %{$self->{children}} if $self->{children};
  987         2508  
479              
480             # relativ to another, or has children (relativ to us)
481 988 100 100     6276 if (defined $self->{origin} || $children > 0)
482             {
483             # The coordinates of the origin node. Because 'dx' and 'dy' give
484             # our distance from the origin, we can compute the origin by doing
485             # "$x - $dx"
486              
487 74         138 my $grandpa = $self; my $ox = 0; my $oy = 0;
  74         114  
  74         106  
488             # Find our grandparent (e.g. the root of origin chain), and the distance
489             # from $x,$y to it:
490 74 100       230 ($grandpa,$ox,$oy) = $self->find_grandparent() if $self->{origin};
491              
492             # Traverse all children and check their places, place them if poss.
493             # This will also place ourselves, because we are a grandchild of $grandpa
494 74         363 return $grandpa->_place_children($x + $ox,$y + $oy,$parent);
495             }
496              
497             # finally place this node at the requested position
498 914         3602 $self->_place($x,$y,$parent);
499             }
500              
501             #############################################################################
502              
503             sub _wrapped_label
504             {
505             # returns the label wrapped automatically to use the least space
506 8     8   21 my ($self, $label, $align, $wrap) = @_;
507              
508 8 100       27 return (@{$self->{cache}->{label}}) if $self->{cache}->{label};
  3         17  
509              
510             # XXX TODO: handle "paragraphs"
511 5         32 $label =~ s/\\(n|r|l|c)/ /g; # replace line splits by spaces
512              
513             # collapse multiple spaces
514 5         37 $label =~ s/\s+/ /g;
515              
516             # find out where to wrap
517 5 50       16 if ($wrap eq 'auto')
518             {
519 0         0 $wrap = int(sqrt(length($label)) * 1.4);
520             }
521 5 50       15 $wrap = 2 if $wrap < 2;
522              
523             # run through the text and insert linebreaks
524 5         8 my $i = 0;
525 5         8 my $line_len = 0;
526 5         8 my $last_space = 0;
527 5         8 my $last_hyphen = 0;
528 5         7 my @lines;
529 5         19 while ($i < length($label))
530             {
531 158         177 my $c = substr($label,$i,1);
532 158 100       249 $last_space = $i if $c eq ' ';
533 158 100       238 $last_hyphen = $i if $c eq '-';
534 158         124 $line_len ++;
535 158 100 100     341 if ($line_len >= $wrap && ($last_space != 0 || $last_hyphen != 0))
      66        
536             {
537             # print STDERR "# wrap at $line_len\n";
538              
539 14         15 my $w = $last_space; my $replace = '';
  14         18  
540 14 100       27 if ($last_hyphen > $last_space)
541             {
542 1         2 $w = $last_hyphen; $replace = '-';
  1         3  
543             }
544              
545             # print STDERR "# wrap at $w\n";
546              
547             # "foo bar-baz" => "foo bar" (lines[0]) and "baz" (label afterwards)
548              
549             # print STDERR "# first part '". substr($label, 0, $w) . "'\n";
550              
551 14         79 push @lines, substr($label, 0, $w) . $replace;
552 14         33 substr($label, 0, $w+1) = '';
553             # reset counters
554 14         16 $line_len = 0;
555 14         12 $i = 0;
556 14         14 $last_space = 0;
557 14         12 $last_hyphen = 0;
558 14         34 next;
559             }
560 144         248 $i++;
561             }
562             # handle what is left over
563 5 100       17 push @lines, $label if $label ne '';
564              
565             # generate the align array
566 5         14 my @aligns;
567 5         13 my $al = substr($align,0,1);
568 5         15 for my $i (0.. scalar @lines)
569             {
570 23         45 push @aligns, $al;
571             }
572             # cache the result to avoid costly recomputation
573 5         22 $self->{cache}->{label} = [ \@lines, \@aligns ];
574 5         27 (\@lines, \@aligns);
575             }
576              
577             sub _aligned_label
578             {
579             # returns the label lines and for each one the alignment l/r/c
580 4229     4229   7660 my ($self, $align, $wrap) = @_;
581              
582 4229 100       9440 $align = 'center' unless $align;
583 4229 100       17587 $wrap = $self->attribute('textwrap') unless defined $wrap;
584              
585 4229         15658 my $name = $self->label();
586              
587 4229 100       12412 return $self->_wrapped_label($name,$align,$wrap) unless $wrap eq 'none';
588              
589 4221         5179 my (@lines,@aligns);
590 4221         16874 my $al = substr($align,0,1);
591 4221         6042 my $last_align = $al;
592              
593             # split up each line from the front
594 4221         15054 while ($name ne '')
595             {
596 2518         21810 $name =~ s/^(.*?([^\\]|))(\z|\\(n|r|l|c))//;
597 2518         6770 my $part = $1;
598 2518   100     12073 my $a = $3 || '\n';
599              
600 2518         5111 $part =~ s/\\\|/\|/g; # \| => |
601 2518         4086 $part =~ s/\\\\/\\/g; # '\\' to '\'
602 2518         7156 $part =~ s/^\s+//; # remove spaces at front
603 2518         6515 $part =~ s/\s+\z//; # remove spaces at end
604 2518         7872 $a =~ s/\\//; # \n => n
605 2518 100       7947 $a = $al if $a eq 'n';
606            
607 2518         5598 push @lines, $part;
608 2518         4987 push @aligns, $last_align;
609              
610 2518         7410 $last_align = $a;
611             }
612              
613             # XXX TODO: should remove empty lines at start/end?
614 4221         30277 (\@lines, \@aligns);
615             }
616              
617             #############################################################################
618             # as_html conversion and helper functions related to that
619              
620             my $remap = {
621             node => {
622             align => undef,
623             background => undef,
624             basename => undef,
625             border => undef,
626             borderstyle => undef,
627             borderwidth => undef,
628             bordercolor => undef,
629             columns => undef,
630             fill => 'background',
631             origin => undef,
632             offset => undef,
633             pointstyle => undef,
634             pointshape => undef,
635             rows => undef,
636             size => undef,
637             shape => undef,
638             },
639             edge => {
640             fill => undef,
641             border => undef,
642             },
643             all => {
644             align => 'text-align',
645             autolink => undef,
646             autotitle => undef,
647             comment => undef,
648             fontsize => undef,
649             font => 'font-family',
650             flow => undef,
651             format => undef,
652             label => undef,
653             link => undef,
654             linkbase => undef,
655             style => undef,
656             textstyle => undef,
657             title => undef,
658             textwrap => \&Graph::Easy::_remap_text_wrap,
659             group => undef,
660             },
661             };
662              
663             sub _extra_params
664             {
665             # return text with a leading " ", that will be appended to "td" when
666             # generating HTML
667 77     77   198 '';
668             }
669              
670             # XXX TODO: ?
671             my $pod = {
672             B => [ '', '' ],
673             O => [ '', '' ],
674             S => [ '', '' ],
675             U => [ '', '' ],
676             C => [ '', '' ],
677             I => [ '', '' ],
678             };
679              
680             sub _convert_pod
681             {
682 0     0   0 my ($self, $type, $text) = @_;
683              
684 0 0       0 my $t = $pod->{$type} or return $text;
685              
686             # "" . "text" . ""
687 0         0 $t->[0] . $text . $t->[1];
688             }
689              
690             sub _label_as_html
691             {
692             # Build the text from the lines, by inserting for each break
693             # Also align each line, and if nec., convert B to bold.
694 124     124   230 my ($self) = @_;
695              
696 124         329 my $align = $self->attribute('align');
697 124         534 my $text_wrap = $self->attribute('textwrap');
698              
699 124         201 my ($lines,$aligns);
700 124 50       272 if ($text_wrap eq 'auto')
701             {
702             # set "white-space: nowrap;" in CSS and ignore linebreaks in label
703 0         0 $lines = [ $self->label() ];
704 0         0 $aligns = [ substr($align,0,1) ];
705             }
706             else
707             {
708 124         722 ($lines,$aligns) = $self->_aligned_label($align,$text_wrap);
709             }
710              
711             # Since there is no "float: center;" in CSS, we must set the general
712             # text-align to center when we encounter any \c and the default is
713             # left or right:
714              
715 124         230 my $switch_to_center = 0;
716 124 100       306 if ($align ne 'center')
717             {
718 27         53 local $_;
719 27         84 $switch_to_center = grep /^c/, @$aligns;
720             }
721              
722 124 100       313 $align = 'center' if $switch_to_center;
723 124         572 my $a = substr($align,0,1); # center => c
724              
725 124         447 my $format = $self->attribute('format');
726              
727 124         226 my $name = '';
728 124         173 my $i = 0;
729 124         368 while ($i < @$lines)
730             {
731 93         188 my $line = $lines->[$i];
732 93         162 my $al = $aligns->[$i];
733              
734             # This code below will not handle B due to the
735             # line break. Also, nesting does not work due to returned "<" and ">".
736              
737 93 50       200 if ($format eq 'pod')
738             {
739             # first inner-most, then go outer until there are none left
740 0         0 $line =~ s/([BOSUCI])<([^<>]+)>/ $self->_convert_pod($1,$2);/eg
  0         0  
741             while ($line =~ /[BOSUCI]<[^<>]+>/)
742             }
743             else
744             {
745 93         211 $line =~ s/&/&/g; # quote &
746 93         174 $line =~ s/>/>/g; # quote >
747 93         161 $line =~ s/
748 93         166 $line =~ s/\\\\/\\/g; # "\\" to "\"
749             }
750              
751             # insert a span to align the line unless the default already covers it
752 93 100       258 $line = '' . $line . ''
753             if $a ne $al;
754 93         237 $name .= '
' . $line;
755              
756 93         246 $i++; # next line
757             }
758 124         390 $name =~ s/^
//; # remove first
759              
760 124         592 ($name, $switch_to_center);
761             }
762              
763             sub quoted_comment
764             {
765             # Comment of this object, quoted suitable as to be embedded into HTML/SVG
766 197     197 1 405 my $self = shift;
767              
768 197         621 my $cmt = $self->attribute('comment');
769 197 100       638 if ($cmt ne '')
770             {
771 6         20 $cmt =~ s/&/&/g;
772 6         12 $cmt =~ s/
773 6         27 $cmt =~ s/>/>/g;
774 6         17 $cmt = '\n";
775             }
776              
777 197         1442 $cmt;
778             }
779              
780             sub as_html
781             {
782             # return node as HTML
783 77     77 1 172 my ($self) = @_;
784              
785 77         165 my $shape = 'rect';
786 77 100       616 $shape = $self->attribute('shape') unless $self->isa_cell();
787              
788 77 50       271 if ($shape eq 'edge')
789             {
790 0         0 my $edge = Graph::Easy::Edge->new();
791 0         0 my $cell = Graph::Easy::Edge::Cell->new( edge => $edge );
792 0         0 $cell->{w} = $self->{w};
793 0         0 $cell->{h} = $self->{h};
794 0         0 $cell->{att}->{label} = $self->label();
795 0         0 $cell->{type} =
796             Graph::Easy::Edge::Cell->EDGE_HOR +
797             Graph::Easy::Edge::Cell->EDGE_LABEL_CELL;
798 0         0 return $cell->as_html();
799             }
800              
801 77         410 my $extra = $self->_extra_params();
802 77         208 my $taga = "td$extra";
803 77         119 my $tagb = 'td';
804              
805 77         208 my $id = $self->{graph}->{id};
806 77         145 my $a = $self->{att};
807 77         135 my $g = $self->{graph};
808              
809 77         277 my $class = $self->class();
810              
811             # how many rows/columns will this node span?
812 77   100     318 my $rs = ($self->{cy} || 1) * 4;
813 77   100     248 my $cs = ($self->{cx} || 1) * 4;
814              
815             # shape: invisible; must result in an empty cell
816 77 100 66     298 if ($shape eq 'invisible' && $class ne 'node.anon')
817             {
818 6         44 return " <$taga colspan=$cs rowspan=$rs style=\"border: none; background: inherit;\">\n";
819             }
820              
821 71         123 my $c = $class; $c =~ s/\./_/g; # node.city => node_city
  71         203  
822              
823 71         256 my $html = " <$taga colspan=$cs rowspan=$rs##class####style##";
824            
825 71         332 my $title = $self->title();
826 71         151 $title =~ s/'//g; # replace quotation marks
827              
828 71 100 66     292 $html .= " title='$title'" if $title ne '' && $shape ne 'img'; # add mouse-over title
829              
830 71         121 my ($name, $switch_to_center);
831              
832 71 50       238 if ($shape eq 'point')
    50          
833             {
834 0         0 require Graph::Easy::As_ascii; # for _u8 and point-style
835              
836 0         0 local $self->{graph}->{_ascii_style} = 1; # use utf-8
837 0         0 $name = $self->_point_style( $self->attribute('pointshape'), $self->attribute('pointstyle') );
838             }
839             elsif ($shape eq 'img')
840             {
841             # take the label as the URL, but escape critical characters
842 0         0 $name = $self->label();
843 0         0 $name =~ s/\s/\+/g; # space
844 0         0 $name =~ s/'/%27/g; # replace quotation marks
845 0         0 $name =~ s/[\x0d\x0a]//g; # remove 0x0d0x0a and similiar
846 0 0       0 my $t = $title; $t = $name if $t eq '';
  0         0  
847 0         0 $name = "$t";
848             }
849             else
850             {
851 71         267 ($name,$switch_to_center) = $self->_label_as_html();
852             }
853              
854             # if the label is "", the link wouldn't be clickable
855 71 100       175 my $link = ''; $link = $self->link() unless $name eq '';
  71         438  
856              
857             # the attributes in $out will be applied to either the TD, or the inner DIV,
858             # unless if we have a link, then most of them will be moved to the A HREF
859 71         309 my $att = $self->raw_attributes();
860 71         494 my $out = $self->{graph}->_remap_attributes( $self, $att, $remap, 'noquote', 'encode', 'remap_colors');
861              
862 71 100       213 $out->{'text-align'} = 'center' if $switch_to_center;
863              
864             # only for nodes, not for edges
865 71 100       651 if (!$self->isa('Graph::Easy::Edge'))
866             {
867 69         225 my $bc = $self->attribute('bordercolor');
868 69         230 my $bw = $self->attribute('borderwidth');
869 69         730 my $bs = $self->attribute('borderstyle');
870              
871 69         279 $out->{border} = Graph::Easy::_border_attribute_as_html( $bs, $bw, $bc );
872              
873             # we need to specify the border again for the inner div
874 69 100       255 if ($shape !~ /(rounded|ellipse|circle)/)
875             {
876 61         272 my $DEF = $self->default_attribute('border');
877              
878 61 100 66     622 delete $out->{border} if $out->{border} =~ /^\s*\z/ || $out->{border} eq $DEF;
879             }
880              
881 69 50 100     336 delete $out->{border} if $class eq 'node.anon' && $out->{border} && $out->{border} eq 'none';
      66        
882             }
883              
884             # we compose the inner part as $inner_start . $label . $inner_end:
885 71         149 my $inner_start = '';
886 71         109 my $inner_end = '';
887              
888 71 100       213 if ($shape =~ /(rounded|ellipse|circle)/)
889             {
890             # set the fill on the inner part, but the background and no border on the :
891 8         18 my $inner_style = '';
892 8         36 my $fill = $self->color_attribute('fill');
893 8 50       33 $inner_style = 'background:' . $fill if $fill;
894 8 50       48 $inner_style .= ';border:' . $out->{border} if $out->{border};
895 8         40 $inner_style =~ s/;\s?\z$//; # remove '; ' at end
896              
897 8         18 delete $out->{background};
898 8         24 delete $out->{border};
899              
900 8         20 my $td_style = '';
901 8         12 $td_style = ' style="border: none;';
902 8         30 my $bg = $self->color_attribute('background');
903 8         25 $td_style .= "background: $bg\"";
904              
905 8         45 $html =~ s/##style##/$td_style/;
906              
907 8         19 $inner_end = '';
908 8 100       25 my $c = substr($shape, 0, 1); $c = 'c' if $c eq 'e'; # 'r' or 'c'
  8         31  
909              
910 8         60 my ($w,$h) = $self->dimensions();
911              
912 8 100       31 if ($shape eq 'circle')
913             {
914             # set both to the biggest size to enforce a circle shape
915 1         3 my $r = $w;
916 1 50       4 $r = $h if $h > $w;
917 1         2 $w = $r; $h = $r;
  1         2  
918             }
919              
920 8 50       135 $out->{top} = ($h / 2 + 0.5) . 'em'; delete $out->{top} if $out->{top} eq '1.5em';
  8         112  
921 8         19 $h = ($h + 2) . 'em';
922 8         23 $w = ($w + 2) . 'em';
923              
924 8         26 $inner_style .= ";width: $w; height: $h";
925              
926 8         16 $inner_style = " style='$inner_style'";
927 8         34 $inner_start = "
";
928             }
929              
930 71 50       206 if ($class =~ /^group/)
931             {
932 0         0 delete $out->{border};
933 0         0 delete $out->{background};
934 0         0 my $group_class = $class; $group_class =~ s/\s.*//; # "group gt" => "group"
  0         0  
935 0         0 my @atr = qw/bordercolor borderwidth fill/;
936              
937             # transform "group_foo gr" to "group_foo" if border eq 'none' (for anon groups)
938 0         0 my $border_style = $self->attribute('borderstyle');
939 0 0       0 $c =~ s/\s+.*// if $border_style eq 'none';
940              
941             # only need the color for the label cell
942 0 0       0 push @atr, 'color' if $self->{has_label};
943 0 0       0 $name = ' ' unless $self->{has_label};
944 0         0 for my $b (@atr)
945             {
946 0         0 my $def = $g->attribute($group_class,$b);
947 0         0 my $v = $self->attribute($b);
948              
949 0 0       0 my $n = $b; $n = 'background' if $b eq 'fill';
  0         0  
950 0 0 0     0 $out->{$n} = $v unless $v eq '' || $v eq $def;
951             }
952 0 0       0 $name = ' ' unless $name ne '';
953             }
954              
955             # "shape: none;" or point means no border, and background instead fill color
956 71 50       387 if ($shape =~ /^(point|none)\z/)
957             {
958 0         0 $out->{background} = $self->color_attribute('background');
959 0         0 $out->{border} = 'none';
960             }
961              
962 71         132 my $style = '';
963 71         303 for my $atr (sort keys %$out)
964             {
965 35 100       103 if ($link ne '')
966             {
967             # put certain styles on the outer container, and not on the link
968 16 100       65 next if $atr =~ /^(background|border)\z/;
969             }
970 30         127 $style .= "$atr: $out->{$atr}; ";
971             }
972              
973             # bold, italic, underline etc. (but not for empty cells)
974 71 100       617 $style .= $self->text_styles_as_css(1,1) if $name !~ /^(| )\z/;
975              
976 71         277 $style =~ s/;\s?\z$//; # remove '; ' at end
977 71         215 $style =~ s/\s+/ /g; # ' ' => ' '
978 71         137 $style =~ s/^\s+//; # remove ' ' at front
979 71 100       847 $style = " style=\"$style\"" if $style;
980              
981 71         201 my $end_tag = "\n";
982              
983 71 100       306 if ($link ne '')
984             {
985             # encode critical entities
986 14         43 $link =~ s/\s/\+/g; # space
987 14         38 $link =~ s/'/%27/g; # replace quotation marks
988              
989 14         35 my $outer_style = '';
990             # put certain styles like border and background on the table cell:
991 14         36 for my $s (qw/background border/)
992             {
993 28 100       95 $outer_style .= "$s: $out->{$s};" if exists $out->{$s};
994             }
995 14         47 $outer_style =~ s/;\s?\z$//; # remove '; ' at end
996 14 100       36 $outer_style = ' style="'.$outer_style.'"' if $outer_style;
997              
998 14         25 $inner_start =~ s/##style##/$outer_style/; # remove from inner_start
999              
1000 14         54 $html =~ s/##style##/$outer_style/; # or HTML, depending
1001 14         34 $inner_start .= ""; # and put on link
1002 14         33 $inner_end = ''.$inner_end;
1003             }
1004              
1005 71 50       291 $c = " class='$c'" if $c ne '';
1006 71         226 $html .= ">$inner_start$name$inner_end$end_tag";
1007 71         331 $html =~ s/##class##/$c/;
1008 71         269 $html =~ s/##style##/$style/;
1009              
1010 71         347 $self->quoted_comment() . $html;
1011             }
1012              
1013             sub angle
1014             {
1015             # return the rotation of the node, dependend on the rotate attribute
1016             # (and if relative, on the flow)
1017 19     19 1 40 my $self = shift;
1018              
1019 19   100     59 my $angle = $self->{att}->{rotate} || 0;
1020              
1021 19 100       44 $angle = 180 if $angle =~ /^(south|down)\z/;
1022 19 50       53 $angle = 0 if $angle =~ /^(north|up)\z/;
1023 19 50       36 $angle = 270 if $angle eq 'west';
1024 19 50       31 $angle = 90 if $angle eq 'east';
1025              
1026             # convert relative angles
1027 19 100       59 if ($angle =~ /^([+-]\d+|left|right|back|front|forward)\z/)
1028             {
1029 12         30 my $base_rot = $self->flow();
1030 12 100       33 $angle = 0 if $angle =~ /^(front|forward)\z/;
1031 12 100       26 $angle = 180 if $angle eq 'back';
1032 12 100       19 $angle = -90 if $angle eq 'left';
1033 12 50       25 $angle = 90 if $angle eq 'right';
1034 12         18 $angle = $base_rot + $angle + 0; # 0 points up, so front points right
1035 12         26 $angle += 360 while $angle < 0;
1036             }
1037              
1038 19 50       69 $self->_croak("Illegal node angle $angle") if $angle !~ /^\d+\z/;
1039              
1040 19 100       38 $angle %= 360 if $angle > 359;
1041              
1042 19         113 $angle;
1043             }
1044              
1045             # for determining the absolute parent flow
1046             my $p_flow =
1047             {
1048             'east' => 90,
1049             'west' => 270,
1050             'north' => 0,
1051             'south' => 180,
1052             'up' => 0,
1053             'down' => 180,
1054             'back' => 270,
1055             'left' => 270,
1056             'right' => 90,
1057             'front' => 90,
1058             'forward' => 90,
1059             };
1060              
1061             sub _parent_flow_absolute
1062             {
1063             # make parent flow absolute
1064 1289     1289   2812 my ($self, $def) = @_;
1065              
1066 1289 50       3955 return '90' if ref($self) eq 'Graph::Easy';
1067              
1068 1289   100     15481 my $flow = $self->parent()->raw_attribute('flow') || $def;
1069              
1070 1289 100       3916 return unless defined $flow;
1071              
1072             # in case of relative flow at parent, convert to absolute (right: east, left: west etc)
1073             # so that "graph { flow: left; }" results in a westward flow
1074 640 100       1930 my $f = $p_flow->{$flow}; $f = $flow unless defined $f;
  640         1860  
1075 640         1607 $f;
1076             }
1077              
1078             sub flow
1079             {
1080             # Calculate the outgoing flow from the incoming flow and the flow at this
1081             # node (either from edge(s) or general flow). Returns an absolute flow:
1082             # See the online manual about flow for a reference and details.
1083 4716     4716 1 7460 my $self = shift;
1084              
1085 49     49   563 no warnings 'recursion';
  49         97  
  49         308683  
1086              
1087 4716         10080 my $cache = $self->{cache};
1088 4716 100       20027 return $cache->{flow} if exists $cache->{flow};
1089              
1090             # detected cycle, so break it
1091 1053 100       4926 return $cache->{flow} = $self->_parent_flow_absolute('90') if exists $self->{_flow};
1092              
1093 1042         2906 local $self->{_flow} = undef; # endless loops really ruin our day
1094              
1095 1042         1577 my $in;
1096 1042         2269 my $flow = $self->{att}->{flow};
1097              
1098 1042 100 66     5222 $flow = $self->_parent_flow_absolute() if !defined $flow || $flow eq 'inherit';
1099              
1100             # if flow is absolute, return it early
1101 1042 100 100     7357 return $cache->{flow} = $flow if defined $flow && $flow =~ /^(0|90|180|270)\z/;
1102 662 100 100     2104 return $cache->{flow} = Graph::Easy->_direction_as_number($flow)
1103             if defined $flow && $flow =~ /^(south|north|east|west|up|down)\z/;
1104            
1105             # for relative flows, compute the incoming flow as base flow
1106              
1107             # check all edges
1108 650         2850 for my $e (ord_values ( $self->{edges} ))
1109             {
1110             # only count incoming edges
1111 774 100 66     5349 next unless $e->{from} != $self && $e->{to} == $self;
1112              
1113             # if incoming edge has flow, we take this
1114 390         1883 $in = $e->flow();
1115             # take the first match
1116 390 50       1380 last if defined $in;
1117             }
1118              
1119 650 100       2056 if (!defined $in)
1120             {
1121             # check all predecessors
1122 260         1425 for my $e (ord_values ( $self->{edges} ))
1123             {
1124 344         681 my $pre = $e->{from};
1125 344 100       1231 $pre = $e->{to} if $e->{bidirectional};
1126 344 100       1583 if ($pre != $self)
1127             {
1128 11         35 $in = $pre->flow();
1129             # take the first match
1130 11 50       42 last if defined $in;
1131             }
1132             }
1133             }
1134              
1135 650 100       2363 $in = $self->_parent_flow_absolute('90') unless defined $in;
1136              
1137 650 100       4334 $flow = Graph::Easy->_direction_as_number($in) unless defined $flow;
1138              
1139 650         2598 $cache->{flow} = Graph::Easy->_flow_as_direction($in,$flow);
1140             }
1141              
1142             #############################################################################
1143             # multi-celled nodes
1144              
1145             sub _calc_size
1146             {
1147             # Calculate the base size in cells from the attributes (before grow())
1148             # Will return a hash that denotes in which direction the node should grow.
1149 1158     1158   1928 my $self = shift;
1150              
1151             # If specified only one of "rows" or "columns", then grow the node
1152             # only in the unspecified direction. Default is grow both.
1153 1158         4409 my $grow_sides = { cx => 1, cy => 1 };
1154              
1155 1158         2949 my $r = $self->{att}->{rows};
1156 1158         2028 my $c = $self->{att}->{columns};
1157 1158 100 100     3868 delete $grow_sides->{cy} if defined $r && !defined $c;
1158 1158 100 100     3335 delete $grow_sides->{cx} if defined $c && !defined $r;
1159              
1160 1158 100       6981 $r = $self->attribute('rows') unless defined $r;
1161 1158 100       6254 $c = $self->attribute('columns') unless defined $c;
1162              
1163 1158   50     5878 $self->{cy} = abs($r || 1);
1164 1158   50     3770 $self->{cx} = abs($c || 1);
1165              
1166 1158         2954 $grow_sides;
1167             }
1168              
1169             sub _grow
1170             {
1171             # Grows the node until it has sufficient cells for all incoming/outgoing
1172             # edges. The initial size will be based upon the attributes 'size' (or
1173             # 'rows' or 'columns', depending on which is set)
1174 1134     1134   1946 my $self = shift;
1175              
1176             # XXX TODO: grow the node based on its label dimensions
1177             # my ($w,$h) = $self->dimensions();
1178             #
1179             # my $cx = int(($w+2) / 5) || 1;
1180             # my $cy = int(($h) / 3) || 1;
1181             #
1182             # $self->{cx} = $cx if $cx > $self->{cx};
1183             # $self->{cy} = $cy if $cy > $self->{cy};
1184              
1185             # satisfy the edge start/end port constraints:
1186              
1187             # We calculate a bitmap (vector) for each side, and mark each
1188             # used port. Edges that have an unspecified port will just be
1189             # counted.
1190              
1191             # bitmap for each side:
1192 1134         5341 my $vec = { north => '', south => '', east => '', west => '' };
1193             # number of edges constrained to one side, but without port number
1194 1134         4765 my $cnt = { north => 0, south => 0, east => 0, west => 0 };
1195             # number of edges constrained to one side, with port number
1196 1134         3966 my $portnr = { north => 0, south => 0, east => 0, west => 0 };
1197             # max number of ports for each side
1198 1134         3569 my $max = { north => 0, south => 0, east => 0, west => 0 };
1199              
1200 1134         4288 my @idx = ( [ 'start', 'from' ], [ 'end', 'to' ] );
1201             # number of slots we need to edges without port restrictions
1202 1134         1742 my $unspecified = 0;
1203              
1204             # count of outgoing edges
1205 1134         1610 my $outgoing = 0;
1206              
1207 1134         9911 for my $e (ord_values ( $self->{edges} ))
1208             {
1209             # count outgoing edges
1210 1774 100       6537 $outgoing++ if $e->{from} == $self;
1211              
1212             # do always both ends, because self-loops can start AND end at this node:
1213 1774         4795 for my $end (0..1)
1214             {
1215             # if the edge starts/ends here
1216 3548 100       13610 if ($e->{$idx[$end]->[1]} == $self) # from/to
1217             {
1218 1805         7207 my ($side, $nr) = $e->port($idx[$end]->[0]); # start/end
1219              
1220 1805 100       3956 if (defined $side)
1221             {
1222 89 100 66     352 if (!defined $nr || $nr eq '')
1223             {
1224             # no port number specified, so just count
1225 41         175 $cnt->{$side}++;
1226             }
1227             else
1228             {
1229             # mark the bit in the vector
1230             # limit to four digits
1231 48 50       140 $nr = 9999 if abs($nr) > 9999;
1232              
1233             # if slot was not used yet, count it
1234 48 100       169 $portnr->{$side} ++ if vec($vec->{$side}, $nr, 1) == 0x0;
1235              
1236             # calculate max number of ports
1237 48 50       111 $nr = abs($nr) - 1 if $nr < 0; # 3 => 3, -3 => 2
1238 48         67 $nr++; # 3 => 4, -3 => 3
1239              
1240             # mark as used
1241 48         166 vec($vec->{$side}, $nr - 1, 1) = 0x01;
1242              
1243 48 100       253 $max->{$side} = $nr if $nr > $max->{$side};
1244             }
1245             }
1246             else
1247             {
1248 1716         13028 $unspecified ++;
1249             }
1250             } # end if port is constrained
1251             } # end for start/end port
1252             } # end for all edges
1253              
1254 1134         6268 for my $e (ord_values ( $self->{edges} ))
1255             {
1256             # the loop above will count all self-loops twice when they are
1257             # unrestricted. So subtract these again. Restricted self-loops
1258             # might start at one port and end at another, and this case is
1259             # covered correctly by the code above.
1260 1774 100       7391 $unspecified -- if $e->{to} == $e->{from};
1261             }
1262              
1263             # Shortcut, if the number of edges is < 4 and we have not restrictions,
1264             # then a 1x1 node suffices
1265 1134 100 100     4240 if ($unspecified < 4 && ($unspecified == keys %{$self->{edges}}))
  1090         5121  
1266             {
1267 1043         5460 $self->_calc_size();
1268 1043         9610 return $self;
1269             }
1270            
1271 91         180 my $need = {};
1272 91         158 my $free = {};
1273 91         203 for my $side (qw/north south east west/)
1274             {
1275             # maximum number of ports we need to reserve, minus edges constrained
1276             # to unique ports: free ports on that side
1277 364         939 $free->{$side} = $max->{$side} - $portnr->{$side};
1278 364         602 $need->{$side} = $max->{$side};
1279 364 100       1667 if ($free->{$side} < 2 * $cnt->{$side})
1280             {
1281 31         105 $need->{$side} += 2 * $cnt->{$side} - $free->{$side} - 1;
1282             }
1283             }
1284             # now $need contains for each side the absolute min. number of ports we need
1285              
1286             # use Data::Dumper;
1287             # print STDERR "# port contraints for $self->{name}:\n";
1288             # print STDERR "# count: ", Dumper($cnt), "# max: ", Dumper($max),"\n";
1289             # print STDERR "# ports: ", Dumper($portnr),"\n";
1290             # print STDERR "# need : ", Dumper($need),"\n";
1291             # print STDERR "# free : ", Dumper($free),"\n";
1292            
1293             # calculate min. size in X and Y direction
1294 91 100       196 my $min_x = $need->{north}; $min_x = $need->{south} if $need->{south} > $min_x;
  91         377  
1295 91 100       168 my $min_y = $need->{west}; $min_y = $need->{east} if $need->{east} > $min_y;
  91         273  
1296              
1297 91         304 my $grow_sides = $self->_calc_size();
1298              
1299             # increase the size if the minimum required size is not met
1300 91 100       316 $self->{cx} = $min_x if $min_x > $self->{cx};
1301 91 100       248 $self->{cy} = $min_y if $min_y > $self->{cy};
1302              
1303 91         304 my $flow = $self->flow();
1304              
1305             # if this is a sink node, grow it more by ignoring free ports on the front side
1306 91         177 my $front_side = 'east';
1307 91 100       263 $front_side = 'west' if $flow == 270;
1308 91 100       334 $front_side = 'south' if $flow == 180;
1309 91 100       223 $front_side = 'north' if $flow == 0;
1310              
1311             # now grow the node based on the general flow first VER, then HOR
1312 91         146 my $grow = 0; # index into @grow_what
1313 91         807 my @grow_what = sort keys %$grow_sides; # 'cx', 'cy' or 'cx' or 'cy'
1314              
1315 91 100       325 if (keys %$grow_sides > 1)
1316             {
1317             # for left/right flow, swap the growing around
1318 89 100 100     446 @grow_what = ( 'cy', 'cx' ) if $flow == 90 || $flow == 270;
1319             }
1320              
1321             # fake a non-sink node for nodes with an offset/children
1322 91 100 100     396 $outgoing = 1 if ref($self->{origin}) || keys %{$self->{children}} > 0;
  69         586  
1323              
1324 91         179 while ( 3 < 5 )
1325             {
1326             # calculate whether we already found a space for all edges
1327 98         173 my $free_ports = 0;
1328 98         213 for my $side (qw/north south/)
1329             {
1330             # if this is a sink node, grow it more by ignoring free ports on the front side
1331 196 100 100     615 next if $outgoing == 0 && $front_side eq $side;
1332 195         751 $free_ports += 1 + int(($self->{cx} - $cnt->{$side} - $portnr->{$side}) / 2);
1333             }
1334 98         235 for my $side (qw/east west/)
1335             {
1336             # if this is a sink node, grow it more by ignoring free ports on the front side
1337 196 100 100     514 next if $outgoing == 0 && $front_side eq $side;
1338 187         585 $free_ports += 1 + int(($self->{cy} - $cnt->{$side} - $portnr->{$side}) / 2);
1339             }
1340 98 100       303 last if $free_ports >= $unspecified;
1341              
1342 7         17 $self->{ $grow_what[$grow] } += 2;
1343              
1344 7 50       11 $grow ++; $grow = 0 if $grow >= @grow_what;
  7         31  
1345             }
1346              
1347 91         1022 $self;
1348             }
1349              
1350             sub is_multicelled
1351             {
1352             # return true if node consist of more than one cell
1353 1697     1697 1 2695 my $self = shift;
1354              
1355 1697 100       5449 $self->_calc_size() unless defined $self->{cx};
1356              
1357 1697         12828 $self->{cx} + $self->{cy} <=> 2; # 1 + 1 == 2: no, cx + xy != 2: yes
1358             }
1359              
1360             sub is_anon
1361             {
1362             # normal nodes are not anon nodes (but "::Anon" are)
1363 5     5 1 21 0;
1364             }
1365              
1366             #############################################################################
1367             # accessor methods
1368              
1369             sub _un_escape
1370             {
1371             # replace \N, \G, \T, \H and \E (depending on type)
1372             # if $label is false, also replace \L with the label
1373 27     27   50 my ($self, $txt, $do_label) = @_;
1374            
1375             # for edges:
1376 27 100       70 if (exists $self->{edge})
1377             {
1378 4         9 my $e = $self->{edge};
1379 4         38 $txt =~ s/\\E/$e->{from}->{name}\->$e->{to}->{name}/g;
1380 4         21 $txt =~ s/\\T/$e->{from}->{name}/g;
1381 4         19 $txt =~ s/\\H/$e->{to}->{name}/g;
1382             # \N for edges is the label of the edge
1383 4 50       20 if ($txt =~ /\\N/)
1384             {
1385 0         0 my $l = $self->label();
1386 0         0 $txt =~ s/\\N/$l/g;
1387             }
1388             }
1389             else
1390             {
1391             # \N for nodes
1392 23         163 $txt =~ s/\\N/$self->{name}/g;
1393             }
1394             # \L with the label
1395 27 100 66     143 if ($txt =~ /\\L/ && $do_label)
1396             {
1397 2         7 my $l = $self->label();
1398 2         7 $txt =~ s/\\L/$l/g;
1399             }
1400              
1401             # \G for edges and nodes
1402 27 100       148 if ($txt =~ /\\G/)
1403             {
1404 21         33 my $g = '';
1405             # the graph itself
1406 21 100       98 $g = $self->attribute('title') unless ref($self->{graph});
1407             # any nodes/edges/groups in it
1408 21 100       91 $g = $self->{graph}->label() if ref($self->{graph});
1409 21         81 $txt =~ s/\\G/$g/g;
1410             }
1411 27         106 $txt;
1412             }
1413              
1414             sub title
1415             {
1416             # Returns a title of the node (or '', if none was set), which can be
1417             # used for mouse-over titles
1418              
1419 110     110 1 194 my $self = shift;
1420              
1421 110         327 my $title = $self->attribute('title');
1422 110 100       441 if ($title eq '')
1423             {
1424 99         303 my $autotitle = $self->attribute('autotitle');
1425 99 50       278 if (defined $autotitle)
1426             {
1427 99         171 $title = ''; # default is none
1428              
1429 99 100       262 if ($autotitle eq 'name') # use name
1430             {
1431 15         42 $title = $self->{name};
1432             # edges do not have a name and fall back on their label
1433 15 100       61 $title = $self->{att}->{label} unless defined $title;
1434             }
1435              
1436 99 100       234 if ($autotitle eq 'label')
1437             {
1438 2         4 $title = $self->{name}; # fallback to name
1439             # defined to avoid overriding "name" with the non-existant label attribute
1440             # do not use label() here, but the "raw" label of the edge:
1441 2 50       7 my $label = $self->label(); $title = $label if defined $label;
  2         10  
1442             }
1443              
1444 99 100       308 $title = $self->link() if $autotitle eq 'link';
1445             }
1446 99 100       248 $title = '' unless defined $title;
1447             }
1448              
1449 110 100 66     579 $title = $self->_un_escape($title, 1) if !$_[0] && $title =~ /\\[EGHNTL]/;
1450              
1451 110         302 $title;
1452             }
1453              
1454             sub background
1455             {
1456             # get the background for this group/edge cell, honouring group membership.
1457 4     4 1 29 my $self = shift;
1458              
1459 4         28 $self->color_attribute('background');
1460             }
1461              
1462             sub label
1463             {
1464 5819     5819 1 13951 my $self = shift;
1465              
1466             # shortcut to speed it up a bit:
1467 5819         13436 my $label = $self->{att}->{label};
1468 5819 100       25524 $label = $self->attribute('label') unless defined $label;
1469              
1470             # for autosplit nodes, use their auto-label first (unless already got
1471             # a label from the class):
1472 5819 100       19829 $label = $self->{autosplit_label} unless defined $label;
1473 5819 100       16708 $label = $self->{name} unless defined $label;
1474              
1475 5819 100       17088 return '' unless defined $label;
1476              
1477 3949 100       9625 if ($label ne '')
1478             {
1479 3634         11279 my $len = $self->attribute('autolabel');
1480 3634 100       17657 if ($len ne '')
1481             {
1482             # allow the old format (pre v0.49), too: "name,12" => 12
1483 8         61 $len =~ s/^name\s*,\s*//;
1484             # restrict to sane values
1485 8 50 50     29 $len = abs($len || 0); $len = 99999 if $len > 99999;
  8         23  
1486 8 100       28 if (length($label) > $len)
1487             {
1488 6   50     18 my $g = $self->{graph} || {};
1489 6 50 50     33 if ((($g->{_ascii_style}) || 0) == 0)
1490             {
1491             # ASCII output
1492 6 50       14 $len = int($len / 2) - 3; $len = 0 if $len < 0;
  6         15  
1493 6         25 $label = substr($label, 0, $len) . ' ... ' . substr($label, -$len, $len);
1494             }
1495             else
1496             {
1497 0 0       0 $len = int($len / 2) - 2; $len = 0 if $len < 0;
  0         0  
1498 0         0 $label = substr($label, 0, $len) . ' … ' . substr($label, -$len, $len);
1499             }
1500             }
1501             }
1502             }
1503              
1504 3949 100 100     29519 $label = $self->_un_escape($label) if !$_[0] && $label =~ /\\[EGHNT]/;
1505              
1506 3949         14304 $label;
1507             }
1508              
1509             sub name
1510             {
1511 1600     1600 1 6666 my $self = shift;
1512              
1513 1600         5102 $self->{name};
1514             }
1515              
1516             sub x
1517             {
1518 4     4 1 10 my $self = shift;
1519              
1520 4         23 $self->{x};
1521             }
1522              
1523             sub y
1524             {
1525 4     4 1 9 my $self = shift;
1526              
1527 4         22 $self->{y};
1528             }
1529              
1530             sub width
1531             {
1532 7     7 1 18 my $self = shift;
1533              
1534 7         36 $self->{w};
1535             }
1536              
1537             sub height
1538             {
1539 6     6 1 16 my $self = shift;
1540              
1541 6         31 $self->{h};
1542             }
1543              
1544             sub origin
1545             {
1546             # Returns node that this node is relative to or undef, if not.
1547 5     5 1 1111 my $self = shift;
1548              
1549 5         28 $self->{origin};
1550             }
1551              
1552             sub pos
1553             {
1554 4     4 1 14 my $self = shift;
1555              
1556 4   50     74 ($self->{x} || 0, $self->{y} || 0);
      50        
1557             }
1558              
1559             sub offset
1560             {
1561 73     73 1 137 my $self = shift;
1562              
1563 73   100     878 ($self->{dx} || 0, $self->{dy} || 0);
      100        
1564             }
1565              
1566             sub columns
1567             {
1568 2     2 1 4 my $self = shift;
1569              
1570 2 50       11 $self->_calc_size() unless defined $self->{cx};
1571              
1572 2         9 $self->{cx};
1573             }
1574              
1575             sub rows
1576             {
1577 2     2 1 6 my $self = shift;
1578              
1579 2 50       10 $self->_calc_size() unless defined $self->{cy};
1580              
1581 2         12 $self->{cy};
1582             }
1583              
1584             sub size
1585             {
1586 27     27 1 50 my $self = shift;
1587              
1588 27 100       135 $self->_calc_size() unless defined $self->{cx};
1589              
1590 27         179 ($self->{cx}, $self->{cy});
1591             }
1592              
1593             sub shape
1594             {
1595 57     57 1 92 my $self = shift;
1596              
1597 57         71 my $shape;
1598 57 100       188 $shape = $self->{att}->{shape} if exists $self->{att}->{shape};
1599 57 100       219 $shape = $self->attribute('shape') unless defined $shape;
1600 57         273 $shape;
1601             }
1602              
1603             sub dimensions
1604             {
1605             # Returns the minimum dimensions of the node/cell derived from the
1606             # label or name, in characters.
1607 1908     1908 1 2968 my $self = shift;
1608              
1609 1908         5825 my $align = $self->attribute('align');
1610 1908         9899 my ($lines,$aligns) = $self->_aligned_label($align);
1611              
1612 1908         3845 my $w = 0; my $h = scalar @$lines;
  1908         4485  
1613 1908         3930 foreach my $line (@$lines)
1614             {
1615 1201 100       5928 $w = length($line) if length($line) > $w;
1616             }
1617 1908         7714 ($w,$h);
1618             }
1619              
1620             #############################################################################
1621             # edges and connections
1622              
1623             sub edges_to
1624             {
1625             # Return all the edge objects that start at this vertex and go to $other.
1626 1236     1236 1 3102 my ($self, $other) = @_;
1627              
1628             # no graph, no dice
1629 1236 100       4756 return unless ref $self->{graph};
1630              
1631 1235         2833 my @edges;
1632 1235         4383 for my $edge (ord_values ( $self->{edges} ))
1633             {
1634 2511 100 100     15513 push @edges, $edge if $edge->{from} == $self && $edge->{to} == $other;
1635             }
1636 1235         5253 @edges;
1637             }
1638              
1639             sub edges_at_port
1640             {
1641             # return all edges that share the same given port
1642 81     81 1 221 my ($self, $attr, $side, $port) = @_;
1643              
1644             # Must be "start" or "end"
1645 81 50       438 return () unless $attr =~ /^(start|end)\z/;
1646              
1647 81 50       199 $self->_croak('side not defined') unless defined $side;
1648 81 50       193 $self->_croak('port not defined') unless defined $port;
1649              
1650 81         118 my @edges;
1651 81         414 for my $e (ord_values ( $self->{edges} ))
1652             {
1653             # skip edges ending here if we look at start
1654 292 100 100     1623 next if $e->{to} eq $self && $attr eq 'start';
1655             # skip edges starting here if we look at end
1656 275 100 100     1242 next if $e->{from} eq $self && $attr eq 'end';
1657              
1658 263         760 my ($s_p,@ss_p) = $e->port($attr);
1659 263 50       665 next unless defined $s_p;
1660              
1661             # same side and same port number?
1662 263 50 66     2197 push @edges, $e
      66        
1663             if $s_p eq $side && @ss_p == 1 && $ss_p[0] eq $port;
1664             }
1665              
1666 81         394 @edges;
1667             }
1668              
1669             sub shared_edges
1670             {
1671             # return all edges that share one port with another edge
1672 0     0 1 0 my ($self) = @_;
1673              
1674 0         0 my @edges;
1675 0         0 for my $e (ord_values ( $self->{edges} ))
1676             {
1677 0         0 my ($s_p,@ss_p) = $e->port('start');
1678 0 0       0 push @edges, $e if defined $s_p;
1679 0         0 my ($e_p,@ee_p) = $e->port('end');
1680 0 0       0 push @edges, $e if defined $e_p;
1681             }
1682 0         0 @edges;
1683             }
1684              
1685             sub nodes_sharing_start
1686             {
1687             # return all nodes that share an edge start with an
1688             # edge from that node
1689 15     15 1 52 my ($self, $side, @port) = @_;
1690              
1691 15         92 my @edges = $self->edges_at_port('start',$side,@port);
1692              
1693 15         23 my $nodes;
1694 15         48 for my $e (@edges)
1695             {
1696             # ignore self-loops
1697 45         77 my $to = $e->{to};
1698 45 50       111 next if $to == $self;
1699              
1700             # remove duplicates
1701 45         174 $nodes->{ $to->{name} } = $to;
1702             }
1703              
1704 15         53 return (ord_values $nodes);
1705             }
1706              
1707             sub nodes_sharing_end
1708             {
1709             # return all nodes that share an edge end with an
1710             # edge from that node
1711 18     18 1 54 my ($self, $side, @port) = @_;
1712              
1713 18         88 my @edges = $self->edges_at_port('end',$side,@port);
1714              
1715 18         36 my $nodes;
1716 18         44 for my $e (@edges)
1717             {
1718             # ignore self-loops
1719 58         92 my $from = $e->{from};
1720 58 50       141 next if $from == $self;
1721              
1722             # remove duplicates
1723 58         288 $nodes->{ $from->{name} } = $from;
1724             }
1725              
1726 18         76 return (ord_values $nodes);
1727             }
1728              
1729             sub incoming
1730             {
1731             # return all edges that end at this node
1732 7     7 1 23 my $self = shift;
1733              
1734             # no graph, no dice
1735 7 100       31 return unless ref $self->{graph};
1736              
1737 6 50       17 if (!wantarray)
1738             {
1739 6         9 my $count = 0;
1740 6         37 for my $edge (ord_values ( $self->{edges} ))
1741             {
1742 10 100       38 $count++ if $edge->{to} == $self;
1743             }
1744 6         36 return $count;
1745             }
1746              
1747 0         0 my @edges;
1748 0         0 for my $edge (ord_values ( $self->{edges} ))
1749             {
1750 0 0       0 push @edges, $edge if $edge->{to} == $self;
1751             }
1752 0         0 @edges;
1753             }
1754              
1755             sub outgoing
1756             {
1757             # return all edges that start at this node
1758 7     7 1 15 my $self = shift;
1759              
1760             # no graph, no dice
1761 7 100       27 return unless ref $self->{graph};
1762              
1763 6 50       17 if (!wantarray)
1764             {
1765 6         11 my $count = 0;
1766 6         24 for my $edge (ord_values ( $self->{edges} ))
1767             {
1768 10 100       42 $count++ if $edge->{from} == $self;
1769             }
1770 6         34 return $count;
1771             }
1772              
1773 0         0 my @edges;
1774 0         0 for my $edge (ord_values ( $self->{edges} ))
1775             {
1776 0 0       0 push @edges, $edge if $edge->{from} == $self;
1777             }
1778 0         0 @edges;
1779             }
1780              
1781             sub connections
1782             {
1783             # return number of connections (incoming+outgoing)
1784 18     18 1 59 my $self = shift;
1785              
1786 18 100       71 return 0 unless defined $self->{graph};
1787              
1788             # We need to count the connections, because "[A]->[A]" creates
1789             # two connections on "A", but only one edge!
1790 15         22 my $con = 0;
1791 15         57 for my $edge (ord_values ( $self->{edges} ))
1792             {
1793 31 100       81 $con ++ if $edge->{to} == $self;
1794 31 100       93 $con ++ if $edge->{from} == $self;
1795             }
1796 15         82 $con;
1797             }
1798              
1799             sub edges
1800             {
1801             # return all the edges
1802 5     5 1 742 my $self = shift;
1803              
1804             # no graph, no dice
1805 5 100       24 return unless ref $self->{graph};
1806              
1807             return (wantarray
1808 1         6 ? ord_values ( $self->{edges} )
1809 4 100       28 : scalar keys %{$self->{edges}}
1810             );
1811             }
1812              
1813             sub sorted_successors
1814             {
1815             # return successors of the node sorted by their chain value
1816             # (e.g. successors with more successors first)
1817 887     887 1 1549 my $self = shift;
1818              
1819 160 50       544 my @suc = sort {
1820 887         2760 scalar $b->successors() <=> scalar $a->successors() ||
1821             scalar $a->{name} cmp scalar $b->{name}
1822             } $self->successors();
1823 887         4052 @suc;
1824             }
1825              
1826             sub successors
1827             {
1828             # return all nodes (as objects) we are linked to
1829 3637     3637 1 9522 my $self = shift;
1830              
1831 3637 100       12444 return () unless defined $self->{graph};
1832              
1833 3633         5620 my %suc;
1834 3633         13631 for my $edge (ord_values ( $self->{edges} ))
1835             {
1836 5156 100       22865 next unless $edge->{from} == $self;
1837 2546         13613 $suc{$edge->{to}->{id}} = $edge->{to}; # weed out doubles
1838             }
1839 3633         14003 return ord_values( \%suc );
1840             }
1841              
1842             sub predecessors
1843             {
1844             # return all nodes (as objects) that link to us
1845 2684     2684 1 4396 my $self = shift;
1846              
1847 2684 100       8161 return () unless defined $self->{graph};
1848              
1849 2681         3430 my %pre;
1850 2681         11182 for my $edge (ord_values ( $self->{edges} ))
1851             {
1852 3583 100       14507 next unless $edge->{to} == $self;
1853 1998         12129 $pre{$edge->{from}->{id}} = $edge->{from}; # weed out doubles
1854             }
1855 2681         11567 return ord_values(\%pre);
1856             }
1857              
1858             sub has_predecessors
1859             {
1860             # return true if node has incoming edges (even from itself)
1861 1175     1175 1 1620 my $self = shift;
1862              
1863 1175 50       3410 return undef unless defined $self->{graph};
1864              
1865 1175         4476 for my $edge (ord_values ( $self->{edges} ))
1866             {
1867 1259 100       8592 return 1 if $edge->{to} == $self; # found one
1868             }
1869 485         3227 0; # found none
1870             }
1871              
1872             sub has_as_predecessor
1873             {
1874             # return true if other is a predecessor of node
1875 8     8 1 18 my ($self,$other) = @_;
1876              
1877 8 50       31 return () unless defined $self->{graph};
1878              
1879 8         33 for my $edge (ord_values ( $self->{edges} ))
1880             {
1881 10 100 100     74 return 1 if
1882             $edge->{to} == $self && $edge->{from} == $other; # found one
1883             }
1884 5         123 0; # found none
1885             }
1886              
1887             sub has_as_successor
1888             {
1889             # return true if other is a successor of node
1890 7     7 1 23 my ($self,$other) = @_;
1891              
1892 7 50       27 return () unless defined $self->{graph};
1893              
1894 7         31 for my $edge (ord_values ( $self->{edges} ))
1895             {
1896 8 100 100     61 return 1 if
1897             $edge->{from} == $self && $edge->{to} == $other; # found one
1898              
1899             }
1900 5         27 0; # found none
1901             }
1902              
1903             #############################################################################
1904             # relatively placed nodes
1905              
1906             sub relative_to
1907             {
1908             # Sets the new origin if passed a Graph::Easy::Node object.
1909 198     198 1 407 my ($self,$parent,$dx,$dy) = @_;
1910              
1911 198 50 33     1502 if (!ref($parent) || !$parent->isa('Graph::Easy::Node'))
1912             {
1913 0         0 require Carp;
1914 0         0 Carp::confess("Can't set origin to non-node object $parent");
1915             }
1916              
1917 198         632 my $grandpa = $parent->find_grandparent();
1918 198 50       722 if ($grandpa == $self)
1919             {
1920 0         0 require Carp;
1921 0         0 Carp::confess( "Detected loop in origin-chain:"
1922             ." tried to set origin of '$self->{name}' to my own grandchild $parent->{name}");
1923             }
1924              
1925             # unregister us with our old parent
1926 198 100       512 delete $self->{origin}->{children}->{$self->{id}} if defined $self->{origin};
1927              
1928 198         440 $self->{origin} = $parent;
1929 198 100       549 $self->{dx} = $dx if defined $dx;
1930 198 100       498 $self->{dy} = $dy if defined $dy;
1931 198 100       523 $self->{dx} = 0 unless defined $self->{dx};
1932 198 100       548 $self->{dy} = 0 unless defined $self->{dy};
1933              
1934             # register us as a new child
1935 198         750 $parent->{children}->{$self->{id}} = $self;
1936              
1937 198         469 $self;
1938             }
1939              
1940             sub find_grandparent
1941             {
1942             # For a node that has no origin (is not relative to another), returns
1943             # $self. For all others, follows the chain of origin back until we
1944             # hit a node without a parent. This code assumes there are no loops,
1945             # which origin() prevents from happening.
1946 2848     2848 1 4686 my $cur = shift;
1947              
1948 2848 100       6047 if (wantarray)
1949             {
1950 18         35 my $ox = 0;
1951 18         25 my $oy = 0;
1952 18         60 while (defined($cur->{origin}))
1953             {
1954 34         58 $ox -= $cur->{dx};
1955 34         55 $oy -= $cur->{dy};
1956 34         92 $cur = $cur->{origin};
1957             }
1958 18         54 return ($cur,$ox,$oy);
1959             }
1960              
1961 2830         8310 while (defined($cur->{origin}))
1962             {
1963 328         992 $cur = $cur->{origin};
1964             }
1965            
1966 2830         8265 $cur;
1967             }
1968              
1969             #############################################################################
1970             # attributes
1971              
1972             sub del_attribute
1973             {
1974 77     77 1 7802 my ($self, $name) = @_;
1975              
1976             # font-size => fontsize
1977 77 100       229 $name = $att_aliases->{$name} if exists $att_aliases->{$name};
1978              
1979 77         163 $self->{cache} = {};
1980              
1981 77         266 my $a = $self->{att};
1982 77         164 delete $a->{$name};
1983 77 100       188 if ($name eq 'size')
1984             {
1985 1         2 delete $a->{rows};
1986 1         3 delete $a->{columns};
1987             }
1988 77 50       174 if ($name eq 'border')
1989             {
1990 0         0 delete $a->{borderstyle};
1991 0         0 delete $a->{borderwidth};
1992 0         0 delete $a->{bordercolor};
1993             }
1994 77         211 $self;
1995             }
1996              
1997             sub set_attribute
1998             {
1999 965     965 1 115600 my ($self, $name, $v, $class) = @_;
2000              
2001 965         2500 $self->{cache} = {};
2002              
2003 965 50       3160 $name = 'undef' unless defined $name;
2004 965 50       4453 $v = 'undef' unless defined $v;
2005              
2006             # font-size => fontsize
2007 965 100       3517 $name = $att_aliases->{$name} if exists $att_aliases->{$name};
2008              
2009             # edge.cities => edge
2010 965 100       5641 $class = $self->main_class() unless defined $class;
2011              
2012             # remove quotation marks, but not for titles, labels etc
2013 965         7674 my $val = Graph::Easy->unquote_attribute($class,$name,$v);
2014              
2015 965         2001 my $g = $self->{graph};
2016            
2017 965 100       3782 $g->{score} = undef if $g; # invalidate layout to force a new layout
2018              
2019 965 100       1401 my $strict = 0; $strict = $g->{strict} if $g;
  965         7785  
2020 965 100       2510 if ($strict)
2021             {
2022 186         806 my ($rc, $newname, $v) = $g->validate_attribute($name,$val,$class);
2023              
2024 186 100       547 return if defined $rc; # error?
2025              
2026 185         603 $val = $v;
2027             }
2028              
2029 964 100       5216 if ($name eq 'class')
    100          
    100          
2030             {
2031 15         101 $self->sub_class($val);
2032 15         40 return $val;
2033             }
2034             elsif ($name eq 'group')
2035             {
2036 2         14 $self->add_to_group($val);
2037 2         5 return $val;
2038             }
2039             elsif ($name eq 'border')
2040             {
2041 31         66 my $c = $self->{att};
2042              
2043 31         320 ($c->{borderstyle}, $c->{borderwidth}, $c->{bordercolor}) =
2044             $g->split_border_attributes( $val );
2045              
2046 31         124 return $val;
2047             }
2048              
2049 916 100       3834 if ($name =~ /^(columns|rows|size)\z/)
2050             {
2051 24 100       82 if ($name eq 'size')
2052             {
2053 16         91 $val =~ /^(\d+)\s*,\s*(\d+)\z/;
2054 16         91 my ($cx, $cy) = (abs(int($1)),abs(int($2)));
2055 16         157 ($self->{att}->{columns}, $self->{att}->{rows}) = ($cx, $cy);
2056             }
2057             else
2058             {
2059 8         34 $self->{att}->{$name} = abs(int($val));
2060             }
2061 24         96 return $self;
2062             }
2063              
2064 892 100       2522 if ($name =~ /^(origin|offset)\z/)
2065             {
2066             # Only the first autosplit node get the offset/origin
2067 151 100 100     752 return $self if exists $self->{autosplit} && !defined $self->{autosplit};
2068              
2069 143 100       434 if ($name eq 'origin')
2070             {
2071             # if it doesn't exist, add it
2072 73         288 my $org = $self->{graph}->add_node($val);
2073 73         342 $self->relative_to($org);
2074            
2075             # set the attributes, too, so get_attribute('origin') works, too:
2076 73         224 $self->{att}->{origin} = $org->{name};
2077             }
2078             else
2079             {
2080             # offset
2081             # if it doesn't exist, add it
2082 70         582 my ($x,$y) = split/\s*,\s*/, $val;
2083 70         251 $x = int($x);
2084 70         116 $y = int($y);
2085 70 50 66     319 if ($x == 0 && $y == 0)
2086             {
2087 0         0 $g->error("Error in attribute: 'offset' is 0,0 in node $self->{name} with class '$class'");
2088 0         0 return;
2089             }
2090 70         204 $self->{dx} = $x;
2091 70         211 $self->{dy} = $y;
2092              
2093             # set the attributes, too, so get_attribute('origin') works, too:
2094 70         374 $self->{att}->{offset} = "$self->{dx},$self->{dy}";
2095             }
2096 143         466 return $self;
2097             }
2098              
2099 741         5640 $self->{att}->{$name} = $val;
2100             }
2101              
2102             sub set_attributes
2103             {
2104 3888     3888 1 8036 my ($self, $atr, $index) = @_;
2105              
2106 3888         17077 foreach my $n (sort keys %$atr)
2107             {
2108 769         1931 my $val = $atr->{$n};
2109 769 100 66     2711 $val = $val->[$index] if ref($val) eq 'ARRAY' && defined $index;
2110              
2111 769 100 66     13416 next if !defined $val || $val eq '';
2112              
2113 762 100       3703 $n eq 'class' ? $self->sub_class($val) : $self->set_attribute($n, $val);
2114             }
2115 3888         14046 $self;
2116             }
2117              
2118             BEGIN
2119             {
2120             # some handy aliases
2121 49     49   398 *text_styles_as_css = \&Graph::Easy::text_styles_as_css;
2122 49         225 *text_styles = \&Graph::Easy::text_styles;
2123 49         219 *_font_size_in_pixels = \&Graph::Easy::_font_size_in_pixels;
2124 49         268 *get_color_attribute = \&color_attribute;
2125 49         177 *link = \&Graph::Easy::link;
2126 49         147 *border_attribute = \&Graph::Easy::border_attribute;
2127 49         158 *get_attributes = \&Graph::Easy::get_attributes;
2128 49         128 *get_attribute = \&Graph::Easy::attribute;
2129 49         147 *raw_attribute = \&Graph::Easy::raw_attribute;
2130 49         114 *get_raw_attribute = \&Graph::Easy::raw_attribute;
2131 49         122 *raw_color_attribute = \&Graph::Easy::raw_color_attribute;
2132 49         119 *raw_attributes = \&Graph::Easy::raw_attributes;
2133 49         114 *raw_attributes = \&Graph::Easy::raw_attributes;
2134 49         104 *attribute = \&Graph::Easy::attribute;
2135 49         179 *color_attribute = \&Graph::Easy::color_attribute;
2136 49         220 *default_attribute = \&Graph::Easy::default_attribute;
2137 49         318 $att_aliases = Graph::Easy::_att_aliases();
2138             }
2139              
2140             #############################################################################
2141              
2142             sub group
2143             {
2144             # return the group this object belongs to
2145 4507     4507 1 7322 my $self = shift;
2146              
2147 4507         13046 $self->{group};
2148             }
2149              
2150             sub add_to_group
2151             {
2152 115     115 1 266 my ($self,$group) = @_;
2153            
2154 115         1189 my $graph = $self->{graph}; # shortcut
2155              
2156             # delete from old group if nec.
2157 115 100       484 $self->{group}->del_member($self) if ref $self->{group};
2158              
2159             # if passed a group name, create or find group object
2160 115 100 66     456 $group = $graph->add_group($group) if (!ref($group) && $graph);
2161              
2162             # To make attribute('group') work:
2163 115         449 $self->{att}->{group} = $group->{name};
2164              
2165 115         505 $group->add_member($self);
2166              
2167 115         513 $self;
2168             }
2169              
2170             sub parent
2171             {
2172             # return parent object, either the group the node belongs to, or the graph
2173 2669     2669 1 3821 my $self = shift;
2174              
2175 2669         4492 my $p = $self->{graph};
2176              
2177 2669 100       7194 $p = $self->{group} if ref($self->{group});
2178              
2179 2669         10782 $p;
2180             }
2181              
2182             sub _update_boundaries
2183             {
2184 4216     4216   9422 my ($self, $parent) = @_;
2185              
2186             # XXX TODO: use current layout parent for recursive layouter:
2187 4216         7082 $parent = $self->{graph};
2188              
2189             # cache max boundaries for A* algorithmn:
2190              
2191 4216         7611 my $x = $self->{x};
2192 4216         8220 my $y = $self->{y};
2193              
2194             # create the cache if it doesn't already exist
2195 4216 100       14135 $parent->{cache} = {} unless ref($parent->{cache});
2196              
2197 4216         6863 my $cache = $parent->{cache};
2198            
2199 4216 100 100     23282 $cache->{min_x} = $x if !defined $cache->{min_x} || $x < $cache->{min_x};
2200 4216 100 100     20972 $cache->{min_y} = $y if !defined $cache->{min_y} || $y < $cache->{min_y};
2201              
2202 4216   100     22784 $x = $x + ($self->{cx}||1) - 1;
2203 4216   100     16505 $y = $y + ($self->{cy}||1) - 1;
2204 4216 100 100     30035 $cache->{max_x} = $x if !defined $cache->{max_x} || $x > $cache->{max_x};
2205 4216 100 100     19653 $cache->{max_y} = $y if !defined $cache->{max_y} || $y > $cache->{max_y};
2206              
2207 4216 50 50     21904 if (($parent->{debug}||0) > 1)
2208             {
2209 0 0       0 my $n = $self->{name}; $n = $self unless defined $n;
  0         0  
2210 0         0 print STDERR "Update boundaries for $n (parent $parent) at $x, $y\n";
2211            
2212 0         0 print STDERR "Boundaries are now: " .
2213             "$cache->{min_x},$cache->{min_y} => $cache->{max_x},$cache->{max_y}\n";
2214             }
2215              
2216 4216         10038 $self;
2217             }
2218              
2219             1;
2220             __END__