| 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 |
|||||||
| 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 |
|||||||
| 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/</g; # quote < | |||||
| 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/</g; | |||||
| 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;\">$tagb>\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 = " |
|||||
| 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 = "$tagb>\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__ |