The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Bio::Graphics::Glyph::dumbbell;
# DAS-compatible package to use for drawing a line of repeating shapes

use strict;
use base qw(Bio::Graphics::Glyph::generic);

use Math::Trig;

sub my_description {
    return <<END;
This glyph draws a "dumbbell" with the same shapes on both ends. 
END
}
sub my_options {
    {
	shape_size => [
	    'integer',
	    10,
	    'The size of the shape on both ends.'],
	end_shape => [
	    ['square','diamond','tree','clover',
	     'star','bubble','arrow','wave'],
	    'square',
	    'The type of shape to place at the ends of the dumbbell.'],
	bubble_text => [
	    'string',
	    undef,
	    'The text to show in the bubble if the bubble option is',
	    'chosen (the -shape_size option is ignored in this case).'],
	antiparallel => [
	    'boolean',
	    undef,
	    'Whether the right arrow is reversed.'],
	arc => [
	    'boolean',
	    undef,
	    'If true, the end shapes will be connected by a curved',
	    'arc rather than a straight line.'],
    }
}

sub default_shape_size
{
  return 10;  
}

sub default_shape
{
  return 'square';  
}

sub draw_end_shape
{
  my ($self, @args) = @_;
  my $shape = $self->option('end_shape') || $self->default_shape();
  my $method = "draw_end_$shape";
  if ($self->can($method))
  {
    return $self->$method(@args);
  }
  else
  {
    return $self->draw_end_square(@args);  
  }
}

sub draw_end_square
{
  my ($self, $gd, $x1, $y1, $shape_size, $fg) = @_;

  my $x2 = $x1 + $shape_size;
  my $y2 = $y1 + $shape_size;

  my $poly_pkg = $self->polygon_package;
  
  my $polygon   = $poly_pkg->new();
  $polygon->addPt($x1,$y1);
  $polygon->addPt($x2,$y1);
  $polygon->addPt($x2,$y2);
  $polygon->addPt($x1, $y2);

  $gd->filledPolygon($polygon,$fg);
  
  return ($x1, $x2);
}

sub draw_end_diamond
{
  my ($self, $gd, $x1, $y1, $shape_size, $fg) = @_;

  my $x2 = $x1 + $shape_size;
  my $y2 = $y1 + $shape_size;

  my $poly_pkg = $self->polygon_package;

  my $midX = ($x1+$x2)/2;
  my $midY = ($y1+$y2)/2;
  
  my $polygon   = $poly_pkg->new();
  $polygon->addPt($x1,$midY);
  $polygon->addPt($midX,$y1);
  $polygon->addPt($x2,$midY);
  $polygon->addPt($midX,$y2);

  $gd->filledPolygon($polygon,$fg);
  
  return ($x1, $x2);  
}

sub translated_polygon
{
  my ($self, $midX, $midY, $scale_factor, @coords) = @_;

  my $poly_pkg = $self->polygon_package;

  my $polygon   = $poly_pkg->new();
  for (my $i=0; $i<(scalar @coords) / 2; $i++)
  {
    $polygon->addPt($coords[2*$i], $coords[2*$i+1]);
  }
  
  $polygon->scale($scale_factor, $scale_factor);
  $polygon->offset($midX, $midY);
	
	return $polygon;
}

sub draw_end_star
{
  my ($self, $gd, $x1, $y1, $shape_size, $fg) = @_;
  
  #my @coords = (95, -31, -58, 81, 0, -100, 58, 81, -95, -31);
  my @coords1 = (31, 42, 31, -42, -49, -30, -38, 0, -49, 30);
  my @coords2 = (100, 0, -81, 59, 31, -95, 31, 95, -81, -58);

  my $star_size = 190;
  
  my $scale_factor = $shape_size / $star_size;
  
  my ($midX, $midY) = ($x1+$shape_size/2, $y1+$shape_size/2);
 
	$gd->filledPolygon($self->translated_polygon($midX, $midY, $scale_factor,  @coords1), $fg);
	$gd->filledPolygon($self->translated_polygon($midX, $midY, $scale_factor, @coords2), $fg);

  return ($midX, $midX);
}
 
sub draw_end_tree
{
  my ($self, $gd, $x1, $y1, $shape_size, $fg) = @_;

  my $x2 = $x1 + $shape_size;
  my $y2 = $y1 + $shape_size;
  
  my $trunk_width = $shape_size/4;

  my $midX = ($x1+$x2)/2;
  my $midY = ($y1+$y2)/2;

  my $poly_pkg = $self->polygon_package;
  
  my $polygon   = $poly_pkg->new();
  $polygon->addPt($midX-$trunk_width/2,$midY);
  $polygon->addPt($midX+$trunk_width/2,$midY);
  $polygon->addPt($midX+$trunk_width/2,$y2);
  $polygon->addPt($midX-$trunk_width/2,$y2);

  $gd->filledPolygon($polygon, $fg);
  
  $self->filled_oval($gd, $x1, $y1, $x2, $y1+2*$shape_size/3, $fg, $fg);
  
  return ($midX, $midX);
}

