The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Graph::Easy - Render graphs as ASCII, HTML, SVG or Graphviz

SYNOPSIS

        use Graph::Easy;
        
        my $graph = Graph::Easy->new();

        my $bonn = Graph::Easy::Node->new(
                name => 'Bonn',
                border => 'solid 1px black',
        );
        my $berlin = Graph::Easy::Node->new(
                name => 'Berlin',
        );

        $graph->add_edge ($bonn, $berlin);

        $graph->layout();

        print $graph->as_ascii( );

        # prints:

        # +------+     +--------+
        # | Bonn | --> | Berlin |
        # +------+     +--------+

        # raw HTML section
        print $graph->as_html( );

        # complete HTML page (with CSS)
        print $graph->as_html_page( );

        # creating a graph from a textual description   
        use Graph::Easy::Parser;
        my $parser = Graph::Easy::Parser->new();

        my $graph = $parser->from_text(
                "[ Bonn ] => [ Berlin ] \n".
                "[ Bonn ] => [ Rostock ]"
        );

        print $graph->as_ascii( );

        # Outputs something like:

        # +------+       +---------+
        # | Bonn |   --> | Rostock |
        # +------+       +---------+
        #   |
        #   |
        #   v
        # +--------+
        # | Berlin |
        # +--------+

        # Other possibilities:

        # SVG:
        print $graph->as_svg( );

        # Graphviz:
        my $graphviz = $graph->as_graphviz();
        `dot -Tpng -o graph.png $graphviz`;

DESCRIPTION

Graph::Easy lets you generate graphs consisting of various shaped boxes connected with arrows.

It works on a grid (manhattan layout), and thus the output is most usefull for flow charts, network diagrams, or hirarchy trees.

Input

Apart from driving the module with Perl code, you can also use Graph::Easy::Parser to parse graph descriptions like:

        [ Bonn ]      --> [ Berlin ]
        [ Frankfurt ] <=> [ Dresden ]
        [ Bonn ]      --> [ Frankfurt ]

See EXAMPLES for how this might be rendered.

Output

The output can be done in various styles:

ASCII ART

Uses things like +, - < and | to render the boxes.

BOX ART

Uses the extended ASCII characters to draw seamless boxes. Not yet implemented.

HTML

HTML tables with CSS making everything "pretty".

SVG

Creates a Scalable Vector Graphics output.

Graphviz

Creates graphviz code that can be feed to 'dot' or similiar programs.

EXAMPLES

The following examples are given in the simple text format that is understood by Graph::Easy::Parser.

If you see no ASCII/HTML graph output in the following examples, then your pod2html or pod2txt converter did not recognize the special graph paragraphs.

You can use the converters in examples/ like pod2txt and pod2html in this distribution to generate a pretty page with nice graph "drawings" from this document.

You can also see many different examples at:

http://bloodgate.com/perl/graph/

One node

The most simple graph (apart from the empty one :) is a graph consisting of only one node:

Two nodes

A simple graph consisting of two nodes, linked together by a directed edge:

Three nodes

A graph consisting of three nodes, and both are linked from the first:

Two not connected graphs

A graph consisting of two seperate parts, both of them not connected to each other:

Three nodes, interlinked

A graph consisting of three nodes, and two of the are connected from the first node:

Different edge styles

A graph consisting of a couple of nodes, linked with the different possible edge styles.

More examples at: http://bloodgate.com/perl/graph/

METHODS

Graph::Easy supports the following methods:

new()

        use Graph::Easy;

        my $graph = Graph::Easy->new( );
        

Creates a new, empty Graph::Easy object.

Takes optinal a hash reference with a list of options. The following are valid options:

        debug                   if true, enables debug output

seed()

        my $seed = $graph->seed();
        $graph->seed(2);

Get/set the random seed for the graph object. See randomize() for a method to set a random seed.

The seed is used to create random numbers for the layouter. For the same graph, the same seed will always lead to the same layout.

randomize()

        $graph->randomize();

Set a random seed for the graph object. See seed().

get_attribute()

        my $value = $graph->get_attribute( $class, $name );

Return the value of attribute $name from class $class.

Example:

        my $color = $graph->attribute( 'node', 'color' );

attribute()

        my $value = $graph->attribute( $class, $name );

attribute is an alias for get_attribute.

set_attribute()

        $graph->set_attribute( $class, $name, $val );

Sets a given attribute named $name to the new value $val in the class specified in $class.

Example:

        $graph->set_attribute( 'graph', 'gid', '123' );

The class can be one of graph, edge, node or group. The last three can also have subclasses like in node.subclassname.

set_attributes()

        $graph->set_attributes( $class, $att );

Given a class name in $class and a hash of mappings between attribute names and values in $att, will set all these attributes.

