GraphViz-2.26/0000755000175000017500000000000014400436444013126 5ustar osboxesosboxesGraphViz-2.26/META.json0000644000175000017500000000342714400436444014555 0ustar osboxesosboxes{ "abstract" : "Interface to AT&T's GraphViz. Deprecated. See GraphViz2", "author" : [ "Leon Brocard " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "GraphViz", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Carp" : "1.01", "File::Which" : "1.09", "Getopt::Long" : "2.34", "IO::Dir" : "1.04", "IO::File" : "1.1", "IPC::Run" : "0.6", "LWP::Simple" : "6", "Parse::RecDescent" : "1.965001", "Pod::Usage" : "1.16", "Time::HiRes" : "1.51", "XML::Twig" : "3.52", "XML::XPath" : "1.13" } }, "test" : { "requires" : { "Test::More" : "1.001002" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://rt.cpan.org/Public/Dist/Display.html?Name=GraphViz" }, "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "type" : "git", "url" : "https://github.com/ronsavage/GraphViz.git", "web" : "https://github.com/ronsavage/GraphViz" } }, "version" : "2.26", "x_serialization_backend" : "JSON::PP version 4.04" } GraphViz-2.26/t/0000755000175000017500000000000014400436444013371 5ustar osboxesosboxesGraphViz-2.26/t/simple.t0000755000175000017500000002405014400435020015040 0ustar osboxesosboxes#!/usr/bin/perl use lib '../lib', 'lib'; use strict; use warnings; use File::Which 'which'; use GraphViz; use Test::More; # ------------------------- if (! defined which('dot') ) { bail_out("Cannot find 'dot'. Please install Graphviz from http://www.graphviz.org/"); } my @lines = ; foreach my $lines ( split '-- test --', ( join "", @lines ) ) { my ( $test, $expect ) = split '-- expect --', $lines; next unless $test; $expect =~ s|^\n||mg; $expect =~ s|\n$||mg; $test =~ s|^\n||mg; $test =~ s|\n$||mg; my $g; eval $test; my $result = $g->_as_debug; $result =~ s|^\n||mg; $result =~ s|\n$||mg; is( $result, $expect ); } done_testing; __DATA__ -- test -- $g = GraphViz->new(); -- expect -- digraph test { ratio="fill"; } -- test -- $g = GraphViz->new(directed => 1) -- expect -- digraph test { ratio="fill"; } -- test -- $g = GraphViz->new(directed => 0) -- expect -- graph test { ratio="fill"; } -- test -- $g = GraphViz->new(rankdir => 'LR') -- expect -- digraph test { rankdir=LR; ratio="fill"; } -- test -- $g = GraphViz->new(); $g->add_node(label => 'London'); -- expect -- digraph test { ratio="fill"; node1 [label="London"]; } -- test -- $g = GraphViz->new(directed => 0); $g->add_node('London'); -- expect -- graph test { ratio="fill"; London [label="London"]; } -- test -- $g = GraphViz->new(); $g->add_node('London', label => 'Big smoke'); -- expect -- digraph test { ratio="fill"; London [label="Big smoke"]; } -- test -- $g = GraphViz->new(); $g->add_node('London', label => 'Big\nsmoke'); -- expect -- digraph test { ratio="fill"; London [label="Big\nsmoke"]; } -- test -- $g = GraphViz->new(); $g->add_node('London', label => 'Big smoke', color => 'red'); -- expect -- digraph test { ratio="fill"; London [color="red", label="Big smoke"]; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_node('Paris'); -- expect -- digraph test { ratio="fill"; London [label="London"]; Paris [label="Paris"]; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_edge('London' => 'London'); -- expect -- digraph test { ratio="fill"; London [label="London"]; London -> London; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_edge('London' => 'London', label => 'Foo'); -- expect -- digraph test { ratio="fill"; London [label="London"]; London -> London [label="Foo"]; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_edge('London' => 'London', color => 'red'); -- expect -- digraph test { ratio="fill"; London [label="London"]; London -> London [color="red"]; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_node('Paris'); $g->add_edge('London' => 'Paris'); -- expect -- digraph test { ratio="fill"; London [label="London"]; Paris [label="Paris"]; London -> Paris; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_node('Paris'); $g->add_edge('London' => 'Paris'); $g->add_edge('Paris' => 'London'); -- expect -- digraph test { ratio="fill"; London [label="London"]; Paris [label="Paris"]; London -> Paris; Paris -> London; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_node('Paris'); $g->add_edge('London' => 'London'); $g->add_edge('Paris' => 'Paris'); $g->add_edge('London' => 'Paris'); $g->add_edge('Paris' => 'London'); -- expect -- digraph test { ratio="fill"; London [label="London"]; Paris [label="Paris"]; London -> London; London -> Paris; Paris -> London; Paris -> Paris; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_node('Paris', label => 'City of\nlurve'); $g->add_node('New York'); $g->add_edge('London' => 'Paris'); $g->add_edge('London' => 'New York', label => 'Far'); $g->add_edge('Paris' => 'London'); -- expect -- digraph test { ratio="fill"; London [label="London"]; "New York" [label="New York"]; Paris [label="City of\nlurve"]; London -> "New York" [label="Far"]; London -> Paris; Paris -> London; } -- test -- # Test clusters $g = GraphViz->new(sort => 1); $g->add_node('London', cluster => 'Europe'); $g->add_node('Paris', label => 'City of\nlurve', cluster => 'Europe'); $g->add_node('New York'); $g->add_edge('London' => 'Paris'); $g->add_edge('London' => 'New York', label => 'Far'); $g->add_edge('Paris' => 'London'); -- expect -- digraph test { ratio="fill"; "New York" [label="New York"]; London -> "New York" [label="Far"]; subgraph cluster_Europe { label="Europe"; London [label="London"]; Paris [label="City of\nlurve"]; London -> Paris; Paris -> London; } } -- test -- $g = GraphViz->new({directed => 0, sort => 1}); foreach my $i (1..16) { my $used = 0; $used = 1 if $i >= 2 and $i <= 4; foreach my $j (2..4) { if ($i != $j && $i % $j == 0) { $g->add_edge($i => $j); $used = 1; } } $g->add_node($i) if $used; } -- expect -- graph test { ratio="fill"; node7 [label="10"]; node8 [label="12"]; node9 [label="14"]; node10 [label="15"]; node11 [label="16"]; node1 [label="2"]; node2 [label="3"]; node3 [label="4"]; node4 [label="6"]; node5 [label="8"]; node6 [label="9"]; node7 -- node1; node8 -- node1; node8 -- node2; node8 -- node3; node9 -- node1; node10 -- node2; node11 -- node1; node11 -- node3; node3 -- node1; node4 -- node1; node4 -- node2; node5 -- node1; node5 -- node3; node6 -- node2; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London', label => ['Heathrow', 'Gatwick']); $g->add_node('Paris', label => 'CDG'); $g->add_node('New York', label => 'JFK'); $g->add_edge('London' => 'Paris', from_port => 0); $g->add_edge('New York' => 'London', to_port => 1); -- expect -- digraph test { ratio="fill"; London [label="Heathrow|Gatwick", shape="record"]; "New York" [label="JFK"]; Paris [label="CDG"]; "London":port0 -> Paris; "New York" -> "London":port1; } -- test -- $g = GraphViz->new(width => 30, height => 20, pagewidth => 8.5, pageheight=> 11, sort => 1); $g->add_node('London'); $g->add_node('Paris'); $g->add_edge('London' => 'London'); $g->add_edge('Paris' => 'Paris'); $g->add_edge('London' => 'Paris'); $g->add_edge('Paris' => 'London'); -- expect -- digraph test { size="30,20"; page="8.5,11"; ratio="fill"; London [label="London"]; Paris [label="Paris"]; London -> London; London -> Paris; Paris -> London; Paris -> Paris; } -- test -- $g = GraphViz->new(concentrate => 1, sort => 1) -- expect -- digraph test { ratio="fill"; concentrate=true; } -- test -- $g = GraphViz->new(epsilon => 0.001, random_start => 1, sort => 1) -- expect -- digraph test { ratio="fill"; epsilon=0.001; start=rand; } -- test -- # Test incremental buildup $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_node('London', cluster => 'Europe'); $g->add_node('London', color => 'blue'); $g->add_node('Paris'); $g->add_node('Paris', label => 'City of\nlurve'); $g->add_node('Paris', cluster => 'Europe'); $g->add_node('Paris', color => 'green'); $g->add_node('New York'); $g->add_node('New York', color => 'yellow'); $g->add_edge('London' => 'Paris'); $g->add_edge('London' => 'New York', label => 'Far', color => 'red'); $g->add_edge('Paris' => 'London'); -- expect -- digraph test { ratio="fill"; "New York" [color="yellow", label="New York"]; London -> "New York" [color="red", label="Far"]; subgraph cluster_Europe { label="Europe"; London [color="blue", label="London"]; Paris [color="green", label="City of\nlurve"]; London -> Paris; Paris -> London; } } -- test -- $g = GraphViz->new(node => { shape => 'box' }, edge => { color => 'red' }, graph => { rotate => "90" }, sort => 1); $g->add_node('London'); $g->add_node('Paris', label => 'City of\nlurve'); $g->add_node('New York'); $g->add_edge('London' => 'Paris'); $g->add_edge('London' => 'New York', label => 'Far'); $g->add_edge('Paris' => 'London'); -- expect -- digraph test { ratio="fill"; node [shape="box"]; edge [color="red"]; graph [rotate="90"]; London [label="London"]; "New York" [label="New York"]; Paris [label="City of\nlurve"]; London -> "New York" [label="Far"]; London -> Paris; Paris -> London; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('a'); $g->add_node('b'); $g->add_node('c'); $g->add_node('d'); $g->add_node('e'); $g->add_node('f'); -- expect -- digraph test { ratio="fill"; a [label="a"]; b [label="b"]; c [label="c"]; d [label="d"]; e [label="e"]; f [label="f"]; } -- test -- $g = GraphViz->new(sort => 1); $g->add_edge('a' => 'b'); $g->add_edge('b' => 'c'); $g->add_edge('c' => 'a'); -- expect -- digraph test { ratio="fill"; a [label="a"]; b [label="b"]; c [label="c"]; a -> b; b -> c; c -> a; } -- test -- $g = GraphViz->new(sort => 1); $g->add_node('London'); $g->add_node('Paris', label => 'City of\nlurve', rank => 'top'); $g->add_node('New York'); $g->add_node('Boston', rank => 'top'); $g->add_edge('Paris' => 'London'); $g->add_edge('London' => 'New York', label => 'Far'); $g->add_edge('Boston' => 'New York'); -- expect -- digraph test { ratio="fill"; Boston [label="Boston", rank="top"]; London [label="London"]; "New York" [label="New York"]; Paris [label="City of\nlurve", rank="top"]; Boston -> "New York"; London -> "New York" [label="Far"]; Paris -> London; {rank=same; Boston; Paris} } -- test -- $g = GraphViz->new(sort => 1, no_overlap => 1); $g->add_node('London'); $g->add_node('Paris', label => 'City of\nlurve', rank => 'top'); $g->add_node('New York'); $g->add_node('Boston', rank => 'top'); $g->add_edge('Paris' => 'London'); $g->add_edge('London' => 'New York', label => 'Far'); $g->add_edge('Boston' => 'New York'); -- expect -- digraph test { ratio="fill"; overlap="false"; Boston [label="Boston", rank="top"]; London [label="London"]; "New York" [label="New York"]; Paris [label="City of\nlurve", rank="top"]; Boston -> "New York"; London -> "New York" [label="Far"]; Paris -> London; {rank=same; Boston; Paris} } -- test -- $g = GraphViz->new(); $g->add_node('GOroot', label => '<root>', shape => 'plaintext'); -- expect -- digraph test { ratio="fill"; GOroot [label=<root>, shape="plaintext"]; } GraphViz-2.26/t/dumper.t0000755000175000017500000000344214400435020015045 0ustar osboxesosboxes#!/usr/bin/perl use lib '../lib', 'lib'; use strict; use warnings; use File::Which 'which'; use GraphViz::Data::Grapher; use Test::More; # -------------------------- if (! defined which('dot') ) { bail_out("Cannot find 'dot'. Please install Graphviz from http://www.graphviz.org/"); } my(@lines) = ; for my $lines ( split '-- test --', ( join "", @lines ) ) { my ( $test, $expect ) = split '-- expect --', $lines; next unless $test; $expect =~ s|^\n||mg; $expect =~ s|\n$||mg; $test =~ s|^\n||mg; $test =~ s|\n$||mg; my($g); eval $test; my($result) = $g->_as_debug; $result =~ s|^\n||mg; $result =~ s|\n$||mg; is($result, $expect, 'Got expected graph'); } done_testing; __DATA__ -- test -- my @d = ("red", { a => [3, 1, 4, 1], b => { q => 'a', w => 'b'}}, "blue", undef, GraphViz::Data::Grapher->new(), 2); $g = GraphViz::Data::Grapher->new(\@d); -- expect -- digraph test { ratio="fill"; GraphViz [color="red", label="GraphViz"]; node1 [color="blue", label="@", shape="record"]; node2 [color="black", label="red|%|blue|undef|Object|2", shape="record"]; node3 [color="brown", label="a|b", shape="record"]; node4 [color="blue", label="@", shape="record"]; node5 [color="black", label="3|1|4|1", shape="record"]; node6 [color="blue", label="%", shape="record"]; node7 [color="brown", label="q|w", shape="record"]; node8 [color="blue", label="a", shape="record"]; node9 [color="blue", label="b", shape="record"]; "node1":port0 -> node2; "node2":port4 -> GraphViz; "node2":port1 -> node3; "node3":port0 -> node4; "node3":port1 -> node6; "node4":port0 -> node5; "node6":port0 -> node7; "node7":port0 -> node8; "node7":port1 -> node9; } GraphViz-2.26/t/foo.t0000644000175000017500000000417214300260460014335 0ustar osboxesosboxes#!/usr/bin/perl use lib '../lib', 'lib'; use strict; use warnings; use File::Which 'which'; use GraphViz; use Test::More; # ------------------------- if (! defined which('dot') ) { bail_out("Cannot find 'dot'. Please install Graphviz from http://www.graphviz.org/"); } # make a nice simple graph and check how output is handled. my $g = GraphViz->new(); $g->add_node( label => 'London' ); { # Check filehandle my $fh = do { local *FH; *FH; }; # doubled to avoid warnings open $fh, ">as_foo.1" or die "Cannot write to as_foo.1: $!"; $g->as_dot($fh); close $fh; my @result = read_file('as_foo.1'); check_result(@result); } { # Check filehandle #2 local *OUT; open OUT, ">as_foo.2" or die "Cannot write to as_foo.2: $!"; $g->as_dot( \*OUT ); close OUT; my @result = read_file('as_foo.2'); check_result(@result); } { # Check filename $g->as_dot('as_foo.3'); my @result = read_file('as_foo.3'); check_result(@result); } { # Check scalar ref my $result; $g->as_dot( \$result ); check_result( split /\n/, $result ); } { # Check returned my $result = $g->as_dot(); check_result( split /\n/, $result ); } { # Check coderef my $result; $g->as_dot( sub { $result .= shift } ); check_result( split /\n/, $result ); } unlink 'as_foo.1'; unlink 'as_foo.2'; unlink 'as_foo.3'; sub read_file { my $filename = shift; local *FILE; open FILE, "<$filename" or die "Cannot read $filename: $!"; return (); } sub check_result { my @result = @_; my $expect = <<'EOF'; Expected something like (except for the line spacing :-(. Hence the join...): digraph test { node [ label = "\N" ]; graph [bb= "0,0,66,38"]; node1 [label=London, pos="33,19", width="0.89", height="0.50"]; } EOF my($result) = join(' ', @result); like( $result, qr/digraph test \{/ ); like( $result, qr/\s*graph\s*\[bb=.*/ ); like( $result, qr/.+ratio=fill/ ); like( $result, qr/\s*node\s*\[\s*label\s*=\s*"\\N"\s*\];\s*/ ); like( $result, qr/.+label=London,/ ); } done_testing; GraphViz-2.26/META.yml0000644000175000017500000000176614400436444014411 0ustar osboxesosboxes--- abstract: "Interface to AT&T's GraphViz. Deprecated. See GraphViz2" author: - 'Leon Brocard ' build_requires: ExtUtils::MakeMaker: '0' Test::More: '1.001002' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: GraphViz no_index: directory: - t - inc requires: Carp: '1.01' File::Which: '1.09' Getopt::Long: '2.34' IO::Dir: '1.04' IO::File: '1.1' IPC::Run: '0.6' LWP::Simple: '6' Parse::RecDescent: '1.965001' Pod::Usage: '1.16' Time::HiRes: '1.51' XML::Twig: '3.52' XML::XPath: '1.13' resources: bugtracker: https://rt.cpan.org/Public/Dist/Display.html?Name=GraphViz license: http://dev.perl.org/licenses/ repository: https://github.com/ronsavage/GraphViz.git version: '2.26' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' GraphViz-2.26/Changes0000644000175000017500000003267114400436375014435 0ustar osboxesosboxes2.26 2023-03-03 - unchanged from 2.25 (except for deps adjustment) which also got deleted from CPAN by co-maint 2.25 2022-08-20 - unchanged from 2.24 which appears to have been deleted from CPAN 2.24 2016-12-27T10:58:00 - Test with Test::More V 1.001002 rather than V 1.302019, and all tests still pass. See RT#115236. Thanx to Kent Fredric for again (sorry!) prompting me over this. Lowering the version of Test::More in Makefile.PL is the only change in this version. 2.23 2016-12-21T08:00:00 - Add no_xxe to XML::Twig instantiation. See RT#118972. Many thanx to Lisa Hare for a very well-crafted set of patches provided via github. 2.22 2016-07-19T09:24:00 - Revert change so we use Test::More instead of Test2::Bundle::Extended. See RT#115236. Thanx to ribasushi for this report, and my apologies for not acting sooner. 2.21 2016-05-22T09:21:00 - Escape a '{' in a regexp in t/foo.t. See RT#114551. Thanx to Slaven Rezic. - Fix a typo in a comment, as provided by Gregor Herrmann in RT#111385. - See http://savage.net.au/Ron/html/My.Workflow.for.Building.Distros.html for notes on the modernization of the repo. This includes updating Makefile.PL and removing Build.PL, MANIFEST and *META.*. - Bail out cleanly if 'dot' is not installed. See RT#105575. Thanx Karen Etheridge. - Update docs to point to a copy of the Perl licence. - Update docs in GraphViz.pm to point to the github repo. - Move t/pod.t into xt/author/. 2.20 2015-12-29T08:13:00 - Merge patch from ntyni via github. This concerned use of defined(@some_array). With thanx. 2.19 2015-11-13T08:30:00 - No code changes. - Accept pull request from Patrice Clement to add the x_deprecated flag to both META.json and META.yml. The docs already had various references to 'deprecated', including directing users to GraphViz2, so no change is needed there. Thanx Patrice. - Add .gitignore to MANIFEST.SKIP. 2.18 2015-05-28T08:43:00 - All patches to this version are from chrony, via a github pull request. With thanx. - Use File::Which on Windows to detect if Graphviz is present. - Clean up the code, including Build.PL and Makefile.PL. 2.17 2015-03-30T08:56:00 - No code changes. - Create github repo. This has been done because ribasushi (Peter Rabbitson) has kindly offered some patches. Hence I've reversed my policy of encouraging this module to die :-). - Update Build.PL and Makefile.PL to point to the repo. None of the pre-reqs were updated. 2.16 2014-08-30T08:25:00 - Update docs on how to download AT&T's Graphviz. Thanx to Alex Becker (see RT#98405). 2.15 2013-11-28T11:23:00 - Fix double-quote escaping bug in GraphViz's _attributes() method. See RT#90528. Many thanx to Smylers for the report. - Rename CHANGES to Changes as per CPAN::Changes::Spec. 2.14 2012-11-09T16:06:00 - No code changes. - Patch t/foo.t to not assume text appears on specific lines of the output test files. 2.13 2012-11-09T08:27:00 - No code changes. - Re-package distro because users get errors during testing. See RT#80709. Since I had this same error during my testing, I assume the uploaded version contains un-patched code. The errors are not in GraphViz, they are in the test code which has hard-coded line numbers where it looks for strings in the output. The output has been reformatted recently, and no longer matches those assumptions. See t/foo.t for details. Note: I did not write those tests :-). 2.12 2012-11-08T12:38:00 - No code changes. - For pre-reqs such as strict, warnings, etc, which ship with Perl, set the version # to 0. Reported as RT#80663 by Father Chrysostomos for Tree::DAG_Node. 2.11 2012-09-18T08:22:00 - Add VDX as an output format. 2.10 2012-03-26T10:11:00 - Accept a patch kindly supplied by Alexander Kriegisch, to change handling of the rankdir attribute. The valid values are BT, LR, RL or TB, or their lower-case equivalents. Previously, only a true value was accepted, which meant LR. Now, any value not in that list defaults to LR. Files changed: README, CHANGES, Changelog.ini, GraphViz.pm, GraphViz/Regex.pm and simple.t. - Patch this file to replace BST with GMT, since both DateTime::Format::HTTP and DateTime::Format::Strptime fail to recognize BST. These modules are used by Module::Metadata::Changes to transform this file into Changelog.ini. 2.09 2011-12-15T11:08:00 - Adopt Flavio Poletti's suggestion of trying to pipe to dot, in Build.PL/Makefile.PL, rather than using File::Which, to see if dot (Graphviz) is installed. This (hopefully) solves the problem of using File::Which on systems where it is not installed, before Build.PL/Makefile.PL has a chance to tell the user that File::Which is required. See: RT#73077. 2.08 Tue Nov 1 10:55:00 2011 - Wind back pre-reqs for various modules to match what was shipped with Perl V 5.8.1. Many thanx to Brian Cassidy for the error report: https://rt.cpan.org/Ticket/Display.html?id=72068. 2.07 Sun Oct 30 16:08:00 2011 - Rewrite Build.PL and Makefile.PL to try loading File::Which rather than assuming it is installed. This avoids the chicken-and-egg problem whereby these 2 programs need File::Which::which to find 'dot'. Many thanx to Richard Clamp for the error report: https://rt.cpan.org/Public/Bug/Display.html?id=71971. 2.06 Tue Oct 25 08:09:00 2011 - Add File::Which to the pre-reqs in Build.PL and Makefile.PL. 2.05 Thu Oct 20 10:52:00 2011 - Add 'Deprecated. See GraphViz2' to the docs. - Add Build.PL. - Add ability to set ORIENTATION. Thanx to Christian Lackas for the patch. See RT#71787. - Add Changelog.ini. - Add META.json, MYMETA.json and MYMETA.yml. - Add MANIFEST.SKIP. - Ensure all modules contain a version number. - Update the docs regarding the list of modules shipped in this distro. - Remove examples/remote.pl because GraphViz::Remote is no longer shipped. - Clean up examples/clusters2.pl. - Clean up Makefile.PL. 2.04 Fri Dec 12 21:31:24 GMT 2008 - perltidy everything - add human- and machine-readable license - add use warnings 2.03 Sun Nov 18 14:40:20 GMT 2007 - make the graph name configurable (patch by Ruslan Zakirov) 2.02 Fri Jan 7 18:51:06 GMT 2005 - remove dependencies on Graph and Math::Bezier - make GraphViz HTML-Like labels work (spotted by Patrice Dehais) - updated (including much documentation) to support newer additions to the dot language (by Max Baker) - new test which tests the POD 2.01 Fri Sep 24 17:02:29 GMT 2004 - no longer *always* quote the label in add_node() in order to let GraphViz::Data::Structure work again (sorry) 2.00 Wed Aug 25 16:30:53 GMT 2004 - thanks to Ron Savage, patched to work under systems which have an executable extension, such as Windows 1.9 Tue Aug 24 15:30:31 GMT 2004 - check for "dot" in the Makefile.PL instead of a test, as suggested by Autrijus Tang - renamed Changes to CHANGES - clusters can now take attributes as a hashref, thanks to patch from Richard A.Wells (see clusters2.pl) - fix docbug in GraphViz::Parse::Yapp (spotted by Mark Fowler) - better quoting (patch by Barrie Slaymaker) - document as_debug (suggested by Richard Clamp) 1.8 Sun Feb 23 09:15:14 GMT 2003 - support for client-side image maps by Dan Boorstein 1.7 Sun Jan 19 21:55:14 GMT 2003 - quote bgcolor so that HSV works 1.6 Sat Jan 18 15:47:26 GMT 2003 - moved tests to Test::More - new test which checks if graphviz is installed - new 'layout' graph attribute to support twopi - you may have to change your programs! - new bgcolor graph attribute (idea by Scott Murman) - labels named "graph" now work 1.5 Sun Jan 13 16:59:14 GMT 2002 - updated code reference docs slightly - removed GraphViz::Remote as it was no longer working - new no_overlap graph attribute which tells the graph solver to not overlap the nodes (idea by Chris Ball) - added patches by Barrie Slaymaker to make GraphViz work under Win32! - this is the Flight 63 edition 1.4 Wed Oct 3 07:57:42 GMT 2001 - added new filehandle, scalar reference, and code reference scheme to as_* to allow streaming of data, rather than accumulating potentially very large output in memory (based on patch by Dave Rolsky) - new pagewidth and pageheight graph attributes for creating PostScript mosaics of large graphs (idea by Nelson Loyola) 1.3 Sun Aug 19 15:43:02 GMT 2001 - labels can now contain quotes - fixed bug: labels can now start with a number - fixed bug in Devel::GraphVizProf so that packages are now grouped seperately (lines with the same text used to be grouped together) - fixed undefined warning in GraphViz::Parse::RecDescent - increased coverage of tests - new 'rank' node attribute allows nodes to be ranked at the same level - make empty cluster names do nothing (patch by Barrie Slaymaker) 1.2 Fri Aug 10 18:54:21 GMT 2001 - removed the images in the examples directory and added a file (make_all.pl) to, errr, make all the images - This is the HAL2001 edition 1.1 Tue Jul 24 23:54:42 GMT 2001 - added extra parameter to as_* to allow easy saving of images: $graph->as_png("pretty.png") - added new GraphViz::Parse::Yapp module to visualise Parse::Yapp grammars - added new GraphViz::Parse::Yacc module to visualise Parse::Yacc grammars - This is the TPC5 edition 1.00 Thu Jun 14 15:10:28 GMT 2001 - finally released as version 1.00! - added a reference to brian d foy's DDJ article on Devel::GraphVizProf - put the entire Perl regular expression test suite through GraphViz::Regex and fixed all the bugs - no longer sort nodes by default (idea by Stephen Riehm), which makes graphs just work better. Not documented, do you want it to be? 0.14 Thu May 3 17:57:57 GMT 2001 - added support for InterpLit node in RecDescent grammars - added cumulative effect for node attributes (patch by Diego Zamboni) - changed the quoting rules again to make it easier to read the dot files (idea by Diego Zamboni) - make add_edge() automatically add any nodes specified for the edge that have not been previously added to stop the Graph module complaining (patch by Diego Zamboni) - new 'node', 'edge', and 'graph' graph attributes to specify global node, edge, and graph attributes (patch by Diego Zamboni) - removed t/regex.t and documented that GraphViz::Regex may not work on various perls - added GraphViz::Regex_YAPE module, another way to graph a regular expression 0.13 Mon Mar 19 19:31:18 GMT 2001 - removed 'use warnings' as suggested by David Adler so we no longer require Perl 5.6 - moved all modules into a new 'lib' directory (and updated examples) so that Devel::GraphVizProf gets installed - new 'concentrate' graph attribute to merge edges in cluttered directed graphs - new 'random_start' graph attribute, which requests an initial random placement for the graph - new 'epsilon' graph attribute, which decides how long the graph solver tries before finding a graph layout, requested by Pierre-Yves Genot - an empty cluster now means not clustered - added GraphViz::Regex and example regexp.pl which visualises a regular expression - now an award-winning module! 0.12 Tue Mar 6 17:37:21 GMT 2001 - fixed bug in redcarpet.pl example - new rankdir graph attribute, which controls the direction the nodes are linked together (patch by Mark Fowler) - new 'width' and 'height' graph attributes control the size of the bounding box of the drawing in inches, requested by Pierre-Yves Genot 0.11 Tue Mar 6 17:37:20 GMT 2001 - rearranged module naming: Data::GraphViz -> GraphViz::Data::Dumper, Parse::RecDescent::GraphViz -> GraphViz::Parse::RecDescent, XML::GraphViz -> GraphViz::XML, - added GraphViz::Remote so that you do not need to install the graphviz tools to use this module 0.10 Mon Mar 5 17:32:14 GMT 2001 - now allow simple add_edge({$from => $to}) syntax (idea by DJ Adams and Brian Ingerson) - much better documentation (especially on attributes) - new module Parse::RecDescent::GraphViz (and example) for graphing Parse::RecDescent grammars (idea by Damian Conway) - new module XML::GraphViz (and example) for graphing XML - new module Data::GraphViz (and example) for graphing data structures - new example ppmgraph.pl by Marcel Grunauer which graphs CPAN tarball dependencies using ActiveState's package list (thanks to Brian Ingerson too ;-) - new, better, testsuite - better quoting (especially in ports) to allow a greater range of characters - new undocumented (it may change) as_graph method, which returns a graph object with the coordinates of nodes and edges 0.09 Fri Jan 12 15:50:17 GMT 2001 - moved back to "dot" and "neato" from "dotneato" - now allow directed and undirected graphs - added GraphViz::No and GraphViz::Small subclasses which aid in visualising the structure of large graphs 0.08 Sun Dec 3 15:15:29 GMT 2000 - minor patch to cope with DESTROY 0.07 Sun Oct 1 15:19:55 2000 - new features: allows clusters and ports - includes the talk I gave on this at yapc::Europe 19100 - many more examples (well, see the examples directory!), including quite a few PNGs 0.06 Thu Aug 24 09:33:21 2000 - better quoting of nodes and edges (they can now have really wierd names) - new examples directory with xref.pl: "graphing subroutine cross-reference reports for Perl modules" and example graph to see what kind of things it can do 0.05 Wed Aug 18 13:12:25 2000 - now use dotneato to layout the graphs and can now ouput in a variety of file formats 0.04 Wed Aug 9 16:14:35 2000 - first released version GraphViz-2.26/lib/0000755000175000017500000000000014400436444013674 5ustar osboxesosboxesGraphViz-2.26/lib/GraphViz.pm0000644000175000017500000011450114400436301015756 0ustar osboxesosboxespackage GraphViz; use 5.006; use strict; use warnings; our $AUTOLOAD; use Carp; use Config; use IPC::Run qw(run binary); our $VERSION = '2.26'; =pod =head1 NAME GraphViz - Interface to AT&T's GraphViz. Deprecated. See GraphViz2 =head1 SYNOPSIS use GraphViz; my $g = GraphViz->new(); $g->add_node('London'); $g->add_node('Paris', label => 'City of\nlurve'); $g->add_node('New York'); $g->add_edge('London' => 'Paris'); $g->add_edge('London' => 'New York', label => 'Far'); $g->add_edge('Paris' => 'London'); print $g->as_png; =head1 DESCRIPTION This module provides an interface to layout and image generation of directed and undirected graphs in a variety of formats (PostScript, PNG, etc.) using the "dot", "neato", "twopi", "circo" and "fdp" programs from the Graphviz project (http://www.graphviz.org/ or http://www.research.att.com/sw/tools/graphviz/). GraphViz is deprecated in favour of L. =head1 Installation Of course you need to install AT&T's Graphviz before using this module. See L. You are strongly advised to download the stable version of Graphviz, because the development snapshots (click on 'Source code'), are sometimes non-functional. Install L as you would for any C module: Run: cpanm GraphViz Note: cpanm ships in App::cpanminus. See also App::perlbrew. or run: sudo cpan GraphViz or unpack the distro, and then either: perl Build.PL ./Build ./Build test sudo ./Build install or: perl Makefile.PL make (or dmake or nmake) make test make install =head1 Overview =head2 Modules in this distro =over 4 =item o GraphViz =item o GraphViz::No =item o GraphViz::Small =item o GraphViz::Regex =item o GraphViz::XML =item o GraphViz::Data::Grapher =item o GraphViz::Parse::RecDescent =item o GraphViz::Parse::Yacc =item o GraphViz::Parse::Yapp =back =head2 What is a graph? A (undirected) graph is a collection of nodes linked together with edges. A directed graph is the same as a graph, but the edges have a direction. =head2 What is GraphViz? This module is an interface to the GraphViz toolset (http://www.graphviz.org/). The GraphViz tools provide automatic graph layout and drawing. This module simplifies the creation of graphs and hides some of the complexity of the GraphViz module. Laying out graphs in an aesthetically-pleasing way is a hard problem - there may be multiple ways to lay out the same graph, each with their own quirks. GraphViz luckily takes part of this hard problem and does a pretty good job in a couple of seconds for most graphs. =head2 Why should I use this module? Observation aids comprehension. That is a fancy way of expressing that popular faux-Chinese proverb: "a picture is worth a thousand words". Text is not always the best way to represent anything and everything to do with a computer programs. Pictures and images are easier to assimilate than text. The ability to show a particular thing graphically can aid a great deal in comprehending what that thing really represents. Diagrams are computationally efficient, because information can be indexed by location; they group related information in the same area. They also allow relations to be expressed between elements without labeling the elements. A friend of mine used this to his advantage when trying to remember important dates in computer history. Instead of sitting down and trying to remember everything, he printed over a hundred posters (each with a date and event) and plastered these throughout his house. His spatial memory is still so good that asked last week (more than a year since the experiment) when Lisp was invented, he replied that it was upstairs, around the corner from the toilet, so must have been around 1958. Spreadsheets are also a wonderfully simple graphical representation of computational models. =head2 Applications Bundled with this module are several modules to help graph data structures (GraphViz::Data::Dumper), XML (GraphViz::XML), and Parse::RecDescent, Parse::Yapp, and yacc grammars (GraphViz::Parse::RecDescent, GraphViz::Parse::Yapp, and GraphViz::Parse::Yacc). Note that Marcel Grunauer has released some modules on CPAN to graph various other structures. See GraphViz::DBI and GraphViz::ISA for example. brian d foy has written an article about Devel::GraphVizProf for Dr. Dobb's Journal: http://www.ddj.com/columns/perl/2001/0104pl002/0104pl002.htm =head2 Award winning! I presented a paper and talk on "Graphing Perl" using GraphViz at the 3rd German Perl Workshop and received the "Best Knowledge Transfer" prize. Talk: http://www.astray.com/graphing_perl/graphing_perl.pdf Slides: http://www.astray.com/graphing_perl/ =head1 METHODS =head2 new This is the constructor. It accepts several attributes. my $g = GraphViz->new(); my $g = GraphViz->new(directed => 0); my $g = GraphViz->new(layout => 'neato', ratio => 'compress'); my $g = GraphViz->new(rankdir => 'BT'); my $g = GraphViz->new(width => 8.5, height => 11); my $g = GraphViz->new(width => 30, height => 20, pagewidth => 8.5, pageheight => 11); The most two important attributes are 'layout' and 'directed'. =over =item layout The 'layout' attribute determines which layout algorithm GraphViz.pm will use. Possible values are: =over =item dot The default GraphViz layout for directed graph layouts =item neato For undirected graph layouts - spring model =item twopi For undirected graph layouts - radial =item circo For undirected graph layouts - circular =item fdp For undirected graph layouts - force directed spring model =back =item directed The 'directed' attribute, which defaults to 1 (true) specifies directed (edges have arrows) graphs. Setting this to zero produces undirected graphs (edges do not have arrows). =item rankdir Another attribute 'rankdir' controls the direction in which the nodes are linked together. The default is 'TB' (arrows from top to bottom). Other legal values are 'BT' (bottom->top), 'LR' (left->right) and 'RL' (right->left). =item width, height The 'width' and 'height' attributes control the size of the bounding box of the drawing in inches. This is more useful for PostScript output as for raster graphic (such as PNG) the pixel dimensions can not be set, although there are generally 96 pixels per inch. =item pagewidth, pageheight The 'pagewidth' and 'pageheight' attributes set the PostScript pagination size in inches. That is, if the image is larger than the page then the resulting PostScript image is a sequence of pages that can be tiled or assembled into a mosaic of the full image. (This only works for PostScript output). =item concentrate The 'concentrate' attribute controls enables an edge merging technique to reduce clutter in dense layouts of directed graphs. The default is not to merge edges. =item orientation This option controls the angle, in degrees, used to rotate polygon node shapes. =item random_start For undirected graphs, the 'random_start' attribute requests an initial random placement for the graph, which may give a better result. The default is not random. =item epsilon For undirected graphs, the 'epsilon' attribute decides how long the graph solver tries before finding a graph layout. Lower numbers allow the solver to fun longer and potentially give a better layout. Larger values can decrease the running time but with a reduction in layout quality. The default is 0.1. =item overlap The 'overlap' option allows you to set layout behavior for graph nodes that overlap. (From GraphViz documentation:) Determines if and how node overlaps should be removed. =over =item true (the default) overlaps are retained. =item scale overlaps are removed by uniformly scaling in x and y. =item false If the value converts to "false", node overlaps are removed by a Voronoi-based technique. =item scalexy x and y are separately scaled to remove overlaps. =item orthoxy, orthxy If the value is "orthoxy" or "orthoyx", overlaps are moved by optimizing two constraint problems, one for the x axis and one for the y. The suffix indicates which axis is processed first. B: The methods related to "orthoxy" and "orthoyx" are still evolving. The semantics of these may change, or these methods may disappear altogether. =item compress If the value is "compress", the layout will be scaled down as much as possible without introducing any overlaps. =back Except for the Voronoi method, all of these transforms preserve the orthogonal ordering of the original layout. That is, if the x coordinates of two nodes are originally the same, they will remain the same, and if the x coordinate of one node is originally less than the x coordinate of another, this relation will still hold in the transformed layout. The similar properties hold for the y coordinates. =item no_overlap The 'no_overlap' overlap option, if set, tells the graph solver to not overlap the nodes. Deprecated, Use 'overlap' => 'false'. =item ratio The 'ratio' option sets the aspect ratio (drawing height/drawing width) for the drawing. Note that this is adjusted before the size attribute constraints are enforced. Default value is C. =over =item numeric If ratio is numeric, it is taken as the desired aspect ratio. Then, if the actual aspect ratio is less than the desired ratio, the drawing height is scaled up to achieve the desired ratio; if the actual ratio is greater than that desired ratio, the drawing width is scaled up. =item fill If ratio = C and the size attribute is set, node positions are scaled, separately in both x and y, so that the final drawing exactly fills the specified size. =item compress If ratio = C and the size attribute is set, dot attempts to compress the initial layout to fit in the given size. This achieves a tighter packing of nodes but reduces the balance and symmetry. This feature only works in dot. =item expand If ratio = C the size attribute is set, and both the width and the height of the graph are less than the value in size, node positions are scaled uniformly until at least one dimension fits size exactly. Note that this is distinct from using size as the desired size, as here the drawing is expanded before edges are generated and all node and text sizes remain unchanged. =item auto If ratio = C the page attribute is set and the graph cannot be drawn on a single page, then size is set to an ``ideal'' value. In particular, the size in a given dimension will be the smallest integral multiple of the page size in that dimension which is at least half the current size. The two dimensions are then scaled independently to the new size. This feature only works in dot. =back =item bgcolor The 'bgcolor' option sets the background colour. A colour value may be "h,s,v" (hue, saturation, brightness) floating point numbers between 0 and 1, or an X11 color name such as 'white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', or 'burlywood'. =item name The 'name' option sets name of the graph. This option is useful in few situations, like client side image map generation, see cmapx. By default 'test' is used. =item node,edge,graph The 'node', 'edge' and 'graph' attributes allow you to specify global node, edge and graph attributes (in addition to those controlled by the special attributes described above). The value should be a hash reference containing the corresponding key-value pairs. For example, to make all nodes box-shaped (unless explicitly given another shape): my $g = GraphViz->new(node => {shape => 'box'}); =back =cut sub new { my $proto = shift; my $config = shift; my $class = ref($proto) || $proto; my $self = {}; # Cope with the old hashref format if ( ref($config) ne 'HASH' ) { my %config; %config = ( $config, @_ ) if @_; $config = \%config; } $self->{NODES} = {}; $self->{NODELIST} = []; $self->{EDGES} = []; if ( exists $config->{directed} ) { $self->{DIRECTED} = $config->{directed}; } else { $self->{DIRECTED} = 1; # default to directed } if ( exists $config->{layout} ) { $self->{LAYOUT} = $config->{layout}; } else { $self->{LAYOUT} = "dot"; # default layout } if ( exists $config->{name} ) { $self->{NAME} = $config->{name}; } else { $self->{NAME} = 'test'; } if ( exists $config->{bgcolor} ) { $self->{BGCOLOR} = $config->{bgcolor}; } $self->{RANK_DIR} = $config->{rankdir} if ( exists $config->{rankdir} ); $self->{WIDTH} = $config->{width} if ( exists $config->{width} ); $self->{HEIGHT} = $config->{height} if ( exists $config->{height} ); $self->{PAGEWIDTH} = $config->{pagewidth} if ( exists $config->{pagewidth} ); $self->{PAGEHEIGHT} = $config->{pageheight} if ( exists $config->{pageheight} ); $self->{CONCENTRATE} = $config->{concentrate} if ( exists $config->{concentrate} ); $self->{ORIENTATION} = $config->{orientation} if ( exists $config->{orientation} ); $self->{RANDOM_START} = $config->{random_start} if ( exists $config->{random_start} ); $self->{EPSILON} = $config->{epsilon} if ( exists $config->{epsilon} ); $self->{SORT} = $config->{sort} if ( exists $config->{sort} ); $self->{OVERLAP} = $config->{overlap} if ( exists $config->{overlap} ); # no_overlap overrides overlap setting. $self->{OVERLAP} = 'false' if ( exists $config->{no_overlap} ); $self->{RATIO} = $config->{ratio} || 'fill'; # Global node, edge and graph attributes $self->{NODE_ATTRS} = $config->{node} if ( exists $config->{node} ); $self->{EDGE_ATTRS} = $config->{edge} if ( exists $config->{edge} ); $self->{GRAPH_ATTRS} = $config->{graph} if ( exists $config->{graph} ); bless( $self, $class ); return $self; } =head2 add_node A graph consists of at least one node. All nodes have a name attached which uniquely represents that node. The add_node method creates a new node and optionally assigns it attributes. The simplest form is used when no attributes are required, in which the string represents the name of the node: $g->add_node('Paris'); Various attributes are possible: "label" provides a label for the node (the label defaults to the name if none is specified). The label can contain embedded newlines with '\n', as well as '\c', '\l', '\r' for center, left, and right justified lines: $g->add_node('Paris', label => 'City of\nlurve'); Attributes need not all be specified in the one line: successive declarations of the same node have a cumulative effect, in that any later attributes are just added to the existing ones. For example, the following two lines are equivalent to the one above: $g->add_node('Paris'); $g->add_node('Paris', label => 'City of\nlurve'); Note that multiple attributes can be specified. Other attributes include: =over 4 =item height, width sets the minimum height or width =item shape sets the node shape. This can be one of: 'record', 'plaintext', 'ellipse', 'circle', 'egg', 'triangle', 'box', 'diamond', 'trapezium', 'parallelogram', 'house', 'hexagon', 'octagon' =item fontsize sets the label size in points =item fontname sets the label font family name =item color sets the outline colour, and the default fill colour if the 'style' is 'filled' and 'fillcolor' is not specified A colour value may be "h,s,v" (hue, saturation, brightness) floating point numbers between 0 and 1, or an X11 color name such as 'white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', or 'burlywood' =item fillcolor sets the fill colour when the style is 'filled'. If not specified, the 'fillcolor' when the 'style' is 'filled' defaults to be the same as the outline color =item style sets the style of the node. Can be one of: 'filled', 'solid', 'dashed', 'dotted', 'bold', 'invis' =item URL sets the url for the node in image map and PostScript files. The string '\N' value will be replaced by the node name. In PostScript files, URL information is embedded in such a way that Acrobat Distiller creates PDF files with active hyperlinks =back If you wish to add an anonymous node, that is a node for which you do not wish to generate a name, you may use the following form, where the GraphViz module generates a name and returns it for you. You may then use this name later on to refer to this node: my $nodename = $g->add_node('label' => 'Roman city'); Nodes can be clustered together with the "cluster" attribute, which is drawn by having a labelled rectangle around all the nodes in a cluster. An empty string means not clustered. $g->add_node('London', cluster => 'Europe'); $g->add_node('Amsterdam', cluster => 'Europe'); Clusters can also take a hashref so that you can set attributes: my $eurocluster = { name =>'Europe', style =>'filled', fillcolor =>'lightgray', fontname =>'arial', fontsize =>'12', }; $g->add_node('London', cluster => $eurocluster, @default_attrs); Nodes can be located in the same rank (that is, at the same level in the graph) with the "rank" attribute. Nodes with the same rank value are ranked together. $g->add_node('Paris', rank => 'top'); $g->add_node('Boston', rank => 'top'); Also, nodes can consist of multiple parts (known as ports). This is implemented by passing an array reference as the label, and the parts are displayed as a label. GraphViz has a much more complete port system, this is just a simple interface to it. See the 'from_port' and 'to_port' attributes of add_edge: $g->add_node('London', label => ['Heathrow', 'Gatwick']); =cut sub add_node { my $self = shift; my $node = shift; # Cope with the new simple notation if ( ref($node) ne 'HASH' ) { my $name = $node; my %node; if ( @_ % 2 == 1 ) { # No name passed %node = ( $name, @_ ); } else { # Name passed %node = ( @_, name => $name ); } $node = \%node; } $self->add_node_munge($node) if $self->can('add_node_munge'); # The _code attribute is our internal name for the node $node->{_code} = $self->_quote_name( $node->{name} ); if ( not exists $node->{name} ) { $node->{name} = $node->{_code}; } if ( not exists $node->{label} ) { if ( exists $self->{NODES}->{ $node->{name} } and defined $self->{NODES}->{ $node->{name} }->{label} ) { # keep our old label if we already exist $node->{label} = $self->{NODES}->{ $node->{name} }->{label}; } else { $node->{label} = $node->{name}; } } else { $node->{label} =~ s#([|<>\[\]{}"])#\\$1#g unless $node->{shape} && ($node->{shape} eq 'record' || ( $node->{label} =~ /^<{shape} eq 'plaintext' ) ); } delete $node->{cluster} if exists $node->{cluster} && !length $node->{cluster}; $node->{_label} = $node->{label}; # Deal with ports if ( ref( $node->{label} ) eq 'ARRAY' ) { $node->{shape} = 'record'; # force a record my $nports = 0; $node->{label} = join '|', map { $_ =~ s#([|<>\[\]{}"])#\\$1#g; '' . $_ } ( @{ $node->{label} } ); } # Save ourselves if ( !exists( $self->{NODES}->{ $node->{name} } ) ) { $self->{NODES}->{ $node->{name} } = $node; } else { # If the node already exists, add or overwrite attributes. foreach ( keys %$node ) { $self->{NODES}->{ $node->{name} }->{$_} = $node->{$_}; } } $self->{CODES}->{ $node->{_code} } = $node->{name}; # Add the node to the nodelist, which contains the names of # all the nodes in the order that they were inserted (but only # if it's not already there) push @{ $self->{NODELIST} }, $node->{name} unless grep { $_ eq $node->{name} } @{ $self->{NODELIST} }; return $node->{name}; } =head2 add_edge Edges are directed (or undirected) links between nodes. This method creates a new edge between two nodes and optionally assigns it attributes. The simplest form is when now attributes are required, in which case the nodes from and to which the edge should be are specified. This works well visually in the program code: $g->add_edge('London' => 'Paris'); Attributes such as 'label' can also be used. This specifies a label for the edge. The label can contain embedded newlines with '\n', as well as '\c', '\l', '\r' for center, left, and right justified lines. $g->add_edge('London' => 'New York', label => 'Far'); Note that multiple attributes can be specified. Other attributes include: =over 4 =item minlen sets an integer factor that applies to the edge length (ranks for normal edges, or minimum node separation for flat edges) =item weight sets the integer cost of the edge. Values greater than 1 tend to shorten the edge. Weight 0 flat edges are ignored for ordering nodes =item fontsize sets the label type size in points =item fontname sets the label font family name =item fontcolor sets the label text colour =item color sets the line colour for the edge A colour value may be "h,s,v" (hue, saturation, brightness) floating point numbers between 0 and 1, or an X11 color name such as 'white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', or 'burlywood' =item style sets the style of the node. Can be one of: 'filled', 'solid', 'dashed', 'dotted', 'bold', 'invis' =item dir sets the arrow direction. Can be one of: 'forward', 'back', 'both', 'none' =item tailclip, headclip when set to false disables endpoint shape clipping =item arrowhead, arrowtail sets the type for the arrow head or tail. Can be one of: 'none', 'normal', 'inv', 'dot', 'odot', 'invdot', 'invodot.' =item arrowsize sets the arrow size: (norm_length=10,norm_width=5, inv_length=6,inv_width=7,dot_radius=2) =item headlabel, taillabel sets the text for port labels. Note that labelfontcolor, labelfontname, labelfontsize are also allowed =item labeldistance, port_label_distance sets the distance from the edge / port to the label. Also labelangle =item decorate if set, draws a line from the edge to the label =item samehead, sametail if set aim edges having the same value to the same port, using the average landing point =item constraint if set to false causes an edge to be ignored for rank assignment =back Additionally, adding edges between ports of a node is done via the 'from_port' and 'to_port' parameters, which currently takes in the offset of the port (ie 0, 1, 2...). $g->add_edge('London' => 'Paris', from_port => 0); =cut sub add_edge { my $self = shift; my $edge = shift; # Also cope with simple $from => $to if ( ref($edge) ne 'HASH' ) { my $from = $edge; my %edge = ( from => $from, to => shift, @_ ); $edge = \%edge; } $self->add_edge_munge($edge) if $self->can('add_edge_munge'); if ( not exists $edge->{from} or not exists $edge->{to} ) { carp("GraphViz add_edge: 'from' or 'to' parameter missing!"); return; } my $from = $edge->{from}; my $to = $edge->{to}; $self->add_node($from) unless exists $self->{NODES}->{$from}; $self->add_node($to) unless exists $self->{NODES}->{$to}; push @{ $self->{EDGES} }, $edge; # should remove! } =head2 as_canon, as_text, as_gif etc. methods There are a number of methods which generate input for dot / neato / twopi / circo / fdp or output the graph in a variety of formats. Note that if you pass a filename, the data is written to that filename. If you pass a filehandle, the data will be streamed to the filehandle. If you pass a scalar reference, then the data will be stored in that scalar. If you pass it a code reference, then it is called with the data (note that the coderef may be called multiple times if the image is large). Otherwise, the data is returned: B you will probably want to binmode any filehandles you write the output to if you want your application to be portable to Win32. my $png_image = $g->as_png; # or $g->as_png("pretty.png"); # save image # or $g->as_png(\*STDOUT); # stream image to a filehandle # or #g->as_png(\$text); # save data in a scalar # or $g->as_png(sub { $png_image .= shift }); =over 4 =item as_debug The as_debug method returns the dot file which we pass to GraphViz. It does not lay out the graph. This is mostly useful for debugging. print $g->as_debug; =item as_canon The as_canon method returns the canonical dot / neato / twopi / circo / fdp file which corresponds to the graph. It does not layout the graph - every other as_* method does. print $g->as_canon; # prints out something like: digraph test { node [ label = "\N" ]; London [label=London]; Paris [label="City of\nlurve"]; New_York [label="New York"]; London -> Paris; London -> New_York [label=Far]; Paris -> London; } =item as_text The as_text method returns text which is a layed-out dot / neato / twopi / circo / fdp format file. print $g->as_text; # prints out something like: digraph test { node [ label = "\N" ]; graph [bb= "0,0,162,134"]; London [label=London, pos="33,116", width="0.89", height="0.50"]; Paris [label="City of\nlurve", pos="33,23", width="0.92", height="0.62"]; New_York [label="New York", pos="123,23", width="1.08", height="0.50"]; London -> Paris [pos="e,27,45 28,98 26,86 26,70 27,55"]; London -> New_York [label=Far, pos="e,107,40 49,100 63,85 84,63 101,46", lp="99,72"]; Paris -> London [pos="s,38,98 39,92 40,78 40,60 39,45"]; } =item as_ps Returns a string which contains a layed-out PostScript-format file. print $g->as_ps; =item as_hpgl Returns a string which contains a layed-out HP pen plotter-format file. print $g->as_hpgl; =item as_pcl Returns a string which contains a layed-out Laserjet printer-format file. print $g->as_pcl; =item as_mif Returns a string which contains a layed-out FrameMaker graphics-format file. print $g->as_mif; =item as_pic Returns a string which contains a layed-out PIC-format file. print $g->as_pic; =item as_gd Returns a string which contains a layed-out GD-format file. print $g->as_gd; =item as_gd2 Returns a string which contains a layed-out GD2-format file. print $g->as_gd2; =item as_gif Returns a string which contains a layed-out GIF-format file. print $g->as_gif; =item as_jpeg Returns a string which contains a layed-out JPEG-format file. print $g->as_jpeg; =item as_png Returns a string which contains a layed-out PNG-format file. print $g->as_png; $g->as_png("pretty.png"); # save image =item as_wbmp Returns a string which contains a layed-out Windows BMP-format file. print $g->as_wbmp; =item as_cmap (deprecated) Returns a string which contains a layed-out HTML client-side image map format file. Use as_cmapx instead. print $g->as_cmap; =item as_cmapx Returns a string which contains a layed-out HTML HTML/X client-side image map format file. Name and id attributes of map element are set to name of the graph. print $g->as_cmapx; =item as_ismap (deprecated) Returns a string which contains a layed-out old-style server-side image map format file. Use as_imap instead. print $g->as_ismap; =item as_imap Returns a string which contains a layed-out HTML new-style server-side image map format file. print $g->as_imap; =item as_vdx Returns a string which contains a VDX-format (Microsoft Visio) file. print $g->as_vdx; =item as_vrml Returns a string which contains a layed-out VRML-format file. print $g->as_vrml; =item as_vtx Returns a string which contains a layed-out VTX (Visual Thought) format file. print $g->as_vtx; =item as_mp Returns a string which contains a layed-out MetaPost-format file. print $g->as_mp; =item as_fig Returns a string which contains a layed-out FIG-format file. print $g->as_fig; =item as_svg Returns a string which contains a layed-out SVG-format file. print $g->as_svg; =item as_svgz Returns a string which contains a layed-out SVG-format file that is compressed. print $g->as_svgz; =item as_plain Returns a string which contains a layed-out simple-format file. print $g->as_plain; =back =cut # Generate magic methods to save typing sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak("$self is not an object"); my $output = shift; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion return if $name =~ /DESTROY/; if ( $name eq 'as_text' ) { $name = "as_dot"; } if ( $name =~ /^as_(ps|hpgl|pcl|mif|pic|gd|gd2|gif|jpeg|png|wbmp|cmapx?|ismap|imap|vdx|vrml|vtx|mp|fig|svgz?|dot|canon|plain)$/ ) { my $data = $self->_as_generic( '-T' . $1, $self->_as_debug, $output ); return $data; } croak "Method $name not defined!"; } # Return the main dot text sub as_debug { my $self = shift; return $self->_as_debug(@_); } sub _as_debug { my $self = shift; my $dot; my $graph_type = $self->{DIRECTED} ? 'digraph' : 'graph'; $dot .= $graph_type . " " . $self->{NAME} . " {\n"; # the direction of the graph if ($self->{RANK_DIR}) { $self->{RANK_DIR} = uc $self->{RANK_DIR}; my(%valid) = (BT => 1, LR => 1, RL => 1, TB => 1); $self->{RANK_DIR} = 'LR' if (! $valid{$self->{RANK_DIR} }); $dot .= "\trankdir=" . $self->{RANK_DIR} . ";\n"; } # the size of the graph $dot .= "\tsize=\"" . $self->{WIDTH} . "," . $self->{HEIGHT} . "\";\n" if $self->{WIDTH} && $self->{HEIGHT}; $dot .= "\tpage=\"" . $self->{PAGEWIDTH} . "," . $self->{PAGEHEIGHT} . "\";\n" if $self->{PAGEWIDTH} && $self->{PAGEHEIGHT}; # Ratio setting $dot .= "\tratio=\"" . $self->{RATIO} . "\";\n"; # edge merging $dot .= "\tconcentrate=true;\n" if $self->{CONCENTRATE}; # Orientation $dot .= "\torientation=$self->{ORIENTATION};\n" if $self->{ORIENTATION}; # epsilon $dot .= "\tepsilon=" . $self->{EPSILON} . ";\n" if $self->{EPSILON}; # random start $dot .= "\tstart=rand;\n" if $self->{RANDOM_START}; # overlap $dot .= "\toverlap=\"" . $self->{OVERLAP} . "\";\n" if $self->{OVERLAP}; # color, bgcolor $dot .= "\tbgcolor=\"" . $self->{BGCOLOR} . "\";\n" if $self->{BGCOLOR}; # Global node, edge and graph attributes $dot .= "\tnode" . _attributes( $self->{NODE_ATTRS} ) . ";\n" if exists( $self->{NODE_ATTRS} ); $dot .= "\tedge" . _attributes( $self->{EDGE_ATTRS} ) . ";\n" if exists( $self->{EDGE_ATTRS} ); $dot .= "\tgraph" . _attributes( $self->{GRAPH_ATTRS} ) . ";\n" if exists( $self->{GRAPH_ATTRS} ); my %clusters = (); my %cluster_nodes = (); my %clusters_edge = (); my $arrow = $self->{DIRECTED} ? ' -> ' : ' -- '; # Add all the nodes my @nodelist = @{ $self->{NODELIST} }; @nodelist = sort @nodelist if $self->{SORT}; foreach my $name (@nodelist) { my $node = $self->{NODES}->{$name}; # Note all the clusters if ( exists $node->{cluster} && $node->{cluster} ) { # map "name" to value in case cluster attribute is not a simple string $clusters{ $node->{cluster} } = $node->{cluster}; push @{ $cluster_nodes{ $node->{cluster} } }, $name; next; } $dot .= "\t" . $node->{_code} . _attributes($node) . ";\n"; } # Add all the edges foreach my $edge ( sort { $a->{from} cmp $b->{from} || $a->{to} cmp $b->{to} } @{ $self->{EDGES} } ) { my $from = $self->{NODES}->{ $edge->{from} }->{_code}; my $to = $self->{NODES}->{ $edge->{to} }->{_code}; # Deal with ports if ( exists $edge->{from_port} ) { $from = '"' . $from . '"' . ':port' . $edge->{from_port}; } if ( exists $edge->{to_port} ) { $to = '"' . $to . '"' . ':port' . $edge->{to_port}; } if ( exists $self->{NODES}->{$from} && exists $self->{NODES}->{$from}->{cluster} && exists $self->{NODES}->{$to} && exists $self->{NODES}->{$to}->{cluster} && $self->{NODES}->{$from}->{cluster} eq $self->{NODES}->{$to}->{cluster} ) { $clusters_edge{ $self->{NODES}->{$from}->{cluster} } .= "\t\t" . $from . $arrow . $to . _attributes($edge) . ";\n"; } else { $dot .= "\t" . $from . $arrow . $to . _attributes($edge) . ";\n"; } } foreach my $clustername ( sort keys %cluster_nodes ) { my $cluster = $clusters{$clustername}; my $attrs; my $name; if ( ref($cluster) eq 'HASH' ) { if ( exists $cluster->{label} ) { $name = $cluster->{label}; } elsif ( exists $cluster->{name} ) { # "coerce" name attribute into label attribute $name = $cluster->{name}; $cluster->{label} = $name; delete $cluster->{name}; } $attrs = _attributes($cluster); } else { $name = $cluster; $attrs = _attributes( { label => $cluster } ); } # rewrite attributes string slightly $attrs =~ s/^\s\[//o; $attrs =~ s/,/;/go; $attrs =~ s/\]$//o; $dot .= "\tsubgraph cluster_" . $self->_quote_name($name) . " {\n"; $dot .= "\t\t$attrs;\n"; $dot .= join "", map { "\t\t" . $self->{NODES}->{$_}->{_code} . _attributes( $self->{NODES}->{$_} ) . ";\n"; } ( @{ $cluster_nodes{$cluster} } ); $dot .= $clusters_edge{$cluster} if exists $clusters_edge{$cluster}; $dot .= "\t}\n"; } # Deal with ranks my %ranks; foreach my $name (@nodelist) { my $node = $self->{NODES}->{$name}; next unless exists $node->{rank}; push @{ $ranks{ $node->{rank} } }, $name; } foreach my $rank ( keys %ranks ) { $dot .= qq|\t{rank=same; |; $dot .= join '; ', map { $self->_quote_name($_) } @{ $ranks{$rank} }; $dot .= qq|}\n|; } # {rank=same; Paris; Boston} $dot .= "}\n"; return $dot; } # Call dot / neato / twopi / circo / fdp with the input text and any parameters sub _as_generic { my ( $self, $type, $dot, $output ) = @_; my $buffer; my $out; if ( ref $output || UNIVERSAL::isa( \$output, 'GLOB' ) ) { # $output is a filehandle or a scalar reference or something. # have to take a reference to a bare filehandle or run will # complain $out = ref $output ? $output : \$output; } elsif ( defined $output ) { # if it's defined it must be a filename so we'll write to it. $out = $output; } else { # but otherwise we capture output in a scalar $out = \$buffer; } my $program = $self->{LAYOUT}; run [ $program, $type ], \$dot, ">", binary(), $out; return $buffer unless defined $output; } # Quote a node/edge name using dot / neato / circo / fdp / twopi's quoting rules sub _quote_name { my ( $self, $name ) = @_; my $realname = $name; return $self->{_QUOTE_NAME_CACHE}->{$name} if $name && exists $self->{_QUOTE_NAME_CACHE}->{$name}; if ( defined $name && $name =~ /^[a-zA-Z]\w*$/ && $name ne "graph" ) { # name is fine } elsif ( defined $name && $name =~ /^[a-zA-Z](\w| )*$/ ) { # name contains spaces, so quote it $name = '"' . $name . '"'; } else { # name contains weird characters - let's make up a name for it $name = 'node' . ++$self->{_NAME_COUNTER}; } $self->{_QUOTE_NAME_CACHE}->{$realname} = $name if defined $realname; # warn "# $realname -> $name\n"; return $name; } # Return the attributes of a node or edge as a dot / neato / circo / fdp / twopi attribute # string sub _attributes { my $thing = shift; my @attributes; foreach my $key ( keys %$thing ) { next if $key =~ /^_/; next if $key =~ /^(to|from|name|cluster|from_port|to_port)$/; my $value = $thing->{$key} || ''; if ( $key ne 'label' || $value !~ /^<:1: syntax error near line 1 context: digraph >>> Graph <<< { Graphviz reserves some words as keywords, meaning they can't be used as an ID, e.g. for the name of the graph. So, don't do this: strict graph graph{...} strict graph Graph{...} strict graph strict{...} etc... Likewise for non-strict graphs, and digraphs. You can however add double-quotes around such reserved words: strict graph "graph"{...} Even better, use a more meaningful name for your graph... The keywords are: node, edge, graph, digraph, subgraph and strict. Compass points are not keywords. See L in the discussion of the syntax of DOT for details. =head2 How do you handle XXE within XML? Due to security risks with XXE in XML, Graphviz does not support XML that contains XXE. Thus it automatically prevents external entities being parsed by using the no_xxe option in L when calling XML::Twig -> new(). And for this reason also the pre-reqs in Makefile.PL specify XML::Twig V 3.52. See L =head1 NOTES Older versions of GraphViz used a slightly different syntax for node and edge adding (with hash references). The new format is slightly clearer, although for the moment we support both. Use the new, clear syntax, please. =head1 SEE ALSO GraphViz is deprecated in favour of L. =head1 Machine-Readable Change Log The file Changes was converted into Changelog.ini by L. =head1 Repository L =head1 AUTHOR Leon Brocard: EFE. Current maintainer: Ron Savage Iron@savage.net.auE>. My homepage: L. =head1 COPYRIGHT Copyright (C) 2000-4, Leon Brocard =head1 LICENSE This module is free software; you can redistribute it or modify it under the Perl License, a copy of which is available at L. =cut 1; GraphViz-2.26/lib/Devel/0000755000175000017500000000000014400436444014733 5ustar osboxesosboxesGraphViz-2.26/lib/Devel/GraphVizProf.pm0000644000175000017500000002504114400435020017641 0ustar osboxesosboxespackage Devel::GraphVizProf; # To help the CPAN indexer to identify us our $VERSION = '2.24'; package DB; use Time::HiRes 'time'; use strict; BEGIN { $DB::drop_zeros = 0; $DB::profile = 1; if (-e '.smallprof') { do '.smallprof'; } $DB::prevf = ''; $DB::prevl = 0; my($diff,$cdiff); my($testDB) = sub { my($pkg,$filename,$line) = caller; $DB::profile || return; %DB::packages && !$DB::packages{$pkg} && return; }; # "Null time" compensation code $DB::nulltime = 0; for (1..100) { my($u,$s,$cu,$cs) = times; $DB::cstart = $u+$s+$cu+$cs; $DB::start = time; &$testDB; ($u,$s,$cu,$cs) = times; $DB::cdone = $u+$s+$cu+$cs; $DB::done = time; $diff = $DB::done - $DB::start; $DB::nulltime += $diff; } $DB::nulltime /= 100; my($u,$s,$cu,$cs) = times; $DB::cstart = $u+$s+$cu+$cs; $DB::start = time; } sub DB { my($pkg,$filename,$line) = caller; $DB::profile || return; %DB::packages && !$DB::packages{$pkg} && return; my($u,$s,$cu,$cs) = times; $DB::cdone = $u+$s+$cu+$cs; $DB::done = time; # Now save the _< array for later reference. If we don't do this here, # evals which do not define subroutines will disappear. no strict 'refs'; $DB::listings{$filename} = \@{"main::_<$filename"} if @{"main::_<$filename"}; use strict 'refs'; # warn $DB::prevl . " -> " . $line . "\n"; # $DB::calls{$DB::prevf}->{$DB::prevl}->{$filename}->{$line}++; $DB::calls{$filename}->{$line}->{$DB::prevf}->{$DB::prevl}++; my($delta); $delta = $DB::done - $DB::start; $delta = ($delta > $DB::nulltime) ? $delta - $DB::nulltime : 0; $DB::profiles{$filename}->[$line]++; $DB::times{$DB::prevf}->[$DB::prevl] += $delta; $DB::ctimes{$DB::prevf}->[$DB::prevl] += ($DB::cdone - $DB::cstart); ($DB::prevf, $DB::prevl) = ($filename, $line); ($u,$s,$cu,$cs) = times; $DB::cstart = $u+$s+$cu+$cs; $DB::start = time; } END { # Get time on last line executed. my($u,$s,$cu,$cs) = times; $DB::cdone = $u+$s+$cu+$cs; $DB::done = time; my($delta); $delta = $DB::done - $DB::start; $delta = ($delta > $DB::nulltime) ? $delta - $DB::nulltime : 0; $DB::times{$DB::prevf}->[$DB::prevl] += $delta; $DB::ctimes{$DB::prevf}->[$DB::prevl] += ($DB::cdone - $DB::cstart); # Now write out the results. # open(OUT,">graphvizprof.dot"); # select OUT; my($i,$stat,$time,$ctime,$line,$file,$page); $page = 1; my %seenlabel; my $maxcalls = 1; my $maxtime = 0; foreach $file (sort keys %DB::profiles) { $- = 0; if (defined($DB::listings{$file})) { $i = -1; foreach $line (@{$DB::listings{$file}}) { ++$i or next; my $time = defined($DB::ctimes{$file}->[$i]) ? $DB::ctimes{$file}->[$i] : 0; $maxtime = $time if $time > $maxtime; foreach my $file (sort keys %{$DB::calls{$file}->{$i}}) { foreach my $j (sort {$a <=> $b} keys %{$DB::calls{$file}->{$i}->{$file}}) { my $calls = $DB::calls{$file}->{$i}->{$file}->{$j}; $maxcalls = $calls if $calls > $maxcalls; } } } } } use GraphViz; my $g = GraphViz->new(); foreach $file (sort keys %DB::profiles) { $- = 0; if (defined($DB::listings{$file})) { $i = -1; foreach $line (@{$DB::listings{$file}}) { ++$i or next; $line = "" unless defined $line; chomp($line); $stat = $DB::profiles{$file}->[$i] || 0 or !$DB::drop_zeros or next; $time = defined($DB::times{$file}->[$i]) ? $DB::times{$file}->[$i] : 0; $ctime = defined($DB::ctimes{$file}->[$i]) ? $DB::ctimes{$file}->[$i] : 0; my $label = getlabel($file . $i); my $name = getname($file, $i); foreach my $file (sort keys %{$DB::calls{$file}->{$i}}) { foreach my $j (sort {$a <=> $b} keys %{$DB::calls{$file}->{$i}->{$file}}) { my $calls = $DB::calls{$file}->{$i}->{$file}->{$j}; # next unless $calls > 2; my $fromlabel = getlabel($file . $j); my $ratio = $ctime / $maxtime; $g->add_node("$file/$name", label => $name, color => "0,1,$ratio") unless ($name =~ m|/| || $seenlabel{"$file/$name"}++); my $fromtime = defined($DB::ctimes{$file}->[$j]) ? $DB::times{$file}->[$j] : 0; $ratio = $fromtime / $maxtime; my $fromname = getname($file, $j); $g->add_node("$file/$fromname", label => $fromname, color => "0,1,$ratio") unless $seenlabel{"$file/$fromname"}++; my $ratio = $calls / $maxcalls; my $w = 100 * (1 - $ratio); $g->add_edge("$file/$fromname" => "$file/$name", color => "0,1,$ratio", w => $w, len => 2); } } } } else { # print "# The code for $file is not in the symbol table."; } } print $g->_as_debug; } sub getname { my($file, $lineno) = @_; # return "$file line $lineno"; my $line = $DB::listings{$file}->[$lineno]; $line = "" unless defined $line; chomp $line; $line =~ s|"|\\"|g; $line =~ s|^\s+||g; # return "$file: $lineno"; # return "$lineno: $line"; return $line; } { my $labelcount; my %label; sub getlabel { my $url = shift; return $label{$url} if exists $label{$url}; $labelcount++; # warn "miss $url\n"; my $label = 'n' . $labelcount; $label{$url} = $label; return $label; } } sub sub { no strict 'refs'; goto &$DB::sub unless $DB::profile; if (defined($DB::sub{$DB::sub})) { my($m,$s) = ($DB::sub{$DB::sub} =~ /.+(?=:)|[^:-]+/g); $DB::profiles{$m}->[$s]++; $DB::listings{$m} = \@{"main::_<$m"} if @{"main::_<$m"}; } goto &$DB::sub; } 1; __END__ =head1 NAME Devel::GraphVizProf - per-line Perl profiler (with graph output) =head1 SYNOPSIS perl -d:GraphVizProf test.pl > test.dot dot -Tpng test.dot > test.png =head1 DESCRIPTION NOTE: This module is a hack of Devel::SmallProf by Ted Ashton. It has been modified by Leon Brocard to produce output for GraphViz, but otherwise the only thing I have done is change the name. I hope to get my patches put into the main Devel::SmallProf code eventually, or alternatively read the output of Devel::SmallProf. Anyway, the normal documentation, which you can probably ignore, follows. The Devel::GraphVizProf profiler is focused on the time taken for a program run on a line-by-line basis. It is intended to be as "small" in terms of impact on the speed and memory usage of the profiled program as possible and also in terms of being simple to use. Those statistics are placed in the file F in the following format: