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();

        $graph->add_edge ('Bonn', 'Berlin');

        print $graph->as_ascii( );

        # prints:

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

        # slightly more verbose way:

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

        my $bonn = Graph::Easy->add_node('Bonn');
        $bonn->set_attribute('border', 'solid 1px black')

        my $berlin = $graph->add_node('Berlin');

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

        print $graph->as_ascii( );

        # adding edges with attributes:

        my $edge = Graph::Easy::Edge->new();
        $edge->set_attributes(
                label => 'train',
                style => 'dotted',
                color => 'red',
        );

        # now with the optional edge object
        $graph->add_edge ($bonn, $berlin, $edge);

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

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

        # 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 (possible after you installed Graph::Easy::As_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 nodes connected by edges (with optional labels).

It works on a grid (manhattan layout), and thus the output is most usefull for flow charts, network diagrams, or hierarchy 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 the EXAMPLES section below for how this might be rendered.

Creating graphs

First, create a graph object:

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

Then add a node to it:

        my $node = $graph->add_node('Koblenz');

Don't worry, adding the node again will do nothing:

        $node = $graph->add_node('Koblenz');

You can get back a node by its name with node():

        $node = $graph->node('Koblenz');

You can either add another node:

        my $second = $graph->node('Frankfurt');

Or add an edge straight-away:

        my ($first,$second,$edge) = $graph->add_edge('Mainz','Ulm');

Adding the edge the second time creates another edge from 'Mainz' to 'Ulm':

        my $other_edge;
         ($first,$second,$other_edge) = $graph->add_edge('Mainz','Ulm');

You can even add a self-loop:

        $graph->add_edge('Bremen','Bremen');

Output

The output can be done in various styles:

ASCII ART

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

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', 'neato' or similiar programs.

EXAMPLES

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

You can also see many more 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:

        [ Dresden ]

Two nodes

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

        [ Bonn ] -> [ Berlin ]

Three nodes

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

        [ Bonn ] -> [ Berlin ]
        [ Bonn ] -> [ Hamburg ]

Three nodes in a chain

A graph consisting of three nodes, showing that you can chain connections together:

        [ Bonn ] -> [ Berlin ] -> [ Hamburg ]

Two not connected graphs

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

        [ Bonn ] -> [ Berlin ]
        [ Freiburg ] -> [ Hamburg ]

Three nodes, interlinked

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

        [ Bonn ] -> [ Berlin ]
        [ Berlin ] -> [ Hamburg ]
        [ Bonn ] -> [ Hamburg ]

Different edge styles

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

        [ Bonn ] <-> [ Berlin ]         # bidirectional
        [ Berlin ] ==> [ Rostock ]      # double
        [ Hamburg ] ..> [ Altona ]      # dotted
        [ Dresden ] - > [ Bautzen ]     # dashed
        [ Leipzig ] ~~> [ Kirchhain ]   # wave
        [ Hof ] .-> [ Chemnitz ]        # dot-dash
        [ Magdeburg ] <=> [ Ulm ]       # bidrectional, double etc
        [ Magdeburg ] -- [ Ulm ]        # arrow-less edge

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

error()

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

Returns the last error or '' for none. Optionally, takes an error message to be set.

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

add_edge()

        my ($first, $second, $edge) = $graph->add_edge( 'node 1', 'node 2');
        my $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.

When called in scalar context, will return $edge. In array/list context it will return the two nodes and the edge object.

$x and $y should be either plain scalars with the names of the nodes, or objects of Graph::Easy::Node, while the optional $edge should be Graph::Easy::Edge.

Note: Graph::Easy graphs are multi-edged, and adding the same edge twice will result in two edges going from $x to $y!

You can use edge() to check whether an edge from X to Y already exists in the graph.

add_node()

        my $node = $graph->add_node( 'Node 1' );
        $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.

It returns an Graph::Easy::Node object.

del_node()

        $graph->del_node('Node name');
        $graph->del_node($node);

Delete the node with the given name from the graph.

del_edge()

        $graph->del_edge($edge);

Delete the given edge object from the graph. You can use edge() to find an edge from Node A to B:

        $graph->del_edge( $graph->edge('A','B') );

merge_nodes()

        $graph->merge_nodes( $first_node, $second_node );

Merge two nodes. Will delete all connections between the two nodes, then move over any connection to/from the second node to the first, then delete the second node from the graph.