The class can be one of graph, edge, node or group. The last three can also have subclasses like in node.subclassname.

Example:

        $graph->set_attributes( 'node', { color => 'red', background => 'none' } );

score()

        my $score = $graph->score();

Returns the score of the graph, or undef if layout() has not yet been called.

Higher scores are better, although you cannot compare scores for different graphs. The score should only be used to compare different layouts of the same graph against each other:

        my $max = undef;

        $graph->randomize();
        my $seed = $graph->seed(); 

        $graph->layout();
        $max = $graph->score(); 

        for (1..10)
          {
          $graph->randomize();                  # select random seed
          $graph->layout();                     # layout with that seed
          if ($graph->score() > $max)
            {
            $max = $graph->score();             # store the new max store
            $seed = $graph->seed();             # and it's seed
            }
          }

        # redo the best layout
        if ($seed ne $graph->seed())
          {
          $graph->seed($seed);
          $graph->layout();
          }
        # output graph:
        print $graph->as_ascii();               # or as_html() etc

error()

        my $error = $graph->error();

Returns the last error. Optionally, takes an error message to be set.

        $graph->error( 'Expected Foo, but found Bar.' );

layout()

Creates the internal structures to layout the graph. This will be done behind the scenes of you call any of the as_FOO methods.

as_ascii()

        print $graph->as_ascii();

Return the graph layout in ASCII art.

as_ascii_html()

        print $graph->as_ascii_html();

Return the graph layout in ASCII art, suitable to be embedded into an HTML page. Basically wraps the output from as_ascii() into <pre> </pre>.

as_html()

        print $graph->as_html();

Return the graph layout as HTML section. See css() to get the CSS section to go with that HTML code. If you want a complete HTML page then use as_html_page().

as_html_page()

        print $graph->as_html_page();

Return the graph layout as HTML complete with headers, CSS section and footer. Can be viewed in the browser of your choice.

html_page_header()

        my $header = $graph->html_page_header();

Return the header of an HTML page. Used together with html_page_footer by as_html_page to construct a complete HTML page.

html_page_footer()

        my $footer = $graph->html_page_footer();

Return the footer of an HTML page. Used together with html_page_header by as_html_page to construct a complete HTML page.

css()

        my $css = $graph->css();

Return CSS code for that graph. See as_html().

as_txt()

        print $graph->as_txt();

Return the graph as a textual representation, that can be parsed with Graph::Easy::Parser back to a graph.

This does not call layout() since the actual text representation is more a dump of the graph, then a certain layout.

add_edge()

        $graph->add_edge( $x, $y, $edge);
        $graph->add_edge( $x, $y);

Add an edge between nodes X and Y. The optional edge object defines the style of the edge, if not present, a default object will be used.

$x and $y should be objects of Graph::Easy::Node, while $edge should be Graph::Easy::Edge.

add_node()

        $graph->add_node( $x );

Add a single node X to the graph. $x should be either a Graph::Easy::Node object, or a unique name for the node. Will do nothing if the node already exists in the graph.

nodes()

        my $nodes = $graph->nodes();

In scalar context, returns the number of nodes/vertices the graph has. In list context returns a list of all the node objects (as reference).

sorted_nodes()

        my $nodes =
         $graph->sorted_nodes( );               # default sort on 'id'
        my $nodes = 
         $graph->sorted_nodes( 'name' );        # sort on 'name'
        my $nodes = 
         $graph->sorted_nodes( 'layer', 'id' ); # sort on 'layer', then on 'id'

In scalar context, returns the number of nodes/vertices the graph has. In list context returns a list of all the node objects (as reference), sorted by their attribute(s) given as arguments. The default is 'id', e.g. their internal ID number, which amounts more or less to the order they have been inserted.

node()

        my $node = $graph->node('node name');

Return node by unique name (case sensitive). Returns undef if the node does not exist in the graph.

edge()

        my $edge = $graph->edge( $node1, $node2 );

Return edge object between nodes $node1 and $node2. Both nodes can be either names or Graph::Easy::Node objects.

Returns undef if the edge does not yet exist.

id()

        my $graph_id = $graph->id();
        $graph->id('123');

Returns the id of the graph. You can also set a new ID with this routine. The default is ''.

The graph's ID is used to generate unique CSS classes for each graph, in the case you want to have more than one graph in an HTML page.

EXPORT

Exports nothing.

SEE ALSO

Graph::Layout::Aesthetic, Graph and Graph::Easy::Parser.

There is also an very old, unrelated project from ca. 1995, which does something similiar. See http://rw4.cs.uni-sb.de/users/sander/html/gsvcg1.html.

Testcases and more examples under:

http://bloodgate.com/perl/graph/.

