# Copyright (c) 1997-2007 # Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany) # http://www.math.tu-berlin.de/polymake, mailto:polymake@math.tu-berlin.de # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any # later version: http://www.gnu.org/licenses/gpl.txt. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. #----------------------------------------------------------------------------- # $Project: polymake $$Id: VisualGraph.pm 7578 2007-01-21 22:48:26Z gawrilow $ ############################################################################## # # This is an intermediate layer between the concrete graph representation # as a polymake property and the graph description in an application-specific # format (such as GML for graphlet or JVX for javaview) # ############################################################################## package Visual::Graph; use Struct ( [ '@ISA' => 'Visual::Object' ], # \@list - graph in polymake format, may have node and/or edge attributes [ '$Graph' => 'unify_graph(#%)', default => 'croak("Graph missing")' ], # boolean '$Directed', # Graph embedding in R^2 or R^3: # [ "x y [z]", ... ] - coordinates of nodes # or Visual::Embedding object - if lazy [ '$Vertices | Coord' => 'check_points(#%)' ], # see PointLabels in Visual::PointSet [ '$VertexLabels | NodeLabels' => 'unify_labels(#%)', default => 'undef' ], # the same formats as for PointColor in Visual::PointSet # Some viewers (for example, PostScript, Graphlet) can draw node boxes either filled (default) # or white with colored border. The latter is chosen if the color value starts # with the keyword "border". [ '$VertexColor | NodeColor' => 'unify_decor(#%)', merge => '$this->merge_decor(#%)', default => '$Visual::Color::vertices' ], # the same formats as for PointStyle in Visual::PointSet # recognized styles are: # "thickness N" node box border width # "hidden" draw neither node boxes nor the labels # other possible style keywords are documented in the viewer packages (for example, Graphlet.pm) [ '$VertexStyle | NodeStyle' => 'unify_decor(#%)', merge => '$this->merge_decor(#%)' ], # boolean: when looking for edge color and style, whether to try the reverse edge # if the straight lookup fails [ '$EdgeStyleSymmetric' => '!$this->Directed' ], # "R G B" the same color for all edges # or [ "R G B", ... ] indexed by source node number # or { node => "R G B" } keyed by source node number # or { "node node" => "R G B"  } keyed by incident node numbers # or sub { ... } edge_iterator -> "R G B" [ '$EdgeColor' => '$this->unify_edge_decor(#%)', merge => '$this->merge_edge_decor(#%)', default => '$Visual::Color::edges' ], # the same formats as for EdgeColor # recognized styles are: # "thickness N" the edge line width # as well as other viewer-specific keywords [ '$EdgeStyle' => '$this->unify_edge_decor(#%)', merge => '$this->merge_edge_decor(#%)' ], # boolean: don't show the object at the session start (for interactive viewers) '$Hidden', ); # publicly accessible attributes declare %decorations=( VertexLabels => enum('hidden'), NodeLabels => enum('hidden') ); @decorations{qw( Name Title NodeColor NodeStyle VertexColor VertexStyle EdgeColor EdgeStyle Hidden )}=(); ############################################################################## my $strip_node_attr=qr{ ^\s* \( .*? (\{.*\}) \s* \) \s*$ }x; my $adj_nodes_with_attrs=qr{ \( (\d*) }x; my $adj_nodes_without_attrs=qr{ \d+ }x; sub unify_graph { my ($name, $G)=@_; if (!is_array($G)) { croak( "$name must be an array" ); } # get rid of all attributes if (@$G && !is_array($G->[0]) && !is_object($G->[0])) { my $adj_nodes; $G=[ map { my $inc=$_; $inc =~ s/$strip_node_attr/$1/; $adj_nodes ||= $inc =~ /\(/ ? $adj_nodes_with_attrs : $adj_nodes_without_attrs; [ $inc =~ m{$adj_nodes}g ] } @$G ]; } $G; } ############################################################################## sub unify_edge_decor : method { my ($self, $attr, $get_decor)=@_; if (is_code($get_decor) || !ref($get_decor)) { $get_decor } elsif (is_array($get_decor)) { if ($self->EdgeStyleSymmetric>0) { sub { my ($s,$t)=(shift)->incident_nodes; $get_decor->[$s] || $get_decor->[$t] } } elsif ($self->EdgeStyleSymmetric<0) { sub { my ($s,$t)=(shift)->incident_nodes; $get_decor->[$s] || -$get_decor->[$t] } } else { sub { my ($s,$t)=(shift)->incident_nodes; $get_decor->[$s] } } } else { # is_hash($get_decor) if ($self->EdgeStyleSymmetric>0) { sub { my ($s,$t)=(shift)->incident_nodes; $get_decor->{"$s $t"} || $get_decor->{"$t $s"} || $get_decor->{$s} || $get_decor->{$t} } } elsif ($self->EdgeStyleSymmetric<0) { sub { my ($s,$t)=(shift)->incident_nodes; $get_decor->{"$s $t"} || -$get_decor->{"$t $s"} || $get_decor->{$s} || -$get_decor->{$t} } } else { sub { my ($s,$t)=(shift)->incident_nodes; $get_decor->{"$s $t"} || $get_decor->{$s} } } } } ############################################################################## sub merge_edge_decor : method { my ($self, $attr, $new_decor)=@_; my $base_decor=$self->$attr; $new_decor=unify_edge_decor($self, $attr, $new_decor); if (ref($new_decor)) { if (ref($base_decor)) { $self->$attr = sub { $new_decor->($_[0]) || $base_decor->($_[0]) }; } elsif ($base_decor) { $self->$attr = sub { $new_decor->($_[0]) || $base_decor }; } else { $self->$attr = $new_decor; } } elsif (ref($base_decor)) { $self->$attr = sub { $base_decor->($_[0]) || $new_decor }; } else { $self->$attr = $new_decor; } } ############################################################################ # # The rest of the interface are callback functions for the visualization # program interface. The most of the methods are self-explanatory. # ############################################################################ # () -> integer sub get_number_nodes { my ($self)=@_; return scalar(@{$self->Graph}); } # () -> boolean sub is_embedded { my ($self)=@_; return defined($self->Coord); } my @edge_iterator=qw( Visual::Graph::undirected_edge_iterator Visual::Graph::directed_edge_iterator Visual::Graph::reverse_edge_iterator ); # (node_index) -> iterator sub get_incident_edges { my ($self, $n)=@_; my $iter=$edge_iterator[$self->Directed]->new($n, $self->Graph->[$n]); $iter; } sub get_edge_label { undef } sub get_all_edges { my ($self)=@_; my @edges; for (my $edge=$self->edges; $edge; ++$edge) { my ($s, $t)=$edge->incident_nodes; push @edges, "$s $t"; } @edges; } ################################################################################# sub has_edge { my ($self, $s, $t)=@_; my $g=$self->Graph; $s<=$#$g && $t<=$#$g && binsearch($g->[$s], $t); } ################################################################################# # # edge iterators visit the edges incident to a given node # ################################################################################# package Visual::Graph::directed_edge_iterator; use Struct ( [ new => '$$' ], [ '$from_node' => '#1' ], [ '@adjacent' => '#2' ], [ '$cur' => '0' ], ); sub clone { my ($self)=@_; inherit_class([ @$self ], $self); } use overload ( # move to the next edge '++' => sub { my ($self)=@_; ++$self->cur; $self }, # still edges to visit? 'bool' => sub { my ($self)=@_; $self->cur <= $#{$self->adjacent} }, # copy constructor '=' => \&clone, ); sub incident_nodes { # -> (source_node, target_node) my ($self)=@_; ($self->from_node, $self->adjacent->[$self->cur]) } ################################################################################# # # the same as above, but swaps the source and the target nodes # ################################################################################# package Visual::Graph::reverse_edge_iterator; use Struct [ '@ISA' => 'Visual::Graph::directed_edge_iterator' ]; sub incident_nodes { # -> (target_node, source_node) my ($self)=@_; ($self->adjacent->[$self->cur], $self->from_node) } ################################################################################# # # special case for the undirected graphs: avoids duplication of edges # ################################################################################# package Visual::Graph::undirected_edge_iterator; use Struct [ '@ISA' => 'Visual::Graph::directed_edge_iterator' ]; use overload ( 'bool' => sub { my ($self)=@_; $self->cur <= $#{$self->adjacent} && $self->adjacent->[$self->cur] <= $self->from_node; }, ); ################################################################################# # # visiting all edges in the graph # ################################################################################# package Visual::Graph::all_edge_iterator; use Struct ( [ new => '$' ], [ '$graph' => '#1' ], '$it', ); sub init { my ($self, $n)=@_; $self->it=$edge_iterator[$self->graph->Directed]->new($n, $self->graph->Graph->[$n]); } sub valid_position { my ($self)=@_; my ($n, $last); while (! $self->it) { $n ||= $self->it->from_node; $last ||= $self->graph->get_number_nodes; last if ++$n>=$last; init($self,$n); } } sub new { my $self=&_new; init($self,0); valid_position($self); $self; } use overload ( # move to the next edge '++' => sub { my ($self)=@_; ++$self->it; valid_position($self); $self }, # still edges to visit? 'bool' => sub { $_[0]->it }, # copy constructor '=' => \&Visual::Graph::directed_edge_iterator::clone, ); sub incident_nodes { $_[0]->it->incident_nodes } sub Visual::Graph::edges { new Visual::Graph::all_edge_iterator($_[0]); } 1 # Local Variables: # c-basic-offset:3 # End: