Bio::Graphics
Glyph
Toolbar
Summary
Bio::Graphics::Glyph - Base class for Bio::Graphics::Glyph objects
Package variables
Privates (from "my" definitions)
%LAYOUT_COUNT;
Included modules
Carp ' croak '
GD
constant BUMP_SPACING => 2
Synopsis
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.
Methods
_collision_keys | No description | Code |
_connector | No description | Code |
_subseq | No description | Code |
add_collision | No description | Code |
add_feature | No description | Code |
add_group | No description | Code |
all_callbacks | No description | Code |
bgcolor | No description | Code |
bottom | No description | Code |
bounds | No description | Code |
box | No description | Code |
boxes | No description | Code |
bump | No description | Code |
collides | No description | Code |
color | No description | Code |
configure | No description | Code |
connector | No description | Code |
connector_color | No description | Code |
default_factory | No description | Code |
draw | No description | Code |
draw_component | No description | Code |
draw_connector | No description | Code |
draw_connectors | No description | Code |
draw_dashed_connector | No description | Code |
draw_hat_connector | No description | Code |
draw_quill_connector | No description | Code |
draw_solid_connector | No description | Code |
factory | No description | Code |
fgcolor | No description | Code |
fill | No description | Code |
fillcolor | No description | Code |
filled_arrow | No description | Code |
filled_box | No description | Code |
filled_oval | No description | Code |
font | No description | Code |
font2color | No description | Code |
fontcolor | No description | Code |
height | No description | Code |
keyglyph | No description | Code |
layout | No description | Code |
layout_height | No description | Code |
layout_sort | No description | Code |
layout_width | No description | Code |
left | No description | Code |
length | No description | Code |
level | No description | Code |
linewidth | No description | Code |
make_key_feature | No description | Code |
map_no_trunc | No description | Code |
move | No description | Code |
new | No description | Code |
option | No description | Code |
oval | No description | Code |
pad_bottom | No description | Code |
pad_left | No description | Code |
pad_right | No description | Code |
pad_top | No description | Code |
panel | No description | Code |
parts | No description | Code |
point | No description | Code |
right | No description | Code |
scale | No description | Code |
score | No description | Code |
set_pen | No description | Code |
start | No description | Code |
stop | No description | Code |
strand | No description | Code |
subseq | No description | Code |
tkcolor | No description | Code |
top | No description | Code |
unfilled_box | No description | Code |
width | No description | Code |
Methods description
None available.
Methods code
_collision_keys | description | prev | next | Top |
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 _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;
return if $right-$left < 1 && !$self->isa('Bio::Graphics::Glyph::group');
$self->draw_connector($gd,
$top1,$bottom1,$left,
$top2,$bottom2,$right,
); } |
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;
}
} |
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 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);
}
}
}
} |
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 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 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 bottom
{ my $self = shift;
$self->top + $self->layout_height - 1; } |
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 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;
}
} |
sub bump
{ my $self = shift;
return $self->option('bump');
}
} |
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 color
{ my $self = shift;
my $color = shift;
my $index = $self->option($color);
return $self->factory->translate_color($index) if defined $index;
return 0; } |
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;
}
}
} |
sub connector
{ return shift->option('connector',@_);
}
} |
sub connector_color
{ my $self = shift;
$self->color('connector_color') || $self->fgcolor; } |
sub default_factory
{ croak "no default factory implemented";
}
1;
__END__ } |
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) {
@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++) {
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 { $self->draw_connectors($gd,$left,$top)
if $connector && $connector ne 'none' && $self->{level} == 0;
$self->draw_component($gd,$left,$top);
}
}
} |
sub draw_component
{ my $self = shift;
my $gd = shift;
my($x1,$y1,$x2,$y2) = $self->bounds(@_);
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)
}
}
} |
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 {
; } } |
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);
}
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 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_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) { 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) { $gd->line($left,$quarter1,$right-1,$quarter1,$color);
} } |
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 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 factory
{ shift->{factory} } |
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);
}
} |
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 fillcolor
{ my $self = shift;
return $self->bgcolor;
}
} |
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 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;
$gd->rectangle($x1,$y1,$x2,$y2,$fg);
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);
$gd->fill($cx,$cy,$bg); } |
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 font2color
{ my $self = shift;
my $font2color = $self->color('font2color');
return defined $font2color ? $font2color : $self->fgcolor; } |
sub fontcolor
{ my $self = shift;
my $fontcolor = $self->color('fontcolor');
return defined $fontcolor ? $fontcolor : $self->fgcolor; } |
sub height
{ my $self = shift;
return $self->{height} if exists $self->{height};
my $baseheight = $self->option('height'); return $self->{height} = $baseheight; } |
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);
}
} |
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;
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) {
if ($bump_limit > 0 && $bumplevel++ >= $bump_limit) {
$g->{overbumped}++; foreach ($g->parts) {
$_->{overbumped}++;
}
last;
}
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;
} 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 ($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;
}
my $bottom = 0;
foreach (@parts) {
$bottom = $_->bottom if $_->bottom > $bottom;
}
return $self->{layout_height} = $self->pad_bottom + $self->pad_top + $bottom - $self->top + 1;
}
} |
sub layout_height
{ my $self = shift;
return $self->layout; } |
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 {
my @sortbys = split(/\s*\|\s*/o, $opt);
$sortfunc = 'sub { ';
my $sawleft = 0;
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;
}
return sort $sortfunc @_;
}
} |
sub layout_width
{ my $self = shift;
return $self->width + $self->pad_left + $self->pad_right;
}
} |
sub left
{ my $self = shift;
return $self->{left} - $self->pad_left; } |
sub length
{ my $self = shift; $self->stop - $self->start }; } |
sub level
{ shift->{level}; } |
sub linewidth
{ shift->option('linewidth') || 1; } |
sub make_key_feature
{ my $self = shift;
my $scale = 1/$self->scale; # base pairs/pixel
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 map_no_trunc
{ shift->{factory}->map_no_trunc(@_) } |
sub move
{ my $self = shift;
my ($dx,$dy) = @_;
$self->{left} += $dx;
$self->{top} += $dy;
$_->move($dx,0) foreach $self->parts;
}
} |
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) {
@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; 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;
return $self; } |
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)});
}
} |
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 pad_bottom
{ my $self = shift;
return 0; } |
sub pad_left
{ my $self = shift;
return 0; } |
sub pad_right
{ my $self = shift;
my @parts = $self->parts or return 0;
my $max = 0;
foreach (@parts) {
my $pr = $_->pad_right;
$max = $pr if $max < $pr;
}
$max;
}
} |
sub pad_top
{ my $self = shift;
return 0; } |
sub panel
{ shift->factory->panel } |
sub parts
{ my $self = shift;
return unless $self->{parts};
return wantarray ? @{$self->{parts}} : $self->{parts}; } |
sub point
{ shift->{point} } |
sub right
{ my $self = shift;
return $self->left + $self->layout_width - 1; } |
sub scale
{ shift->factory->scale } |
sub score
{ my $self = shift;
return $self->{score} if exists $self->{score};
return $self->{score} = ($self->{feature}->score || 0); } |
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 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;
$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;
$self->{stop} = $self->panel->offset + $self->panel->length + 1 unless defined $self->{stop};
return $self->{stop} } |
sub strand
{ my $self = shift;
return $self->{strand} if exists $self->{strand};
return $self->{strand} = ($self->{feature}->strand || 0); } |
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 tkcolor
{ $self->option('tkcolor') or return;
return $self->color('tkcolor') } |
sub top
{ my $self = shift;
my $g = $self->{top};
$self->{top} = shift if @_;
$g; } |
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;
}
$gd->rectangle($x1,$y1,$x2,$y2,$fg);
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;
}
} |
sub width
{ my $self = shift;
my $g = $self->{width};
$self->{width} = shift if @_;
$g; } |
General documentation
Bio::Graphics::Glyph objects are constructed automatically by an
Bio::Graphics::Glyph::Factory, and are not usually created by
end-developer code.
$glyph = Bio::Graphics::Glyph-E<gt>new(-feature=E<gt>$feature,-factory=>$factory)
Given a sequence feature, creates an
Bio::Graphics::Glyph object to
display it. The
-feature argument points to the Bio:SeqFeatureI
object to display, and
-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
OPTIONS.
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:
$factory = $glyph->factory
Get the Bio::Graphics::Glyph::Factory associated with this object.
This cannot be changed once it is set.
$panel = $glyph->panel
Get the Bio::Graphics::Panel associated with this object. This cannot
be changed once it is set.
$feature = $glyph->feature
Get the sequence feature associated with this object. This cannot be
changed once it is set.
$feature = $glyph->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->add_track().
$feature = $glyph->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.
Retrieving glyph options:
$fgcolor = $glyph->fgcolor
$bgcolor = $glyph->bgcolor
$fontcolor = $glyph->fontcolor
$fontcolor = $glyph->font2color
$fillcolor = $glyph->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.
$color = $glyph->tkcolor
This method returns a color to be used to flood-fill the entire glyph
before drawing (currently used by the "track" glyph).
$width = $glyph->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.
$width = $glyph->layout_width
Returns the width of the glyph including left and right padding.
$width = $glyph->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.
$font = $glyph->font
Return the font for the glyph.
$option = $glyph->option($option)
Return the value of the indicated option.
$index = $glyph->color($color)
Given a symbolic or #RRGGBB-form color name, returns its GD index.
$level = $glyph->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.
Setting an option:
$glyph-E<gt>configure(-name=>$value)
You may change a glyph option after it is created using set_option().
This is most commonly used to configure track glyphs.
Retrieving information about the sequence:
$start = $glyph->start
$end = $glyph->end
These methods return the start and end of the glyph in base pair
units.
$offset = $glyph->offset
Returns the offset of the segment (the base pair at the far left of
the image).
$length = $glyph->length
Returns the length of the sequence segment.
Retrieving formatting information:
$top = $glyph->top
$left = $glyph->left
$bottom = $glyph->bottom
$right = $glyph->right
These methods return the top, left, bottom and right of the glyph in
pixel coordinates.
$height = $glyph->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.
$scale = $glyph->scale
Get the scale for the glyph in pixels/bp.
$height = $glyph->labelheight
Return the height of the label, if any.
$label = $glyph->label
Return a human-readable label for the glyph.
These methods are called by Bio::Graphics::Track during the layout
process:
$glyph->move($dx,$dy)
Move the glyph in pixel coordinates by the indicated delta-x and
delta-y values.
($x1,$y1,$x2,$y2) = $glyph->box
Return the current position of the glyph.
These methods are intended to be overridden in subclasses:
$glyph->calculate_height
Calculate the height of the glyph.
$glyph->calculate_left
Calculate the left side of the glyph.
$glyph->calculate_right
Calculate the right side of the glyph.
$glyph->draw($gd,$left,$top)
Optionally offset the glyph by the indicated amount and draw it onto
the GD::Image object.
$glyph->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.
These methods are useful utility routines:
$pixels = $glyph->map_pt($bases);
Map the indicated base position, given in base pair units, into
pixels, using the current scale and glyph position.
$glyph->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.
$glyph->filled_oval($gd,$x1,$y1,$x2,$y2)
As above, but draws an oval inscribed on the rectangle.
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
-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
-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
-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
-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
-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
-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
-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,
-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.
SUBCLASSING Bio::Graphics::Glyph | Top |
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->line() twice to generate the criss-cross.
For more complex draw() methods, see Bio::Graphics::Glyph::transcript
and Bio::Graphics::Glyph::segments.
Please report them.
Bio::DB::GFF::Feature,
Ace::Sequence,
Bio::Graphics::Panel,
Bio::Graphics::Track,
Bio::Graphics::Glyph::anchored_arrow,
Bio::Graphics::Glyph::arrow,
Bio::Graphics::Glyph::box,
Bio::Graphics::Glyph::dna,
Bio::Graphics::Glyph::graded_segments,
Bio::Graphics::Glyph::primers,
Bio::Graphics::Glyph::segments,
Bio::Graphics::Glyph::toomany,
Bio::Graphics::Glyph::transcript,
Bio::Graphics::Glyph::transcript2,
Bio::Graphics::Glyph::wormbase_transcript
Lincoln Stein <lstein@cshl.org>
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.