LIMITATIONS

This module is a proof-of-concept and has currently some limitations. Hopefully further development will lift these.

Syntax

See http://bloodgate.com/perl/graph/ for limits of the syntax. Mostly this are limitations in the parser, which cannot yet handle the following features:

nesting (graph-in-a-node)
node lists

Node lists only work on the left side of an expression. E.g. the first line works, the second and third do not:

        [ Bonn ], [ Hof ] -> [ Berlin ]
        [ Frankfurt ] -> [ Hamburg ], [ Dresden ]
        [ Cottbus ], [ Kamenz ] -> [ Plauen ], [ Bamberg ]

Paths

No crossing

Currently edges (paths from node to node) cannot cross each other.

No more than two bends

All nodes must be either in straight line of sight (up, down, left or right) of each other or connectable by a path with at most two bends, like shown here:

        +------+     +--------+
        | Bonn | --> | Berlin |
        +------+     +--------+
          |            |
          |            |
          |            v
          |          +---------+
          +--------> | Potsdam |
                     +---------+

        +------+     +--------+      +--------+
        | Bonn | --> | Berlin | -- > | Kassel |
        +------+     +--------+      +--------+
          |             |               ^
          |             |               |
          |             v               |
          |          +--------+         |
          |          | Ulm    |         |
          |          +--------+         |
          |                             |
          +-----------------------------+

Thus the following graph output is not yet possible:

                     +---------+
          +--------> | Koblenz | <---------------+
          |          +---------+                 |
          |             |                        |
          |             |                        |
          |             v                        |
        +------+     +--------+      +--------+  |
        | Bonn | --> | Berlin | -- > | Kassel |  |
        +------+     +--------+      +--------+  |
          |             ^                        |
          |             |                        |
          v             |                        |
        +------+     +---------+                 |
        | Ulm  | --> | Bautzen |                 |
        +------+     +---------+                 |
          |                                      |
          |                                      |
          +--------------------------------------+

For that to work a general path-finding algorithm like A* must be implemented.

No joints

Currently it is not possible that an edge joins another edge like this:

        +------+     +--------+     +-----------+
        | Bonn | --> | Berlin | --> | Magdeburg |
        +------+     +--------+     +-----------+
          |            |              |
          |            |              |
          |            |              v
          |            v            +---------+
          +-----------------------> | Potsdam |
                                    +---------+

This means each node can have at most 4 edges leading to or from it.

No optimizations

The layouter will sometimes generate non-optimal layouts like this:

        +------+     +--------+      +--------+
        | Bonn | --> | Berlin | -- > | Kassel |
        +------+     +--------+      +--------+
          |                             ^
          |                             |
          |                             |
          |                             |
          +-----------------------------+

The layout above should really be converted to this:

        +------+     +--------+
        | Bonn | --> | Berlin |
        +------+     +--------+
          |            |
          |            |
          |            v
          |          +---------+
          +--------> | Kassel  |
                     +---------+

Other non-optimal layouts like this one might also appear from time to time:

        +------+     +--------+
        | Bonn | --> | Berlin |
        +------+     +--------+
                       ^
                       |
                       |
        +---------+    |
        | Kassel  | ---+
        +---------+

A second-stage optimizer that simplifies these layouts does not yet exist.

All the flaws with the edges can be corrected easily, but there was simple not enough time for that yet.

Node-Size

A node is currently always one cell big. Overly broad/wide nodes, or nodes with multiple lines shoud occupy more than one cell. This would also enable them to have more than 4 incoming/outgoing edges.

Placement

Currently the node placement is dependend on the order the nodes were inserted into the graph. In reality it should start with nodes having no or little incoming edges and then progress to nodes with more incoming edges.

A side-effect of this problem is that:

        [ Bonn ] -> [ Berlin ]

results in:

        +------+     +--------+
        | Bonn | --> | Berlin |
        +------+     +--------+

while this equivalent graph:

        [ Berlin ] [ Bonn ] -> [ Berlin ]

results in something like this:

        +--------+     +------+
        | Berlin | <-- | Bonn |
        +--------+     +------+

Grouping

The output of the graphs in ASCII does not yet include the group information.

Other formats

Formats other than ASCII and HTML are not yet complete in their implementation. If you notice any bugs or defiencies, please drop me a note!

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms of the GPL version 2. See the LICENSE file for information.

NAME CHANGE

The package is formerly know as Graph::Simple. The name was changed for two reasons:

  • In graph theory, a simple graph is a special type of graph. This software, however, supports more than simple graphs.

  • Creating graphs should be easy, but the created graphs can also be quite complex.

AUTHOR

Copyright (C) 2004 - 2005 by Tels http://bloodgate.com