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