diff variant_effect_predictor/Bio/Graphics/Glyph.pm @ 0:1f6dce3d34e0

Uploaded
author mahtabm
date Thu, 11 Apr 2013 02:01:53 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/variant_effect_predictor/Bio/Graphics/Glyph.pm	Thu Apr 11 02:01:53 2013 -0400
@@ -0,0 +1,1474 @@
+package Bio::Graphics::Glyph;
+use GD;
+
+use strict;
+use Carp 'croak';
+use constant BUMP_SPACING => 2; # vertical distance between bumped glyphs
+
+
+my %LAYOUT_COUNT;
+
+# the CM1 and CM2 constants control the size of the hash used to
+# detect collisions.
+use constant CM1 => 200; # big bin, x axis
+use constant CM2 => 50;  # big bin, y axis
+use constant CM3 => 50;  # small bin, x axis
+use constant CM4 => 50;  # small bin, y axis
+
+use constant QUILL_INTERVAL => 8;  # number of pixels between Jim Kent style intron "quills"
+
+# a bumpable graphical object that has bumpable graphical subparts
+
+# args:  -feature => $feature_object (may contain subsequences)
+#        -factory => $factory_object (called to create glyphs for subsequences)
+# In this scheme, the factory decides based on stylesheet information what glyph to
+# draw and what configurations options to us. This allows for heterogeneous tracks.
+sub new {
+  my $class = shift;
+  my %arg = @_;
+
+  my $feature = $arg{-feature} or die "No feature";
+  my $factory = $arg{-factory} || $class->default_factory;
+  my $level   = $arg{-level} || 0;
+  my $flip    = $arg{-flip};
+
+  my $self = bless {},$class;
+  $self->{feature} = $feature;
+  $self->{factory} = $factory;
+  $self->{level}   = $level;
+  $self->{flip}++  if $flip;
+  $self->{top} = 0;
+
+  my @subglyphs;
+  my @subfeatures = $self->subseq($feature);
+
+  if (@subfeatures) {
+
+    # dynamic glyph resolution
+    @subglyphs = map { $_->[0] }
+          sort { $a->[1] <=> $b->[1] }
+             map { [$_, $_->left ] } 
+    $factory->make_glyph($level+1,@subfeatures);
+
+    $self->{parts}   = \@subglyphs;
+  }
+
+  my ($start,$stop) = ($self->start, $self->stop);
+  if (defined $start && defined $stop) {
+    ($start,$stop) = ($stop,$start) if $start > $stop;  # sheer paranoia
+    # the +1 here is critical for allowing features to meet nicely at nucleotide resolution
+    my ($left,$right) = $factory->map_pt($start,$stop+1);
+    $self->{left}    = $left;
+    $self->{width}   = $right - $left + 1;
+  }
+  if (@subglyphs) {
+      my $l            = $subglyphs[0]->left;
+      $self->{left}    = $l if !defined($self->{left}) || $l < $self->{left};
+      my $right        = (
+			  sort { $b<=>$a } 
+			  map {$_->right} @subglyphs)[0];
+      my $w            = $right - $self->{left} + 1;
+      $self->{width}   = $w if !defined($self->{width}) || $w > $self->{width};
+  }
+
+  $self->{point} = $arg{-point} ? $self->height : undef;
+  #Handle glyphs that don't actually fill their space, but merely mark a point.
+  #They need to have their collision bounds altered.  We will (for now)
+  #hard code them to be in the center of their feature.
+# note: this didn't actually seem to work properly, all features were aligned on
+# their right edges.  It works to do it in individual point-like glyphs such as triangle.
+#  if($self->option('point')){
+#    my ($left,$right) = $factory->map_pt($self->start,$self->stop);
+#    my $center = int(($left+$right)/2 + 0.5);
+
+#    $self->{width} = $self->height;
+#    $self->{left}  = $center - ($self->{width});
+#    $self->{right} = $center + ($self->{width});
+#  }
+
+  return $self;
+}
+
+sub parts      {
+  my $self = shift;
+  return unless $self->{parts};
+  return wantarray ? @{$self->{parts}} : $self->{parts};
+}
+
+sub feature { shift->{feature} }
+sub factory { shift->{factory} }
+sub panel   { shift->factory->panel }
+sub point   { shift->{point}   }
+sub scale   { shift->factory->scale }
+sub start   {
+  my $self = shift;
+  return $self->{start} if exists $self->{start};
+  $self->{start} = $self->{flip} ? $self->panel->end + 1 - $self->{feature}->end : $self->{feature}->start;
+
+  # handle the case of features whose endpoints are undef
+  # (this happens with wormbase clones where one or more clone end is not defined)
+  # in this case, we set the start to one minus the beginning of the panel
+  $self->{start} = $self->panel->offset - 1 unless defined $self->{start};
+
+  return $self->{start};
+}
+sub stop    {
+  my $self = shift;
+  return $self->{stop} if exists $self->{stop};
+  $self->{stop} = $self->{flip} ?  $self->panel->end + 1 - $self->{feature}->start : $self->{feature}->end;
+
+  # handle the case of features whose endpoints are undef
+  # (this happens with wormbase clones where one or more clone end is not defined)
+  # in this case, we set the start to one plus the end of the panel
+  $self->{stop} = $self->panel->offset + $self->panel->length + 1 unless defined $self->{stop};
+
+  return $self->{stop}
+}
+sub end     { shift->stop }
+sub length { my $self = shift; $self->stop - $self->start };
+sub score {
+    my $self = shift;
+    return $self->{score} if exists $self->{score};
+    return $self->{score} = ($self->{feature}->score || 0);
+}
+sub strand {
+    my $self = shift;
+    return $self->{strand} if exists $self->{strand};
+    return $self->{strand} = ($self->{feature}->strand || 0);
+}
+sub map_pt  { shift->{factory}->map_pt(@_) }
+sub map_no_trunc { shift->{factory}->map_no_trunc(@_) }
+
+# add a feature (or array ref of features) to the list
+sub add_feature {
+  my $self       = shift;
+  my $factory    = $self->factory;
+  for my $feature (@_) {
+    if (ref $feature eq 'ARRAY') {
+      $self->add_group(@$feature);
+    } else {
+      push @{$self->{parts}},$factory->make_glyph(0,$feature);
+    }
+  }
+}
+
+# link a set of features together so that they bump as a group
+sub add_group {
+  my $self = shift;
+  my @features = ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_;
+  my $f    = Bio::Graphics::Feature->new(
+					 -segments=>\@features,
+					 -type => 'group'
+					);
+  $self->add_feature($f);
+}
+
+sub top {
+  my $self = shift;
+  my $g = $self->{top};
+  $self->{top} = shift if @_;
+  $g;
+}
+sub left {
+  my $self = shift;
+  return $self->{left} - $self->pad_left;
+}
+sub right {
+  my $self = shift;
+  return $self->left + $self->layout_width - 1;
+}
+sub bottom {
+  my $self = shift;
+  $self->top + $self->layout_height - 1;
+}
+sub height {
+  my $self = shift;
+  return $self->{height} if exists $self->{height};
+  my $baseheight = $self->option('height');  # what the factory says
+  return $self->{height} = $baseheight;
+}
+sub width {
+  my $self = shift;
+  my $g = $self->{width};
+  $self->{width} = shift if @_;
+  $g;
+}
+sub layout_height {
+  my $self = shift;
+  return $self->layout;
+}
+sub layout_width {
+  my $self = shift;
+  return $self->width + $self->pad_left + $self->pad_right;
+}
+
+# returns the rectangle that surrounds the physical part of the
+# glyph, excluding labels and other "extra" stuff
+sub calculate_boundaries {return shift->bounds(@_);}
+
+sub bounds {
+  my $self = shift;
+  my ($dx,$dy) = @_;
+  $dx += 0; $dy += 0;
+  ($dx + $self->{left},
+   $dy + $self->top    + $self->pad_top,
+   $dx + $self->{left} + $self->{width} - 1,
+   $dy + $self->bottom - $self->pad_bottom);
+}
+
+
+sub box {
+  my $self = shift;
+  return ($self->left,$self->top,$self->right,$self->bottom);
+}
+
+
+sub unfilled_box {
+  my $self = shift;
+  my $gd   = shift;
+  my ($x1,$y1,$x2,$y2,$fg,$bg) = @_;
+
+  my $linewidth = $self->option('linewidth') || 1;
+
+  unless ($fg) {
+      $fg ||= $self->fgcolor;
+  $fg = $self->set_pen($linewidth,$fg) if $linewidth > 1;
+  }
+
+  unless ($bg) {
+      $bg ||= $self->bgcolor;
+      $bg = $self->set_pen($linewidth,$bg) if $linewidth > 1;
+  }
+
+  # draw a box
+  $gd->rectangle($x1,$y1,$x2,$y2,$fg);
+
+  # if the left end is off the end, then cover over
+  # the leftmost line
+  my ($width) = $gd->getBounds;
+
+  $gd->line($x1,$y1+$linewidth,$x1,$y2-$linewidth,$bg)
+    if $x1 < $self->panel->pad_left;
+
+  $gd->line($x2,$y1+$linewidth,$x2,$y2-$linewidth,$bg)
+    if $x2 > $width - $self->panel->pad_right;
+}
+
+
+# return boxes surrounding each part
+sub boxes {
+  my $self = shift;
+  my ($left,$top) = @_;
+  $top  += 0; $left += 0;
+  my @result;
+
+  $self->layout;
+  my @parts = $self->parts;
+  @parts    = $self if !@parts && $self->option('box_subparts') && $self->level>0;
+
+  for my $part ($self->parts) {
+    if (eval{$part->feature->primary_tag} eq 'group' or
+	($part->level == 0 && $self->option('box_subparts'))) {
+      push @result,$part->boxes($left+$self->left+$self->pad_left,$top+$self->top+$self->pad_top);
+    } else {
+      my ($x1,$y1,$x2,$y2) = $part->box;
+      push @result,[$part->feature,$x1,$top+$self->top+$self->pad_top+$y1,
+		                   $x2,$top+$self->top+$self->pad_top+$y2];
+    }
+  }
+  return wantarray ? @result : \@result;
+}
+
+# this should be overridden for labels, etc.
+# allows glyph to make itself thicker or thinner depending on
+# domain-specific knowledge
+sub pad_top {
+  my $self = shift;
+  return 0;
+}
+sub pad_bottom {
+  my $self = shift;
+  return 0;
+}
+sub pad_left {
+  my $self = shift;
+  return 0;
+}
+sub pad_right {
+  my $self = shift;
+# this shouldn't be necessary
+  my @parts = $self->parts or return 0;
+  my $max = 0;
+  foreach (@parts) {
+    my $pr = $_->pad_right;
+    $max = $pr if $max < $pr;
+  }
+  $max;
+}
+
+# move relative to parent
+sub move {
+  my $self = shift;
+  my ($dx,$dy) = @_;
+  $self->{left} += $dx;
+  $self->{top}  += $dy;
+
+  # because the feature parts use *absolute* not relative addressing
+  # we need to move each of the parts horizontally, but not vertically
+  $_->move($dx,0) foreach $self->parts;
+}
+
+# get an option
+sub option {
+  my $self = shift;
+  my $option_name = shift;
+  my $factory = $self->factory;
+  return unless $factory;
+  $factory->option($self,$option_name,@{$self}{qw(partno total_parts)});
+}
+
+# set an option globally
+sub configure {
+  my $self = shift;
+  my $factory = $self->factory;
+  my $option_map = $factory->option_map;
+  while (@_) {
+    my $option_name  = shift;
+    my $option_value = shift;
+    ($option_name = lc $option_name) =~ s/^-//;
+    $option_map->{$option_name} = $option_value;
+  }
+}
+
+# some common options
+sub color {
+  my $self = shift;
+  my $color = shift;
+  my $index = $self->option($color);
+  # turn into a color index
+  return $self->factory->translate_color($index) if defined $index;
+  return 0;
+}
+
+sub connector {
+  return shift->option('connector',@_);
+}
+
+# return value:
+#              0    no bumping
+#              +1   bump down
+#              -1   bump up
+sub bump {
+  my $self = shift;
+  return $self->option('bump');
+}
+
+# we also look for the "color" option for Ace::Graphics compatibility
+sub fgcolor {
+  my $self = shift;
+  my $color = $self->option('fgcolor');
+  my $index = defined $color ? $color : $self->option('color');
+  $index = 'black' unless defined $index;
+  $self->factory->translate_color($index);
+}
+
+#add for compatibility
+sub fillcolor {
+    my $self = shift;
+    return $self->bgcolor;
+}
+
+# we also look for the "background-color" option for Ace::Graphics compatibility
+sub bgcolor {
+  my $self = shift;
+  my $bgcolor = $self->option('bgcolor');
+  my $index = defined $bgcolor ? $bgcolor : $self->option('fillcolor');
+  $index = 'white' unless defined $index;
+  $self->factory->translate_color($index);
+}
+sub font {
+  my $self = shift;
+  my $font = $self->option('font');
+  unless (UNIVERSAL::isa($font,'GD::Font')) {
+    my $ref    = {
+		  gdTinyFont  => gdTinyFont,
+		  gdSmallFont => gdSmallFont,
+		  gdMediumBoldFont => gdMediumBoldFont,
+		  gdLargeFont => gdLargeFont,
+		  gdGiantFont => gdGiantFont};
+    my $gdfont = $ref->{$font} || $font;
+    $self->configure(font=>$gdfont);
+    return $gdfont;
+  }
+  return $font;
+}
+sub fontcolor {
+  my $self = shift;
+  my $fontcolor = $self->color('fontcolor');
+  return defined $fontcolor ? $fontcolor : $self->fgcolor;
+}
+sub font2color {
+  my $self = shift;
+  my $font2color = $self->color('font2color');
+  return defined $font2color ? $font2color : $self->fgcolor;
+}
+sub tkcolor { # "track color"
+  my $self = shift;
+  $self->option('tkcolor') or return;
+  return $self->color('tkcolor')
+}
+sub connector_color {
+  my $self = shift;
+  $self->color('connector_color') || $self->fgcolor;
+}
+
+sub layout_sort {
+
+    my $self = shift;
+    my $sortfunc;
+
+    my $opt = $self->option("sort_order");
+    if (!$opt) {
+       $sortfunc = eval 'sub { $a->left <=> $b->left }';
+    } elsif (ref $opt eq 'CODE') {
+       $sortfunc = $opt;
+    } elsif ($opt =~ /^sub\s+\{/o) {
+       $sortfunc = eval $opt;
+    } else {
+       # build $sortfunc for ourselves:
+       my @sortbys = split(/\s*\|\s*/o, $opt);
+       $sortfunc = 'sub { ';
+       my $sawleft = 0;
+
+       # not sure I can make this schwartzian transfored
+       for my $sortby (@sortbys) {
+	 if ($sortby eq "left" || $sortby eq "default") {
+	   $sortfunc .= '($a->left <=> $b->left) || ';
+	   $sawleft++;
+	 } elsif ($sortby eq "right") {
+	   $sortfunc .= '($a->right <=> $b->right) || ';
+	 } elsif ($sortby eq "low_score") {
+	   $sortfunc .= '($a->score <=> $b->score) || ';
+	 } elsif ($sortby eq "high_score") {
+	   $sortfunc .= '($b->score <=> $a->score) || ';
+	 } elsif ($sortby eq "longest") {
+	   $sortfunc .= '(($b->length) <=> ($a->length)) || ';
+	 } elsif ($sortby eq "shortest") {
+	   $sortfunc .= '(($a->length) <=> ($b->length)) || ';
+	 } elsif ($sortby eq "strand") {
+	   $sortfunc .= '($b->strand <=> $a->strand) || ';
+	 } elsif ($sortby eq "name") {
+	   $sortfunc .= '($a->feature->display_name cmp $b->feature->display_name) || ';
+	 }
+       }
+       unless ($sawleft) {
+           $sortfunc .= ' ($a->left <=> $b->left) ';
+       } else {
+           $sortfunc .= ' 0';
+       }
+       $sortfunc .= '}';
+       $sortfunc = eval $sortfunc;
+    }
+
+    # cache this
+    # $self->factory->set_option(sort_order => $sortfunc);
+
+    return sort $sortfunc @_;
+}
+
+# handle collision detection
+sub layout {
+  my $self = shift;
+  return $self->{layout_height} if exists $self->{layout_height};
+
+  my @parts = $self->parts;
+  return $self->{layout_height}
+    = $self->height + $self->pad_top + $self->pad_bottom unless @parts;
+
+  my $bump_direction = $self->bump;
+  my $bump_limit = $self->option('bump_limit') || -1;
+
+  $_->layout foreach @parts;  # recursively lay out
+
+  # no bumping requested, or only one part here
+  if (@parts == 1 || !$bump_direction) {
+    my $highest = 0;
+    foreach (@parts) {
+      my $height = $_->layout_height;
+      $highest   = $height > $highest ? $height : $highest;
+    }
+    return $self->{layout_height} = $highest + $self->pad_top + $self->pad_bottom;
+  }
+
+  my (%bin1,%bin2);
+  for my $g ($self->layout_sort(@parts)) {
+
+    my $pos = 0;
+    my $bumplevel = 0;
+    my $left   = $g->left;
+    my $right  = $g->right;
+    my $height = $g->{layout_height};
+
+    while (1) {
+
+      # stop bumping if we've gone too far down
+      if ($bump_limit > 0 && $bumplevel++ >= $bump_limit) {
+	$g->{overbumped}++;  # this flag can be used to suppress label and description
+	foreach ($g->parts) {
+	  $_->{overbumped}++;
+	}
+	last;
+      }
+
+      # look for collisions
+      my $bottom = $pos + $height;
+      $self->collides(\%bin1,CM1,CM2,$left,$pos,$right,$bottom) or last;
+      my $collision = $self->collides(\%bin2,CM3,CM4,$left,$pos,$right,$bottom) or last;
+
+      if ($bump_direction > 0) {
+	$pos += $collision->[3]-$collision->[1] + BUMP_SPACING;    # collision, so bump
+
+      } else {
+	$pos -= BUMP_SPACING;
+      }
+
+    }
+
+    $g->move(0,$pos);
+    $self->add_collision(\%bin1,CM1,CM2,$left,$g->top,$right,$g->bottom);
+    $self->add_collision(\%bin2,CM3,CM4,$left,$g->top,$right,$g->bottom);
+  }
+
+  # If -1 bumping was allowed, then normalize so that the top glyph is at zero
+  if ($bump_direction < 0) {
+    my $topmost;
+    foreach (@parts) {
+      my $top  = $_->top;
+      $topmost = $top if !defined($topmost) or $top < $topmost;
+    }
+    my $offset = - $topmost;
+    $_->move(0,$offset) foreach @parts;
+  }
+
+  # find new height
+  my $bottom = 0;
+  foreach (@parts) {
+    $bottom = $_->bottom if $_->bottom > $bottom;
+  }
+  return $self->{layout_height} = $self->pad_bottom + $self->pad_top + $bottom - $self->top  + 1;
+}
+
+# the $%occupied structure is a hash of {left,top} = [left,top,right,bottom]
+sub collides {
+  my $self = shift;
+  my ($occupied,$cm1,$cm2,$left,$top,$right,$bottom) = @_;
+  my @keys = $self->_collision_keys($cm1,$cm2,$left,$top,$right,$bottom);
+  my $collides = 0;
+  for my $k (@keys) {
+    next unless exists $occupied->{$k};
+    for my $bounds (@{$occupied->{$k}}) {
+      my ($l,$t,$r,$b) = @$bounds;
+      next unless $right >= $l and $left <= $r and $bottom >= $t and $top <= $b;
+      $collides = $bounds;
+      last;
+    }
+  }
+  $collides;
+}
+
+sub add_collision {
+  my $self = shift;
+  my ($occupied,$cm1,$cm2,$left,$top,$right,$bottom) = @_;
+  my $value = [$left,$top,$right+2,$bottom];
+  my @keys = $self->_collision_keys($cm1,$cm2,@$value);
+  push @{$occupied->{$_}},$value foreach @keys;
+}
+
+sub _collision_keys {
+  my $self = shift;
+  my ($binx,$biny,$left,$top,$right,$bottom) = @_;
+  my @keys;
+  my $bin_left   = int($left/$binx);
+  my $bin_right  = int($right/$binx);
+  my $bin_top    = int($top/$biny);
+  my $bin_bottom = int($bottom/$biny);
+  for (my $x=$bin_left;$x<=$bin_right; $x++) {
+    for (my $y=$bin_top;$y<=$bin_bottom; $y++) {
+      push @keys,join(',',$x,$y);
+    }
+  }
+  @keys;
+}
+
+sub draw {
+  my $self = shift;
+  my $gd = shift;
+  my ($left,$top,$partno,$total_parts) = @_;
+
+  local($self->{partno},$self->{total_parts});
+  @{$self}{qw(partno total_parts)} = ($partno,$total_parts);
+
+  my $connector =  $self->connector;
+  if (my @parts = $self->parts) {
+
+    # invoke sorter if use wants to sort always and we haven't already sorted
+    # during bumping.
+    @parts = $self->layout_sort(@parts) if !$self->bump && $self->option('always_sort');
+
+    my $x = $left;
+    my $y = $top  + $self->top + $self->pad_top;
+    $self->draw_connectors($gd,$x,$y) if $connector && $connector ne 'none';
+
+    my $last_x;
+    for (my $i=0; $i<@parts; $i++) {
+      # lie just a little bit to avoid lines overlapping and
+      # make the picture prettier
+      my $fake_x = $x;
+      $fake_x-- if defined $last_x && $parts[$i]->left - $last_x == 1;
+      $parts[$i]->draw($gd,$fake_x,$y,$i,scalar(@parts));
+      $last_x = $parts[$i]->right;
+    }
+  }
+
+  else {  # no part
+    $self->draw_connectors($gd,$left,$top)
+      if $connector && $connector ne 'none' && $self->{level} == 0;
+    $self->draw_component($gd,$left,$top);
+  }
+}
+
+# the "level" is the level of testing of the glyph
+# groups are level -1, top level glyphs are level 0, subcomponents are level 1 and so forth.
+sub level {
+  shift->{level};
+}
+
+sub draw_connectors {
+  my $self = shift;
+  return if $self->{overbumped};
+  my $gd = shift;
+  my ($dx,$dy) = @_;
+  my @parts = sort { $a->left <=> $b->left } $self->parts;
+  for (my $i = 0; $i < @parts-1; $i++) {
+    $self->_connector($gd,$dx,$dy,$parts[$i]->bounds,$parts[$i+1]->bounds);
+  }
+
+  # extra connectors going off ends
+  if (@parts) {
+    my($x1,$y1,$x2,$y2) = $self->bounds(0,0);
+    my($xl,$xt,$xr,$xb) = $parts[0]->bounds;
+    $self->_connector($gd,$dx,$dy,$x1,$xt,$x1,$xb,$xl,$xt,$xr,$xb)      if $x1 < $xl;
+    my ($xl2,$xt2,$xr2,$xb2) = $parts[-1]->bounds;
+    $self->_connector($gd,$dx,$dy,$parts[-1]->bounds,$x2,$xt2,$x2,$xb2) if $x2 > $xr;
+  }
+
+}
+
+sub _connector {
+  my $self = shift;
+  my ($gd,
+      $dx,$dy,
+      $xl,$xt,$xr,$xb,
+      $yl,$yt,$yr,$yb) = @_;
+  my $left   = $dx + $xr;
+  my $right  = $dx + $yl;
+  my $top1     = $dy + $xt;
+  my $bottom1  = $dy + $xb;
+  my $top2     = $dy + $yt;
+  my $bottom2  = $dy + $yb;
+  # restore this comment if you don't like the group dash working
+  # its way backwards.
+  return if $right-$left < 1 && !$self->isa('Bio::Graphics::Glyph::group');
+
+  $self->draw_connector($gd,
+			$top1,$bottom1,$left,
+			$top2,$bottom2,$right,
+		       );
+}
+
+sub draw_connector {
+  my $self   = shift;
+  my $gd     = shift;
+
+  my $color          = $self->connector_color;
+  my $connector_type = $self->connector or return;
+
+  if ($connector_type eq 'hat') {
+    $self->draw_hat_connector($gd,$color,@_);
+  } elsif ($connector_type eq 'solid') {
+    $self->draw_solid_connector($gd,$color,@_);
+  } elsif ($connector_type eq 'dashed') {
+    $self->draw_dashed_connector($gd,$color,@_);
+  } elsif ($connector_type eq 'quill') {
+    $self->draw_quill_connector($gd,$color,@_);
+  } else {
+    ; # draw nothing
+  }
+}
+
+sub draw_hat_connector {
+  my $self = shift;
+  my $gd   = shift;
+  my $color = shift;
+  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
+
+  my $center1  = ($top1 + $bottom1)/2;
+  my $quarter1 = $top1 + ($bottom1-$top1)/4;
+  my $center2  = ($top2 + $bottom2)/2;
+  my $quarter2 = $top2 + ($bottom2-$top2)/4;
+
+  if ($center1 != $center2) {
+    $self->draw_solid_connector($gd,$color,@_);
+    return;
+  }
+
+  if ($right - $left > 4) {  # room for the inverted "V"
+      my $middle = $left + int(($right - $left)/2);
+      $gd->line($left,$center1,$middle,$top1,$color);
+      $gd->line($middle,$top1,$right-1,$center1,$color);
+    } elsif ($right-$left > 1) { # no room, just connect
+      $gd->line($left,$quarter1,$right-1,$quarter1,$color);
+    }
+
+}
+
+sub draw_solid_connector {
+  my $self = shift;
+  my $gd   = shift;
+  my $color = shift;
+  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
+
+  my $center1  = ($top1 + $bottom1)/2;
+  my $center2  = ($top2 + $bottom2)/2;
+
+  $gd->line($left,$center1,$right,$center2,$color);
+}
+
+sub draw_dashed_connector {
+  my $self = shift;
+  my $gd   = shift;
+  my $color = shift;
+  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
+
+  my $center1  = ($top1 + $bottom1)/2;
+  my $center2  = ($top2 + $bottom2)/2;
+
+  $gd->setStyle($color,$color,gdTransparent,gdTransparent,);
+  $gd->line($left,$center1,$right,$center2,gdStyled);
+}
+
+sub draw_quill_connector {
+  my $self = shift;
+  my $gd   = shift;
+  my $color = shift;
+  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
+
+  my $center1  = ($top1 + $bottom1)/2;
+  my $center2  = ($top2 + $bottom2)/2;
+
+  $gd->line($left,$center1,$right,$center2,$color);
+  my $direction = $self->feature->strand;
+  return unless $direction;
+
+  if ($direction > 0) {
+    my $start = $left+4;
+    my $end   = $right-1;
+    for (my $position=$start; $position <= $end; $position += QUILL_INTERVAL) {
+      $gd->line($position,$center1,$position-2,$center1-2,$color);
+      $gd->line($position,$center1,$position-2,$center1+2,$color);
+    }
+  } else {
+    my $start = $left+1;
+    my $end   = $right-4;
+    for (my $position=$start; $position <= $end; $position += QUILL_INTERVAL) {
+      $gd->line($position,$center1,$position+2,$center1-2,$color);
+      $gd->line($position,$center1,$position+2,$center1+2,$color);
+    }
+  }
+}
+
+sub filled_box {
+  my $self = shift;
+  my $gd = shift;
+  my ($x1,$y1,$x2,$y2,$bg,$fg) = @_;
+
+  $bg ||= $self->bgcolor;
+  $fg ||= $self->fgcolor;
+  my $linewidth = $self->option('linewidth') || 1;
+
+  $gd->filledRectangle($x1,$y1,$x2,$y2,$bg);
+
+  $fg = $self->set_pen($linewidth,$fg) if $linewidth > 1;
+
+  # draw a box
+  $gd->rectangle($x1,$y1,$x2,$y2,$fg);
+
+  # if the left end is off the end, then cover over
+  # the leftmost line
+  my ($width) = $gd->getBounds;
+
+  $bg = $self->set_pen($linewidth,$bg) if $linewidth > 1;
+
+  $gd->line($x1,$y1+$linewidth,$x1,$y2-$linewidth,$bg)
+    if $x1 < $self->panel->pad_left;
+
+  $gd->line($x2,$y1+$linewidth,$x2,$y2-$linewidth,$bg)
+    if $x2 > $width - $self->panel->pad_right;
+}
+
+sub filled_oval {
+  my $self = shift;
+  my $gd = shift;
+  my ($x1,$y1,$x2,$y2,$bg,$fg) = @_;
+  my $cx = ($x1+$x2)/2;
+  my $cy = ($y1+$y2)/2;
+
+  $fg ||= $self->fgcolor;
+  $bg ||= $self->bgcolor;
+  my $linewidth = $self->linewidth;
+
+  $fg = $self->set_pen($linewidth) if $linewidth > 1;
+  $gd->arc($cx,$cy,$x2-$x1,$y2-$y1,0,360,$fg);
+
+  # and fill it
+  $gd->fill($cx,$cy,$bg);
+}
+
+sub oval {
+  my $self = shift;
+  my $gd = shift;
+  my ($x1,$y1,$x2,$y2) = @_;
+  my $cx = ($x1+$x2)/2;
+  my $cy = ($y1+$y2)/2;
+
+  my $fg = $self->fgcolor;
+  my $linewidth = $self->linewidth;
+
+  $fg = $self->set_pen($linewidth) if $linewidth > 1;
+  $gd->arc($cx,$cy,$x2-$x1,$y2-$y1,0,360,$fg);
+}
+
+sub filled_arrow {
+  my $self = shift;
+  my $gd  = shift;
+  my $orientation = shift;
+  $orientation *= -1 if $self->{flip};
+
+  my ($x1,$y1,$x2,$y2) = @_;
+
+  my ($width) = $gd->getBounds;
+  my $indent = $y2-$y1 < $x2-$x1 ? $y2-$y1 : ($x2-$x1)/2;
+
+  return $self->filled_box($gd,@_)
+    if ($orientation == 0)
+      or ($x1 < 0 && $orientation < 0)
+        or ($x2 > $width && $orientation > 0)
+	  or ($indent <= 0)
+	    or ($x2 - $x1 < 3);
+
+  my $fg = $self->fgcolor;
+  if ($orientation >= 0) {
+    $gd->line($x1,$y1,$x2-$indent,$y1,$fg);
+    $gd->line($x2-$indent,$y1,$x2,($y2+$y1)/2,$fg);
+    $gd->line($x2,($y2+$y1)/2,$x2-$indent,$y2,$fg);
+    $gd->line($x2-$indent,$y2,$x1,$y2,$fg);
+    $gd->line($x1,$y2,$x1,$y1,$fg);
+    my $left = $self->panel->left > $x1 ? $self->panel->left : $x1;
+    $gd->fillToBorder($left+1,($y1+$y2)/2,$fg,$self->bgcolor);
+  } else {
+    $gd->line($x1,($y2+$y1)/2,$x1+$indent,$y1,$fg);
+    $gd->line($x1+$indent,$y1,$x2,$y1,$fg);
+    $gd->line($x2,$y2,$x1+$indent,$y2,$fg);
+    $gd->line($x1+$indent,$y2,$x1,($y1+$y2)/2,$fg);
+    $gd->line($x2,$y1,$x2,$y2,$fg);
+    my $right = $self->panel->right < $x2 ? $self->panel->right : $x2;
+    $gd->fillToBorder($right-1,($y1+$y2)/2,$fg,$self->bgcolor);
+  }
+}
+
+sub linewidth {
+  shift->option('linewidth') || 1;
+}
+
+sub fill {
+  my $self = shift;
+  my $gd   = shift;
+  my ($x1,$y1,$x2,$y2) = @_;
+  if ( ($x2-$x1) >= 2 && ($y2-$y1) >= 2 ) {
+    $gd->fill($x1+1,$y1+1,$self->bgcolor);
+  }
+}
+sub set_pen {
+  my $self = shift;
+  my ($linewidth,$color) = @_;
+  $linewidth ||= $self->linewidth;
+  $color     ||= $self->fgcolor;
+  return $color unless $linewidth > 1;
+  $self->panel->set_pen($linewidth,$color);
+}
+
+sub draw_component {
+  my $self = shift;
+  my $gd = shift;
+  my($x1,$y1,$x2,$y2) = $self->bounds(@_);
+
+  # clipping
+  my $panel = $self->panel;
+  return unless $x2 >= $panel->left and $x1 <= $panel->right;
+
+  if ($self->option('strand_arrow') || $self->option('stranded')) {
+    $self->filled_arrow($gd,$self->feature->strand,
+			$x1, $y1,
+			$x2, $y2)
+  } else {
+    $self->filled_box($gd,
+		      $x1, $y1,
+		      $x2, $y2)
+  }
+}
+
+# memoize _subseq -- it's a bottleneck with segments
+sub subseq {
+  my $self    = shift;
+  my $feature = shift;
+  return $self->_subseq($feature) unless ref $self;
+  return @{$self->{cached_subseq}{$feature}} if $self->{cached_subseq}{$feature};
+  my @ss = $self->_subseq($feature);
+  $self->{cached_subseq}{$feature} = \@ss;
+  @ss;
+}
+
+sub _subseq {
+  my $class   = shift;
+  my $feature = shift;
+  return $feature->merged_segments         if $feature->can('merged_segments');
+  return $feature->segments                if $feature->can('segments');
+  my @split = eval { my $id   = $feature->location->seq_id;
+		     my @subs = $feature->location->sub_Location;
+		     grep {$id eq $_->seq_id} @subs};
+  return @split if @split;
+  return $feature->sub_SeqFeature          if $feature->can('sub_SeqFeature');
+  return;
+}
+
+# synthesize a key glyph
+sub keyglyph {
+  my $self = shift;
+  my $feature = $self->make_key_feature;
+  my $factory = $self->factory->clone;
+  $factory->set_option(label       => 1);
+  $factory->set_option(description => 0);
+  $factory->set_option(bump  => 0);
+  $factory->set_option(connector  => 'solid');
+  return $factory->make_glyph(0,$feature);
+}
+
+# synthesize a key glyph
+sub make_key_feature {
+  my $self = shift;
+
+  my $scale = 1/$self->scale;  # base pairs/pixel
+
+  # one segments, at pixels 0->80
+  my $offset = $self->panel->offset;
+
+
+  my $feature =
+    Bio::Graphics::Feature->new(-start =>0 * $scale +$offset,
+				-end   =>80*$scale+$offset,
+				-name => $self->option('key'),
+				-strand => '+1');
+  return $feature;
+}
+
+sub all_callbacks {
+  my $self = shift;
+  my $track_level = $self->option('all_callbacks');
+  return $track_level if defined $track_level;
+  return $self->panel->all_callbacks;
+}
+
+sub default_factory {
+  croak "no default factory implemented";
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bio::Graphics::Glyph - Base class for Bio::Graphics::Glyph objects
+
+=head1 SYNOPSIS
+
+See L<Bio::Graphics::Panel>.
+
+=head1 DESCRIPTION
+
+Bio::Graphics::Glyph is the base class for all glyph objects.  Each
+glyph is a wrapper around an Bio:SeqFeatureI object, knows how to
+render itself on an Bio::Graphics::Panel, and has a variety of
+configuration variables.
+
+End developers will not ordinarily work directly with
+Bio::Graphics::Glyph objects, but with Bio::Graphics::Glyph::generic
+and its subclasses.  Similarly, most glyph developers will want to
+subclass from Bio::Graphics::Glyph::generic because the latter
+provides labeling and arrow-drawing facilities.
+
+=head1 METHODS
+
+This section describes the class and object methods for
+Bio::Graphics::Glyph.
+
+=head2 CONSTRUCTORS
+
+Bio::Graphics::Glyph objects are constructed automatically by an
+Bio::Graphics::Glyph::Factory, and are not usually created by
+end-developer code.
+
+=over 4
+
+=item $glyph = Bio::Graphics::Glyph-E<gt>new(-feature=E<gt>$feature,-factory=E<gt>$factory)
+
+Given a sequence feature, creates an Bio::Graphics::Glyph object to
+display it.  The B<-feature> argument points to the Bio:SeqFeatureI
+object to display, and B<-factory> indicates an
+Bio::Graphics::Glyph::Factory object from which the glyph will fetch
+all its run-time configuration information.  Factories are created and
+manipulated by the Bio::Graphics::Panel object.
+
+A standard set of options are recognized.  See L<OPTIONS>.
+
+=back
+
+=head2 OBJECT METHODS
+
+Once a glyph is created, it responds to a large number of methods.  In
+this section, these methods are grouped into related categories.
+
+Retrieving glyph context:
+
+=over 4
+
+=item $factory = $glyph-E<gt>factory
+
+Get the Bio::Graphics::Glyph::Factory associated with this object.
+This cannot be changed once it is set.
+
+=item $panel = $glyph-E<gt>panel
+
+Get the Bio::Graphics::Panel associated with this object.  This cannot
+be changed once it is set.
+
+=item $feature = $glyph-E<gt>feature
+
+Get the sequence feature associated with this object.  This cannot be
+changed once it is set.
+
+=item $feature = $glyph-E<gt>add_feature(@features)
+
+Add the list of features to the glyph, creating subparts.  This is
+most common done with the track glyph returned by
+Ace::Graphics::Panel-E<gt>add_track().
+
+=item $feature = $glyph-E<gt>add_group(@features)
+
+This is similar to add_feature(), but the list of features is treated
+as a group and can be configured as a set.
+
+=back
+
+Retrieving glyph options:
+
+=over 4
+
+=item $fgcolor = $glyph-E<gt>fgcolor
+
+=item $bgcolor = $glyph-E<gt>bgcolor
+
+=item $fontcolor = $glyph-E<gt>fontcolor
+
+=item $fontcolor = $glyph-E<gt>font2color
+
+=item $fillcolor = $glyph-E<gt>fillcolor
+
+These methods return the configured foreground, background, font,
+alternative font, and fill colors for the glyph in the form of a
+GD::Image color index.
+
+=item $color = $glyph-E<gt>tkcolor
+
+This method returns a color to be used to flood-fill the entire glyph
+before drawing (currently used by the "track" glyph).
+
+=item $width = $glyph-E<gt>width([$newwidth])
+
+Return the width of the glyph, not including left or right padding.
+This is ordinarily set internally based on the size of the feature and
+the scale of the panel.
+
+=item $width = $glyph-E<gt>layout_width
+
+Returns the width of the glyph including left and right padding.
+
+=item $width = $glyph-E<gt>height
+
+Returns the height of the glyph, not including the top or bottom
+padding.  This is calculated from the "height" option and cannot be
+changed.
+
+
+=item $font = $glyph-E<gt>font
+
+Return the font for the glyph.
+
+=item $option = $glyph-E<gt>option($option)
+
+Return the value of the indicated option.
+
+=item $index = $glyph-E<gt>color($color)
+
+Given a symbolic or #RRGGBB-form color name, returns its GD index.
+
+=item $level = $glyph-E<gt>level
+
+The "level" is the nesting level of the glyph.
+Groups are level -1, top level glyphs are level 0,
+subparts (e.g. exons) are level 1 and so forth.
+
+=back
+
+Setting an option:
+
+=over 4
+
+=item $glyph-E<gt>configure(-name=E<gt>$value)
+
+You may change a glyph option after it is created using set_option().
+This is most commonly used to configure track glyphs.
+
+=back
+
+Retrieving information about the sequence:
+
+=over 4
+
+=item $start = $glyph-E<gt>start
+
+=item $end   = $glyph-E<gt>end
+
+These methods return the start and end of the glyph in base pair
+units.
+
+=item $offset = $glyph-E<gt>offset
+
+Returns the offset of the segment (the base pair at the far left of
+the image).
+
+=item $length = $glyph-E<gt>length
+
+Returns the length of the sequence segment.
+
+=back
+
+
+Retrieving formatting information:
+
+=over 4
+
+=item $top = $glyph-E<gt>top
+
+=item $left = $glyph-E<gt>left
+
+=item $bottom = $glyph-E<gt>bottom
+
+=item $right = $glyph-E<gt>right
+
+These methods return the top, left, bottom and right of the glyph in
+pixel coordinates.
+
+=item $height = $glyph-E<gt>height
+
+Returns the height of the glyph.  This may be somewhat larger or
+smaller than the height suggested by the GlyphFactory, depending on
+the type of the glyph.
+
+=item $scale = $glyph-E<gt>scale
+
+Get the scale for the glyph in pixels/bp.
+
+=item $height = $glyph-E<gt>labelheight
+
+Return the height of the label, if any.
+
+=item $label = $glyph-E<gt>label
+
+Return a human-readable label for the glyph.
+
+=back
+
+These methods are called by Bio::Graphics::Track during the layout
+process:
+
+=over 4
+
+=item $glyph-E<gt>move($dx,$dy)
+
+Move the glyph in pixel coordinates by the indicated delta-x and
+delta-y values.
+
+=item ($x1,$y1,$x2,$y2) = $glyph-E<gt>box
+
+Return the current position of the glyph.
+
+=back
+
+These methods are intended to be overridden in subclasses:
+
+=over 4
+
+=item $glyph-E<gt>calculate_height
+
+Calculate the height of the glyph.
+
+=item $glyph-E<gt>calculate_left
+
+Calculate the left side of the glyph.
+
+=item $glyph-E<gt>calculate_right
+
+Calculate the right side of the glyph.
+
+=item $glyph-E<gt>draw($gd,$left,$top)
+
+Optionally offset the glyph by the indicated amount and draw it onto
+the GD::Image object.
+
+
+=item $glyph-E<gt>draw_label($gd,$left,$top)
+
+Draw the label for the glyph onto the provided GD::Image object,
+optionally offsetting by the amounts indicated in $left and $right.
+
+=back
+
+These methods are useful utility routines:
+
+=over 4
+
+=item $pixels = $glyph-E<gt>map_pt($bases);
+
+Map the indicated base position, given in base pair units, into
+pixels, using the current scale and glyph position.
+
+=item $glyph-E<gt>filled_box($gd,$x1,$y1,$x2,$y2)
+
+Draw a filled rectangle with the appropriate foreground and fill
+colors, and pen width onto the GD::Image object given by $gd, using
+the provided rectangle coordinates.
+
+=item $glyph-E<gt>filled_oval($gd,$x1,$y1,$x2,$y2)
+
+As above, but draws an oval inscribed on the rectangle.
+
+=back
+
+=head2 OPTIONS
+
+The following options are standard among all Glyphs.  See individual
+glyph pages for more options.
+
+  Option      Description                      Default
+  ------      -----------                      -------
+
+  -fgcolor      Foreground color	       black
+
+  -outlinecolor	Synonym for -fgcolor
+
+  -bgcolor      Background color               turquoise
+
+  -fillcolor    Synonym for -bgcolor
+
+  -linewidth    Line width                     1
+
+  -height       Height of glyph		       10
+
+  -font         Glyph font		       gdSmallFont
+
+  -connector    Connector type                 undef (false)
+
+  -connector_color
+                Connector color                black
+
+  -strand_arrow Whether to indicate            undef (false)
+                 strandedness
+
+  -label        Whether to draw a label	       undef (false)
+
+  -description  Whether to draw a description  undef (false)
+
+  -sort_order   Specify layout sort order      "default"
+
+  -always_sort  Sort even when bumping is off  undef (false)
+
+  -bump_limit   Maximum number of levels to bump undef (unlimited)
+
+For glyphs that consist of multiple segments, the B<-connector> option
+controls what's drawn between the segments.  The default is undef (no
+connector).  Options include:
+
+   "hat"     an upward-angling conector
+   "solid"   a straight horizontal connector
+   "quill"   a decorated line with small arrows indicating strandedness
+             (like the UCSC Genome Browser uses)
+   "dashed"  a horizontal dashed line.  
+
+The B<-connector_color> option controls the color of the connector, if
+any.
+
+The label is printed above the glyph.  You may pass an anonymous
+subroutine to B<-label>, in which case the subroutine will be invoked
+with the feature as its single argument.  and is expected to return
+the string to use as the description.  If you provide the numeric
+value "1" to B<-description>, the description will be read off the
+feature's seqname(), info() and primary_tag() methods will be called
+until a suitable name is found.  To create a label with the
+text "1", pass the string "1 ".  (A 1 followed by a space).
+
+The description is printed below the glyph.  You may pass an anonymous
+subroutine to B<-description>, in which case the subroutine will be
+invoked with the feature as its single argument and is expected to
+return the string to use as the description.  If you provide the
+numeric value "1" to B<-description>, the description will be read off
+the feature's source_tag() method.  To create a description with the
+text "1", pass the string "1 ".  (A 1 followed by a space).
+
+In the case of ACEDB Ace::Sequence feature objects, the feature's
+info(), Brief_identification() and Locus() methods will be called to
+create a suitable description.
+
+The B<-strand_arrow> option, if true, requests that the glyph indicate
+which strand it is on, usually by drawing an arrowhead.  Not all
+glyphs will respond to this request.  For historical reasons,
+B<-stranded> is a synonym for this option.
+
+By default, features are drawn with a layout based only on the
+position of the feature, assuring a maximal "packing" of the glyphs
+when bumped.  In some cases, however, it makes sense to display the
+glyphs sorted by score or some other comparison, e.g. such that more
+"important" features are nearer the top of the display, stacked above
+less important features.  The -sort_order option allows a few
+different built-in values for changing the default sort order (which
+is by "left" position): "low_score" (or "high_score") will cause
+features to be sorted from lowest to highest score (or vice versa).
+"left" (or "default") and "right" values will cause features to be
+sorted by their position in the sequence.  "longer" (or "shorter")
+will cause the longest (or shortest) features to be sorted first, and
+"strand" will cause the features to be sorted by strand: "+1"
+(forward) then "0" (unknown, or NA) then "-1" (reverse).  Lastly,
+"name" will sort features alphabetically by their display_name()
+attribute.
+
+In all cases, the "left" position will be used to break any ties.  To
+break ties using another field, options may be strung together using a
+"|" character; e.g. "strand|low_score|right" would cause the features
+to be sorted first by strand, then score (lowest to highest), then by
+"right" position in the sequence.  Finally, a subroutine coderef can
+be provided, which should expect to receive two feature objects (via
+the special sort variables $a and $b), and should return -1, 0 or 1
+(see Perl's sort() function for more information); this subroutine
+will be used without further modification for sorting.  For example,
+to sort a set of database search hits by bits (stored in the features'
+"score" fields), scaled by the log of the alignment length (with
+"left" position breaking any ties):
+
+  sort_order = sub { ( $b->score/log($b->length)
+                                      <=>
+                       $a->score/log($a->length) )
+                                      ||
+                     ( $a->start <=> $b->start )
+                   }
+
+The -always_sort option, if true, will sort features even if bumping
+is turned off.  This is useful if you would like overlapping features
+to stack in a particular order.  Features towards the end of the list
+will overlay those towards the beginning of the sort order.
+
+=head1 SUBCLASSING Bio::Graphics::Glyph
+
+By convention, subclasses are all lower-case.  Begin each subclass
+with a preamble like this one:
+
+ package Bio::Graphics::Glyph::crossbox;
+
+ use strict;
+ use vars '@ISA';
+ @ISA = 'Bio::Graphics::Glyph';
+
+Then override the methods you need to.  Typically, just the draw()
+method will need to be overridden.  However, if you need additional
+room in the glyph, you may override calculate_height(),
+calculate_left() and calculate_right().  Do not directly override
+height(), left() and right(), as their purpose is to cache the values
+returned by their calculating cousins in order to avoid time-consuming
+recalculation.
+
+A simple draw() method looks like this:
+
+ sub draw {
+  my $self = shift;
+  $self->SUPER::draw(@_);
+  my $gd = shift;
+
+  # and draw a cross through the box
+  my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_);
+  my $fg = $self->fgcolor;
+  $gd->line($x1,$y1,$x2,$y2,$fg);
+  $gd->line($x1,$y2,$x2,$y1,$fg);
+ }
+
+This subclass draws a simple box with two lines criss-crossed through
+it.  We first call our inherited draw() method to generate the filled
+box and label.  We then call calculate_boundaries() to return the
+coordinates of the glyph, disregarding any extra space taken by
+labels.  We call fgcolor() to return the desired foreground color, and
+then call $gd-E<gt>line() twice to generate the criss-cross.
+
+For more complex draw() methods, see Bio::Graphics::Glyph::transcript
+and Bio::Graphics::Glyph::segments.
+
+=head1 BUGS
+
+Please report them.
+
+=head1 SEE ALSO
+
+L<Bio::DB::GFF::Feature>,
+L<Ace::Sequence>,
+L<Bio::Graphics::Panel>,
+L<Bio::Graphics::Track>,
+L<Bio::Graphics::Glyph::anchored_arrow>,
+L<Bio::Graphics::Glyph::arrow>,
+L<Bio::Graphics::Glyph::box>,
+L<Bio::Graphics::Glyph::dna>,
+L<Bio::Graphics::Glyph::graded_segments>,
+L<Bio::Graphics::Glyph::primers>,
+L<Bio::Graphics::Glyph::segments>,
+L<Bio::Graphics::Glyph::toomany>,
+L<Bio::Graphics::Glyph::transcript>,
+L<Bio::Graphics::Glyph::transcript2>,
+L<Bio::Graphics::Glyph::wormbase_transcript>
+
+=head1 AUTHOR
+
+Lincoln Stein E<lt>lstein@cshl.orgE<gt>
+
+Copyright (c) 2001 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