diff variant_effect_predictor/Bio/Graphics/Panel.pm @ 0:2bc9b66ada89 draft default tip

Uploaded
author mahtabm
date Thu, 11 Apr 2013 06:29:17 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/variant_effect_predictor/Bio/Graphics/Panel.pm	Thu Apr 11 06:29:17 2013 -0400
@@ -0,0 +1,1831 @@
+package Bio::Graphics::Panel;
+
+use strict;
+use Bio::Graphics::Glyph::Factory;
+use Bio::Graphics::Feature;
+use GD;;
+
+
+use constant KEYLABELFONT => gdMediumBoldFont;
+use constant KEYSPACING   => 5; # extra space between key columns
+use constant KEYPADTOP    => 5;  # extra padding before the key starts
+use constant KEYCOLOR     => 'wheat';
+use constant KEYSTYLE     => 'bottom';
+use constant KEYALIGN     => 'left';
+use constant GRIDCOLOR    => 'lightcyan';
+use constant MISSING_TRACK_COLOR =>'gray';
+
+my %COLORS;  # translation table for symbolic color names to RGB triple
+
+# Create a new panel of a given width and height, and add lists of features
+# one by one
+sub new {
+  my $class = shift;
+  my %options = @_;
+
+  $class->read_colors() unless %COLORS;
+
+  my $length = $options{-length} || 0;
+  my $offset = $options{-offset}  || 0;
+  my $spacing = $options{-spacing} || 5;
+  my $bgcolor = $options{-bgcolor} || 0;
+  my $keyfont = $options{-key_font} || KEYLABELFONT;
+  my $keycolor = $options{-key_color} || KEYCOLOR;
+  my $keyspacing = $options{-key_spacing} || KEYSPACING;
+  my $keystyle = $options{-key_style} || KEYSTYLE;
+  my $keyalign = $options{-key_align} || KEYALIGN;
+  my $allcallbacks = $options{-all_callbacks} || 0;
+  my $gridcolor    = $options{-gridcolor} || GRIDCOLOR;
+  my $grid         = $options{-grid}       || 0;
+  my $flip         = $options{-flip}       || 0;
+  my $empty_track_style   = $options{-empty_tracks} || 'key';
+  my $truecolor    = $options{-truecolor}  || 0;
+
+  if (my $seg = $options{-segment}) {
+    $offset = eval {$seg->start-1} || 0;
+    $length = $seg->length;
+  }
+
+  $offset   ||= $options{-start}-1 if defined $options{-start};
+  $length   ||= $options{-stop}-$options{-start}+1 
+     if defined $options{-start} && defined $options{-stop};
+
+  return bless {
+		tracks => [],
+		width      => $options{-width} || 600,
+		pad_top    => $options{-pad_top}||0,
+		pad_bottom => $options{-pad_bottom}||0,
+		pad_left   => $options{-pad_left}||0,
+		pad_right  => $options{-pad_right}||0,
+		length => $length,
+		offset => $offset,
+		gridcolor => $gridcolor,
+		grid    => $grid,
+		bgcolor => $bgcolor,
+		height => 0, # AUTO
+		spacing => $spacing,
+		key_font => $keyfont,
+		key_color => $keycolor,
+		key_spacing => $keyspacing,
+		key_style => $keystyle,
+		key_align => $keyalign,
+		all_callbacks => $allcallbacks,
+		truecolor     => $truecolor,
+		flip          => $flip,
+		empty_track_style    => $empty_track_style,
+	       },$class;
+}
+
+sub pad_left {
+  my $self = shift;
+  my $g = $self->{pad_left};
+  $self->{pad_left} = shift if @_;
+  $g;
+}
+sub pad_right {
+  my $self = shift;
+  my $g = $self->{pad_right};
+  $self->{pad_right} = shift if @_;
+  $g;
+}
+sub pad_top {
+  my $self = shift;
+  my $g = $self->{pad_top};
+  $self->{pad_top} = shift if @_;
+  $g;
+}
+sub pad_bottom {
+  my $self = shift;
+  my $g = $self->{pad_bottom};
+  $self->{pad_bottom} = shift if @_;
+  $g;
+}
+
+sub flip {
+  my $self = shift;
+  my $g = $self->{flip};
+  $self->{flip} = shift if @_;
+  $g;
+}
+
+# values of empty_track_style are:
+#    "suppress" -- suppress empty tracks entirely (default)
+#    "key"      -- show just the key in "between" mode
+#    "line"     -- draw a thin grey line
+#    "dashed"   -- draw a dashed line
+sub empty_track_style {
+  my $self = shift;
+  my $g = $self->{empty_track_style};
+  $self->{empty_track_style} = shift if @_;
+  $g;
+}
+
+sub key_style {
+  my $self = shift;
+  my $g = $self->{key_style};
+  $self->{key_style} = shift if @_;
+  $g;
+}
+
+# public routine for mapping from a base pair
+# location to pixel coordinates
+sub location2pixel {
+  my $self   = shift;
+  my $end    = $self->end + 1;
+  my @coords = $self->{flip} ? map { $end-$_ } @_ : @_;
+  $self->map_pt(@coords);
+}
+
+# numerous direct calls into array used here for performance considerations
+sub map_pt {
+  my $self   = shift;
+  my $offset = $self->{offset};
+  my $scale  = $self->{scale} || $self->scale;
+  my $pl     = $self->{pad_left};
+  my $pr     = $self->{width} - $self->{pad_right};
+  my $flip   = $self->{flip};
+  my $length = $self->{length};
+  my @result;
+  foreach (@_) {
+    my $val = $flip ? int (0.5 + $pr - ($length - ($_- 1)) * $scale) : int (0.5 + $pl + ($_-$offset-1) * $scale);
+    $val = $pl-1 if $val < $pl;
+    $val = $pr+1 if $val > $pr;
+    push @result,$val;
+  }
+  @result;
+}
+
+sub map_no_trunc {
+  my $self   = shift;
+  my $offset = $self->{offset};
+  my $scale  = $self->scale;
+  my $pl     = $self->{pad_left};
+  my $pr     = $self->{width} - $self->{pad_right};
+  my $flip   = $self->{flip};
+  my $length = $self->{length};
+  my $end    = $offset+$length;
+  my @result;
+  foreach (@_) {
+    my $val = $flip ? int (0.5 + $pl + ($end - ($_- 1)) * $scale) : int (0.5 + $pl + ($_-$offset-1) * $scale);
+    push @result,$val;
+  }
+  @result;
+}
+
+sub scale {
+  my $self = shift;
+  $self->{scale} ||= ($self->{width}-$self->pad_left-$self->pad_right)/($self->length);
+}
+
+sub start { shift->{offset}+1}
+sub end   { $_[0]->start + $_[0]->{length}-1}
+
+sub offset { shift->{offset} }
+sub width {
+  my $self = shift;
+  my $d = $self->{width};
+  $self->{width} = shift if @_;
+  $d;
+#  $d + $self->pad_left + $self->pad_right;
+}
+
+sub left {
+  my $self = shift;
+  $self->pad_left;
+}
+sub right {
+  my $self = shift;
+  $self->width - $self->pad_right;
+}
+
+sub spacing {
+  my $self = shift;
+  my $d = $self->{spacing};
+  $self->{spacing} = shift if @_;
+  $d;
+}
+
+sub key_spacing {
+  my $self = shift;
+  my $d = $self->{key_spacing};
+  $self->{key_spacing} = shift if @_;
+  $d;
+}
+
+sub length {
+  my $self = shift;
+  my $d = $self->{length};
+  if (@_) {
+    my $l = shift;
+    $l = $l->length if ref($l) && $l->can('length');
+    $self->{length} = $l;
+  }
+  $d;
+}
+
+sub gridcolor {shift->{gridcolor}}
+
+sub all_callbacks { shift->{all_callbacks} }
+
+sub add_track {
+  my $self = shift;
+  $self->_do_add_track(scalar(@{$self->{tracks}}),@_);
+}
+
+sub unshift_track {
+  my $self = shift;
+  $self->_do_add_track(0,@_);
+}
+
+sub insert_track {
+  my $self = shift;
+  my $position = shift;
+  $self->_do_add_track($position,@_);
+}
+
+
+# create a feature and factory pair
+# see Factory.pm for the format of the options
+# The thing returned is actually a generic Glyph
+sub _do_add_track {
+  my $self     = shift;
+  my $position = shift;
+
+  # due to indecision, we accept features
+  # and/or glyph types in the first two arguments
+  my ($features,$glyph_name) = ([],undef);
+  while ( @_ && $_[0] !~ /^-/) {
+    my $arg = shift;
+    $features   = $arg and next if ref($arg);
+    $glyph_name = $arg and next unless ref($arg);
+  }
+
+  my %args = @_;
+  my ($map,$ss,%options);
+
+  foreach (keys %args) {
+    (my $canonical = lc $_) =~ s/^-//;
+    if ($canonical eq 'glyph') {
+      $map = $args{$_};
+      delete $args{$_};
+    } elsif ($canonical eq 'stylesheet') {
+      $ss  = $args{$_};
+      delete $args{$_};
+    } else {
+      $options{$canonical} = $args{$_};
+    }
+  }
+
+  $glyph_name = $map if defined $map;
+  $glyph_name ||= 'generic';
+
+  local $^W = 0;  # uninitialized variable warnings under 5.00503
+
+  my $panel_map =
+    ref($map) eq 'CODE' ?  sub {
+      my $feature = shift;
+      return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag  eq 'track' };
+      return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag  eq 'group' };
+      return $map->($feature);
+    }
+   : ref($map) eq 'HASH' ? sub {
+     my $feature = shift;
+     return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag  eq 'track' };
+     return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag  eq 'group' };
+     return eval {$map->{$feature->primary_tag}} || 'generic';
+   }
+   : sub {
+     my $feature = shift;
+     return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag  eq 'track' };
+     return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag  eq 'group' };
+     return $glyph_name;
+   };
+
+  $self->_add_track($position,$features,-map=>$panel_map,-stylesheet=>$ss,-options=>\%options);
+}
+
+sub _add_track {
+  my $self = shift;
+  my ($position,$features,@options) = @_;
+
+  # build the list of features into a Bio::Graphics::Feature object
+  $features = [$features] unless ref $features eq 'ARRAY';
+
+  # optional middle-level glyph is the group
+  foreach my $f (grep {ref $_ eq 'ARRAY'} @$features) {
+    next unless ref $f eq 'ARRAY';
+    $f = Bio::Graphics::Feature->new(
+				     -segments=>$f,
+				     -type => 'group'
+				    );
+  }
+
+  # top-level glyph is the track
+  my $feature = Bio::Graphics::Feature->new(
+					    -segments=>$features,
+					    -start => $self->offset+1,
+					    -stop  => $self->offset+$self->length,
+					    -type => 'track'
+					   );
+
+  my $factory = Bio::Graphics::Glyph::Factory->new($self,@options);
+  my $track   = $factory->make_glyph(-1,$feature);
+
+  splice(@{$self->{tracks}},$position,0,$track);
+  return $track;
+}
+
+sub height {
+  my $self = shift;
+  my $spacing           = $self->spacing;
+  my $key_height        = $self->format_key;
+  my $empty_track_style = $self->empty_track_style;
+  my $key_style         = $self->key_style;
+  my $bottom_key        = $key_style eq 'bottom';
+  my $between_key       = $key_style eq 'between';
+  my $draw_empty        = $empty_track_style =~ /^(line|dashed)$/;
+  my $keyheight         = $self->{key_font}->height;
+  my $height = 0;
+  for my $track (@{$self->{tracks}}) {
+    my $draw_between =  $between_key && $track->option('key');
+    my $has_parts = $track->parts;
+    next if !$has_parts && ($empty_track_style eq 'suppress'
+		        or  $empty_track_style eq 'key' && $bottom_key);
+    $height += $keyheight if $draw_between;
+    $height += $self->spacing;
+    $height += $track->layout_height;
+  }
+
+  # get rid of spacing under last track
+  $height -= $self->spacing unless $bottom_key;
+  return $height + $key_height + $self->pad_top + $self->pad_bottom;
+}
+
+sub gd {
+  my $self        = shift;
+  my $existing_gd = shift;
+
+  local $^W = 0;  # can't track down the uninitialized variable warning
+
+  return $self->{gd} if $self->{gd};
+
+  my $width  = $self->width;
+  my $height = $self->height;
+
+  my $gd = $existing_gd || GD::Image->new($width,$height,
+					  ($self->{truecolor} && GD::Image->can('isTrueColor') ? 1 : ())
+					 );
+
+  my %translation_table;
+  for my $name ('white','black',keys %COLORS) {
+    my $idx = $gd->colorAllocate(@{$COLORS{$name}});
+    $translation_table{$name} = $idx;
+  }
+
+  $self->{translations} = \%translation_table;
+  $self->{gd}           = $gd;
+  if ($self->bgcolor) {
+    $gd->fill(0,0,$self->bgcolor);
+  } elsif (eval {$gd->isTrueColor}) {
+    $gd->fill(0,0,$translation_table{'white'});
+  }
+
+  my $pl = $self->pad_left;
+  my $pt = $self->pad_top;
+  my $offset = $pt;
+  my $keyheight   = $self->{key_font}->height;
+  my $bottom_key  = $self->{key_style} eq 'bottom';
+  my $between_key = $self->{key_style} eq 'between';
+  my $left_key    = $self->{key_style} eq 'left';
+  my $right_key   = $self->{key_style} eq 'right';
+  my $empty_track_style = $self->empty_track_style;
+  my $spacing = $self->spacing;
+
+  # we draw in two steps, once for background of tracks, and once for
+  # the contents.  This allows the grid to sit on top of the track background.
+  for my $track (@{$self->{tracks}}) {
+    my $draw_between = $between_key && $track->option('key');
+    next if !$track->parts && ($empty_track_style eq 'suppress'
+			   or  $empty_track_style eq 'key' && $bottom_key);
+    $gd->filledRectangle($pl,
+			 $offset,
+			 $width-$self->pad_right,
+			 $offset+$track->layout_height
+			 + ($between_key ? $self->{key_font}->height : 0),
+			 $track->tkcolor)
+      if defined $track->tkcolor;
+    $offset += $keyheight if $draw_between;
+    $offset += $track->layout_height + $spacing;
+  }
+
+  $self->draw_grid($gd)  if $self->{grid};
+
+  $offset = $pt;
+  for my $track (@{$self->{tracks}}) {
+    my $draw_between = $between_key && $track->option('key');
+    my $has_parts = $track->parts;
+    next if !$has_parts && ($empty_track_style eq 'suppress'
+			or  $empty_track_style eq 'key' && $bottom_key);
+
+    if ($draw_between) {
+      $offset += $self->draw_between_key($gd,$track,$offset);
+    }
+
+    elsif ($self->{key_style} =~ /^(left|right)$/) {
+      $self->draw_side_key($gd,$track,$offset,$self->{key_style});
+    }
+
+    $self->draw_empty($gd,$offset,$empty_track_style)
+      if !$has_parts && $empty_track_style=~/^(line|dashed)$/;
+
+    $track->draw($gd,0,$offset,0,1);
+    $self->track_position($track,$offset);
+    $offset += $track->layout_height + $spacing;
+  }
+
+
+  $self->draw_bottom_key($gd,$pl,$offset) if $self->{key_style} eq 'bottom';
+
+  return $self->{gd} = $gd;
+}
+
+sub boxes {
+  my $self = shift;
+  my @boxes;
+  my $offset = 0;
+
+  my $pl = $self->pad_left;
+  my $pt = $self->pad_top;
+  my $between_key       = $self->{key_style} eq 'between';
+  my $bottom_key        = $self->{key_style} eq 'bottom';
+  my $empty_track_style = $self->empty_track_style;
+  my $keyheight         = $self->{key_font}->height;
+  my $spacing = $self->spacing;
+
+  for my $track (@{$self->{tracks}}) {
+    my $draw_between =  $between_key && $track->option('key');
+    next if !$track->parts && ($empty_track_style eq 'suppress'
+			    or  $empty_track_style eq 'key' && $bottom_key);
+    $offset += $keyheight if $draw_between;
+    my $boxes = $track->boxes(0,$offset+$pt);
+    $self->track_position($track,$offset);
+    push @boxes,@$boxes;
+    $offset += $track->layout_height + $self->spacing;
+  }
+  return wantarray ? @boxes : \@boxes;
+}
+
+sub track_position {
+  my $self  = shift;
+  my $track = shift;
+  my $d = $self->{_track_position}{$track};
+  $self->{_track_position}{$track} = shift if @_;
+  $d;
+}
+
+# draw the keys -- between
+sub draw_between_key {
+  my $self   = shift;
+  my ($gd,$track,$offset) = @_;
+  my $key = $track->option('key') or return 0;
+  my $x =   $self->{key_align} eq 'center' ? $self->width - (CORE::length($key) * $self->{key_font}->width)/2
+          : $self->{key_align} eq 'right'  ? $self->width - CORE::length($key)
+          : $self->pad_left;
+  $gd->string($self->{key_font},$x,$offset,$key,1);
+  return $self->{key_font}->height;
+}
+
+# draw the keys -- left or right side
+sub draw_side_key {
+  my $self   = shift;
+  my ($gd,$track,$offset,$side) = @_;
+  my $key = $track->option('key') or return;
+  my $pos = $side eq 'left' ? $self->pad_left - $self->{key_font}->width * CORE::length($key)-3
+                            : $self->width - $self->pad_right+3;
+  $gd->string($self->{key_font},$pos,$offset,$key,1);
+}
+
+# draw the keys -- bottom
+sub draw_bottom_key {
+  my $self = shift;
+  my ($gd,$left,$top) = @_;
+  my $key_glyphs = $self->{key_glyphs} or return;
+
+  my $color = $self->translate_color($self->{key_color});
+  $gd->filledRectangle($left,$top,$self->width - $self->pad_right,$self->height-$self->pad_bottom,$color);
+  $gd->string($self->{key_font},$left,KEYPADTOP+$top,"KEY:",1);
+  $top += $self->{key_font}->height + KEYPADTOP;
+
+  $_->draw($gd,$left,$top) foreach @$key_glyphs;
+}
+
+# Format the key section, and return its height
+sub format_key {
+  my $self = shift;
+  return 0 unless $self->key_style eq 'bottom';
+
+  return $self->{key_height} if defined $self->{key_height};
+
+  my $suppress = $self->{empty_track_style} eq 'suppress';
+  my $between  = $self->{key_style}         eq 'between';
+
+  if ($between) {
+    my @key_tracks = $suppress
+      ? grep {$_->option('key') && $_->parts} @{$self->{tracks}}
+      : grep {$_->option('key')} @{$self->{tracks}};
+    return $self->{key_height} = @key_tracks * $self->{key_font}->height;
+  }
+
+  elsif ($self->{key_style} eq 'bottom') {
+
+    my ($height,$width) = (0,0);
+    my %tracks;
+    my @glyphs;
+
+    # determine how many glyphs become part of the key
+    # and their max size
+    for my $track (@{$self->{tracks}}) {
+
+      next unless $track->option('key');
+      next if $suppress && !$track->parts;
+
+      my $glyph;
+      if (my @parts = $track->parts) {
+	$glyph = $parts[0]->keyglyph;
+      } else {
+	my $t = Bio::Graphics::Feature->new(-segments=>
+					    [Bio::Graphics::Feature->new(-start => $self->offset,
+									 -stop  => $self->offset+$self->length)]);
+	my $g = $track->factory->make_glyph(0,$t);
+	$glyph = $g->keyglyph;
+      }
+      next unless $glyph;
+
+
+      $tracks{$track} = $glyph;
+      my ($h,$w) = ($glyph->layout_height,
+		    $glyph->layout_width);
+      $height = $h if $h > $height;
+      $width  = $w if $w > $width;
+      push @glyphs,$glyph;
+
+    }
+
+    $width += $self->key_spacing;
+
+    # no key glyphs, no key
+    return $self->{key_height} = 0 unless @glyphs;
+
+    # now height and width hold the largest glyph, and $glyph_count
+    # contains the number of glyphs.  We will format them into a
+    # box that is roughly 3 height/4 width (golden mean)
+    my $rows = 0;
+    my $cols = 0;
+    my $maxwidth = $self->width - $self->pad_left - $self->pad_right;
+    while (++$rows) {
+      $cols = @glyphs / $rows;
+      $cols = int ($cols+1) if $cols =~ /\./;  # round upward for fractions
+      my $total_width  = $cols * $width;
+      my $total_height = $rows * $width;
+      last if $total_width < $maxwidth;
+    }
+
+    # move glyphs into row-major format
+    my $spacing = $self->key_spacing;
+    my $i = 0;
+    for (my $c = 0; $c < $cols; $c++) {
+      for (my $r = 0; $r < $rows; $r++) {
+	my $x = $c * ($width  + $spacing);
+	my $y = $r * ($height + $spacing);
+	next unless defined $glyphs[$i];
+	$glyphs[$i]->move($x,$y);
+	$i++;
+      }
+    }
+
+    $self->{key_glyphs} = \@glyphs;     # remember our key glyphs
+    # remember our key height
+    return $self->{key_height} =
+      ($height+$spacing) * $rows + $self->{key_font}->height +KEYPADTOP;
+  }
+
+  else {  # no known key style, neither "between" nor "bottom"
+    return $self->{key_height} = 0;
+  }
+}
+
+sub draw_empty {
+  my $self  = shift;
+  my ($gd,$offset,$style) = @_;
+  $offset  += $self->spacing/2;
+  my $left  = $self->pad_left;
+  my $right = $self->width-$self->pad_right;
+  my $color = $self->translate_color(MISSING_TRACK_COLOR);
+  if ($style eq 'dashed') {
+    $gd->setStyle($color,$color,gdTransparent,gdTransparent);
+    $gd->line($left,$offset,$right,$offset,gdStyled);
+  } else {
+    $gd->line($left,$offset,$right,$offset,$color);
+  }
+  $offset;
+}
+
+# draw a grid
+sub draw_grid {
+  my $self = shift;
+  my $gd = shift;
+
+  my $gridcolor = $self->translate_color($self->{gridcolor});
+  my @positions;
+  if (ref $self->{grid} eq 'ARRAY') {
+    @positions = @{$self->{grid}};
+  } else {
+    my ($major,$minor) = $self->ticks;
+    my $first_tick = $minor * int(0.5 + $self->start/$minor);
+    for (my $i = $first_tick; $i < $self->end; $i += $minor) {
+      push @positions,$i;
+    }
+  }
+  my $pl = $self->pad_left;
+  my $pt = $self->pad_top;
+  my $pb = $self->height - $self->pad_bottom;
+  local $self->{flip} = 0;
+  for my $tick (@positions) {
+    my ($pos) = $self->map_pt($tick);
+    $gd->line($pos,$pt,$pos,$pb,$gridcolor);
+  }
+}
+
+# calculate major and minor ticks, given a start position
+sub ticks {
+  my $self = shift;
+  my ($length,$minwidth) = @_;
+
+  $length   = $self->{length}       unless defined $length;
+  $minwidth = gdSmallFont->width*7  unless defined $minwidth;
+
+  my ($major,$minor);
+
+  # figure out tick mark scale
+  # we want no more than 1 major tick mark every 40 pixels
+  # and enough room for the labels
+  my $scale = $self->scale;
+
+  my $interval = 1;
+
+  while (1) {
+    my $pixels = $interval * $scale;
+    last if $pixels >= $minwidth;
+    $interval *= 10;
+  }
+
+  # to make sure a major tick shows up somewhere in the first half
+  #
+  $interval *= .5 if ($interval > 0.5*$length);
+
+  return ($interval,$interval/10);
+}
+
+# reverse of translate(); given index, return rgb triplet
+sub rgb {
+  my $self = shift;
+  my $idx  = shift;
+  my $gd = $self->{gd} or return;
+  return $gd->rgb($idx);
+}
+
+sub translate_color {
+  my $self = shift;
+  my @colors = @_;
+  if (@colors == 3) {
+    my $gd = $self->gd or return 1;
+    return $self->colorClosest($gd,@colors);
+  }
+  elsif ($colors[0] =~ /^\#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) {
+    my $gd = $self->gd or return 1;
+    my ($r,$g,$b) = (hex($1),hex($2),hex($3));
+    return $self->colorClosest($gd,$r,$g,$b);
+  }
+  else {
+    my $color = $colors[0];
+    my $table = $self->{translations} or return 1;
+    return defined $table->{$color} ? $table->{$color} : 1;
+  }
+}
+
+# workaround for bad GD
+sub colorClosest {
+  my ($self,$gd,@c) = @_;
+  return $self->{closestcache}{"@c"} if exists $self->{closestcache}{"@c"};
+  return $self->{closestcache}{"@c"} = $gd->colorClosest(@c) if $GD::VERSION < 2.04;
+  my ($value,$index);
+  for (keys %COLORS) {
+    my ($r,$g,$b) = @{$COLORS{$_}};
+    my $dist = ($r-$c[0])**2 + ($g-$c[1])**2 + ($b-$c[2])**2;
+    ($value,$index) = ($dist,$_) if !defined($value) || $dist < $value;
+  }
+  return $self->{closestcache}{"@c"} = $self->{translations}{$index};
+}
+
+sub bgcolor {
+   my $self = shift;
+   return unless $self->{bgcolor};
+   $self->translate_color($self->{bgcolor});
+}
+
+sub set_pen {
+  my $self = shift;
+  my ($linewidth,$color) = @_;
+  return $self->{pens}{$linewidth,$color} if $self->{pens}{$linewidth,$color};
+
+  my $pen = $self->{pens}{$linewidth} = GD::Image->new($linewidth,$linewidth);
+  my @rgb = $self->rgb($color);
+  my $bg = $pen->colorAllocate(255,255,255);
+  my $fg = $pen->colorAllocate(@rgb);
+  $pen->fill(0,0,$fg);
+  $self->{gd}->setBrush($pen);
+  return gdBrushed;
+}
+
+sub png {
+  my $gd = shift->gd;
+  $gd->png;
+}
+
+sub read_colors {
+  my $class = shift;
+  while (<DATA>) {
+    chomp;
+    last if /^__END__/;
+    my ($name,$r,$g,$b) = split /\s+/;
+    $COLORS{$name} = [hex $r,hex $g,hex $b];
+  }
+}
+
+sub color_name_to_rgb {
+  my $class = shift;
+  my $color_name  = shift;
+  $class->read_colors() unless %COLORS;
+  return unless $COLORS{$color_name};
+  return wantarray ? @{$COLORS{$color_name}}
+                   : $COLORS{$color_name};
+}
+
+sub color_names {
+    my $class = shift;
+    $class->read_colors unless %COLORS;
+    return wantarray ? keys %COLORS : [keys %COLORS];
+}
+
+1;
+
+__DATA__
+white                FF           FF            FF
+black                00           00            00
+aliceblue            F0           F8            FF
+antiquewhite         FA           EB            D7
+aqua                 00           FF            FF
+aquamarine           7F           FF            D4
+azure                F0           FF            FF
+beige                F5           F5            DC
+bisque               FF           E4            C4
+blanchedalmond       FF           EB            CD
+blue                 00           00            FF
+blueviolet           8A           2B            E2
+brown                A5           2A            2A
+burlywood            DE           B8            87
+cadetblue            5F           9E            A0
+chartreuse           7F           FF            00
+chocolate            D2           69            1E
+coral                FF           7F            50
+cornflowerblue       64           95            ED
+cornsilk             FF           F8            DC
+crimson              DC           14            3C
+cyan                 00           FF            FF
+darkblue             00           00            8B
+darkcyan             00           8B            8B
+darkgoldenrod        B8           86            0B
+darkgray             A9           A9            A9
+darkgreen            00           64            00
+darkkhaki            BD           B7            6B
+darkmagenta          8B           00            8B
+darkolivegreen       55           6B            2F
+darkorange           FF           8C            00
+darkorchid           99           32            CC
+darkred              8B           00            00
+darksalmon           E9           96            7A
+darkseagreen         8F           BC            8F
+darkslateblue        48           3D            8B
+darkslategray        2F           4F            4F
+darkturquoise        00           CE            D1
+darkviolet           94           00            D3
+deeppink             FF           14            100
+deepskyblue          00           BF            FF
+dimgray              69           69            69
+dodgerblue           1E           90            FF
+firebrick            B2           22            22
+floralwhite          FF           FA            F0
+forestgreen          22           8B            22
+fuchsia              FF           00            FF
+gainsboro            DC           DC            DC
+ghostwhite           F8           F8            FF
+gold                 FF           D7            00
+goldenrod            DA           A5            20
+gray                 80           80            80
+green                00           80            00
+greenyellow          AD           FF            2F
+honeydew             F0           FF            F0
+hotpink              FF           69            B4
+indianred            CD           5C            5C
+indigo               4B           00            82
+ivory                FF           FF            F0
+khaki                F0           E6            8C
+lavender             E6           E6            FA
+lavenderblush        FF           F0            F5
+lawngreen            7C           FC            00
+lemonchiffon         FF           FA            CD
+lightblue            AD           D8            E6
+lightcoral           F0           80            80
+lightcyan            E0           FF            FF
+lightgoldenrodyellow FA           FA            D2
+lightgreen           90           EE            90
+lightgrey            D3           D3            D3
+lightpink            FF           B6            C1
+lightsalmon          FF           A0            7A
+lightseagreen        20           B2            AA
+lightskyblue         87           CE            FA
+lightslategray       77           88            99
+lightsteelblue       B0           C4            DE
+lightyellow          FF           FF            E0
+lime                 00           FF            00
+limegreen            32           CD            32
+linen                FA           F0            E6
+magenta              FF           00            FF
+maroon               80           00            00
+mediumaquamarine     66           CD            AA
+mediumblue           00           00            CD
+mediumorchid         BA           55            D3
+mediumpurple         100          70            DB
+mediumseagreen       3C           B3            71
+mediumslateblue      7B           68            EE
+mediumspringgreen    00           FA            9A
+mediumturquoise      48           D1            CC
+mediumvioletred      C7           15            85
+midnightblue         19           19            70
+mintcream            F5           FF            FA
+mistyrose            FF           E4            E1
+moccasin             FF           E4            B5
+navajowhite          FF           DE            AD
+navy                 00           00            80
+oldlace              FD           F5            E6
+olive                80           80            00
+olivedrab            6B           8E            23
+orange               FF           A5            00
+orangered            FF           45            00
+orchid               DA           70            D6
+palegoldenrod        EE           E8            AA
+palegreen            98           FB            98
+paleturquoise        AF           EE            EE
+palevioletred        DB           70            100
+papayawhip           FF           EF            D5
+peachpuff            FF           DA            B9
+peru                 CD           85            3F
+pink                 FF           C0            CB
+plum                 DD           A0            DD
+powderblue           B0           E0            E6
+purple               80           00            80
+red                  FF           00            00
+rosybrown            BC           8F            8F
+royalblue            41           69            E1
+saddlebrown          8B           45            13
+salmon               FA           80            72
+sandybrown           F4           A4            60
+seagreen             2E           8B            57
+seashell             FF           F5            EE
+sienna               A0           52            2D
+silver               C0           C0            C0
+skyblue              87           CE            EB
+slateblue            6A           5A            CD
+slategray            70           80            90
+snow                 FF           FA            FA
+springgreen          00           FF            7F
+steelblue            46           82            B4
+tan                  D2           B4            8C
+teal                 00           80            80
+thistle              D8           BF            D8
+tomato               FF           63            47
+turquoise            40           E0            D0
+violet               EE           82            EE
+wheat                F5           DE            B3
+whitesmoke           F5           F5            F5
+yellow               FF           FF            00
+yellowgreen          9A           CD            32
+gradient1	00 ff 00
+gradient2	0a ff 00
+gradient3	14 ff 00
+gradient4	1e ff 00
+gradient5	28 ff 00
+gradient6	32 ff 00
+gradient7	3d ff 00
+gradient8	47 ff 00
+gradient9	51 ff 00
+gradient10	5b ff 00
+gradient11	65 ff 00
+gradient12	70 ff 00
+gradient13	7a ff 00
+gradient14	84 ff 00
+gradient15	8e ff 00
+gradient16	99 ff 00
+gradient17	a3 ff 00
+gradient18	ad ff 00
+gradient19	b7 ff 00
+gradient20	c1 ff 00
+gradient21	cc ff 00
+gradient22	d6 ff 00
+gradient23	e0 ff 00
+gradient24	ea ff 00
+gradient25	f4 ff 00
+gradient26	ff ff 00
+gradient27	ff f4 00
+gradient28	ff ea 00
+gradient29	ff e0 00
+gradient30	ff d6 00
+gradient31	ff cc 00
+gradient32	ff c1 00
+gradient33	ff b7 00
+gradient34	ff ad 00
+gradient35	ff a3 00
+gradient36	ff 99 00
+gradient37	ff 8e 00
+gradient38	ff 84 00
+gradient39	ff 7a 00
+gradient40	ff 70 00
+gradient41	ff 65 00
+gradient42	ff 5b 00
+gradient43	ff 51 00
+gradient44	ff 47 00
+gradient45	ff 3d 00
+gradient46	ff 32 00
+gradient47	ff 28 00
+gradient48	ff 1e 00
+gradient49	ff 14 00
+gradient50	ff 0a 00
+__END__
+
+=head1 NAME
+
+Bio::Graphics::Panel - Generate GD images of Bio::Seq objects
+
+=head1 SYNOPSIS
+
+ # This script parses a GenBank or EMBL file named on the command
+ # line and produces a PNG rendering of it.  Call it like this:
+ # render.pl my_file.embl | display -
+
+ use strict;
+ use Bio::Graphics;
+ use Bio::SeqIO;
+
+ my $file = shift                       or die "provide a sequence file as the argument";
+ my $io = Bio::SeqIO->new(-file=>$file) or die "could not create Bio::SeqIO";
+ my $seq = $io->next_seq                or die "could not find a sequence in the file";
+
+ my @features = $seq->all_SeqFeatures;
+
+ # sort features by their primary tags
+ my %sorted_features;
+ for my $f (@features) {
+   my $tag = $f->primary_tag;
+   push @{$sorted_features{$tag}},$f;
+ }
+
+ my $panel = Bio::Graphics::Panel->new(
+                                      -length    => $seq->length,
+ 				      -key_style => 'between',
+ 				      -width     => 800,
+ 				      -pad_left  => 10,
+ 				      -pad_right => 10,
+ 				      );
+ $panel->add_track( arrow => Bio::SeqFeature::Generic->new(-start=>1,
+                                                           -end=>$seq->length),
+ 		  -bump => 0,
+ 		  -double=>1,
+ 		  -tick => 2);
+ $panel->add_track(generic => Bio::SeqFeature::Generic->new(-start=>1,
+							  -end=>$seq->length),
+ 		  -glyph  => 'generic',
+ 		  -bgcolor => 'blue',
+ 		  -label  => 1,
+ 		 );
+
+ # general case
+ my @colors = qw(cyan orange blue purple green chartreuse magenta yellow aqua);
+ my $idx    = 0;
+ for my $tag (sort keys %sorted_features) {
+   my $features = $sorted_features{$tag};
+   $panel->add_track($features,
+ 		    -glyph    =>  'generic',
+ 		    -bgcolor  =>  $colors[$idx++ % @colors],
+ 		    -fgcolor  => 'black',
+ 		    -font2color => 'red',
+ 		    -key      => "${tag}s",
+ 		    -bump     => +1,
+ 		    -height   => 8,
+ 		    -label    => 1,
+ 		    -description => 1,
+ 		   );
+ }
+
+ print $panel->png;
+ exit 0;
+
+=head1 DESCRIPTION
+
+The Bio::Graphics::Panel class provides drawing and formatting
+services for any object that implements the Bio::SeqFeatureI
+interface, including Ace::Sequence::Feature and Das::Segment::Feature
+objects.  It can be used to draw sequence annotations, physical
+(contig) maps, or any other type of map in which a set of discrete
+ranges need to be laid out on the number line.
+
+The module supports a drawing style in which each type of feature
+occupies a discrete "track" that spans the width of the display.  Each
+track will have its own distinctive "glyph", a configurable graphical
+representation of the feature.
+
+The module also supports a more flexible style in which several
+different feature types and their associated glyphs can occupy the
+same track.  The choice of glyph is under run-time control.
+
+Semantic zooming (for instance, changing the type of glyph depending
+on the density of features) is supported by a callback system for
+configuration variables.  The module has built-in support for Bio::Das
+stylesheets, and stylesheet-driven configuration can be intermixed
+with semantic zooming, if desired.
+
+You can add a key to the generated image using either of two key
+styles.  One style places the key captions at the top of each track.
+The other style generates a graphical key at the bottom of the image.
+
+Note that this modules depends on GD.
+
+=head1 METHODS
+
+This section describes the class and object methods for
+Bio::Graphics::Panel.
+
+Typically you will begin by creating a new Bio::Graphics::Panel
+object, passing it the desired width of the image to generate and an
+origin and length describing the coordinate range to display.  The
+Bio::Graphics::Panel-E<gt>new() method has may configuration variables
+that allow you to control the appearance of the image.
+
+You will then call add_track() one or more times to add sets of
+related features to the picture.  add_track() places a new horizontal
+track on the image, and is likewise highly configurable.  When you
+have added all the features you desire, you may call png() to convert
+the image into a PNG-format image, or boxes() to return coordinate
+information that can be used to create an imagemap.
+
+=head2 CONSTRUCTORS
+
+new() is the constructor for Bio::Graphics::Panel:
+
+=over 4
+
+=item $panel = Bio::Graphics::Panel-E<gt>new(@options)
+
+The new() method creates a new panel object.  The options are
+a set of tag/value pairs as follows:
+
+  Option      Value                                  Default
+  ------      -----                                  -------
+
+  -offset     Base pair to place at extreme left     none
+	      of image, in zero-based coordinates
+
+  -length     Length of sequence segment, in bp      none
+
+  -start      Start of range, in 1-based             none
+              coordinates.
+
+  -stop       Stop of range, in 1-based              none
+	      coordinates.
+
+  -segment    A Bio::SeqI or Das::Segment            none
+              object, used to derive sequence
+	      range if not otherwise specified.
+
+  -width      Desired width of image, in pixels      600
+
+  -spacing    Spacing between tracks, in pixels      5
+
+  -pad_top    Additional whitespace between top      0
+	      of image and contents, in pixels
+
+  -pad_bottom Additional whitespace between top      0
+	      of image and bottom, in pixels
+
+  -pad_left   Additional whitespace between left     0
+	      of image and contents, in pixels
+
+  -pad_right  Additional whitespace between right    0
+	      of image and bottom, in pixels
+
+  -bgcolor    Background color for the panel as a    white
+	      whole
+
+  -key_color  Background color for the key printed   wheat
+              at bottom of panel (if any)
+
+  -key_spacing Spacing between key glyphs in the     10
+               key printed at bottom of panel
+               (if any)
+
+  -key_font    Font to use in printed key            gdMediumBoldFont
+	       captions.
+
+  -key_style   Whether to print key at bottom of     none
+	       panel ("bottom"), between each
+	       track ("between"), to the left of
+               each track ("left"), to the right
+               of each track ("right") or
+               not at all ("none").
+
+  -empty_tracks What to do when a track is empty.    suppress
+              Options are to suppress the track
+              completely ("suppress"), to show just
+              the key in "between" mode ("key"),
+              to draw a thin grey line ("line"),
+              or to draw a dashed line ("dashed").
+
+  -flip       flip the drawing coordinates left     false
+              to right, so that lower coordinates
+              are to the right.  This can be
+              useful for drawing (-) strand
+              features.
+
+  -all_callbacks Whether to invoke callbacks on      false
+               the automatic "track" and "group"
+               glyphs.
+
+  -grid        Whether to draw a vertical grid in    false
+               the background.  Pass a scalar true
+               value to have a grid drawn at
+               regular intervals (corresponding
+               to the minor ticks of the arrow
+	       glyph).  Pass an array reference
+               to draw the grid at the specified
+               positions.
+
+  -gridcolor   Color of the grid                     lightcyan
+
+
+Typically you will pass new() an object that implements the
+Bio::RangeI interface, providing a length() method, from which the
+panel will derive its scale.
+
+  $panel = Bio::Graphics::Panel->new(-segment => $sequence,
+				     -width   => 800);
+
+new() will return undef in case of an error.
+
+Note that if you use the "left" or "right" key styles, you are
+responsible for allocating sufficient -pad_left or -pad_right room for
+the labels to appear.  The necessary width is the number of characters
+in the longest key times the font width (gdMediumBoldFont by default)
+plus 3 pixels of internal padding.  The simplest way to calculate this
+is to iterate over the possible track labels, find the largest one,
+and then to compute its width using the formula:
+
+  $width = gdMediumBoldFont->width * length($longest_key) +3;
+
+=back
+
+=head2 OBJECT METHODS
+
+=over 4
+
+=item $track = $panel-E<gt>add_track($glyph,$features,@options)
+
+The add_track() method adds a new track to the image. 
+
+Tracks are horizontal bands which span the entire width of the panel.
+Each track contains a number of graphical elements called "glyphs",
+corresponding to a sequence feature. 
+
+There are a large number of glyph types.  By default, each track will
+be homogeneous on a single glyph type, but you can mix several glyph
+types on the same track by providing a code reference to the -glyph
+argument.  Other options passed to add_track() control the color and
+size of the glyphs, whether they are allowed to overlap, and other
+formatting attributes.  The height of a track is determined from its
+contents and cannot be directly influenced.
+
+The first two arguments are the glyph name and an array reference
+containing the list of features to display.  The order of the
+arguments is irrelevant, allowing either of these idioms:
+
+  $panel->add_track(arrow => \@features);
+  $panel->add_track(\@features => 'arrow');
+
+
+The glyph name indicates how each feature is to be rendered.  A
+variety of glyphs are available, and the number is growing. You may
+omit the glyph name entirely by providing a B<-glyph> argument among
+@options, as described below.
+
+Currently, the following glyphs are available:
+
+  Name        Description
+  ----        -----------
+
+  anchored_arrow
+              a span with vertical bases |---------|.  If one or
+              the other end of the feature is off-screen, the base
+              will be replaced by an arrow.
+
+  arrow	      An arrow; can be unidirectional or bidirectional.
+	      It is also capable of displaying a scale with
+	      major and minor tickmarks, and can be oriented
+	      horizontally or vertically.
+
+  cds         Draws CDS features, using the phase information to
+              show the reading frame usage.  At high magnifications
+              draws the protein translation.
+
+  crossbox    A box with a big "X" inside it.
+
+  diamond     A diamond, useful for point features like SNPs.
+
+  dna         At high magnification draws the DNA sequence.  At
+              low magnifications draws the GC content.
+
+  dot         A circle, useful for point features like SNPs, stop
+              codons, or promoter elements.
+
+  ellipse     An oval.
+
+  extending_arrow
+              Similar to arrow, but a dotted line indicates when the
+              feature extends beyond the end of the canvas.
+
+  generic     A filled rectangle, nondirectional.
+
+  graded_segments
+              Similar to segments, but the intensity of the color
+              is proportional to the score of the feature.  This
+              is used for showing the intensity of blast hits or
+              other alignment features.
+
+  group	      A group of related features connected by a dashed line.
+	      This is used internally by Panel.
+
+  heterogeneous_segments
+              Like segments, but you can use the source field of the feature
+              to change the color of each segment.
+
+  line        A simple line.
+
+  pinsertion  A triangle designed to look like an insertion location
+              (e.g. a transposon insertion).
+
+  processed_transcript  multi-purpose representation of a spliced mRNA, including
+			positions of UTRs
+
+  primers     Two inward pointing arrows connected by a line.
+	      Used for STSs.
+
+  redgreen_box A box that changes from green->yellow->red as the score
+              of the feature increases from 0.0 to 1.0.  Useful for
+              representing microarray results.
+
+  rndrect     A round-cornered rectangle.
+
+  segments    A set of filled rectangles connected by solid lines.
+	      Used for interrupted features, such as gapped
+	      alignments.
+
+  ruler_arrow An arrow with major and minor tick marks and interval
+              labels.
+
+  toomany     Tries to show many features as a cloud.  Not very successful.
+
+  track	      A group of related features not connected by a line.
+	      This is used internally by Panel.
+
+  transcript  Similar to segments, but the connecting line is
+	      a "hat" shape, and the direction of transcription
+	      is indicated by a small arrow.
+
+  transcript2  Similar to transcript, but the direction of
+              transcription is indicated by a terminal exon
+              in the shape of an arrow.
+
+  translation 1, 2 and 3-frame translations.  At low magnifications,
+              can be configured to show start and stop codon locations.
+              At high magnifications, shows the multi-frame protein
+              translation.
+
+  triangle    A triangle whose width and orientation can be altered.
+
+  xyplot      Histograms and other graphs plotted against the genome.
+
+If the glyph name is omitted from add_track(), the "generic" glyph
+will be used by default.  To get more information about a glyph, run
+perldoc on "Bio::Graphics::Glyph::glyphname", replacing "glyphname"
+with the name of the glyph you are interested in.
+
+The @options array is a list of name/value pairs that control the
+attributes of the track.  Some options are interpretered directly by
+the track.  Others are passed down to the individual glyphs (see
+L<"GLYPH OPTIONS">).  The following options are track-specific:
+
+  Option      Description                  Default
+  ------      -----------                  -------
+
+  -tkcolor    Track color                  white
+
+  -glyph      Glyph class to use.         "generic"
+
+  -stylesheet Bio::Das::Stylesheet to     none
+              use to generate glyph
+	      classes and options.
+
+B<-tkcolor> controls the background color of the track as a whole.
+
+B<-glyph> controls the glyph type.  If present, it supersedes the
+glyph name given in the first or second argument to add_track().  The
+value of B<-glyph> may be a constant string, a hash reference, or a
+code reference.  In the case of a constant string, that string will be
+used as the class name for all generated glyphs.  If a hash reference
+is passed, then the feature's primary_tag() will be used as the key to
+the hash, and the value, if any, used to generate the glyph type.  If
+a code reference is passed, then this callback will be passed each
+feature in turn as its single argument.  The callback is expected to
+examine the feature and return a glyph name as its single result.
+
+Example:
+
+  $panel->add_track(\@exons,
+		    -glyph => sub { my $feature = shift;
+                                    $feature->source_tag eq 'curated'
+                                          ? 'ellipse' : 'generic'; }
+                    );
+
+The B<-stylesheet> argument is used to pass a Bio::Das stylesheet
+object to the panel.  This stylesheet will be called to determine both
+the glyph and the glyph options.  If both a stylesheet and direct
+options are provided, the latter take precedence.
+
+If successful, add_track() returns an Bio::Graphics::Glyph object.
+You can use this object to add additional features or to control the
+appearance of the track with greater detail, or just ignore it.
+Tracks are added in order from the top of the image to the bottom.  To
+add tracks to the top of the image, use unshift_track().
+
+B<Adding groups of features:> It is not uncommon to add a group of
+features which are logically connected, such as the 5' and 3' ends of
+EST reads.  To group features into sets that remain on the same
+horizontal position and bump together, pass the sets as an anonymous
+array.  For example:
+
+  $panel->add_track(segments => [[$abc_5,$abc_3],
+				 [$xxx_5,$xxx_3],
+				 [$yyy_5,$yyy_3]]
+		    );
+
+Typical usage is:
+
+ $panel->add_track( transcript    => \@genes,
+ 		    -fillcolor =>  'green',
+ 		    -fgcolor   =>  'black',
+ 		    -bump      =>  +1,
+ 		    -height    => 10,
+ 		    -label     => 1);
+
+=item $track = unshift_track($glyph,$features,@options)
+
+unshift_track() works like add_track(), except that the new track is
+added to the top of the image rather than the bottom.
+
+=item $gd = $panel-E<gt>gd([$gd])
+
+The gd() method lays out the image and returns a GD::Image object
+containing it.  You may then call the GD::Image object's png() or
+jpeg() methods to get the image data.
+
+Optionally, you may pass gd() a preexisting GD::Image object that you
+wish to draw on top of.  If you do so, you should call the width() and
+height() methods first to ensure that the image has sufficient
+dimensions.
+
+=item $png = $panel-E<gt>png
+
+The png() method returns the image as a PNG-format drawing, without
+the intermediate step of returning a GD::Image object.
+
+=item $boxes = $panel-E<gt>boxes
+
+=item @boxes = $panel-E<gt>boxes
+
+The boxes() method returns the coordinates of each glyph, useful for
+constructing an image map.  In a scalar context, boxes() returns an
+array ref.  In an list context, the method returns the array directly.
+
+Each member of the list is an anonymous array of the following format:
+
+  [ $feature, $x1, $y1, $x2, $y2 ]
+
+The first element is the feature object; either an
+Ace::Sequence::Feature, a Das::Segment::Feature, or another Bioperl
+Bio::SeqFeatureI object.  The coordinates are the topleft and
+bottomright corners of the glyph, including any space allocated for
+labels.
+
+=item $position = $panel-E<gt>track_position($track)
+
+After calling gd() or boxes(), you can learn the resulting Y
+coordinate of a track by calling track_position() with the value
+returned by add_track() or unshift_track().  This will return undef if
+called before gd() or boxes() or with an invalid track.
+
+=item @pixel_coords = $panel-E<gt>location2pixel(@feature_coords)
+
+Public routine to map feature coordinates (in base pairs) into pixel
+coordinates relative to the left-hand edge of the picture.
+
+=back
+
+=head1 GLYPH OPTIONS
+
+Each glyph has its own specialized subset of options, but
+some are shared by all glyphs:
+
+  Option      Description                  Default
+  ------      -----------                  -------
+
+  -fgcolor    Foreground color		   black
+
+  -bgcolor    Background color             turquoise
+
+  -linewidth  Width of lines drawn by	   1
+	      glyph
+
+  -height     Height of glyph		   10
+
+  -font       Glyph font		   gdSmallFont
+
+  -fontcolor  Primary font color	   black
+
+  -font2color Secondary font color	   turquoise
+
+  -label      Whether to draw a label	   false
+
+  -description  Whether to draw a          false
+              description
+
+  -bump	      Bump direction		   0
+
+  -sort_order Specify layout sort order    "default"
+
+  -bump_limit Maximum number of levels     undef (unlimited)
+              to bump
+
+  -strand_arrow Whether to indicate        undef (false)
+                 strandedness
+
+  -stranded    Synonym for -strand_arrow   undef (false)
+
+  -connector  Type of connector to         none
+	      use to connect related
+	      features.  Options are
+	      "solid," "hat", "dashed", 
+              "quill" and "none".
+
+  -key        Description of track for     undef
+	      use in key.
+
+  -all_callbacks Whether to invoke         undef
+              callbacks for autogenerated
+              "track" and "group" glyphs
+
+  -box_subparts Return boxes around feature          false
+               subparts rather than around the
+               feature itself.
+
+
+B<Specifying colors:> Colors can be expressed in either of two ways:
+as symbolic names such as "cyan" and as HTML-style #RRGGBB triples.
+The symbolic names are the 140 colors defined in the Netscape/Internet
+Explorer color cube, and can be retrieved using the
+Bio::Graphics::Panel-E<gt>color_names() method.
+
+B<Foreground color:> The -fgcolor option controls the foreground
+color, including the edges of boxes and the like.
+
+B<Background color:> The -bgcolor option controls the background used
+for filled boxes and other "solid" glyphs.  The foreground color
+controls the color of lines and strings.  The -tkcolor argument
+controls the background color of the entire track.
+
+B<Track color:> The -tkcolor option used to specify the background of
+the entire track.
+
+B<Font color:> The -fontcolor option controls the color of primary
+text, such as labels
+
+B<Secondary Font color:> The -font2color option controls the color of
+secondary text, such as descriptions.
+
+B<Labels:> The -label argument controls whether or not the ID of the
+feature should be printed next to the feature.  It is accepted by all
+glyphs.  By default, the label is printed just above the glyph and
+left aligned with it.  
+
+-label can be a constant string or a code reference.  Values can be
+any of:
+
+  -label value     Description
+  ------------     -----------
+
+    0              Don't draw a label
+    1              Calculate a label based on primary tag of sequence
+    "a string"     Use "a string" as the label
+    code ref       Invoke the code reference to compute the label
+
+A known bug with this naming scheme is that you can't label a feature
+with the string "1".  To work around this, use "1 " (note the terminal 
+space).
+
+B<Descriptions:> The -description argument controls whether or not a
+brief description of the feature should be printed next to it.  By
+default, the description is printed just below the glyph and
+left-aligned with it.  A value of 0 will suppress the description.  A
+value of 1 will call the source_tag() method of the feature.  A code
+reference will be invoked to calculate the description on the fly.
+Anything else will be treated as a string and used verbatim.
+
+B<Connectors:> A glyph can contain subglyphs, recursively.  The top
+level glyph is the track, which contains one or more groups, which
+contain features, which contain subfeatures, and so forth.  By
+default, the "group" glyph draws dotted lines between each of its
+subglyphs, the "segment" glyph draws a solid line between each of its
+subglyphs, and the "transcript" and "transcript2" glyphs draw
+hat-shaped lines between their subglyphs.  All other glyphs do not
+connect their components.  You can override this behavior by providing 
+a -connector option, to explicitly set the type of connector.  Valid
+options are:
+
+
+   "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.
+
+B<Collision control:> The -bump argument controls what happens when
+glyphs collide.  By default, they will simply overlap (value 0).  A
+-bump value of +1 will cause overlapping glyphs to bump downwards
+until there is room for them.  A -bump value of -1 will cause
+overlapping glyphs to bump upwards.  The bump argument can also be a
+code reference; see below.
+
+B<Keys:> The -key argument declares that the track is to be shown in a
+key appended to the bottom of the image.  The key contains a picture
+of a glyph and a label describing what the glyph means.  The label is
+specified in the argument to -key.
+
+B<box_subparts:> Ordinarily, when you invoke the boxes() methods to
+retrieve the rectangles surrounding the glyphs (which you need to do
+to create clickable imagemaps, for example), the rectangles will
+surround the top level features.  If you wish for the rectangles to
+surround subpieces of the glyph, such as the exons in a transcript,
+set box_subparts to a true value.
+
+B<strand_arrow:> If set to true, some glyphs will indicate their
+strandedness, usually by drawing an arrow.  For this to work, the
+Bio::SeqFeature must have a strand of +1 or -1.  The glyph will ignore
+this directive if the underlying feature has a strand of zero or
+undef.
+
+B<sort_order>: 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).
+
+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 )
+                   }
+
+B<bump_limit>: When bumping is chosen, colliding features will
+ordinarily move upward or downward without limit.  When many features
+collide, this can lead to excessively high images.  You can limit the
+number of levels that features will bump by providing a numeric
+B<bump_limit> option.
+
+=head2 Options and Callbacks
+
+Instead of providing a constant value to an option, you may subsitute
+a code reference.  This code reference will be called every time the
+panel needs to configure a glyph.  The callback will be called with
+three arguments like this:
+
+   sub callback {
+      my ($feature,$option_name,$part_no,$total_parts,$glyph) = @_;
+      # do something which results in $option_value being set
+      return $option_value;
+   }
+
+The five arguments are C<$feature>, a reference to the IO::SeqFeatureI
+object, C<$option_name>, the name of the option to configure,
+C<$part_no>, an integer index indicating which subpart of the feature
+is being drawn, C<$total_parts>, an integer indicating the total
+number of subfeatures in the feature, and finally C<$glyph>, the Glyph
+object itself.  The latter fields are useful in the case of treating
+the first or last subfeature differently, such as using a different
+color for the terminal exon of a gene.  Usually you will only need to
+examine the first argument.  This example shows a callback examining
+the score() attribute of a feature (possibly a BLAST hit) and return
+the color "red" for high-scoring features, and "green" for low-scoring
+features:
+
+  sub callback {
+     my $feature = shift;
+     if ($feature->score > 90) {
+       return 'red';
+     else {
+       return 'green';
+    }
+  }
+
+The callback should return a string indicating the desired value of
+the option.  To tell the panel to use the default value for this
+option, return the string "*default*".
+
+When you install a callback for a feature that contains subparts, the
+callback will be invoked first for the top-level feature, and then for
+each of its subparts (recursively).  You should make sure to examine
+the feature's type to determine whether the option is appropriate.
+
+Some glyphs deliberately disable this recursive feature.  The "track",
+"group", "transcript", "transcript2" and "segments" glyphs selectively
+disable the -bump, -label and -description options.  This is to avoid,
+for example, a label being attached to each exon in a transcript, or
+the various segments of a gapped alignment bumping each other.  You
+can override this behavior and force your callback to be invoked by
+providing add_track() with a true B<-all_callbacks> argument.  In this
+case, you must be prepared to handle configuring options for the
+"group" and "track" glyphs.
+
+In particular, this means that in order to control the -bump option
+with a callback, you should specify -all_callbacks=E<gt>1, and turn on
+bumping when the callback is in the track or group glyphs.
+
+=head2 ACCESSORS
+
+The following accessor methods provide access to various attributes of
+the panel object.  Called with no arguments, they each return the
+current value of the attribute.  Called with a single argument, they
+set the attribute and return its previous value.
+
+Note that in most cases you must change attributes prior to invoking
+gd(), png() or boxes().  These three methods all invoke an internal
+layout() method which places the tracks and the glyphs within them,
+and then caches the result.
+
+   Accessor Name      Description
+   -------------      -----------
+
+   width()	      Get/set width of panel
+   spacing()	      Get/set spacing between tracks
+   key_spacing()      Get/set spacing between keys
+   length()	      Get/set length of segment (bp)
+   flip()             Get/set coordinate flipping
+   pad_top()	      Get/set top padding
+   pad_left()	      Get/set left padding
+   pad_bottom()	      Get/set bottom padding
+   pad_right()	      Get/set right padding
+   start()            Get the start of the sequence (bp; read only)
+   end()              Get the end of the sequence (bp; read only)
+   left()             Get the left side of the drawing area (pixels; read only)
+   right()            Get the right side of the drawing area (pixels; read only)
+
+=head2 COLOR METHODS
+
+The following methods are used internally, but may be useful for those
+implementing new glyph types.
+
+=over 4
+
+=item @names = Bio::Graphics::Panel-E<gt>color_names
+
+Return the symbolic names of the colors recognized by the panel
+object.  In a scalar context, returns an array reference.
+
+=item ($red,$green,$blue) = Bio::Graphics::Panel-E<gt>color_name_to_rgb($color)
+
+Given a symbolic color name, returns the red, green, blue components
+of the color.  In a scalar context, returns an array reference to the
+rgb triplet.  Returns undef for an invalid color name.
+
+=item @rgb = $panel-E<gt>rgb($index)
+
+Given a GD color index (between 0 and 140), returns the RGB triplet
+corresponding to this index.  This method is only useful within a
+glyph's draw() routine, after the panel has allocated a GD::Image and
+is populating it.
+
+=item $index = $panel-E<gt>translate_color($color)
+
+Given a color, returns the GD::Image index.  The color may be
+symbolic, such as "turquoise", or a #RRGGBB triple, as in #F0E0A8.
+This method is only useful within a glyph's draw() routine, after the
+panel has allocated a GD::Image and is populating it.
+
+=item $panel-E<gt>set_pen($width,$color)
+
+Changes the width and color of the GD drawing pen to the values
+indicated.  This is called automatically by the GlyphFactory fgcolor()
+method.  It returns the GD value gdBrushed, which should be used for
+drawing.
+
+=back
+
+=head1 BUGS
+
+Please report them.
+
+=head1 SEE ALSO
+
+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::redgreen_box>,
+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::Graphics::Glyph::xyplot>,
+L<Bio::SeqI>,
+L<Bio::SeqFeatureI>,
+L<Bio::Das>,
+L<GD>
+
+=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
+