Graph-Easy
view release on metacpan or search on metacpan
lib/Graph/Easy.pm view on Meta::CPAN
############################################################################
# Manage, and layout graphs on a flat plane.
#
#############################################################################
package Graph::Easy;
use 5.008002;
use Graph::Easy::Base;
use Graph::Easy::Attributes;
use Graph::Easy::Edge;
use Graph::Easy::Group;
use Graph::Easy::Group::Anon;
use Graph::Easy::Layout;
use Graph::Easy::Node;
use Graph::Easy::Node::Anon;
use Graph::Easy::Node::Empty;
use Scalar::Util qw/weaken/;
$VERSION = '0.76';
@ISA = qw/Graph::Easy::Base/;
use strict;
use warnings;
my $att_aliases;
use Graph::Easy::Util qw(ord_values);
BEGIN
{
# a few aliases for backwards compatibility
*get_attribute = \&attribute;
*as_html_page = \&as_html_file;
*as_graphviz_file = \&as_graphviz;
*as_ascii_file = \&as_ascii;
*as_boxart_file = \&as_boxart;
*as_txt_file = \&as_txt;
*as_vcg_file = \&as_vcg;
*as_gdl_file = \&as_gdl;
*as_graphml_file = \&as_graphml;
# a few aliases for code re-use
*_aligned_label = \&Graph::Easy::Node::_aligned_label;
*quoted_comment = \&Graph::Easy::Node::quoted_comment;
*_un_escape = \&Graph::Easy::Node::_un_escape;
*_convert_pod = \&Graph::Easy::Node::_convert_pod;
*_label_as_html = \&Graph::Easy::Node::_label_as_html;
*_wrapped_label = \&Graph::Easy::Node::_wrapped_label;
*get_color_attribute = \&color_attribute;
*get_custom_attributes = \&Graph::Easy::Node::get_custom_attributes;
*custom_attributes = \&Graph::Easy::Node::get_custom_attributes;
$att_aliases = Graph::Easy::_att_aliases();
# backwards compatibility
*is_simple_graph = \&is_simple;
# compatibility to Graph
*vertices = \&nodes;
}
#############################################################################
sub new
{
# override new() as to not set the {id}
my $class = shift;
# called like "new->('[A]->[B]')":
if (@_ == 1 && !ref($_[0]))
{
require Graph::Easy::Parser;
my $parser = Graph::Easy::Parser->new();
my $self = eval { $parser->from_text($_[0]); };
if (!defined $self)
{
$self = Graph::Easy->new( fatal_errors => 0 );
$self->error( 'Error: ' . $parser->error() ||
'Unknown error while parsing initial text' );
lib/Graph/Easy.pm view on Meta::CPAN
#############################################################################
# as_graphml
sub as_graphml
{
require Graph::Easy::As_graphml;
_as_graphml(@_);
}
#############################################################################
sub add_edge
{
my ($self,$x,$y,$edge) = @_;
my $uc = $self->{use_class};
my $ec = $uc->{edge};
$edge = $ec->new() unless defined $edge;
$edge = $ec->new(label => $edge) unless ref($edge);
$self->_croak("Adding an edge object twice is not possible")
if (exists ($self->{edges}->{$edge->{id}}));
$self->_croak("Cannot add edge $edge ($edge->{id}), it already belongs to another graph")
if ref($edge->{graph}) && $edge->{graph} != $self;
my $nodes = $self->{nodes};
my $groups = $self->{groups};
$self->_croak("Cannot add edge for undefined node names ($x -> $y)")
unless defined $x && defined $y;
my $xn = $x; my $yn = $y;
$xn = $x->{name} if ref($x);
$yn = $y->{name} if ref($y);
# convert plain scalars to Node objects if nec.
# XXX TODO: this might be a problem when adding an edge from a group with the same
# name as a node
$x = $nodes->{$xn} if exists $nodes->{$xn}; # first look them up
$y = $nodes->{$yn} if exists $nodes->{$yn};
$x = $uc->{node}->new( $x ) unless ref $x; # if this fails, create
$y = $x if !ref($y) && $y eq $xn; # make add_edge('A','A') work
$y = $uc->{node}->new( $y ) unless ref $y;
print STDERR "# add_edge '$x->{name}' ($x->{id}) -> '$y->{name}' ($y->{id}) (edge $edge->{id}) ($x -> $y)\n" if $self->{debug};
for my $n ($x,$y)
{
$self->_croak("Cannot add node $n ($n->{name}), it already belongs to another graph")
if ref($n->{graph}) && $n->{graph} != $self;
}
# Register the nodes and the edge with our graph object
# and weaken the references. Be careful to not needlessly
# override and weaken again an already existing reference, this
# is an O(N) operation in most Perl versions, and thus very slow.
weaken($x->{graph} = $self) unless ref($x->{graph});
weaken($y->{graph} = $self) unless ref($y->{graph});
weaken($edge->{graph} = $self) unless ref($edge->{graph});
# Store at the edge from where to where it goes for easier reference
$edge->{from} = $x;
$edge->{to} = $y;
# store the edge at the nodes/groups, too
$x->{edges}->{$edge->{id}} = $edge;
$y->{edges}->{$edge->{id}} = $edge;
# index nodes by their name so that we can find $x from $x->{name} fast
my $store = $nodes; $store = $groups if $x->isa('Graph::Easy::Group');
$store->{$x->{name}} = $x;
$store = $nodes; $store = $groups if $y->isa('Graph::Easy::Group');
$store->{$y->{name}} = $y;
# index edges by "edgeid" so we can find them fast
$self->{edges}->{$edge->{id}} = $edge;
$self->{score} = undef; # invalidate last layout
wantarray ? ($x,$y,$edge) : $edge;
}
sub add_anon_node
{
my ($self) = shift;
$self->warn('add_anon_node does not take argumens') if @_ > 0;
my $node = Graph::Easy::Node::Anon->new();
$self->add_node($node);
$node;
}
sub add_node
{
my ($self,$x) = @_;
my $n = $x;
if (ref($x))
{
$n = $x->{name}; $n = '0' unless defined $n;
}
return $self->_croak("Cannot add node with empty name to graph.") if $n eq '';
return $self->_croak("Cannot add node $x ($n), it already belongs to another graph")
if ref($x) && ref($x->{graph}) && $x->{graph} != $self;
my $no = $self->{nodes};
# already exists?
return $no->{$n} if exists $no->{$n};
my $uc = $self->{use_class};
$x = $uc->{node}->new( $x ) unless ref $x;
# store the node
$no->{$n} = $x;
# Register the nodes and the edge with our graph object
# and weaken the references. Be careful to not needlessly
# override and weaken again an already existing reference, this
# is an O(N) operation in most Perl versions, and thus very slow.
weaken($x->{graph} = $self) unless ref($x->{graph});
$self->{score} = undef; # invalidate last layout
$x;
}
sub add_nodes
{
my $self = shift;
my @rc;
for my $x (@_)
{
my $n = $x;
if (ref($x))
{
$n = $x->{name}; $n = '0' unless defined $n;
}
return $self->_croak("Cannot add node with empty name to graph.") if $n eq '';
return $self->_croak("Cannot add node $x ($n), it already belongs to another graph")
if ref($x) && ref($x->{graph}) && $x->{graph} != $self;
my $no = $self->{nodes};
# this one already exists
next if exists $no->{$n};
my $uc = $self->{use_class};
# make it work with read-only scalars:
my $xx = $x;
$xx = $uc->{node}->new( $x ) unless ref $x;
# store the node
$no->{$n} = $xx;
# Register the nodes and the edge with our graph object
# and weaken the references. Be careful to not needlessly
# override and weaken again an already existing reference, this
# is an O(N) operation in most Perl versions, and thus very slow.
weaken($xx->{graph} = $self) unless ref($xx->{graph});
push @rc, $xx;
}
$self->{score} = undef; # invalidate last layout
@rc;
}
#############################################################################
#############################################################################
# Cloning/merging of graphs and objects
sub copy
{
# create a copy of this graph and return it as new graph
my $self = shift;
my $new = Graph::Easy->new();
# clone all the settings
for my $k (sort keys %$self)
{
$new->{$k} = $self->{$k} unless ref($self->{$k});
}
for my $g (sort keys %{$self->{groups}})
{
my $ng = $new->add_group($g);
# clone the attributes
$ng->{att} = $self->_clone( $self->{groups}->{$g}->{att} );
}
for my $n (ord_values ( $self->{nodes} ))
{
my $nn = $new->add_node($n->{name});
# clone the attributes
$nn->{att} = $self->_clone( $n->{att} );
# restore group membership for the node
$nn->add_to_group( $n->{group}->{name} ) if $n->{group};
}
for my $e (ord_values ( $self->{edges} ))
{
my $ne = $new->add_edge($e->{from}->{name}, $e->{to}->{name} );
# clone the attributes
$ne->{att} = $self->_clone( $e->{att} );
}
# clone the attributes
$new->{att} = $self->_clone( $self->{att});
$new;
}
sub _clone
{
# recursively clone a data structure
my ($self,$in) = @_;
my $out = { };
for my $k (sort keys %$in)
lib/Graph/Easy.pm view on Meta::CPAN
# decouple node from the graph
$node->{graph} = undef;
# reset cached size
$node->{w} = undef;
# drop all edges from the node locally
$node->{edges} = { };
# if the node is a child of another node, deregister it there
delete $node->{origin}->{children}->{$node->{id}} if defined $node->{origin};
$self->{score} = undef; # invalidate last layout
$self;
}
sub del_edge
{
my ($self, $edge) = @_;
$self->_croak("del_edge() needs an object") unless ref $edge;
# if edge is part of a group, delete it there, too
$edge->{group}->_del_edge($edge) if ref $edge->{group};
my $to = $edge->{to}; my $from = $edge->{from};
# delete the edge from the nodes
delete $from->{edges}->{$edge->{id}};
delete $to->{edges}->{$edge->{id}};
# drop the edge from our global edge list
delete $self->{edges}->{$edge->{id}};
$edge->{from} = undef;
$edge->{to} = undef;
$self;
}
#############################################################################
# group management
sub add_group
{
# add a group object
my ($self,$group) = @_;
my $uc = $self->{use_class};
# group with that name already exists?
my $name = $group;
$group = $self->{groups}->{ $group } unless ref $group;
# group with that name doesn't exist, so create new one
$group = $uc->{group}->new( name => $name ) unless ref $group;
# index under the group name for easier lookup
$self->{groups}->{ $group->{name} } = $group;
# register group with ourself and weaken the reference
$group->{graph} = $self;
{
no warnings; # don't warn on already weak references
weaken($group->{graph});
}
$self->{score} = undef; # invalidate last layout
$group;
}
sub del_group
{
# delete group
my ($self,$group) = @_;
delete $self->{groups}->{ $group->{name} };
$self->{score} = undef; # invalidate last layout
$self;
}
sub group
{
# return group by name
my ($self,$name) = @_;
$self->{groups}->{ $name };
}
sub groups
{
# return number of groups (or groups as object list)
my ($self) = @_;
return sort { $a->{name} cmp $b->{name} } values %{$self->{groups}}
if wantarray;
scalar keys %{$self->{groups}};
}
sub groups_within
{
# Return the groups that are directly inside this graph/group. The optional
# level is either -1 (meaning return all groups contained within), or a
# positive number indicating how many levels down we need to go.
my ($self, $level) = @_;
$level = -1 if !defined $level || $level < 0;
# inline call to $self->groups;
if ($level == -1)
{
return sort { $a->{name} cmp $b->{name} } values %{$self->{groups}}
if wantarray;
return scalar keys %{$self->{groups}};
}
my $are_graph = $self->{graph} ? 0 : 1;
# get the groups at level 0
my $current = 0;
my @todo;
( run in 0.757 second using v1.01-cache-2.11-cpan-39bf76dae61 )