Any set attributes on the second node will be lost.

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' } );

del_attribute()

        $graph->del_attribute('border');

Delete the attribute with the given name.

border_attribute()

        my $border = $graph->border_attribute();

Return the combined border attribute like "1px solid red" from the border-(style|color|width) attributes.

split_border_attributes()

        my ($style,$width,$color) = $graph->split_border_attribute($border);

Split the border attribute (like "1px solid red") into the three different parts.

direction_as_number()

        my $graph = direction_as_number($dir);
  

Convert a given direction like "north" or "right" into in degrees (0, 90, 180 or 270).

timeout()

        print $graph->timeout(), " seconds timeout for layouts.\n";
        $graph->timeout(12);

Get/set the timeut for layouts in seconds. If the layout process did not finish after that time, it will be stopped and a warning will be printed.

strict()

        print "Graph has strict checking\n" if $graph->strict();
        $graph->strict(undef);          # disable strict attribute checks

Get/set the strict option. When set to a true value, all attribute names and values will be strictly checked and unknown/invalid one will be rejected.

This option is on by default.

layout()

        $graph->layout();

Creates the internal structures to layout the graph. Usually you need not to call this method, because it will be done automatically when you call any of the as_FOO methods below.

See also: timeout().

output_format()

        $graph->output_format('html');

Set the outputformat. One of 'html', 'ascii', 'graphviz' or 'txt'. See also output().

output()

        my $out = $graph->output();

Output the graph in the format set by output_format().

as_ascii()

        print $graph->as_ascii();

Return the graph layout in ASCII art, in utf-8.

as_ascii_file()

        print $graph->as_ascii_file();

Is an alias for as_ascii.

as_ascii_html()

        print $graph->as_ascii_html();

Return the graph layout in ASCII art, suitable to be embedded into an HTML page. Basically it wraps the output from as_ascii() into <pre> </pre> and inserts real HTML links. The returned string is in utf-8.

as_boxart()

        print $graph->as_box();

Return the graph layout as box drawing using Unicode characters in utf-8.

as_boxart_file()

        print $graph->as_boxart_file();

Is an alias for as_box.

as_boxart_html()

        print $graph->as_boxart_html();

Return the graph layout as box drawing using Unicode characters, as chunk that can be embedded into an HTML page.

Basically it wraps the output from as_boxart() into <pre> </pre> and inserts real HTML links. The returned string is in utf-8.

as_boxart_html_file()

        print $graph->as_boxart_html_file();

Return the graph layout as box drawing using Unicode characters, as a full HTML page complete with header and footer.

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_file().

as_html_page()

        print $graph->as_html_page();

Is an alias for as_html_file.

as_html_file()

        print $graph->as_html_file();

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

add_group()

        my $group = $graph->add_group('Group name');

Add a group to the graph and return it as Graph::Easy::Group object.

group()

        my $group = $graph->group('Name');

Returns the group with the name Name as Graph::Easy::Group object.

groups()

        my @groups = $graph->groups();

Returns the groups of the graph as Graph::Easy::Group objects.

del_group()

        $graph->del_group($name);

Delete the group with the given name.

edges()

        my @edges = $graph->edges();

Returns the edges of the graph as Graph::Easy::Edge objects.

is_simple_graph()

        if ($graph->is_simple_graph())
          {
          }

Returns true if the graph does not have multiedges.

label()

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

Returns the label of the graph.

title()

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

Returns the title of the graph.

link()

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

Return the link, build from linkbase and link (or autolink). Returns '' if there is no link.

as_graphviz()

        print $graph->as_graphviz();

Return the graph as graphviz code, suitable to be feed to a program like dot etc.

as_graphviz_file()

        print $graph->as_graphviz_file();

Is an alias for as_graphviz().

nodes()

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

In scalar context, returns the number of nodes/vertices the graph has.

In list context, returns all nodes as objects.

anon_nodes()

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

In scalar context, returns the number of anon nodes (aka Graph::Easy::Node::Anon) the graph has.

In list context, returns all anon nodes as objects.

html_page_header()

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

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

Takes an optional parameter with the CSS styles to be inserted into the header. If $css is not defined, embedds the result of $self->css().

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, than a certain layout.

as_txt_file()

        print $graph->as_txt_file();

Is an alias for as_txt().

as_svg()

        print $graph->as_svg();

