# Author: Chao-Kuei Hung
# For more info, including license, please see doc/index.html
package Graph;
# Graph
use strict;
use Carp;
use vars qw(@ISA);
@ISA = qw(Collection);
use Collection;
use Vertex;
use DCEdge;
# { package main; require "graph/dfs"; }
# use Data::Dumper;
# We don't distinguish abstract graphs from geometric graphs.
# Every graph is given the DCEL structure. This makes the logic
# of coding easier (at the cost of slight increase in running time).
# Every graph is directed, and every edge is paired. Artificial
# edges are hidden.
sub new {
my ($class, %opts) = @_;
$class = ref($class) if ref($class);
my ($init_data) = delete $opts{-init_data};
my ($operation) = delete $opts{-operation};
my ($t) = delete $opts{-type};
croak "'Graph' code does not know how to process '$t' data\n"
unless ($t eq 'graph' or $t eq 'points');
my ($self) = $class->SUPER::new(%opts);
my (@v_names, $k, $s);
if ($t eq 'points') {
my ($n, $i, $name) = $#$init_data;
$name =
($n < 26) ?
sub { return chr(ord('A')+$_[0]); }
:
sub { 'V' . $_[0] }
;
$k = 0;
$init_data = { map { $name->($k++) => {-pos=>$_} } @$init_data };
}
# When vertices and edges are refered to in a text file, names are
# used. Inside a program, however, they seem to be more naturally
# refered to as objects (blessed references). But don't go too far
# and use objects as hash keys. You would get strings as return
# values when using operator "keys" on such a hash. It does not help
# reducing indirection (even if one manages to get eval work) and it
# makes debugging less convenient. So we decide to make -name an
# option instead of making it the key, and create a temporary table
# mapping names to objects during Graph creation.
# create vertices
my ($n2V); # table of (vertex) names to vertex objects
@v_names = sort keys %{ $init_data };
# $self->{"#Vertices"} = [];
foreach $s (@v_names) {
my (%v_opt) = map { /^-\w+$/ ? ($_=>$init_data->{$s}{$_}) : () }
keys %{$init_data->{$s}};
$n2V->{$s} = $self->v_new($s, %v_opt);
# push @{ $self->{"#Vertices"} }, $n2V->{$s};
}
$self->{-vertices} = $n2V; # convenience, read-only configuration variable
# create edges
my ($n2E); # table of (vertex) names to edge objects
my (@nbr);
foreach $s (@v_names) {
@nbr = sort grep { not /\W/ } keys %{$init_data->{$s}};
foreach $t (@nbr) {
$n2E->{$s}{$t} = DCEdge->new($n2V->{$s}, $n2V->{$t},
-weight=>$init_data->{$s}{$t}, -text=>$init_data->{$s}{$t},
-directed=>$self->cget(-directed)
);
}
$self->{"#OneEdge"} = $n2V->{$s}->{"#OneEdge"} =
$n2E->{$s}{$nbr[0]} if $#nbr>=0;
}
$self->dcelify($n2V, $n2E);
# End of naming scheme conversion. From now on, vertices and
# edges are refered to as objects (blessed references). Code
# readers are reminded to think in object terms.
return $self;
}
sub dcelify {
# build Doubly Connected Edge List pointers
my ($self, $n2V, $n2E) = @_;
my ($s, $t);
# pass one: verify symmetry and identify twins
foreach $s (keys %$n2E) {
foreach $t (keys %{ $n2E->{$s} }) {
if (not exists $n2E->{$t}{$s}) {
$n2E->{$t}{$s} = DCEdge->new($n2V->{$t}, $n2V->{$s}, -arrow=>"last");
if ($self->cget(-directed)) {
$n2E->{$t}{$s}->phantomize();
} else {
warn "one way edge detected";
$n2E->{$t}{$s}->configure(-status=>"alert", -directed=>1);
}
}
$n2E->{$s}{$t}->twin($n2E->{$t}{$s});
$n2E->{$t}{$s}->twin($n2E->{$s}{$t});
my ($w) = $n2E->{$t}{$s}->cget(-weight);
if (not $self->cget(-directed)) {
if (not defined $w or $w != $n2E->{$s}{$t}->cget(-weight)) {
warn "$s-$t is different from $t-$s in an undirected graph";
$n2E->{$t}{$s}->configure(-status=>"alert",
-arrow=>"last", -directed=>1);
}
}
}
}
# pass two: sort edges around each vertex
foreach $s (keys %$n2V) {
my ($neighbor);
my ($src_pos) = $n2V->{$s}->pos();
foreach $t (keys %{ $n2E->{$s} }) {
my ($tgt_pos) = $n2V->{$t}->pos();
my ($a) = $tgt_pos - $src_pos;
push @$neighbor, {name=>$t, angle=>atan2($a->[1], $a->[0])};
}
next unless $#$neighbor >= 0;
$neighbor = [ map { $_->{name} }
sort { $a->{angle} <=> $b->{angle} } @$neighbor
];
push @$neighbor, $neighbor->[0];
my ($i);
for ($i=0; $i<$#$neighbor; ++$i) {
my ($edge) = $n2E->{$s}{$neighbor->[$i]};
my ($prev) = $n2E->{$neighbor->[$i+1]}{$s};
$edge->prev($prev);
$prev->next($edge);
}
}
#my ($e, $k);
#foreach $e ( @{ $self->{"#EdgeList"} } ) {
# print "[$e]\n";
# foreach $k (keys %{ $e->{adj} }) {
# print " $k: $e->{adj}{$k}\n";
# }
#}
}
sub v_new {
my ($self, $name, %opts) = @_;
%opts = ( %{ $self->cget(-node_opts) }, %opts );
# as always, the host should take care of prepending %opts with -node_opts
my ($pos) = Vector2->new(@{ delete $opts{-pos} });
my ($v) = Vertex->new($self, $pos, -name=>$name, %opts);
# $v->configure(-text=>$v->cget(-display)->($v)) unless defined $opts{-text};
return $v;
}
# sub e_new {
# my ($self, $src, $tgt, %opts) = @_;
# my ($e1, $e2);
# $e1 = DCEdge->new($src, $tgt, -host=>$self, -arrow=>"last", %opts);
# return $e1 if $self->{-directed};
# $e2 = DCEdge->new($tgt, $src, -host=>$self, -arrow=>"last", %opts);
# return wantarray ? ($e1, $e2) : $e1;
# }
sub one_edge {
my ($self, $v) = @_;
return $#_ >= 1 ? $v->{"#OneEdge"} : $self->{"#OneEdge"};
}
sub edges_around {
my ($self, $v) = @_;
my ($e, @s, $start);
$e = $self->one_edge($v);
return () unless $e;
croak "broken 'OneEdge' on vertex $v" unless $e->source() eq $v;
$start = $e;
do {
push @s, $e unless $e->is_phantom();
$e = $e->prev()->twin();
} while ($e ne $start);
return @s;
}
sub destroy {
my ($self) = @_;
::pfs($self, $self->cget(-canvas),
-priority=>"sbs",
-on_vertex=>sub { $_[0]->destroy(); },
-on_edge=>sub { $_[0]->destroy(); },
);
undef $self;
}
if ($0 =~ /Graph.pm$/) {
# being tested as a stand-alone program, so run test code.
require "utilalgo";
my ($mw, $ctrl, $can);
$mw = MainWindow->new(-title=>"main_test");
# $can->{main} = gen_can($mw, undef, -elevation=>1, -maxlevel=>3);
$can->{main} = gen_can($mw, undef, -elevation=>2, -maxlevel=>3);
$can->{pq} = gen_can($mw, "Fringe (Frontier)", -elevation=>1, -maxlevel=>3);
$ctrl = gen_ctrl($mw, $can);
my ($g) = Graph->new(-canvas=>$can->{main}, %{ do "data/trc.gr" });
sub disp_vert_val {
my ($v, $val) = @_;
$v->configure(-text=>"$v\n$val");
}
$::Config->{Graph} = {
};
#::dfs($g);
# Theoretically, Dijkstra's single-source-shortest path algorithm should
# not be applied to graphs like trc.gr which has edges with negative weights.
# But we are lucky with this particular example :-)
{ package main; require "graph/pfs"; }
::pfs($g, $can->{pq}, -start=>"lin", -priority=>"dijk", -on_vertex=>\&disp_vert_val);
#{ package main; require "graph/flwa"; }
#::flwa($g);
$ctrl->configure(-recorder=>0);
my ($v);
$v = $g->one_edge->source();
print "$v: ", join(",", map {$_->target()} $g->edges_around($v)), "\n";
#my ($rc, $s);
#$rc = $can->{pq}{SubWidget}{scrolled};
#foreach $s (@{ $rc->{"#history"} }) {
# foreach my $l (@{ $s->{mark} }) {
# printf "%4d", $l;
# }
# print " | ";
# foreach my $l ($rc->relative_mark($s->{mark})) {
# printf "%4d", $l;
# }
# print "\n";
#}
# $can->{main}->set_mark(1);
$ctrl->configure(-recorder=>0);
# If the canvas refuses to show any change, remember to verify that:
# - set_mark() was called at least once
# - -recorder is set to zero before entering MainLoop
# Failing to do either of the above will result in a mysterious bug
# that takes days to figure out !@#$%
Tk::MainLoop();
}
1;
syntax highlighted by Code2HTML, v. 0.9.1