1package GraphViz2; 2 3use strict; 4use warnings; 5use warnings qw(FATAL utf8); # Fatalize encoding glitches. 6 7use Data::Section::Simple 'get_data_section'; 8use File::Temp; # For newdir(). 9use File::Which; # For which(). 10use Moo; 11use IPC::Run3; # For run3(). 12use Types::Standard qw/Any ArrayRef HasMethods HashRef Int Str/; 13 14our $VERSION = '2.66'; 15 16my $DATA_SECTION = get_data_section; # load once 17my $DEFAULT_COMBINE = 1; # default for combine_node_and_port 18my %CONTEXT_QUOTING = ( 19 label => '\\{\\}\\|<>\\s"', 20 label_legacy => '"', 21); 22my %PORT_QUOTING = map +($_ => sprintf "%%%02x", ord $_), qw(% \\ : " { } | < >); 23my $PORT_QUOTE_CHARS = join '', '[', (map quotemeta, sort keys %PORT_QUOTING), ']'; 24 25has command => 26( 27 default => sub{[]}, 28 is => 'ro', 29 isa => ArrayRef, 30 required => 0, 31); 32 33has dot_input => 34( 35 is => 'lazy', 36 isa => Str, 37 required => 0, 38); 39 40sub _build_dot_input { 41 my ($self) = @_; 42 join('', @{ $self->command }) . "}\n"; 43} 44 45has dot_output => 46( 47 default => sub{return ''}, 48 is => 'rw', 49 isa => Str, 50 required => 0, 51); 52 53has edge => 54( 55 default => sub{return {} }, 56 is => 'rw', 57 isa => HashRef, 58 required => 0, 59); 60 61has edge_hash => 62( 63 default => sub{return {} }, 64 is => 'rw', 65 isa => HashRef, 66 required => 0, 67); 68 69has global => 70( 71 default => sub{return {} }, 72 is => 'rw', 73 isa => HashRef, 74 required => 0, 75); 76 77has graph => 78( 79 default => sub{return {} }, 80 is => 'rw', 81 isa => HashRef, 82 required => 0, 83); 84 85has im_meta => 86( 87 default => sub{return {} }, 88 is => 'rw', 89 isa => HashRef, 90 required => 0, 91); 92 93has logger => 94( 95 is => 'rw', 96 isa => HasMethods[qw(debug error)], 97 required => 0, 98); 99 100has node => 101( 102 default => sub{return {} }, 103 is => 'rw', 104 isa => HashRef, 105 required => 0, 106); 107 108has node_hash => 109( 110 default => sub{return {} }, 111 is => 'rw', 112 isa => HashRef, 113 required => 0, 114); 115 116has scope => 117( 118 default => sub{[]}, 119 is => 'ro', 120 isa => ArrayRef, 121 required => 0, 122); 123 124has subgraph => 125( 126 default => sub{return {} }, 127 is => 'rw', 128 isa => HashRef, 129 required => 0, 130); 131 132has verbose => 133( 134 default => sub{return 0}, 135 is => 'rw', 136 isa => Int, 137 required => 0, 138); 139 140my $VALID_ATTRIBUTES = _build_valid_attributes(); 141sub valid_attributes { $VALID_ATTRIBUTES } 142sub _build_valid_attributes { 143 my %data = map +($_ => [ 144 grep !/^$/ && !/^(?:\s*)#/, split /\n/, $$DATA_SECTION{$_} 145 ]), keys %$DATA_SECTION; 146 # Reorder them so the major key is the context and the minor key is the attribute. 147 # I.e. $attribute{global}{directed} => undef means directed is valid in a global context. 148 my %attribute; 149 # Common attributes are a special case, since one attribute can be valid is several contexts... 150 # Format: attribute_name => context_1, context_2. 151 for my $a (@{ delete $data{common_attribute} }) { 152 my ($attr, $contexts) = split /\s*=>\s*/, $a; 153 $attribute{$_}{$attr} = undef for split /\s*,\s*/, $contexts; 154 } 155 @{$attribute{$_}}{ @{$data{$_}} } = () for keys %data; 156 @{$attribute{subgraph}}{ keys %{ delete $attribute{cluster} } } = (); 157 \%attribute; 158} 159 160has valid_output_format => ( 161 is => 'lazy', 162 isa => HashRef, 163 required => 0, 164); 165 166sub _build_valid_output_format { 167 my ($self) = @_; 168 run3 169 ['dot', "-T?"], 170 undef, 171 \my $stdout, 172 \my $stderr, 173 ; 174 $stderr =~ s/.*one of:\s+//; 175 +{ map +($_ => undef), split /\s+/, $stderr }; 176} 177 178sub _dor { return $_[0] if defined $_[0]; $_[1] } # // 179 180sub BUILD 181{ 182 my($self) = @_; 183 my($globals) = $self -> global; 184 my($global) = 185 { 186 combine_node_and_port => _dor($$globals{combine_node_and_port}, $DEFAULT_COMBINE), 187 directed => $$globals{directed} ? 'digraph' : 'graph', 188 driver => $$globals{driver} || scalar(which('dot')), 189 format => $$globals{format} || 'svg', 190 im_format => $$globals{im_format} || 'cmapx', 191 label => $$globals{directed} ? '->' : '--', 192 name => _dor($$globals{name}, 'Perl'), 193 record_shape => ($$globals{record_shape} && $$globals{record_shape} =~ /^(M?record)$/) ? $1 : 'Mrecord', 194 strict => _dor($$globals{strict}, 0), 195 timeout => _dor($$globals{timeout}, 10), 196 }; 197 my($im_metas) = $self -> im_meta; 198 my($im_meta) = 199 { 200 URL => $$im_metas{URL} || '', 201 }; 202 203 $self -> global($global); 204 $self -> im_meta($im_meta); 205 $self->validate_params('global', $self->global); 206 $self->validate_params('graph', $self->graph); 207 $self->validate_params('im_meta', $self->im_meta); 208 $self->validate_params('node', $self->node); 209 $self->validate_params('edge', $self->edge); 210 $self->validate_params('subgraph', $self->subgraph); 211 push @{ $self->scope }, { 212 edge => $self -> edge, 213 graph => $self -> graph, 214 node => $self -> node, 215 subgraph => $self -> subgraph, 216 }; 217 218 my(%global) = %{$self -> global}; 219 my(%im_meta) = %{$self -> im_meta}; 220 221 $self -> log(debug => "Default global: $_ => $global{$_}") for sort keys %global; 222 $self -> log(debug => "Default im_meta: $_ => $im_meta{$_}") for grep{$im_meta{$_} } sort keys %im_meta; 223 224 my($command) = (${$self -> global}{strict} ? 'strict ' : '') 225 . (${$self -> global}{directed} . ' ') 226 . ${$self -> global}{name} 227 . " {\n"; 228 229 for my $key (grep{$im_meta{$_} } sort keys %im_meta) 230 { 231 $command .= _indent(qq|$key = "$im_meta{$key}"; \n|, $self->scope); 232 } 233 234 push @{ $self->command }, $command; 235 236 $self -> default_graph; 237 $self -> default_node; 238 $self -> default_edge; 239 240} # End of BUILD. 241 242sub _edge_name_port { 243 my ($self, $name) = @_; 244 $name = _dor($name, ''); 245 # Remove :port:compass, if any, from name. 246 # But beware Perl-style node names like 'A::Class'. 247 my @field = split /(:(?!:))/, $name; 248 $field[0] = $name if !@field; 249 # Restore Perl module names: 250 # o A: & B to A::B. 251 # o A: & B: & C to A::B::C. 252 splice @field, 0, 3, "$field[0]:$field[2]" while $field[0] =~ /:$/; 253 # Restore: 254 # o : & port to :port. 255 # o : & port & : & compass to :port:compass. 256 $name = shift @field; 257 ($name, join '', @field); 258} 259 260sub add_edge 261{ 262 my($self, %arg) = @_; 263 my $from = _dor(delete $arg{from}, ''); 264 my $to = _dor(delete $arg{to}, ''); 265 my $label = _dor($arg{label}, ''); 266 $label =~ s/^\s*(<)\n?/$1/; 267 $label =~ s/\n?(>)\s*$/$1/; 268 $arg{label} = $label if (defined $arg{label}); 269 270 $self->validate_params('edge', \%arg); 271 272 my @nodes; 273 for my $tuple ([ $from, 'tailport' ], [ $to, 'headport' ]) { 274 my ($name, $argname) = @$tuple; 275 my $port = ''; 276 if ($self->global->{combine_node_and_port}) { 277 ($name, $port) = $self->_edge_name_port($name); 278 } elsif (defined(my $value = delete $arg{$argname})) { 279 $port = join ':', '', map qq{"$_"}, map escape_port($_), ref $value ? @$value : $value; 280 } 281 push @nodes, [ $name, $port ]; 282 next if (my $nh = $self->node_hash)->{$name}; 283 $self->log(debug => "Implicitly added node: $name"); 284 $nh->{$name}{attributes} = {}; 285 } 286 287 # Add this edge to the hashref of all edges. 288 push @{$self->edge_hash->{$nodes[0][0]}{$nodes[1][0]}}, { 289 attributes => \%arg, 290 from_port => $nodes[0][1], 291 to_port => $nodes[1][1], 292 }; 293 294 # Add this edge to the DOT output string. 295 my $dot = $self->stringify_attributes(join(" ${$self->global}{label} ", map qq|"$_->[0]"$_->[1]|, @nodes), \%arg); 296 push @{ $self->command }, _indent($dot, $self->scope); 297 $self -> log(debug => "Added edge: $dot"); 298 299 return $self; 300} # End of add_edge. 301 302sub _indent { 303 my ($text, $scope) = @_; 304 return '' if $text !~ /\S/; 305 (' ' x @$scope) . $text; 306} 307 308sub _compile_record { 309 my ($port_count, $item, $add_braces, $quote_more) = @_; 310 my $text; 311 if (ref $item eq 'ARRAY') { 312 my @parts; 313 for my $l (@$item) { 314 ($port_count, my $t) = _compile_record($port_count, $l, 1, $quote_more); 315 push @parts, $t; 316 } 317 $text = join '|', @parts; 318 $text = "{$text}" if $add_braces; 319 } elsif (ref $item eq 'HASH') { 320 my $port = $item->{port} || 0; 321 $text = escape_some_chars(_dor($item->{text}, ''), $CONTEXT_QUOTING{$quote_more ? 'label' : 'label_legacy'}); 322 if ($port) { 323 $port =~ s/^\s*<?//; 324 $port =~ s/>?\s*$//; 325 $port = escape_port($port); 326 $text = "<$port> $text"; 327 } 328 } else { 329 $text = "<port".++$port_count."> " . escape_some_chars($item, $CONTEXT_QUOTING{$quote_more ? 'label' : 'label_legacy'}); 330 } 331 ($port_count, $text); 332} 333 334sub add_node { 335 my ($self, %arg) = @_; 336 my $name = _dor(delete $arg{name}, ''); 337 $self->validate_params('node', \%arg); 338 my $node = $self->node_hash; 339 %arg = (%{$$node{$name}{attributes} || {}}, %arg); 340 $$node{$name}{attributes} = \%arg; 341 my $label = _dor($arg{label}, ''); 342 $label =~ s/^\s*(<)\n?/$1/; 343 $label =~ s/\n?(>)\s*$/$1/; 344 $arg{label} = $label if defined $arg{label}; 345 # Handle ports. 346 if (ref $label eq 'ARRAY') { 347 (undef, $arg{label}) = _compile_record(0, $label, 0, !$self->global->{combine_node_and_port}); 348 $arg{shape} ||= $self->global->{record_shape}; 349 } elsif ($arg{shape} && ( ($arg{shape} =~ /M?record/) || ( ($arg{shape} =~ /(?:none|plaintext)/) && ($label =~ /^</) ) ) ) { 350 # Do not escape anything. 351 } elsif ($label) { 352 $arg{label} = escape_some_chars($arg{label}, $CONTEXT_QUOTING{label_legacy}); 353 } 354 my $dot = $self->stringify_attributes(qq|"$name"|, \%arg); 355 push @{ $self->command }, _indent($dot, $self->scope); 356 $self->log(debug => "Added node: $dot"); 357 return $self; 358} 359 360sub default_edge 361{ 362 my($self, %arg) = @_; 363 364 $self->validate_params('edge', \%arg); 365 366 my $scope = $self->scope->[-1]; 367 $$scope{edge} = {%{$$scope{edge} || {}}, %arg}; 368 369 push @{ $self->command }, _indent($self->stringify_attributes('edge', $$scope{edge}), $self->scope); 370 $self -> log(debug => 'Default edge: ' . join(', ', map{"$_ => $$scope{edge}{$_}"} sort keys %{$$scope{edge} }) ); 371 372 return $self; 373 374} # End of default_edge. 375 376# ----------------------------------------------- 377 378sub default_graph 379{ 380 my($self, %arg) = @_; 381 382 $self->validate_params('graph', \%arg); 383 384 my $scope = $self->scope->[-1]; 385 $$scope{graph} = {%{$$scope{graph} || {}}, %arg}; 386 387 push @{ $self->command }, _indent($self->stringify_attributes('graph', $$scope{graph}), $self->scope); 388 $self -> log(debug => 'Default graph: ' . join(', ', map{"$_ => $$scope{graph}{$_}"} sort keys %{$$scope{graph} }) ); 389 390 return $self; 391 392} # End of default_graph. 393 394# ----------------------------------------------- 395 396sub default_node 397{ 398 my($self, %arg) = @_; 399 400 $self->validate_params('node', \%arg); 401 402 my $scope = $self->scope->[-1]; 403 $$scope{node} = {%{$$scope{node} || {}}, %arg}; 404 405 push @{ $self->command }, _indent($self->stringify_attributes('node', $$scope{node}), $self->scope); 406 $self -> log(debug => 'Default node: ' . join(', ', map{"$_ => $$scope{node}{$_}"} sort keys %{$$scope{node} }) ); 407 408 return $self; 409 410} # End of default_node. 411 412# ----------------------------------------------- 413 414sub default_subgraph 415{ 416 my($self, %arg) = @_; 417 418 $self->validate_params('subgraph', \%arg); 419 420 my $scope = $self->scope->[-1]; 421 $$scope{subgraph} = {%{$$scope{subgraph} || {}}, %arg}; 422 423 push @{ $self->command }, _indent($self->stringify_attributes('subgraph', $$scope{subgraph}), $self->scope); 424 $self -> log(debug => 'Default subgraph: ' . join(', ', map{"$_ => $$scope{subgraph}{$_}"} sort keys %{$$scope{subgraph} }) ); 425 426 return $self; 427 428} # End of default_subgraph. 429 430sub escape_port { 431 my ($s) = @_; 432 $s =~ s/($PORT_QUOTE_CHARS)/$PORT_QUOTING{$1}/g; 433 $s; 434} 435 436sub escape_some_chars { 437 my ($s, $quote_chars) = @_; 438 return $s if substr($s, 0, 1) eq '<'; # HTML label 439 $s =~ s/(\\.)|([$quote_chars])/ defined($1) ? $1 : '\\' . $2 /ge; 440 return $s; 441} 442 443sub log 444{ 445 my($self, $level, $message) = @_; 446 $level ||= 'debug'; 447 $message ||= ''; 448 449 if ($self->logger) { 450 $self->logger->$level($message); 451 } else { 452 die $message if $level eq 'error'; 453 print "$level: $message\n" if $self->verbose; 454 } 455 456 return $self; 457 458} # End of log. 459 460# ----------------------------------------------- 461 462sub pop_subgraph 463{ 464 my($self) = @_; 465 466 pop @{ $self->scope }; 467 push @{ $self->command }, _indent("}\n", $self->scope); 468 469 return $self; 470 471} # End of pop_subgraph. 472 473# ----------------------------------------------- 474 475sub push_subgraph 476{ 477 my($self, %arg) = @_; 478 my($name) = delete $arg{name}; 479 $name = defined($name) && length($name) ? qq|"$name"| : ''; 480 481 $self->validate_params('graph', $arg{graph}); 482 $self->validate_params('node', $arg{node}); 483 $self->validate_params('edge', $arg{edge}); 484 $self->validate_params('subgraph', $arg{subgraph}); 485 486 $arg{subgraph} = { %{ $self->subgraph||{} }, %{$arg{subgraph}||{}} }; 487 488 push @{ $self->command }, "\n" . _indent(join(' ', grep length, "subgraph", $name, "{\n"), $self->scope); 489 push @{ $self->scope }, \%arg; 490 $self -> default_graph; 491 $self -> default_node; 492 $self -> default_edge; 493 $self -> default_subgraph; 494 495 return $self; 496 497} # End of push_subgraph. 498 499# ----------------------------------------------- 500 501sub run 502{ 503 my($self, %arg) = @_; 504 my($driver) = delete $arg{driver} || ${$self -> global}{driver}; 505 my($format) = delete $arg{format} || ${$self -> global}{format}; 506 my($im_format) = delete $arg{im_format} || ${$self -> global}{im_format}; 507 my($timeout) = delete $arg{timeout} || ${$self -> global}{timeout}; 508 my($output_file) = delete $arg{output_file} || ''; 509 my($im_output_file) = delete $arg{im_output_file} || ''; 510 511 for ($format, $im_format) { 512 my $prefix = $_; 513 $prefix =~ s/:.+$//; # In case of 'png:gd', etc. 514 $self->log(error => "Error: '$prefix' is not a valid output format") 515 if !exists $self->valid_output_format->{$prefix}; 516 } 517 518 $self -> log(debug => $self -> dot_input); 519 520 # Warning: Do not use $im_format in this 'if', because it has a default value. 521 522 if ($im_output_file) 523 { 524 return $self -> run_map($driver, $output_file, $format, $timeout, $im_output_file, $im_format); 525 } 526 else 527 { 528 return $self -> run_mapless($driver, $output_file, $format, $timeout); 529 } 530 531} # End of run. 532 533# ----------------------------------------------- 534 535sub run_map 536{ 537 my($self, $driver, $output_file, $format, $timeout, $im_output_file, $im_format) = @_; 538 $self -> log(debug => "Driver: $driver. Output file: $output_file. Format: $format. IM output file: $im_output_file. IM format: $im_format. Timeout: $timeout second(s)"); 539 # The EXLOCK option is for BSD-based systems. 540 my($temp_dir) = File::Temp -> newdir('temp.XXXX', CLEANUP => 1, EXLOCK => 0, TMPDIR => 1); 541 my($temp_file) = File::Spec -> catfile($temp_dir, 'temp.gv'); 542 open(my $fh, '> :raw', $temp_file) || die "Can't open(> $temp_file): $!"; 543 print $fh $self -> dot_input; 544 close $fh; 545 my(@args) = ("-T$im_format", "-o$im_output_file", "-T$format", "-o$output_file", $temp_file); 546 system($driver, @args); 547 return $self; 548} # End of run_map. 549 550# ----------------------------------------------- 551 552sub run_mapless 553{ 554 my($self, $driver, $output_file, $format, $timeout) = @_; 555 $self -> log(debug => "Driver: $driver. Output file: $output_file. Format: $format. Timeout: $timeout second(s)"); 556 # Usage of utf8 here relies on ISO-8859-1 matching Unicode for low chars. 557 # It saves me the effort of determining if the input contains Unicode. 558 run3 559 [$driver, "-T$format"], 560 \$self -> dot_input, 561 \my $stdout, 562 \my $stderr, 563 { 564 binmode_stdin => ':utf8', 565 binmode_stdout => ':raw', 566 binmode_stderr => ':raw', 567 }; 568 die $stderr if ($stderr); 569 $self -> dot_output($stdout); 570 if ($output_file) 571 { 572 open(my $fh, '> :raw', $output_file) || die "Can't open(> $output_file): $!"; 573 print $fh $stdout; 574 close $fh; 575 $self -> log(debug => "Wrote $output_file. Size: " . length($stdout) . ' bytes'); 576 } 577 return $self; 578} # End of run_mapless. 579 580sub stringify_attributes { 581 my($self, $context, $option) = @_; 582 # Add double-quotes around anything (e.g. labels) which does not look like HTML. 583 my @pairs; 584 for my $key (sort keys %$option) { 585 my $text = _dor($$option{$key}, ''); 586 $text =~ s/^\s+(<)/$1/; 587 $text =~ s/(>)\s+$/$1/; 588 $text = qq|"$text"| if $text !~ /^<.+>$/s; 589 push @pairs, qq|$key=$text|; 590 } 591 return join(' ', @pairs) . "\n" if $context eq 'subgraph'; 592 return join(' ', $context, '[', @pairs, ']') . "\n" if @pairs; 593 $context =~ /^(?:edge|graph|node)/ ? '' : "$context\n"; 594} 595 596sub validate_params 597{ 598 my($self, $context, $attributes) = @_; 599 my $valid = $VALID_ATTRIBUTES->{$context}; 600 my @invalid = grep !exists $valid->{$_}, keys %$attributes; 601 $self->log(error => "Error: '$_' is not a valid attribute in the '$context' context") for sort @invalid; 602 return $self; 603 604} # End of validate_params. 605 606sub from_graph { 607 my ($self, $g) = @_; 608 die "from_graph: '$g' not a Graph" if !$g->isa('Graph'); 609 my %g_attrs = %{ $g->get_graph_attribute('graphviz') || {} }; 610 my $global = { directed => $g->is_directed, %{delete $g_attrs{global}||{}} }; 611 my $groups = delete $g_attrs{groups} || []; 612 if (ref $self) { 613 for (sort keys %g_attrs) { 614 my $method = "default_$_"; 615 $self->$method(%{ $g_attrs{$_} }); 616 } 617 } else { 618 $self = $self->new(global => $global, %g_attrs); 619 } 620 for my $group (@$groups) { 621 $self->push_subgraph(%{ $group->{attributes} || {} }); 622 $self->add_node(name => $_) for @{ $group->{nodes} || [] }; 623 $self->pop_subgraph; 624 } 625 my ($is_multiv, $is_multie) = map $g->$_, qw(multivertexed multiedged); 626 my ($v_attr, $e_attr) = qw(get_vertex_attribute get_edge_attribute); 627 $v_attr .= '_by_id' if $is_multiv; 628 $e_attr .= '_by_id' if $is_multie; 629 my %first2edges; 630 for my $e (sort {$a->[0] cmp $b->[0] || $a->[1] cmp $b->[1]} $g->unique_edges) { 631 my @edges = $is_multie 632 ? map $g->$e_attr(@$e, $_, 'graphviz') || {}, sort $g->get_multiedge_ids(@$e) 633 : $g->$e_attr(@$e, 'graphviz')||{}; 634 push @{ $first2edges{$e->[0]} }, map [ from => $e->[0], to => $e->[1], %$_ ], @edges; 635 } 636 for my $v (sort $g->unique_vertices) { 637 my @vargs = $v; 638 if ($is_multiv) { 639 my ($found_id) = grep $g->has_vertex_attribute_by_id($v, $_, 'graphviz'), sort $g->get_multivertex_ids($v); 640 @vargs = defined $found_id ? (@vargs, $found_id) : (); 641 } 642 my $attrs = @vargs ? $g->$v_attr(@vargs, 'graphviz') || {} : {}; 643 $self->add_node(name => $v, %$attrs) if keys %$attrs or $g->is_isolated_vertex($v); 644 $self->add_edge(@$_) for @{ $first2edges{$v} }; 645 } 646 $self; 647} 648 649# ----------------------------------------------- 650 6511; 652 653=pod 654 655=head1 NAME 656 657GraphViz2 - A wrapper for AT&T's Graphviz 658 659=head1 Synopsis 660 661=head2 Sample output 662 663See L<https://graphviz-perl.github.io/>. 664 665=head2 Perl code 666 667=head3 Typical Usage 668 669 use strict; 670 use warnings; 671 use File::Spec; 672 use GraphViz2; 673 674 use Log::Handler; 675 my $logger = Log::Handler->new; 676 $logger->add(screen => { 677 maxlevel => 'debug', message_layout => '%m', minlevel => 'error' 678 }); 679 680 my $graph = GraphViz2->new( 681 edge => {color => 'grey'}, 682 global => {directed => 1}, 683 graph => {label => 'Adult', rankdir => 'TB'}, 684 logger => $logger, 685 node => {shape => 'oval'}, 686 ); 687 688 $graph->add_node(name => 'Carnegie', shape => 'circle'); 689 $graph->add_node(name => 'Murrumbeena', shape => 'box', color => 'green'); 690 $graph->add_node(name => 'Oakleigh', color => 'blue'); 691 $graph->add_edge(from => 'Murrumbeena', to => 'Carnegie', arrowsize => 2); 692 $graph->add_edge(from => 'Murrumbeena', to => 'Oakleigh', color => 'brown'); 693 694 $graph->push_subgraph( 695 name => 'cluster_1', 696 graph => {label => 'Child'}, 697 node => {color => 'magenta', shape => 'diamond'}, 698 ); 699 $graph->add_node(name => 'Chadstone', shape => 'hexagon'); 700 $graph->add_node(name => 'Waverley', color => 'orange'); 701 $graph->add_edge(from => 'Chadstone', to => 'Waverley'); 702 $graph->pop_subgraph; 703 704 $graph->default_node(color => 'cyan'); 705 706 $graph->add_node(name => 'Malvern'); 707 $graph->add_node(name => 'Prahran', shape => 'trapezium'); 708 $graph->add_edge(from => 'Malvern', to => 'Prahran'); 709 $graph->add_edge(from => 'Malvern', to => 'Murrumbeena'); 710 711 my $format = shift || 'svg'; 712 my $output_file = shift || File::Spec->catfile('html', "sub.graph.$format"); 713 $graph->run(format => $format, output_file => $output_file); 714 715=head1 Description 716 717=head2 Overview 718 719This module provides a Perl interface to the amazing L<Graphviz|http://www.graphviz.org/>, an open source graph visualization tool from AT&T. 720 721It is called GraphViz2 so that pre-existing code using (the Perl module) GraphViz continues to work. 722 723To avoid confusion, when I use L<GraphViz2> (note the capital V), I'm referring to this Perl module, and 724when I use L<Graphviz|http://www.graphviz.org/> (lower-case v) I'm referring to the underlying tool (which is in fact a set of programs). 725 726Version 1.00 of L<GraphViz2> is a complete re-write, by Ron Savage, of GraphViz V 2, which was written by Leon Brocard. The point of the re-write 727is to provide access to all the latest options available to users of L<Graphviz|http://www.graphviz.org/>. 728 729GraphViz2 V 1 is not backwards compatible with GraphViz V 2, despite the considerable similarity. It was not possible to maintain compatibility 730while extending support to all the latest features of L<Graphviz|http://www.graphviz.org/>. 731 732To ensure L<GraphViz2> is a light-weight module, L<Moo> has been used to provide getters and setters, 733rather than L<Moose>. 734 735As of V 2.43, C<GraphViz2> supports image maps, both client and server side. 736 737See L</Image Maps> below. 738 739=head2 What is a Graph? 740 741An undirected graph is a collection of nodes optionally linked together with edges. 742 743A directed graph is the same, except that the edges have a direction, normally indicated by an arrow head. 744 745A quick inspection of L<Graphviz|http://www.graphviz.org/>'s L<gallery|http://www.graphviz.org/gallery/> will show better than words 746just how good L<Graphviz|http://www.graphviz.org/> is, and will reinforce the point that humans are very visual creatures. 747 748=head1 Installation 749 750Of course you need to install AT&T's Graphviz before using this module. 751See L<http://www.graphviz.org/download/>. 752 753=head1 Constructor and Initialization 754 755=head2 Calling new() 756 757C<new()> is called as C<< my($obj) = GraphViz2 -> new(k1 => v1, k2 => v2, ...) >>. 758 759It returns a new object of type C<GraphViz2>. 760 761Key-value pairs accepted in the parameter list: 762 763=head3 edge => $hashref 764 765The I<edge> key points to a hashref which is used to set default attributes for edges. 766 767Hence, allowable keys and values within that hashref are anything supported by L<Graphviz|http://www.graphviz.org/>. 768 769The default is {}. 770 771This key is optional. 772 773=head3 global => $hashref 774 775The I<global> key points to a hashref which is used to set attributes for the output stream. 776 777This key is optional. 778 779Valid keys within this hashref are: 780 781=head4 combine_node_and_port 782 783New in 2.58. It defaults to true, but in due course (currently planned 784May 2021) it will default to false. When true, C<add_node> and C<add_edge> 785will escape only some characters in the label and names, and in particular 786the "from" and "to" parameters on edges will combine the node name 787and port in one string, with a C<:> in the middle (except for special 788treatment of double-colons). 789 790When the option is false, any name may be given to nodes, and edges can 791be created between them. To specify ports, give the additional parameter 792of C<tailport> or C<headport>. To specify a compass point in addition, 793give array-refs with two values for these parameters. Also, C<add_node>'s 794treatment of labels is more DWIM, with C<{> etc being transparently 795quoted. 796 797=head4 directed => $Boolean 798 799This option affects the content of the output stream. 800 801directed => 1 outputs 'digraph name {...}', while directed => 0 outputs 'graph name {...}'. 802 803At the Perl level, directed graphs have edges with arrow heads, such as '->', while undirected graphs have 804unadorned edges, such as '--'. 805 806The default is 0. 807 808This key is optional. 809 810=head4 driver => $program_name 811 812This option specifies which external program to run to process the output stream. 813 814The default is to use L<File::Which>'s which() method to find the 'dot' program. 815 816This key is optional. 817 818=head4 format => $string 819 820This option specifies what type of output file to create. 821 822The default is 'svg'. 823 824Output formats of the form 'png:gd' etc are also supported, but only the component before 825the first ':' is validated by L<GraphViz2>. 826 827This key is optional. 828 829=head4 label => $string 830 831This option specifies what an edge looks like: '->' for directed graphs and '--' for undirected graphs. 832 833You wouldn't normally need to use this option. 834 835The default is '->' if directed is 1, and '--' if directed is 0. 836 837This key is optional. 838 839=head4 name => $string 840 841This option affects the content of the output stream. 842 843name => 'G666' outputs 'digraph G666 {...}'. 844 845The default is 'Perl' :-). 846 847This key is optional. 848 849=head4 record_shape => /^(?:M?record)$/ 850 851This option affects the shape of records. The value must be 'Mrecord' or 'record'. 852 853Mrecords have nice, rounded corners, whereas plain old records have square corners. 854 855The default is 'Mrecord'. 856 857See L<Record shapes|http://www.graphviz.org/doc/info/shapes.html#record> for details. 858 859=head4 strict => $Boolean 860 861This option affects the content of the output stream. 862 863strict => 1 outputs 'strict digraph name {...}', while strict => 0 outputs 'digraph name {...}'. 864 865The default is 0. 866 867This key is optional. 868 869=head4 timeout => $integer 870 871This option specifies how long to wait for the external program before exiting with an error. 872 873The default is 10 (seconds). 874 875This key is optional. 876 877=head3 graph => $hashref 878 879The I<graph> key points to a hashref which is used to set default attributes for graphs. 880 881Hence, allowable keys and values within that hashref are anything supported by L<Graphviz|http://www.graphviz.org/>. 882 883The default is {}. 884 885This key is optional. 886 887=head3 logger => $logger_object 888 889Provides a logger object so $logger_object -> $level($message) can be called at certain times. Any object with C<debug> and C<error> methods 890will do, since these are the only levels emitted by this module. 891One option is a L<Log::Handler> object. 892 893Retrieve and update the value with the logger() method. 894 895By default (i.e. without a logger object), L<GraphViz2> prints warning and debug messages to STDOUT, 896and dies upon errors. 897 898However, by supplying a log object, you can capture these events. 899 900Not only that, you can change the behaviour of your log object at any time, by calling 901L</logger($logger_object)>. 902 903See also the verbose option, which can interact with the logger option. 904 905This key is optional. 906 907=head3 node => $hashref 908 909The I<node> key points to a hashref which is used to set default attributes for nodes. 910 911Hence, allowable keys and values within that hashref are anything supported by L<Graphviz|http://www.graphviz.org/>. 912 913The default is {}. 914 915This key is optional. 916 917=head3 subgraph => $hashref 918 919The I<subgraph> key points to a hashref which is used to set attributes for all subgraphs, unless overridden 920for specific subgraphs in a call of the form push_subgraph(subgraph => {$attribute => $string}). 921 922Valid keys within this hashref are: 923 924=over 4 925 926=item * rank => $string 927 928This option affects the content of all subgraphs, unless overridden later. 929 930A typical usage would be new(subgraph => {rank => 'same'}) so that all nodes mentioned within each subgraph 931are constrained to be horizontally aligned. 932 933See scripts/rank.sub.graph.1.pl for sample code. 934 935Possible values for $string are: max, min, same, sink and source. 936 937See the L<Graphviz 'rank' docs|http://www.graphviz.org/doc/info/attrs.html#d:rank> for details. 938 939=back 940 941The default is {}. 942 943This key is optional. 944 945=head3 verbose => $Boolean 946 947Provides a way to control the amount of output when a logger is not specified. 948 949Setting verbose to 0 means print nothing. 950 951Setting verbose to 1 means print the log level and the message to STDOUT, when a logger is not specified. 952 953Retrieve and update the value with the verbose() method. 954 955The default is 0. 956 957See also the logger option, which can interact with the verbose option. 958 959This key is optional. 960 961=head2 Validating Parameters 962 963The secondary keys (under the primary keys 'edge|graph|node') are checked against lists of valid attributes (stored at the end of this 964module, after the __DATA__ token, and made available using L<Data::Section::Simple>). 965 966This mechanism has the effect of hard-coding L<Graphviz|http://www.graphviz.org/> options in the source code of L<GraphViz2>. 967 968Nevertheless, the implementation of these lists is handled differently from the way it was done in V 2. 969 970V 2 ships with a set of scripts, scripts/extract.*.pl, which retrieve pages from the 971L<Graphviz|http://www.graphviz.org/> web site and extract the current lists of valid attributes. 972 973These are then copied manually into the source code of L<GraphViz2>, meaning any time those lists change on the 974L<Graphviz|http://www.graphviz.org/> web site, it's a trivial matter to update the lists stored within this module. 975 976See L<GraphViz2/Scripts Shipped with this Module>. 977 978=head2 Alternate constructor and object method 979 980=head3 from_graph 981 982 my $gv = GraphViz2->from_graph($g); 983 984 # alternatively 985 my $gv = GraphViz2->new; 986 $gv->from_graph($g); 987 988 # for handy debugging of arbitrary graphs: 989 GraphViz2->from_graph($g)->run(format => 'svg', output_file => 'output.svg'); 990 991Takes a L<Graph> object. This module will figure out various defaults from it, 992including whether it is directed or not. 993 994Will also use any node-, edge-, and graph-level attributes named 995C<graphviz> as a hash-ref for setting attributes on the corresponding 996entities in the constructed GraphViz2 object. These will override the 997figured-out defaults referred to above. 998 999For a C<multivertexed> graph, will only create one node per vertex, 1000but will search all the multi-IDs for a C<graphviz> attribute, taking 1001the first one it finds (sorted alphabetically). 1002 1003For a C<multiedged> graph, will create one edge per multi-edge. 1004 1005Will only set the C<global> attribute if called as a constructor. This 1006will be dropped from any passed-in graph-level C<graphviz> attribute 1007when called as an object method. 1008 1009A special graph-level attribute (under C<graphviz>) called C<groups> will 1010be given further special meaning: it is an array-ref of hash-refs. Those 1011will have keys, used to create subgraphs: 1012 1013=over 1014 1015=item * attributes 1016 1017Hash-ref of arguments to supply to C<push_subgraph> for this subgraph. 1018 1019=item * nodes 1020 1021Array-ref of node names to put in this subgraph. 1022 1023=back 1024 1025Example: 1026 1027 $g->set_graph_attribute(graphviz => { 1028 groups => [ 1029 {nodes => [1, 2], attributes => {subgraph=>{rank => 'same'}}}, 1030 ], 1031 # other graph-level attributes... 1032 }); 1033 1034=head1 Attribute Scope 1035 1036=head2 Graph Scope 1037 1038The graphical elements graph, node and edge, have attributes. Attributes can be set when calling new(). 1039 1040Within new(), the defaults are graph => {}, node => {}, and edge => {}. 1041 1042You override these with code such as new(edge => {color => 'red'}). 1043 1044These attributes are pushed onto a scope stack during new()'s processing of its parameters, and they apply thereafter until changed. 1045They are the 'current' attributes. They live at scope level 0 (zero). 1046 1047You change the 'current' attributes by calling any of the methods default_edge(%hash), default_graph(%hash) and default_node(%hash). 1048 1049See scripts/trivial.pl (L<GraphViz2/Scripts Shipped with this Module>) for an example. 1050 1051=head2 Subgraph Scope 1052 1053When you wish to create a subgraph, you call push_subgraph(%hash). The word push emphasises that you are moving into a new scope, 1054and that the default attributes for the new scope are pushed onto the scope stack. 1055 1056This module, as with L<Graphviz|http://www.graphviz.org/>, defaults to using inheritance of attributes. 1057 1058That means the parent's 'current' attributes are combined with the parameters to push_subgraph(%hash) to generate a new set of 'current' 1059attributes for each of the graphical elements, graph, node and edge. 1060 1061After a single call to push_subgraph(%hash), these 'current' attributes will live a level 1 in the scope stack. 1062 1063See scripts/sub.graph.pl (L<GraphViz2/Scripts Shipped with this Module>) for an example. 1064 1065Another call to push_subgraph(%hash), I<without> an intervening call to pop_subgraph(), will repeat the process, leaving you with 1066a set of attributes at level 2 in the scope stack. 1067 1068Both L<GraphViz2> and L<Graphviz|http://www.graphviz.org/> handle this situation properly. 1069 1070See scripts/sub.sub.graph.pl (L<GraphViz2/Scripts Shipped with this Module>) for an example. 1071 1072At the moment, due to design defects (IMHO) in the underlying L<Graphviz|http://www.graphviz.org/> logic, there are some tiny problems with this: 1073 1074=over 4 1075 1076=item * A global frame 1077 1078I can't see how to make the graph as a whole (at level 0 in the scope stack) have a frame. 1079 1080=item * Frame color 1081 1082When you specify graph => {color => 'red'} at the parent level, the subgraph has a red frame. 1083 1084I think a subgraph should control its own frame. 1085 1086=item * Parent and child frames 1087 1088When you specify graph => {color => 'red'} at the subgraph level, both that subgraph and it children have red frames. 1089 1090This contradicts what happens at the global level, in that specifying color there does not given the whole graph a frame. 1091 1092=item * Frame visibility 1093 1094A subgraph whose name starts with 'cluster' is currently forced to have a frame, unless you rig it by specifying a 1095color the same as the background. 1096 1097For sample code, see scripts/sub.graph.frames.pl. 1098 1099=back 1100 1101Also, check L<the pencolor docs|http://www.graphviz.org/doc/info/attrs.html#d:pencolor> for how the color of the frame is 1102chosen by cascading thru a set of options. 1103 1104I've posted an email to the L<Graphviz|http://www.graphviz.org/> mailing list suggesting a new option, framecolor, so deal with 1105this issue, including a special color of 'invisible'. 1106 1107=head1 Image Maps 1108 1109As of V 2.43, C<GraphViz2> supports image maps, both client and server side. 1110For web use, note that these options also take effect when generating SVGs, 1111for a much lighter-weight solution to hyperlinking graph nodes and edges. 1112 1113=head2 The Default URL 1114 1115See the L<Graphviz docs for 'cmapx'|http://www.graphviz.org/doc/info/output.html#d:cmapx>. 1116 1117Their sample code has a dot file - x.gv - containing this line: 1118 1119 URL="http://www.research.att.com/base.html"; 1120 1121The way you set such a url in C<GraphViz2> is via a new parameter to C<new()>. This parameter is called C<im_meta> 1122and it takes a hashref as a value. Currently the only key used within that hashref is the case-sensitive C<URL>. 1123 1124Thus you must do this to set a URL: 1125 1126 my($graph) = GraphViz2 -> new 1127 ( 1128 ... 1129 im_meta => 1130 { 1131 URL => 'http://savage.net.au/maps/demo.3.1.html', # Note: URL must be in caps. 1132 }, 1133 ); 1134 1135See maps/demo.3.pl and maps/demo.4.pl for sample code. 1136 1137=head2 Typical Code 1138 1139Normally you would call C<run()> as: 1140 1141 $graph -> run 1142 ( 1143 format => $format, 1144 output_file => $output_file 1145 ); 1146 1147That line was copied from scripts/cluster.pl. 1148 1149To trigger image map processing, you must include 2 new parameters: 1150 1151 $graph -> run 1152 ( 1153 format => $format, 1154 output_file => $output_file, 1155 im_format => $im_format, 1156 im_output_file => $im_output_file 1157 ); 1158 1159That line was copied from maps/demo.3.pl, and there is an identical line in maps/demo.4.pl. 1160 1161=head2 The New Parameters to run() 1162 1163=over 4 1164 1165=item * im_format => $str 1166 1167Expected values: 'imap' (server-side) and 'cmapx' (client-side). 1168 1169Default value: 'cmapx'. 1170 1171=item * im_output_file => $file_name 1172 1173The name of the output map file. 1174 1175Default: ''. 1176 1177If you do not set it to anything, the new image maps code is ignored. 1178 1179=back 1180 1181=head2 Sample Code 1182 1183Various demos are shipped in the new maps/ directory: 1184 1185Each demo, when FTPed to your web server displays some text with an image in the middle. In each case 1186you can click on the upper oval to jump to one page, or click on the lower oval to jump to a different 1187page, or click anywhere else in the image to jump to a third page. 1188 1189=over 4 1190 1191=item * demo.1.* 1192 1193This set demonstrates a server-side image map but does not use C<GraphViz2>. 1194 1195You have to run demo.1.sh which generates demo.1.map, and then you FTP the whole dir maps/ to your web server. 1196 1197URL: your.domain.name/maps/demo.1.html. 1198 1199=item * demo.2.* 1200 1201This set demonstrates a client-side image map but does not use C<GraphViz2>. 1202 1203You have to run demo.2.sh which generates demo.2.map, and then you manually copy demo.2.map into demo.2.html, 1204replacing any version of the map already present. After that you FTP the whole dir maps/ to your web server. 1205 1206URL: your.domain.name/maps/demo.2.html. 1207 1208=item * demo.3.* 1209 1210This set demonstrates a server-side image map using C<GraphViz2> via demo.3.pl. 1211 1212Note line 54 of demo.3.pl which sets the default C<im_format> to 'imap'. 1213 1214URL: your.domain.name/maps/demo.3.html. 1215 1216=item * demo.4.* 1217 1218This set demonstrates a client-side image map using C<GraphViz2> via demo.4.pl. 1219 1220As with demo.2.* there is some manually editing to be done. 1221 1222Note line 54 of demo.4.pl which sets the default C<im_format> to 'cmapx'. This is the only important 1223difference between this demo and the previous one. 1224 1225There are other minor differences, in that one uses 'svg' and the other 'png'. And of course the urls 1226of the web pages embedded in the code and in those web pages differs, just to demonstate that the maps 1227do indeed lead to different pages. 1228 1229URL: your.domain.name/maps/demo.4.html. 1230 1231=back 1232 1233=head1 Methods 1234 1235=head2 add_edge(from => $from_node_name, to => $to_node_name, [label => $label, %hash]) 1236 1237Adds an edge to the graph. 1238 1239Returns $self to allow method chaining. 1240 1241Here, [] indicate optional parameters. 1242 1243Add a edge from 1 node to another. 1244 1245$from_node_name and $to_node_name default to ''. 1246 1247%hash is any edge attributes accepted as 1248L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1249These are validated in exactly the same way as the edge parameters in the calls to 1250default_edge(%hash), new(edge => {}) and push_subgraph(edge => {}). 1251 1252To make the edge start or finish on a port, see L</combine_node_and_port>. 1253 1254=head2 add_node(name => $node_name, [%hash]) 1255 1256 my $graph = GraphViz2->new(global => {combine_node_and_port => 0}); 1257 $graph->add_node(name => 'struct3', shape => 'record', label => [ 1258 { text => "hello\\nworld" }, 1259 [ 1260 { text => 'b' }, 1261 [ 1262 { text => 'c{}' }, # reproduced literally 1263 { text => 'd', port => 'here' }, 1264 { text => 'e' }, 1265 ] 1266 { text => 'f' }, 1267 ], 1268 { text => 'g' }, 1269 { text => 'h' }, 1270 ]); 1271 1272Adds a node to the graph. 1273 1274Returns $self to allow method chaining. 1275 1276If you want to embed newlines or double-quotes in node names or labels, see scripts/quote.pl in L<GraphViz2/Scripts Shipped with this Module>. 1277 1278If you want anonymous nodes, see scripts/anonymous.pl in L<GraphViz2/Scripts Shipped with this Module>. 1279 1280Here, [] indicates an optional parameter. 1281 1282%hash is any node attributes accepted as 1283L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1284These are validated in exactly the same way as the node parameters in the calls to 1285default_node(%hash), new(node => {}) and push_subgraph(node => {}). 1286 1287The attribute name 'label' may point to a string or an arrayref. 1288 1289=head3 If it is a string... 1290 1291The string is the label. If the C<shape> is a record, you can give any 1292text and it will be passed for interpretation by Graphviz. This means 1293you will need to quote E<lt> and E<gt> (port specifiers), C<|> (cell 1294separator) and C<{> C<}> (structure depth) with C<\> to make them appear 1295literally. 1296 1297For records, the cells start horizontal. Each additional layer of 1298structure will switch the orientation between horizontal and vertical. 1299 1300=head3 If it is an arrayref of strings... 1301 1302=over 4 1303 1304=item * The node is forced to be a record 1305 1306The actual shape, 'record' or 'Mrecord', is set globally, with: 1307 1308 my($graph) = GraphViz2 -> new 1309 ( 1310 global => {record_shape => 'record'}, # Override default 'Mrecord'. 1311 ... 1312 ); 1313 1314Or set locally with: 1315 1316 $graph -> add_node(name => 'Three', label => ['Good', 'Bad'], shape => 'record'); 1317 1318=item * Each element in the array defines a field in the record 1319 1320These fields are combined into a single node 1321 1322=item * Each element is treated as a label 1323 1324=item * Each label is given a port name (1 .. N) of the form "port$port_count" 1325 1326=item * Judicious use of '{' and '}' in the label can make this record appear horizontally or vertically, and even nested 1327 1328=back 1329 1330=head3 If it is an arrayref of hashrefs... 1331 1332=over 4 1333 1334=item * The node is forced to be a record 1335 1336The actual shape, 'record' or 'Mrecord', can be set globally or locally, as explained just above. 1337 1338=item * Each element in the array defines a field in the record 1339 1340=item * Each element is treated as a hashref with keys 'text' and 'port' 1341 1342The 'port' key is optional. 1343 1344=item * The value of the 'text' key is the label 1345 1346=item * The value of the 'port' key is the port 1347 1348=item * Judicious use of '{' and '}' in the label can make this record appear horizontally or vertically, and even nested 1349 1350=back 1351 1352See scripts/html.labels.*.pl and scripts/record.*.pl for sample code. 1353 1354See also L</How labels interact with ports>. 1355 1356For more details on this complex topic, see L<Records|http://www.graphviz.org/doc/info/shapes.html#record> and L<Ports|http://www.graphviz.org/doc/info/attrs.html#k:portPos>. 1357 1358=head2 default_edge(%hash) 1359 1360Sets defaults attributes for edges added subsequently. 1361 1362Returns $self to allow method chaining. 1363 1364%hash is any edge attributes accepted as 1365L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1366These are validated in exactly the same way as the edge parameters in the calls to new(edge => {}) 1367and push_subgraph(edge => {}). 1368 1369=head2 default_graph(%hash) 1370 1371Sets defaults attributes for the graph. 1372 1373Returns $self to allow method chaining. 1374 1375%hash is any graph attributes accepted as 1376L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1377These are validated in exactly the same way as the graph parameter in the calls to new(graph => {}) 1378and push_subgraph(graph => {}). 1379 1380=head2 default_node(%hash) 1381 1382Sets defaults attributes for nodes added subsequently. 1383 1384Returns $self to allow method chaining. 1385 1386%hash is any node attributes accepted as 1387L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1388These are validated in exactly the same way as the node parameters in the calls to new(node => {}) 1389and push_subgraph(node => {}). 1390 1391=head2 default_subgraph(%hash) 1392 1393Sets defaults attributes for clusters and subgraphs. 1394 1395Returns $self to allow method chaining. 1396 1397%hash is any cluster or subgraph attribute accepted as 1398L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1399These are validated in exactly the same way as the subgraph parameter in the calls to 1400new(subgraph => {}) and push_subgraph(subgraph => {}). 1401 1402=head2 dot_input() 1403 1404Returns the output stream, formatted nicely, to be passed to the external program (e.g. dot). 1405 1406=head2 dot_output() 1407 1408Returns the output from calling the external program (e.g. dot). 1409 1410You I<must> call run() before calling dot_output(), since it is only during the call to run() that the output of the 1411external program is stored in the buffer controlled by dot_output(). 1412 1413This output is available even if run() does not write the output to a file. 1414 1415=head2 edge_hash() 1416 1417Returns, at the end of the run, a hashref keyed by node name, specifically the node at the arrowI<tail> end of 1418the hash, i.e. where the edge starts from. 1419 1420Use this to get a list of all nodes and the edges which leave those nodes, the corresponding destination 1421nodes, and the attributes of each edge. 1422 1423 my($node_hash) = $graph -> node_hash; 1424 my($edge_hash) = $graph -> edge_hash; 1425 1426 for my $from (sort keys %$node_hash) 1427 { 1428 my($attr) = $$node_hash{$from}{attributes}; 1429 my($s) = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); 1430 1431 print "Node: $from\n"; 1432 print "\tAttributes: $s\n"; 1433 1434 for my $to (sort keys %{$$edge_hash{$from} }) 1435 { 1436 for my $edge (@{$$edge_hash{$from}{$to} }) 1437 { 1438 $attr = $$edge{attributes}; 1439 $s = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); 1440 1441 print "\tEdge: $from$$edge{from_port} -> $to$$edge{to_port}\n"; 1442 print "\t\tAttributes: $s\n"; 1443 } 1444 } 1445 } 1446 1447If the caller adds the same edge two (or more) times, the attributes from each call are 1448I<not> coalesced (unlike L</node_hash()>), but rather the attributes from each call are stored separately 1449in an arrayref. 1450 1451A bit more formally then, $$edge_hash{$from_node}{$to_node} is an arrayref where each element describes 1452one edge, and which defaults to: 1453 1454 { 1455 attributes => {}, 1456 from_port => $from_port, 1457 to_port => $to_port, 1458 } 1459 1460If I<from_port> is not provided by the caller, it defaults to '' (the empty string). If it is provided, 1461it contains a leading ':'. Likewise for I<to_port>. 1462 1463See scripts/report.nodes.and.edges.pl (a version of scripts/html.labels.1.pl) for a complete example. 1464 1465=head2 log([$level, $message]) 1466 1467Logs the message at the given log level. 1468 1469Returns $self to allow method chaining. 1470 1471Here, [] indicate optional parameters. 1472 1473$level defaults to 'debug', and $message defaults to ''. 1474 1475If called with $level eq 'error', it dies with $message. 1476 1477=head2 logger($logger_object) 1478 1479Gets or sets the log object. 1480 1481Here, [] indicates an optional parameter. 1482 1483=head2 node_hash() 1484 1485Returns, at the end of the run, a hashref keyed by node name. Use this to get a list of all nodes 1486and their attributes. 1487 1488 my($node_hash) = $graph -> node_hash; 1489 1490 for my $name (sort keys %$node_hash) 1491 { 1492 my($attr) = $$node_hash{$name}{attributes}; 1493 my($s) = join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr); 1494 1495 print "Node: $name\n"; 1496 print "\tAttributes: $s\n"; 1497 } 1498 1499If the caller adds the same node two (or more) times, the attributes from each call are 1500I<coalesced> (unlike L</edge_hash()>), meaning all attributes from all calls are combined under the 1501I<attributes> sub-key. 1502 1503A bit more formally then, $$node_hash{$node_name} is a hashref where each element describes one node, and 1504which defaults to: 1505 1506 { 1507 attributes => {}, 1508 } 1509 1510See scripts/report.nodes.and.edges.pl (a version of scripts/html.labels.1.pl) for a complete example, 1511including usage of the corresponding L</edge_hash()> method. 1512 1513=head2 pop_subgraph() 1514 1515Pop off and discard the top element of the scope stack. 1516 1517Returns $self to allow method chaining. 1518 1519=head2 push_subgraph([name => $name, edge => {...}, graph => {...}, node => {...}, subgraph => {...}]) 1520 1521Sets up a new subgraph environment. 1522 1523Returns $self to allow method chaining. 1524 1525Here, [] indicate optional parameters. 1526 1527name => $name is the name to assign to the subgraph. Name defaults to ''. 1528 1529So, without $name, 'subgraph {' is written to the output stream. 1530 1531With $name, 'subgraph "$name" {' is written to the output stream. 1532 1533Note that subgraph names beginning with 'cluster' L<are special to Graphviz|http://www.graphviz.org/doc/info/attrs.html#d:clusterrank>. 1534 1535See scripts/rank.sub.graph.[1234].pl for the effect of various values for $name. 1536 1537edge => {...} is any edge attributes accepted as 1538L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1539These are validated in exactly the same way as the edge parameters in the calls to 1540default_edge(%hash), new(edge => {}) and push_subgraph(edge => {}). 1541 1542graph => {...} is any graph attributes accepted as 1543L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1544These are validated in exactly the same way as the graph parameters in the calls to 1545default_graph(%hash), new(graph => {}) and push_subgraph(graph => {}). 1546 1547node => {...} is any node attributes accepted as 1548L<Graphviz attributes|https://www.graphviz.org/doc/info/attrs.html>. 1549These are validated in exactly the same way as the node parameters in the calls to 1550default_node(%hash), new(node => {}) and push_subgraph(node => {}). 1551 1552subgraph => {..} is for setting attributes applicable to clusters and subgraphs. 1553 1554Currently the only subgraph attribute is C<rank>, but clusters have many attributes available. 1555 1556See the second column of the 1557L<Graphviz attribute docs|https://www.graphviz.org/doc/info/attrs.html> for details. 1558 1559A typical usage would be push_subgraph(subgraph => {rank => 'same'}) so that all nodes mentioned within the subgraph 1560are constrained to be horizontally aligned. 1561 1562See scripts/rank.sub.graph.[12].pl and scripts/sub.graph.frames.pl for sample code. 1563 1564=head2 valid_attributes() 1565 1566Returns a hashref of all attributes known to this module, keyed by type 1567to hashrefs to true values. 1568 1569Stored in this module, using L<Data::Section::Simple>. 1570 1571These attributes are used to validate attributes in many situations. 1572 1573You wouldn't normally need to use this method. 1574 1575See scripts/report.valid.attributes.pl. See L<GraphViz2/Scripts Shipped with this Module>. 1576 1577=head2 run([driver => $exe, format => $string, timeout => $integer, output_file => $output_file]) 1578 1579Runs the given program to process the output stream. 1580 1581Returns $self to allow method chaining. 1582 1583Here, [] indicate optional parameters. 1584 1585$driver is the name of the external program to run. 1586 1587It defaults to the value supplied in the call to new(global => {driver => '...'}), which in turn defaults 1588to L<File::Which>'s which('dot') return value. 1589 1590$format is the type of output file to write. 1591 1592It defaults to the value supplied in the call to new(global => {format => '...'}), which in turn defaults 1593to 'svg'. 1594 1595$timeout is the time in seconds to wait while the external program runs, before dieing with an error. 1596 1597It defaults to the value supplied in the call to new(global => {timeout => '...'}), which in turn defaults 1598to 10. 1599 1600$output_file is the name of the file into which the output from the external program is written. 1601 1602There is no default value for $output_file. If a value is not supplied for $output_file, the only way 1603to recover the output of the external program is to call dot_output(). 1604 1605This method performs a series of tasks: 1606 1607=over 4 1608 1609=item * Run the chosen external program on the L</dot_input> 1610 1611=item * Capture STDOUT and STDERR from that program 1612 1613=item * Die if STDERR contains anything 1614 1615=item * Copies STDOUT to the buffer controlled by the dot_output() method 1616 1617=item * Write the captured contents of STDOUT to $output_file, if $output_file has a value 1618 1619=back 1620 1621=head2 stringify_attributes($context, $option) 1622 1623Returns a string suitable to writing to the output stream. 1624 1625$context is one of 'edge', 'graph', 'node', or a special string. See the code for details. 1626 1627You wouldn't normally need to use this method. 1628 1629=head2 validate_params($context, \%attributes) 1630 1631Validate the given attributes within the given context. 1632 1633Also, if $context is 'subgraph', attributes are allowed to be in the 'cluster' context. 1634 1635Returns $self to allow method chaining. 1636 1637$context is one of 'edge', 'global', 'graph', or 'node'. 1638 1639You wouldn't normally need to use this method. 1640 1641=head2 verbose([$integer]) 1642 1643Gets or sets the verbosity level, for when a logging object is not used. 1644 1645Here, [] indicates an optional parameter. 1646 1647=head1 MISC 1648 1649=head2 Graphviz version supported 1650 1651GraphViz2 targets V 2.34.0 of L<Graphviz|http://www.graphviz.org/>. 1652 1653This affects the list of available attributes per graph item (node, edge, cluster, etc) available. 1654 1655See the second column of the 1656L<Graphviz attribute docs|https://www.graphviz.org/doc/info/attrs.html> for details. 1657 1658=head2 Supported file formats 1659 1660Parses the output of C<dot -T?>, so depends on local installation. 1661 1662=head2 Special characters in node names and labels 1663 1664L<GraphViz2> escapes these 2 characters in those contexts: []. 1665 1666Escaping the 2 chars [] started with V 2.10. Previously, all of []{} were escaped, but {} are used in records 1667to control the orientation of fields, so they should not have been escaped in the first place. 1668 1669It would be nice to also escape | and <, but these characters are used in specifying fields and ports in records. 1670 1671See the next couple of points for details. 1672 1673=head2 Ports 1674 1675Ports are what L<Graphviz|http://www.graphviz.org/> calls those places on the outline of a node where edges 1676leave and terminate. 1677 1678The L<Graphviz|http://www.graphviz.org/> syntax for ports is a bit unusual: 1679 1680=over 4 1681 1682=item * This works: "node_name":port5 1683 1684=item * This doesn't: "node_name:port5" 1685 1686=back 1687 1688Let me repeat - that is Graphviz syntax, not GraphViz2 syntax. In Perl, you must do this: 1689 1690 $graph -> add_edge(from => 'struct1:f1', to => 'struct2:f0', color => 'blue'); 1691 1692You don't have to quote all node names in L<Graphviz|http://www.graphviz.org/>, but some, such as digits, must be quoted, so I've decided to quote them all. 1693 1694=head2 How labels interact with ports 1695 1696You can specify labels with ports in these ways: 1697 1698=over 4 1699 1700=item * As a string 1701 1702 $graph -> add_node(name => 'struct3', label => "hello\nworld |{ b |{c|<here> d|e}| f}| g | h"); 1703 1704Here, the string contains a port (<here>), field markers (|), and orientation markers ({}). 1705 1706Clearly, you must specify the field separator character '|' explicitly. In the next 2 cases, it is implicit. 1707 1708Then you use $graph -> add_edge(...) to refer to those ports, if desired: 1709 1710 $graph -> add_edge(from => 'struct1:f2', to => 'struct3:here', color => 'red'); 1711 1712The same label is specified in the next case. 1713 1714=item * As an arrayref of hashrefs 1715 1716From scripts/record.2.pl: 1717 1718 $graph -> add_node(name => 'struct3', label => 1719 [ 1720 { 1721 text => "hello\nworld", 1722 }, 1723 { 1724 text => '{b', 1725 }, 1726 { 1727 text => '{c', 1728 }, 1729 { 1730 port => '<here>', 1731 text => 'd', 1732 }, 1733 { 1734 text => 'e}', 1735 }, 1736 { 1737 text => 'f}', 1738 }, 1739 { 1740 text => 'g', 1741 }, 1742 { 1743 text => 'h', 1744 }, 1745 ]); 1746 1747Each hashref is a field, and hence you do not specify the field separator character '|'. 1748 1749Then you use $graph -> add_edge(...) to refer to those ports, if desired. Again, from scripts/record.2.pl: 1750 1751 $graph -> add_edge(from => 'struct1:f2', to => 'struct3:here', color => 'red'); 1752 1753The same label is specified in the previous case. 1754 1755=item * As an arrayref of strings 1756 1757From scripts/html.labels.1.pl: 1758 1759 $graph -> add_node(name => 'Oakleigh', shape => 'record', color => 'blue', 1760 label => ['West Oakleigh', 'East Oakleigh']); 1761 1762Here, again, you do not specify the field separator character '|'. 1763 1764What happens is that each string is taken to be the label of a field, and each field is given 1765an auto-generated port name of the form "<port$n>", where $n starts from 1. 1766 1767Here's how you refer to those ports, again from scripts/html.labels.1.pl: 1768 1769 $graph -> add_edge(from => 'Murrumbeena', to => 'Oakleigh:port2', 1770 color => 'green', label => '<Drive<br/>Run<br/>Sprint>'); 1771 1772=back 1773 1774See also the docs for the C<< add_node(name => $node_name, [%hash]) >> method. 1775 1776=head2 Attributes for clusters 1777 1778Just use subgraph => {...}, because the code (as of V 2.22) accepts attributes belonging to either clusters or subgraphs. 1779 1780An example attribute is C<pencolor>, which is used for clusters but not for subgraphs: 1781 1782 $graph->push_subgraph( 1783 graph => {label => 'Child the Second'}, 1784 name => 'cluster Second subgraph', 1785 node => {color => 'magenta', shape => 'diamond'}, 1786 subgraph => {pencolor => 'white'}, # White hides the cluster's frame. 1787 ); 1788 # other nodes or edges can be added within it... 1789 $graph->pop_subgraph; 1790 1791=head1 TODO 1792 1793=over 4 1794 1795=item * Handle edges such as 1 -> 2 -> {A B}, as seen in L<Graphviz|http://www.graphviz.org/>'s graphs/directed/switch.gv 1796 1797But how? 1798 1799=item * Validate parameters more carefully, e.g. to reject non-hashref arguments where appropriate 1800 1801Some method parameter lists take keys whose value must be a hashref. 1802 1803=back 1804 1805=head1 A Extremely Short List of Other Graphing Software 1806 1807L<Axis Maps|http://www.axismaps.com/>. 1808 1809L<Polygon Map Generation|http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/>. 1810Read more on that L<here|http://blogs.perl.org/users/max_maischein/2011/06/display-your-data---randompoissondisc.html>. 1811 1812L<Voronoi Applications|http://www.voronoi.com/wiki/index.php?title=Voronoi_Applications>. 1813 1814=head1 Thanks 1815 1816Many thanks are due to the people who chose to make L<Graphviz|http://www.graphviz.org/> Open Source. 1817 1818And thanks to L<Leon Brocard|http://search.cpan.org/~lbrocard/>, who wrote L<GraphViz>, and kindly gave me co-maint of the module. 1819 1820=head1 Version Numbers 1821 1822Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions. 1823 1824=head1 Repository 1825 1826L<https://github.com/ronsavage/GraphViz2.git> 1827 1828=head1 Author 1829 1830L<GraphViz2> was written by Ron Savage I<E<lt>ron@savage.net.auE<gt>> in 2011. 1831 1832Home page: L<http://savage.net.au/index.html>. 1833 1834=head1 Copyright 1835 1836Australian copyright (c) 2011, Ron Savage. 1837 1838 All Programs of mine are 'OSI Certified Open Source Software'; 1839 you can redistribute them and/or modify them under the terms of 1840 The Perl License, a copy of which is available at: 1841 http://dev.perl.org/licenses/ 1842 1843=cut 1844 1845__DATA__ 1846@@ arrow_modifier 1847l 1848o 1849r 1850 1851@@ arrow 1852box 1853crow 1854curve 1855diamond 1856dot 1857inv 1858none 1859normal 1860tee 1861vee 1862 1863@@ common_attribute 1864Damping => graph 1865K => graph, cluster 1866URL => edge, node, graph, cluster 1867area => node, cluster 1868arrowhead => edge 1869arrowsize => edge 1870arrowtail => edge 1871bb => graph 1872bgcolor => graph, cluster 1873center => graph 1874charset => graph 1875clusterrank => graph 1876color => edge, node, cluster 1877colorscheme => edge, node, cluster, graph 1878comment => edge, node, graph 1879compound => graph 1880concentrate => graph 1881constraint => edge 1882decorate => edge 1883defaultdist => graph 1884dim => graph 1885dimen => graph 1886dir => edge 1887diredgeconstraints => graph 1888distortion => node 1889dpi => graph 1890edgeURL => edge 1891edgehref => edge 1892edgetarget => edge 1893edgetooltip => edge 1894epsilon => graph 1895esep => graph 1896fillcolor => node, edge, cluster 1897fixedsize => node 1898fontcolor => edge, node, graph, cluster 1899fontname => edge, node, graph, cluster 1900fontnames => graph 1901fontpath => graph 1902fontsize => edge, node, graph, cluster 1903forcelabels => graph 1904gradientangle => node, cluster, graph 1905group => node 1906headURL => edge 1907head_lp => edge 1908headclip => edge 1909headhref => edge 1910headlabel => edge 1911headport => edge 1912headtarget => edge 1913headtooltip => edge 1914height => node 1915href => graph, cluster, node, edge 1916id => graph, cluster, node, edge 1917image => node 1918imagepath => graph 1919imagescale => node 1920inputscale => graph 1921label => edge, node, graph, cluster 1922labelURL => edge 1923label_scheme => graph 1924labelangle => edge 1925labeldistance => edge 1926labelfloat => edge 1927labelfontcolor => edge 1928labelfontname => edge 1929labelfontsize => edge 1930labelhref => edge 1931labeljust => graph, cluster 1932labelloc => node, graph, cluster 1933labeltarget => edge 1934labeltooltip => edge 1935landscape => graph 1936layer => edge, node, cluster 1937layerlistsep => graph 1938layers => graph 1939layerselect => graph 1940layersep => graph 1941layout => graph 1942len => edge 1943levels => graph 1944levelsgap => graph 1945lhead => edge 1946lheight => graph, cluster 1947lp => edge, graph, cluster 1948ltail => edge 1949lwidth => graph, cluster 1950margin => node, cluster, graph 1951maxiter => graph 1952mclimit => graph 1953mindist => graph 1954minlen => edge 1955mode => graph 1956model => graph 1957mosek => graph 1958nodesep => graph 1959nojustify => graph, cluster, node, edge 1960normalize => graph 1961nslimit => graph 1962ordering => graph, node 1963orientation => node 1964orientation => graph 1965outputorder => graph 1966overlap => graph 1967overlap_scaling => graph 1968overlap_shrink => graph 1969pack => graph 1970packmode => graph 1971pad => graph 1972page => graph 1973pagedir => graph 1974pencolor => cluster 1975penwidth => cluster, node, edge 1976peripheries => node, cluster 1977pin => node 1978pos => edge, node 1979quadtree => graph 1980quantum => graph 1981rank => subgraph 1982rankdir => graph 1983ranksep => graph 1984ratio => graph 1985rects => node 1986regular => node 1987remincross => graph 1988repulsiveforce => graph 1989resolution => graph 1990root => graph, node 1991rotate => graph 1992rotation => graph 1993samehead => edge 1994sametail => edge 1995samplepoints => node 1996scale => graph 1997searchsize => graph 1998sep => graph 1999shape => node 2000shapefile => node 2001showboxes => edge, node, graph 2002sides => node 2003size => graph 2004skew => node 2005smoothing => graph 2006sortv => graph, cluster, node 2007splines => graph 2008start => graph 2009style => edge, node, cluster, graph 2010stylesheet => graph 2011tailURL => edge 2012tail_lp => edge 2013tailclip => edge 2014tailhref => edge 2015taillabel => edge 2016tailport => edge 2017tailtarget => edge 2018tailtooltip => edge 2019target => edge, node, graph, cluster 2020tooltip => node, edge, cluster 2021truecolor => graph 2022vertices => node 2023viewport => graph 2024voro_margin => graph 2025weight => edge 2026width => node 2027xdotversion => graph 2028xlabel => edge, node 2029xlp => node, edge 2030z => node 2031 2032@@ global 2033combine_node_and_port 2034directed 2035driver 2036format 2037im_format 2038label 2039name 2040record_shape 2041strict 2042timeout 2043 2044@@ im_meta 2045URL 2046 2047@@ node 2048Mcircle 2049Mdiamond 2050Msquare 2051assembly 2052box 2053box3d 2054cds 2055circle 2056component 2057diamond 2058doublecircle 2059doubleoctagon 2060egg 2061ellipse 2062fivepoverhang 2063folder 2064hexagon 2065house 2066insulator 2067invhouse 2068invtrapezium 2069invtriangle 2070larrow 2071lpromoter 2072none 2073note 2074noverhang 2075octagon 2076oval 2077parallelogram 2078pentagon 2079plaintext 2080point 2081polygon 2082primersite 2083promoter 2084proteasesite 2085proteinstab 2086rarrow 2087rect 2088rectangle 2089restrictionsite 2090ribosite 2091rnastab 2092rpromoter 2093septagon 2094signature 2095square 2096star 2097tab 2098terminator 2099threepoverhang 2100trapezium 2101triangle 2102tripleoctagon 2103underline 2104utr 2105