Return the graph as SVG (Scalable Vector Graphics), which can be embedded into HTML pages. You need to install Graph::Easy::As_svg first to make this work.

See also as_svg_file().

as_svg_file()

        print $graph->as_svg_file();

Returns SVG just like as_svg(), but this time as standalone SVG, suitable for storing it in a file and referencing it externally.

After calling as_svg_file() or as_svg(), you can retrieve some SVG information, notable width and height via svg_information.

svg_information()

        my $info = $graph->svg_information();

        print "Size: $info->{width}, $info->{height}\n";

Return information about the graph created by the last as_svg() or as_svg_file() call.

The following fields are set:

        width           width of the SVG in pixels
        height          height of the SVG in pixels

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( $x, $y );

Returns the edge objects between nodes $x and $y. Both $x and $y can be either scalars with names or Graph::Easy::Node objects.

Returns undef if the edge does not yet exist.

In list context it will return all edges from $x to $y, in scalar context it will return only one (arbitrary) edge.

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.

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().

debug()

        my $debug = $graph->debug();    # get
        $graph->debug(1);               # enable
        $graph->debug(0);               # disable

Enable, disable or read out the debug status. When the debug status is true, additional debug messages will be printed on STDERR.

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

valid_attribute()

        my $new_value =
          Graph::Easy->valid_attribute( $name, $value, $class );

        if (ref($new_value) eq 'ARRAY')
          {
          # throw error
          die ("'$name' is not a valid attribute name for '$class'");
          }
        elsif (!defined $new_value)
          {
          # throw error
          die ("'$value' is no valid '$name' for '$class'");
          }

Check that a $name,$value pair is a valid attribute in class $class, and returns a new value.

The return value can differ from the passed in value, f.i.:

        print Graph::Easy->valid_attribute( 'color', 'red' );

This would print '#ff0000';

It returns an array ref if the attribute name is invalid, and undef if the value is invalid.

angle()

        my $degrees = Graph::Easy->angle( 'right' );
        my $degrees = Graph::Easy->angle( 120 );

Check an angle for being valid and return a value between -359 and 359 degrees. The special values right, left, up and down are also valid and converted to 90, -90, 0 and 180 degrees, respectively.

color_as_hex()

        my $hexred   = Graph::Easy->color_as_hex( 'red' );
        my $hexblue  = Graph::Easy->color_as_hex( '#0000ff' );
        my $hexcyan  = Graph::Easy->color_as_hex( '#f0f' );
        my $hexgreen = Graph::Easy->color_as_hex( 'rgb(0,255,0)' );

Takes a valid color name or definition (hex, short hex, or RGB) and returns the color in hex like #ff00ff.

color_name()

        my $color = Graph::Easy->color_name( 'red' );   # red
        print Graph::Easy->color_name( '#ff0000' );     # red

Takes a hex color value and returns the name of the color.

color_names()

        my $names = Graph::Easy->color_names();

Return a hash with name => value mapping for all known colors.

text_style()

        if ($graph->text_style('bold, italic'))
          {
          ...
          }

Checks the given style list for being valid.

text_styles()

        my $styles = $graph->text_styles();     # or $edge->text_styles() etc.

        if ($styles->{'italic'})
          {
          print 'is italic\n';
          }

Return a hash with the given text-style properties, aka 'underline', 'bold' etc.

text_styles_as_css()

        my $styles = $graph->text_styles_as_css();      # or $edge->...() etc.

Return the text styles as a chunk of CSS styling that can be embedded into a style="" parameter.

EXPORT

Exports nothing.

SEE ALSO

Graph::Easy::As_svg, 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.

Scoring

Scoring is not yet implemented, each generated graph will be the same regardless of the random seed.

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-graph)
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 ]
scopes

Paths

Too bendy paths

The A* algorithm sometimes creates unnecessary bends in a path. A tweak which will prevent would be decreasing the value of an already open node, but this has not yet been implemented.

No joints

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

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

Non-optimal layouts like this one might appear from time to time:

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

A second-stage optimizer that simplifies these layouts is not yet implemented.

In addition the general placement/processing strategy as well as the local strategy might be improved.

Output formats

Some output formats are not yet complete in their implementation. Please see the online manual at http://bloodgate.com/perl/graph/manual under "Output" for details.

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 was formerly known 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 even when the graphs are quite complex.

AUTHOR

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