sub draw_end_clover
{
  my ($self, $gd, $x1, $y1, $shape_size, $fg) = @_;

  my $x2 = $x1 + $shape_size;
  my $y2 = $y1 + $shape_size;
  
  my $trunk_width = $shape_size/4;

  my $midX = ($x1+$x2)/2;

  my $poly_pkg = $self->polygon_package;
  
  my $polygon   = $poly_pkg->new();
  $polygon->addPt($midX-$trunk_width/2,$y1+0.4*$shape_size);
  $polygon->addPt($midX+$trunk_width/2,$y1+0.4*$shape_size);
  $polygon->addPt($midX+$trunk_width/2,$y2);
  $polygon->addPt($midX-$trunk_width/2,$y2);

  $gd->filledPolygon($polygon, $fg);
  
  my $radius = $shape_size / 4.3;
  
  $self->filled_oval($gd, $midX-$radius, $y1, $midX+$radius, $y1+2*$radius, $fg, $fg);
  $self->filled_oval($gd, $x1, $y1+1.3*$radius, $x1+2*$radius, $y1+3.3*$radius, $fg, $fg);
  $self->filled_oval($gd, $x2-2*$radius, $y1+1.3*$radius, $x2, $y1+3.3*$radius, $fg, $fg);
  
  return ($midX, $midX);
}

sub draw_end_bubble
{
  my ($self, $gd, $x1, $y1, $shape_size, $fg) = @_;
  
  my $x2 = $x1 + $shape_size;
  my $y2 = $y1 + $shape_size;

  my $midX = ($x1+$x2)/2;
  my $midY = ($y1+$y2)/2;
  
  my $bubble_text = defined $self->option('bubble_text') ? $self->option('bubble_text') : "Text";

  my $font = $self->option('labelfont') || $self->font;
  my $bubble_text_length = $self->string_width($bubble_text,$font);
  my $bubble_text_x = $midX -  $bubble_text_length/2;
  my $bubble_text_y = $midY - $self->font_height($font)/2;
  
  $gd->string($font, $bubble_text_x, $bubble_text_y, $bubble_text, $self->fontcolor);
  
  my $oval_width = $bubble_text_length * 1.414;
  my $oval_height = $self->font_height($font) * 1.414;

  $self->oval($gd, $midX-$oval_width/2, $midY-$oval_height/2, $midX+$oval_width/2, $midY+$oval_height/2);

  return ($midX-$oval_width/2, $midX+$oval_width/2);
}

sub draw_end_arrow
{
  my ($self, $gd, $x1, $y1, $shape_size, $fg, $antiparallel) = @_;
  
  my $x2 = $x1 + $shape_size;
  my $y2 = $y1 + $shape_size;
  
  my $angle = deg2rad(30);
  my $dx = 2*$shape_size*cos($angle)/5;
  my $dy = 2*$shape_size*sin($angle)/5;
  my $midX = $x2-$dx;
  my $midY = ($y1+$y2)/2;

  $gd->line($x1, $midY, $x2, $midY, $fg);
  if ($antiparallel)
  {
    $gd->line($x1, $midY, $x1+$dx, $midY-$dy, $fg);
    $gd->line($x1, $midY, $x1+$dx, $midY+$dy, $fg);
  }  
  else
  {
    $gd->line($x2, $midY, $x2-$dx, $midY-$dy, $fg);
    $gd->line($x2, $midY, $x2-$dx, $midY+$dy, $fg);
  }  
  return ($x1, $x2);
}

sub draw_end_wave
{
  my ($self, $gd, $x1, $y1, $shape_size, $fg) = @_;
  
  my $x2 = $x1 + $shape_size;
  
  #Make the heigh constant
  my $y2 = $y1 + $shape_size/2;
  $y1 = $y2-10;
  
  my $step = $shape_size/6;
  $gd->line($x1, $y2, $x1+$step, $y1, $fg);
  $gd->line($x1+$step, $y1, $x1+2*$step, $y2, $fg);
  $gd->line($x1+2*$step, $y2, $x1+3*$step, $y1, $fg);
  $gd->line($x1+3*$step, $y1, $x1+4*$step, $y2, $fg);
  $gd->line($x1+4*$step, $y2, $x1+5*$step, $y1, $fg);
  $gd->line($x1+5*$step, $y1, $x1+6*$step, $y2, $fg);
  return ($x1, $x2);
}

