# Copyright (c) 1997-2006 # 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: Visual.pm 7540 2006-12-21 21:04:18Z gawrilow $ package Modules::common; sub prepare_visual_objects { my @args=@_; for (my $i=0; $i<=$#args; ++$i) { if (is_object($args[$i])) { my $attached=$args[$i]->attached; if (@{$attached}) { splice @args, $i+1, 0, @$attached; @$attached=(); } if (instanceof Visual::Container($args[$i])) { $args[$i]->propagate_defaults; splice @args, $i, 1, @{$args[$i]->elements}; redo; } } } @args; } # Viewer, VisualObject, ... => sub visualize_with($@) { my $viewer_pkg=shift; my $viewer; foreach my $vis (@_) { my $title=defined($vis->Title) ? $vis->Title : $vis->Name; $viewer=$viewer_pkg->new_drawing($title); foreach my $obj (prepare_visual_objects($vis)) { if (is_object($obj)) { $viewer->draw($obj); } elsif (@$obj) { my $method=$viewer->can("draw", $obj->[0], undef) or die "no matching method ", ref($viewer), "::draw(", ref($obj->[0]), ")\n"; foreach my $elem (@$obj) { $method->($viewer,$elem); } } } } $viewer; } sub start_deferred_visualizer { my ($pkg, $title)=@_; my $viewer=($main::scope->deferred->{$pkg} ||= $pkg->new); $viewer->new_drawing($title); } # # the central dispatching function # sub visualize($) { my $first=shift; return $first if defined wantarray; my @args=prepare_visual_objects($first); my $title=defined($first->Title) ? $first->Title : $first->Name; my ($method, $obj); if (@args==1) { eval { if (is_object($args[0])) { # a single object to draw - can use the overloading directly $method=Poly::Overload::Global::draw($args[0]); $method->(start_deferred_visualizer(method_owner($method),$title), $args[0]); } else { return undef if !@{$args[0]}; # should be a homogeneous array of drawables - resolve only once $method=Poly::Overload::Global::draw($args[0]->[0]); my $vis=start_deferred_visualizer(method_owner($method),$title); foreach $obj (@{$args[0]}) { $method->($vis, $obj, undef); } } }; if ($@) { if ($@ =~ /no matching overloaded instance of Poly::Overload::Global::draw/) { die "do not know how to visualize ", ref($args[0]) eq "ARRAY" ? ref($args[0]->[0]) : ref($args[0]), "\nprobably you should install some missing visualization packages\n"; } if ($@ =~ /Undefined subroutine &Poly::Overload::Global::draw/) { die "cannot visualize anything: no visualization packages installed\n"; } die $@; } } else { # several drawable objects: # first obtain all viable visualizers, even if not preferred $obj=$args[0]; my @methods; if (defined (my $list=resolve Poly::Overload "Poly::Overload::Global::draw", is_object($obj) ? $obj : $obj->[0], undef)) { # must try all visualizers in the preference order until find such a one # that can cope with all drawable objects TRY: foreach my $sub (is_ARRAY($list) ? Poly::Preference::Label::get_items($list) : ($list)) { $method=&$sub; @methods=($method); my $vis=method_owner($method); foreach $obj (@args[1..$#args]) { if (my $method_next=$vis->can("draw", is_object($obj) ? $obj : $obj->[0], undef)) { push @methods, $method_next; } else { @methods=(); next TRY; } } last; } } if (@methods) { my $vis=start_deferred_visualizer(method_owner($methods[0]),$title); foreach $obj (@args) { $method=shift @methods; foreach my $elem (is_object($obj) ? ($obj) : @$obj) { $method->($vis, $elem, undef); } } } else { croak( "do not know how to visualize (", join(", ", map { ref($_) eq "ARRAY" ? "ARRAY<".ref($_->[0]).">" : ref($_) || $_ } @args), ") together" ); } } } ############################################################################ # the common part of explicit visualization functions # Visual::Object, { options }, "Package" => sub visualize_explicit { my ($opts, $Package)=splice @_, -2; my $to_file=$opts->{File}; my $viewer; if (defined($to_file)) { my $file_package="$Package\::File::Writer"; if ($to_file eq "AUTO") { $viewer=Visual::FileWriter::Auto->new($file_package); } else { if (@_>1 && ! $file_package->multiple) { my $caller_sub=(caller(1))[3]; $caller_sub=~s/.*::([^:]+)$/$1/; $caller_sub=~s/^__(\w+)__OV__.*/$1/; die << "."; The file format for $Package does not support multiple independent scenes. If you really want to create several pictures, you should either specify File => "AUTO", or call $caller_sub with each object (and different file names) separately. If you intended to put the objects together in one drawing instead, bundle them with compose() like this: $caller_sub(compose(VISUAL1, VISUAL2, ...)) . } if ($to_file !~ /^[-&|]/ && $to_file !~ /\.\w+$/) { $to_file.=$file_package->suffix; } $viewer=$file_package->new($to_file); } } else { $viewer="$Package\::Viewer"->new; } visualize_with($viewer, @_)->proceed; () # empty return value } package Visual; ############################################################################ # useful regex for parsing the styles # $1=!fill $2=NAME $3="R G B" $4=R $5=G $6=B my $color_re=qr{(?: \b(?: solid | (border) )\s+)? (?: \b([a-zA-Z]\w+) | ((\d+) \s+ (\d+) \s+ (\d+)) )}x; my %RGBtxt; sub loadRGBtxt { die "color list rgb.txt not found, can't use symbolic color names\n" unless $Visual::Color::RGBtxt_path; open my $RGB, $Visual::Color::RGBtxt_path or die "can't parse color list $Visual::Color::RGBtxt_path: $!\n"; local $/="\n"; local ($_, $1, $2); while (<$RGB>) { next if /^\s* (?: ! | $ )/x; if (/^ \s* (\d+) \s+ (\d+) \s+ (\d+) \s+ ([a-zA-Z]\w+ (?:\s+ [a-zA-Z]\w+)*) \s* $/x) { $RGBtxt{$4}=[ "$1 $2 $3", $1, $2, $3 ]; } } } # => (!fill, "R G B", R, G, B) sub parse_color { my $color=shift; if ($color =~ $color_re) { if ($2) { loadRGBtxt unless keys %RGBtxt; my $list=$RGBtxt{$2}; $list ? ($1, @$list) : (); } else { ($1, $3, $4, $5, $6) } } else { () } } # $1=linewidth declare $thickness_re=qr{\bthickness \s+ ([.\d]+)}x; declare $transparency_re=qr{\btransparency \s+ ([.\d]+)}x; declare $hidden_re=qr{\bhidden\b}; sub hsv2rgb { my ($h, $s, $v) = @_; my ($i, $f, $p, $q, $t,$r,$g,$b); if ($s == 0) { $r = $b = $g = $v; } else { $h /= 60; $i = int $h; $f = $h - $i; $p = $v * (1 - $s); $q = $v * (1 - $s * $f); $t = $v * (1 - $s * (1 - $f)); SWITCH: { if ($i == 0) { $r = $v; $g = $t; $b = $p; last SWITCH; } if ($i == 1) { $r = $q; $g = $v; $b = $p; last SWITCH; } if ($i == 2) { $r = $p; $g = $v; $b = $t; last SWITCH; } if ($i == 3) { $r = $p; $g = $q; $b = $v; last SWITCH; } if ($i == 4) { $r = $t; $g = $p; $b = $v; last SWITCH; } $r = $v; $g = $p; $b = $q; } } return ($r, $g, $b); } ############################################################################### # # Basic visual object # package Visual::Object; use Struct ( [ new => '%' ], '$Name', [ '$Title' => 'undef' ], [ '@attached' => '##' ], ); sub check_points { my ($name, $pts)=@_; if (is_object($pts) && $pts->isa("Visual::Embedding") || is_array($pts)) { $pts; } else { croak( "$name neither an array nor a Visual::Embedding" ); } } sub unify_labels { my ($name, $labels)=@_; if (defined $labels) { if (is_code($labels)) { $labels } elsif (is_array($labels)) { sub { $labels->[shift] } } elsif ($labels eq "hidden") { undef } else { $labels=split_labels($labels); sub { $labels->[shift] } } } else { sub { shift } } } sub unify_decor { my ($name, $decor)=@_; is_code($decor) ? $decor : is_array($decor) ? sub { $decor->[$_[0]] } : is_hash($decor) ? sub { $decor->{$_[0]} } : $decor } sub merge_decor : method { my ($self, $attr, $new_decor)=@_; my $base_decor=$self->$attr; $new_decor=unify_decor($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; } } sub clone { my $src=shift; my $self=bless [ @$src ], ref($src); while (my ($key, $value)=splice @_, 0, 2) { $self->$key=$value; } $self; } ############################################################################### # # Container for several visualization objects - to be derived from # package Visual::Container; use Struct ( [ new => '%@' ], [ '@ISA' => 'Visual::Object' ], [ '@elements' => '@' ], '%defaults', ); sub propagate_defaults { my $self=shift; while (my ($name, $val)=each %{$self->defaults}) { my $applied; foreach my $c (@{$self->elements}) { my $obj=is_object($c) ? $c : $c->[0]; if (my $access_method=UNIVERSAL::can($obj,$name)) { my $filter=Struct::get_field_filter($access_method); foreach my $vis ($c==$obj ? ($obj) : @$c) { if (Struct::is_default($access_method->($vis))) { if ($filter) { $val=$filter->($name,$val); undef $filter; } $access_method->($vis)=$val; } } $applied=1; } elsif (instanceof Container($obj)) { foreach my $vis ($c==$obj ? ($obj) : @$c) { $vis->defaults->{$name} ||= $val; } } } croak( "default attribute $name is not applicable to any element of the ", ref($self) ) unless $applied; } } ############################################################################### package Visual::Embedding; sub new { my $class=shift; bless [ @_ ], $class; } use overload 'bool' => sub { 1 }, '==' => \&refcmp, '!=' => sub { !&refcmp }, '@{}' => "compute"; sub push { my $self=shift; defuse_magic($self); push @$self, @_; } sub splice { my ($self, $at)=splice @_, 0, 2; defuse_magic($self); splice @$self, $at, 0, @_; } ########################################################################################### # # Direct writing to a file without starting a GUI # package Visual::FileWriter; sub import { (undef, my %params)=@_; my $pkg=caller; my ($top_pkg)= $pkg =~ /^([^:]+)/; my $suffix=$params{suffix} or croak( "default file suffix not specified" ); my $multiple=$params{multiple}; my $symtab=get_pkg($pkg); define_function($symtab, "graphics", \&self); define_function($symtab, "proceed", \&proceed); define_function($symtab, "suffix", sub { $suffix }); define_function($symtab, "multiple", sub { $multiple }); no strict 'refs'; @{"$pkg\::ISA"}=( "$top_pkg\::File", "$top_pkg\::Viewer" ); } sub self : method { shift } sub proceed : method { shift->print_it; } ########################################################################################### # # Generator of file names # package Visual::FileWriter::Auto; use Struct ( [ new => '$' ], [ '$Package' => '#1' ], [ '$viewer' => 'undef' ], ); sub new_drawing { my ($self, $title)=@_; if ($self->viewer) { # write previous file $self->viewer->proceed; } # eliminate dangerous characters in the file name (my $filename=$title) =~ s|[ /.]|_|g; $filename .= $self->Package->suffix; print STDERR "writing to file $filename\n" if $Switches::v; ($self->viewer=$self->Package->new($filename))->new_drawing($title); } 1 # Local Variables: # c-basic-offset:3 # End: