#!/usr/bin/perl -w
###################################################################
# Non-modperl users should change this variable if needed to point
# to the directory in which the configuration files are stored.
#
$CONF_DIR = '/bio/argos/gmod/in01/conf/gbrowse.conf';
#
###################################################################
$VERSION = 1.67;
use lib '/bio/argos/gmod/in01/lib';
# $Id: gbrowse_details.PLS,v 1.28.4.2.2.12.2.1 2007/03/22 02:24:24 scottcain Exp $
use strict;
use CGI qw(:standard *table *TR escape);
use Bio::DB::GFF;
use Bio::Graphics::Browser;
use Bio::Graphics::Browser::Markup;
use Bio::Graphics::Browser::Util;
use Bio::Graphics::Browser::Realign 'align';
use vars qw($CONFIG $VERSION $CONF_DIR $LANG @COLORS $INDEX %strands %COLORS %URLS %formatterCache);
use constant DEBUG=>0;
@COLORS = qw(none lightgrey yellow pink orange brown
tan teal cyan lime green blue gray);
$CONF_DIR = conf_dir($CONF_DIR); # conf_dir() is exported from Util.pm
$CONFIG = open_config($CONF_DIR); # open_config() is exported from Util.pm
$INDEX = 0;
%COLORS = ();
%URLS = ();
%formatterCache = ();
my $src = param('src') || param('source');
my $name = param('name');
my $class = param('class');
my $ref = param('ref');
my $start = param('start');
my $end = param('end');
my $f_id = param('feature_id');
# Migrate from 1.56 way of specifying source to 1.57 way
# This may cause a redirect and exit 0 at this point!
redirect_legacy_url($src);
$CONFIG->source(get_source());
# This populates the %URLS global with link rules from the config file.
getLinkURLs(\%URLS);
my $head_name = $class eq 'Sequence' ? $name : "$class:$name"; # gff3 compatibility
print_top("GBrowse Details: $head_name");
print $CONFIG->header || h1("$head_name Details");
my $db = open_database();
my @features;
if ($f_id) {
@features = $CONFIG->_feature_get($db,$name,$class,$start,$end,1,0,$f_id);
}
else {
@features = sort {$b->length<=>$a->length} $CONFIG->_feature_get($db,$name,$class);
@features = sort {$b->length<=>$a->length} $CONFIG->_feature_get($db,$ref,$class,$start,$end,1)
unless @features;
}
warn "features = @features" if DEBUG;
warn "segments = ",join ' ',$features[0]->segments if (DEBUG && @features);
if (@features) {
# dgg: test gbrowse_edit
# print h4("Test Gbrowse_Chado_Editor");
print print_features(\@features);
} else {
print p({-class=>'error'},'Requested feature not found in database.');
}
# footer
print_bottom($VERSION);
exit 0;
######################
sub print_features {
my $features = shift;
my $subf = shift || 0;
my $string;
for my $f (@$features) {
my $method = $f->primary_tag . $subf;
warn "index = $INDEX, method = $method" if DEBUG;
$COLORS{$method} ||= $COLORS[$INDEX++ % @COLORS];
my $options = {-bgcolor => $COLORS{$method}} unless $COLORS{$method} eq 'none';
$string .= start_table({-cellspacing=>0});
unless ($subf) {
$string .= PrintMultiple($f,$options,'Name',$f->name);
$string .= PrintMultiple($f,$options,'Class',$f->class) unless $f->class eq 'Sequence';
}
$string .= PrintMultiple($f,$options,'Type',$f->primary_tag);
$string .= PrintMultiple($f,$options,'Source',$f->source_tag) if $f->source_tag;
$string .= PrintMultiple($f,$options,"Position",$f);
$string .= PrintMultiple($f,$options,"Length",$f->length);
if ($f->can('target') && $f->target) {
# try to correct for common GFF2 error of indicating a -/- alignment
# using a (-) src strand and a target_start > target_end
my $bug = $f->abs_strand < 0 && $f->target->abs_strand < 0;
$string .= PrintMultiple($f,$options,'Target',$f->target->seq_id);
$string .= PrintMultiple($f,$options,"Matches",$f);
$string .= PrintMultiple($f,$options,'',print_matches($f,$f->target,$bug)) if $subf;
}
$string .= PrintMultiple($f,$options,"Score",$f->score) if $f->can('score') && defined $f->score;
my %attributes = $f->attributes if $f->can('attributes');
for my $a (sort grep {!/Target/} keys %attributes) {
$string .= PrintMultiple($f,$options,$a,$f->attributes($a));
}
$string .= TR({-valign=>'top',-class=>'databody'},th({-height=>3},''),td({-height=>3},''));
my @subfeatures;
# sort features with targets so that target is in order
if ($f->can('target') && $f->target) {
@subfeatures = sort {$a->target->start <=> $b->target->start} $f->get_SeqFeatures;
} else {
@subfeatures = sort {$a->start <=> $b->start} $f->get_SeqFeatures;
}
my $subtable = PrintMultiple($f,$options,'Parts',print_features(\@subfeatures,$subf+1)) if @subfeatures;
$string .= $subtable || ''; # prevent uninit variable warning
$string .= end_table();
if ($subtable or $subf==0) {
my $dna = $f->seq;
$dna = $dna->seq if ref $dna; # compensate for API changes
$string .= print_dna($f,$dna,$f->abs_start,$f->strand,\@subfeatures,$subf+1) if $dna;
}
}
$string;
}
sub print_dna {
my ($feature,$dna,$start,$strand,$features,$subf) = @_;
my %seenit;
warn "dna=$dna" if DEBUG;
my $markup = Bio::Graphics::Browser::Markup->new;
for my $f (@$features) {
warn "f = $f" if DEBUG;
my $method = $f->primary_tag . $subf;
warn "$method => $COLORS{$method}" if DEBUG;
next if $COLORS{$method} eq 'none';
$markup->add_style($method => "BGCOLOR $COLORS{$method}");
}
# add a newline every 80 positions
$markup->add_style('newline',"\n");
# add a space every 10 positions
$markup->add_style('space'," ");
my @markup;
for my $f (@$features) {
my ($s,$e);
if ($strand >=0) {
$s = $f->low - $start;
$e = $f->high - $start;
} else {
if ($start - $f->high < 0) { #how much of a hack is this!
#it fixes chado feature differences
$s = $start + length($dna) - $f->low -1;
$e = $start + length($dna) - $f->high -1;
} else {
$s = $start - $f->low;
$e = $start - $f->high;
}
}
($s,$e) = ($e,$s) if $s > $e;
my $method = $f->primary_tag . $subf;
next if $COLORS{$method} eq 'none';
push @markup,[$method,$s,$e+1]; # Duelling off-by-one errors....
}
push @markup,map {['newline',80*$_]} (1..length($dna)/80);
push @markup,map {['space',10*$_]} grep {$_ % 8} (1..length($dna)/10);
$markup->markup(\$dna,\@markup);
my $position = position($feature);
my $name = $feature->name;
my $class = $feature->class;
return pre(">$name class=$class position=$position\n".$dna);
}
sub print_matches {
my ($src,$tgt,$bug) = @_;
my $sdna = $src->dna or return '';
my $tdna = $tgt->dna or return '';
my $top_label = $src->abs_ref;
my $bot_label = $tgt->abs_ref;
my $src_x = $src->abs_start;
my $src_y = $src->abs_end;
my $tgt_x = $tgt->abs_start;
my $tgt_y = $tgt->abs_end;
# my $tdir = $tgt->abs_strand || +1;
# my $sdir = $src->abs_strand || +1;
my $tdir = $tgt->strand || +1;
my $sdir = $src->strand || +1;
if ($bug) { # correct for buggy data files that show -/- alignments; really -/+
$tdir = +1;
($tgt_x,$tgt_y) = ($tgt_y,$tgt_x);
$tdna =~ tr/gatcGATC/ctagCTAG/;
$tdna = reverse $tdna;
}
warn ("sdir = $sdir, $src_x -> $src_y / $tgt_x -> $tgt_y") if DEBUG;
my ($top,$middle,$bottom) = align($sdna,$tdna);
my $m = max(length($top_label),length($bot_label));
my $p = max(length($src_x),length($src_y),length($tgt_x),length($tgt_y));
my $l = ' ' x ($m+$p+2); # adjusting for HTML
my $string;
my @top = $top =~ /(.{1,60})/g;
my @middle = $middle =~ /(.{1,60})/g;
my @bottom = $bottom =~ /(.{1,60})/g;
$src_x = $src_y if $sdir < 0;
for (my $i=0; $i<@top; $i++) {
my $src_delta = $sdir * (length($top[$i]) - $top[$i]=~tr/-/-/);
my $tgt_delta = $tdir * (length($bottom[$i]) - $bottom[$i]=~tr/-/-/);
$string .= sprintf("%${m}s %${p}d %s %d\n$l%s\n%${m}s %${p}d %s %d\n\n",
$top_label,$src_x,$top[$i],$src_x + $src_delta - $sdir,
$middle[$i],
$bot_label,$tgt_x,$bottom[$i],$tgt_x + $tgt_delta - $tdir);
$src_x += $src_delta;
$tgt_x += $tgt_delta;
}
return pre($string);
}
sub max {
if (@_ == 2) {
return $_[0] > $_[1] ? $_[0] : $_[1];
} else {
return (sort {$b<=>$a} @_)[0];
}
}
sub PrintMultiple {
local $^W = 0; # get rid of uninit variable warnings
my $feature = shift;
my $options = shift;
my $label = shift;
$options ||= {};
my @a = formatValues($feature,$label,@_);
return '' unless @a;
my $LINK = "";
my $isFirst=1;
my $string = ' ' ;
for my $obj (@a) {
if ($URLS{$label}){
$LINK = $URLS{$label};
if ( ref ($LINK) eq 'CODE' ){ #Testing subs
$LINK= eval { $LINK->($label,$obj)};
$LINK = $LINK ? "$obj" : $obj;
}
else { #end testing subs
$LINK =~ s/\$tag/$label/;
$LINK=~ s/\$value/$obj/;
$LINK = "$obj";
} # testing subs
}
# for EST alignment features, create a link to get the orignal EST sequence
if (($label eq 'Target') && ($URLS{'alignment'}) && ($obj =~ /alignment/i)){
my $name = shift @a;
$LINK = $URLS{'alignment'};
$LINK=~ s/\$value/$name/;
$LINK = "$obj : (Aligned Sequence)";
}
$obj =~ s/([^<>\s'"\/;&]{60})/$1 /g; # wrap way long lines. Note : ading '" prevent this regexp from wrapping html tags
if ($isFirst) {
$isFirst =0 ;
$string .= join '',TR({-valign=>'top',-class=>'databody'},
th({-align=>'LEFT',-valign=>'top',-class=>'datatitle',-width=>100},length $label>0 ? "$label: " : ''),
td($options, $LINK ? $LINK : $obj)
);
} else {
$string .= join '', TR({-class=>'databody'},
th({-align=>'RIGHT',-class=>'datatitle',-width=>100},' '),
td($options,$LINK?$LINK:$obj)
);
}
$LINK='';
}
$string;
}
sub position {
my $f = shift;
my $simple = shift;
my $bug = shift; # for (-) (-) alignments
my $ref = $f->abs_ref;
my $start = $f->abs_start;
my $end = $f->abs_end;
if ($simple) {
($start,$end) = ($end,$start) if $f->strand < 0;
return "$ref $start..$end";
}
my $s = $f->strand;
if ($bug) { # data bug
($start,$end) = ($end,$start);
$s *= -1;
}
my $strand = $s > 0 ? '+' : $s < 0 ? '-' : '';
my $src = escape($CONFIG->source);
my $url = "../gbrowse/$src?name=$ref:$start..$end";
return a({-href=>$url},$strand ? "$ref:$start..$end ($strand strand)" : "$ref:$start..$end");
}
sub getLinkURLs {
my $urls = shift;
my $THIS_CONFIG = $CONFIG->config;
$THIS_CONFIG->safe(0);
my @LINK_CONFIGS = map{$_=~/\:DETAILS$/?$_:undef} $THIS_CONFIG->setting;
foreach (@LINK_CONFIGS){
next unless $_;
next unless $_=~/(.*?)\:DETAILS/;
next unless $1;
my $URL = $THIS_CONFIG->setting("$_", 'url');
next unless $URL;
$urls->{$1}=$URL;
}
}
sub formatValues {
my ($feature,$tag,@values) = @_;
my $formatter = getFormatter($feature,$tag);
return @values unless $formatter;
if (ref $formatter eq 'CODE') {
return map {$formatter->($_,$tag,$feature)} @values;
}
my $name = $feature->display_name;
my $start = $feature->start || '';
my $end = $feature->end || '';
my $strand = $feature->strand || '';
my $method = $feature->primary_tag || '';
my $source = $feature->source_tag || '';
my $type = eval {$feature->type} || $method || '';
my $class = eval {$feature->class} || '';
my $description = eval { join ' ',$feature->notes } || '';
$formatter =~ s/\$tag/$tag/g;
$formatter =~ s/\$name/$name/g;
$formatter =~ s/\$start/$start/g;
$formatter =~ s/\$end/$end/g;
$formatter =~ s/\$stop/$end/g;
$formatter =~ s/\$strand/$strand/g;
$formatter =~ s/\$method/$method/g;
$formatter =~ s/\$source/$source/g;
$formatter =~ s/\$type/$type/g;
$formatter =~ s/\$class/$class/g;
$formatter =~ s/\$description/$description/g;
return map {$formatter =~ s/\$value/$_/g; $formatter} @values;
}
sub getFormatter {
my ($feature,$tag) = @_;
my $method = $feature->primary_tag;
my $source = $feature->source_tag;
my $key = join ':',$method,$source,$tag;
return $formatterCache{$key} if exists $formatterCache{$key};
my $config = $CONFIG->config;
my $s;
# implement simple search path for formatters
SEARCH:
for my $base ("$method:$source",$method,'default') {
for my $option ($tag,'default') {
$s ||= $config->code_setting("$base:details" => lc $option);
$s ||= $config->code_setting("$base:DETAILS" => lc $option);
last SEARCH if defined $s;
}
}
unless (defined $s) {
$s = \&format_position if $tag eq 'Position';
$s = \&format_matches if $tag eq 'Matches';
$s = \&format_name if $tag eq 'Name';
}
return $formatterCache{$key} = $s;
}
sub get_source {
my $new_source = param('source') || param('src') || path_info();
$new_source =~ s!^/!!; # get rid of leading / from path_info()
my $old_source = cookie('gbrowse_source') unless $new_source && request_method() eq 'GET';
my $source = $new_source || $old_source;
$source ||= $CONFIG->source; # the default, whatever it is
return ($source,$old_source);
}
sub format_position {
my (undef,undef,$feature) = @_;
position($feature);
}
sub format_matches {
my (undef,undef,$feature) = @_;
# try to correct for common GFF2 error of indicating a -/- alignment
# using a (-) src strand and a target_start > target_end
my $bug = $feature->abs_strand < 0 && $feature->target->abs_strand < 0;
position($feature->target,undef,$bug)
}
sub format_name {
my $name = shift;
b($name)
}