sub draw_component {
  my $self = shift;
  my $gd = shift;
  my $fg = $self->fgcolor;
  
  my $shape_size = defined $self->option('shape_size') ? $self->option('shape_size') : $self->default_shape_size;

  # find the center and vertices
  my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_);
  
  if ($x2-$x1 < $shape_size)
  {
    return $self->SUPER::draw_component($gd, @_);  
  }
  
  my $midX = ($x2-$x1) / 2 + $x1;
  my $midY = ($y2-$y1) / 2 + $y1;
  my $startY = $midY - $shape_size/2;
  
  my $antiparallel = $self->option('antiparallel');
  
  #We need to store the bounds of the shapes drawn because the connecting line will have
  #different length depending on them.
  my ($leftX1, $leftX2) = $self->draw_end_shape($gd, $x1, $startY, $shape_size, $fg);
  my ($rightX1, $rightX2) = $self->draw_end_shape($gd, $x2-$shape_size, $startY, $shape_size, $fg, $antiparallel);

  if ($self->option('arc') == 1)
  {
    #Draw an arc of an ellipse relative to the midpoint between shapes
    #whose center is at (0, -q) and which intersects with the X axis at (p,0) and (-p, 0).
    my $p = ($rightX1 - $leftX2) / 2;
    my $q = $shape_size/2;
    
    my $c = 2 * $p / sqrt(3);
    my $d = 2 * $q;
    my $b = $q - $d;
    my $angle = atan2(sqrt(3), 1);
    my $deg = rad2deg($angle);
    $gd->arc($leftX2+$p,$midY+$q,2*$c,2*$d,270-$deg,270+$deg,$self->factory->translate_color('black'));
  }  
  else
  {
    $gd->line($leftX2,$midY,$rightX1,$midY,$fg);  
  }
  
  if (my $caption = $self->option('caption'))
  {
    my $font = $self->option('labelfont') || $self->font;
    my $midX = ($x2-$x1-2*$shape_size)/2+$x1+$shape_size;
    my $startCaption = $midX - $self->string_width($caption,$font)/2;
    $gd->string($font, $startCaption, $midY-$self->font_height($font), $caption, $self->fontcolor);
  }
}


1;

__END__

=head1 NAME

Bio::Graphics::Glyph::dumbbell - A glyph that draws a "dumbbell" with the same shapes on both ends. 

=head1 SYNOPSIS

  See L<Bio::Graphics::Panel> and L<Bio::Graphics::Glyph>.

=head1 DESCRIPTION

This glyph draws a "dumbbell" with the same shapes on both ends. 

=head2 OPTIONS

In addition to the common options, the following glyph-specific
options are recognized:

  Option      Description                       Default
  ------      -----------                       -------

  -shape_size The size of the shape               10
              on both ends.

  -end_shape  One of 'square', 'diamond',         square
              'tree', 'clover', 'star',
              'bubble', 'arrow', 'wave'

  -bubble_text The text to show in the bubble     Text
                if the bubble option is chosen
                above (shape_size is then ignored)

  -antiparallel Whether the right arrow               0
                is reversed

  -arc        Whether the shapes are               0
              connected by an arc
              (a straight line is the default).

=head1 BUGS

Please report them.

=head1 SEE ALSO

L<Bio::Graphics::Panel>,
L<Bio::Graphics::Glyph>,
L<Bio::Graphics::Glyph::arrow>,
L<Bio::Graphics::Glyph::cds>,
L<Bio::Graphics::Glyph::crossbox>,
L<Bio::Graphics::Glyph::diamond>,
L<Bio::Graphics::Glyph::dna>,
L<Bio::Graphics::Glyph::dot>,
L<Bio::Graphics::Glyph::ellipse>,
L<Bio::Graphics::Glyph::extending_arrow>,
L<Bio::Graphics::Glyph::generic>,
L<Bio::Graphics::Glyph::graded_segments>,
L<Bio::Graphics::Glyph::heterogeneous_segments>,
L<Bio::Graphics::Glyph::line>,
L<Bio::Graphics::Glyph::pinsertion>,
L<Bio::Graphics::Glyph::primers>,
L<Bio::Graphics::Glyph::rndrect>,
L<Bio::Graphics::Glyph::segments>,
L<Bio::Graphics::Glyph::ruler_arrow>,
L<Bio::Graphics::Glyph::toomany>,
L<Bio::Graphics::Glyph::transcript>,
L<Bio::Graphics::Glyph::transcript2>,
L<Bio::Graphics::Glyph::translation>,
L<Bio::Graphics::Glyph::triangle>,
L<Bio::DB::GFF>,
L<Bio::SeqI>,
L<Bio::SeqFeatureI>,
L<Bio::Das>,
L<GD>

=head1 AUTHOR

Vsevolod (Simon) Ilyushchenko E<lt>simonf@cshl.eduE<gt>.

Copyright (c) 2004 Cold Spring Harbor Laboratory

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.  See DISCLAIMER.txt for
disclaimers of warranty.

=cut