package Surface; # # Graph topology landscape - gene@ology.net # use strict; use base qw( Entity ); use Node; use GD; use constant PI => 2 * atan2( 1,0 ); # The irrational number. use constant FLARE => 0.9 * PI; # The angle of the arrowhead. use constant SCALE => 0.07; # Scaling factor for vector magnitude. use constant FACTOR => 10; # Inconsequential "registration mark" factor. sub new { my $proto = shift; my $class = ref $proto || $proto; my %args = @_; my $self = Entity->new(); $self->{_image} = $args{'image'} || undef; $self->{_colors} = $args{'colors'} || {}; bless $self, $class; $self->_init( \%args ); return $self; } sub destroy { } sub image { my $self = shift; $self->{_image} = shift if @_; return $self->{_image}; } sub colors { my $self = shift; $self->{_colors} = shift if @_; return $self->{_colors}; } #--------------------# sub _init { my( $self, $args ) = @_; die "No positive dimension or filename provided.\n" unless $args->{'name'} && $args->{'size'} > 0; $self->size( $args->{'size'} ); $self->name( $args->{'name'} ); # Allocate the goodness. $self->image( GD::Image->new( $self->size, $self->size ) ); # Argh! GD appears to require that you allocate white first. $self->colors->{'fill'} = $self->image->colorAllocate( 255,255,255 ); #white $self->colors->{'grid'} = $self->image->colorAllocate( 210,210,210 ); #light grey $self->colors->{'node_fill'} = $self->image->colorAllocate( 100,100,100 ); #dark grey $self->colors->{'node_border'} = $self->image->colorAllocate( 0,0,255 ); #blue $self->colors->{'border'} = $self->image->colorAllocate( 0,0,0 ); #black # Make the picture a white-transparent, interlaced background. $self->image->transparent( $self->colors->{'fill'} ); # $self->image->interlaced( 'true' ); # For the sake of typing/reading convienience: my $grid = $self->colors->{'grid'}; my( $half, $end ) = ( $self->size / 2, $self->size - 1 ); # Put a "flat" radius grade on the picture. $self->image->line( $half, 1, $half, $half, $grid ); # Put registration marks on the picture. for( my $i = FACTOR; $i < $half; $i += FACTOR ) { $self->image->arc( $half, $half, 2 * $i, 2 * $i, 0, 360, $grid ); $self->image->string( gdTinyFont, $half + 1, $i, $i, $self->colors->{'node_fill'} ); } # Put a frame around the picture. $self->image->arc( $half, $half, $end, $end, 0, 360, $self->colors->{'border'} ); # Fill the corners. $self->image->fill( 0, 0, $grid ); $self->image->fill( 0, $end, $grid ); $self->image->fill( $end, 0, $grid ); $self->image->fill( $end, $end, $grid ); $self->image->string( gdTinyFont, FACTOR/2, FACTOR/2, 'Total: '. $self->size, $self->colors->{'border'} ); return; } sub pic_output { my $self = shift; # Make sure we are writing to a binary stream first! binmode STDOUT; open F, '>'. $self->name or die "Can't open ", $self->name, " - $!\n"; binmode F; print F $self->image->png; close F; return; } sub draw_node { my( $self, $node ) = @_; $self->image->arc( $node->x, $node->y, $node->size, $node->size, 0, 360, $self->colors->{'node_border'} ); $self->image->fillToBorder( $node->x, $node->y, $self->colors->{'node_border'}, $self->colors->{'node_fill'} ); warn sprintf "%9s - %-4d [%6.2f %6.2f]\n", $node->name, $node->total, $node->x, $node->y; $self->image->string( gdTinyFont, ($node->x + $node->size), ($node->y - $node->size / 2), $node->name .':'.$node->total, $self->colors->{'border'} ); return; } sub draw_edge { my( $self, $node1, $node2 ) = @_; $self->image->line( $node1->x, $node1->y, $node2->x, $node2->y, $self->colors->{'node_fill'} ); $self->_draw_arrowhead( $node1, $node2 ); return; } sub _draw_arrowhead { my( $self, $tail, $head ) = @_; # Calculate the size and distance of the vector. my $dx = $head->x - $tail->x; my $dy = $head->y - $tail->y; my $len = sqrt( $dx*$dx + $dy*$dy ); # Calculate the angle of the vector in radians. my $angle = atan2( $dy, $dx ); my $poly = GD::Polygon->new; $poly->addPt( $head->x, $head->y ); # for( $angle + FLARE, $angle - FLARE ) { # "full arrow" for( $angle + FLARE, $angle + PI ) { # "half arrow" my $unitx = cos($_) * 2 * $tail->size; #$len * SCALE; # Use fixed or my $unity = sin($_) * 2 * $tail->size; #$len * SCALE; # proportional scaling. $dx = $head->x + $unitx; $dy = $head->y + $unity; $poly->addPt( $dx, $dy ); } $self->image->filledPolygon( $poly, $self->colors->{'node_fill'} ); } #--------------------# 1;