~ubuntu-branches/ubuntu/raring/bioperl/raring

« back to all changes in this revision

Viewing changes to Bio/Graphics/FeatureFile.pm

  • Committer: Bazaar Package Importer
  • Author(s): Charles Plessy
  • Date: 2008-03-18 14:44:57 UTC
  • mfrom: (4 hardy)
  • mto: This revision was merged to the branch mainline in revision 6.
  • Revision ID: james.westby@ubuntu.com-20080318144457-1jjoztrvqwf0gruk
* debian/control:
  - Removed MIA Matt Hope (dopey) from the Uploaders field.
    Thank you for your work, Matt. I hope you are doing well.
  - Downgraded some recommended package to the 'Suggests' priority,
    according to the following discussion on Upstream's mail list.
    http://bioperl.org/pipermail/bioperl-l/2008-March/027379.html
    (Closes: #448890)
* debian/copyright converted to machine-readable format.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
package Bio::Graphics::FeatureFile;
2
2
 
3
 
# $Id: FeatureFile.pm,v 1.41 2003/12/13 17:17:50 lstein Exp $
 
3
# $Id: FeatureFile.pm,v 1.78.4.7 2006/12/02 18:35:32 lstein Exp $
4
4
# This package parses and renders a simple tab-delimited format for features.
5
5
# It is simpler than GFF, but still has a lot of expressive power.
6
6
# See __END__ for the file format
53
53
its consistuent features and their settings, or render the entire file
54
54
onto a Bio::Graphics::Panel.
55
55
 
56
 
This moduel is a precursor of Jason Stajich's
 
56
This module is a precursor of Jason Stajich's
57
57
Bio::Annotation::Collection class, and fulfills a similar function of
58
58
storing a collection of sequence features.  However, it also stores
59
59
rendering information about the features, and does not currently
117
117
use strict;
118
118
use Bio::Graphics::Feature;
119
119
use Bio::DB::GFF::Util::Rearrange;
120
 
use Carp;
121
 
use Bio::DB::GFF;
 
120
use Carp 'cluck','carp','croak';
 
121
# use Bio::DB::GFF; # not needed - load later
122
122
use IO::File;
123
 
use Text::Shellwords;
 
123
use Text::ParseWords 'shellwords';
124
124
 
125
125
# default colors for unconfigured features
126
126
my @COLORS = qw(cyan blue red yellow green wheat turquoise orange);
 
127
 
127
128
use constant WIDTH => 600;
 
129
use constant MAX_REMAP => 100;
128
130
 
129
131
=head2 METHODS
130
132
 
201
203
                   },$class;
202
204
  $self->{coordinate_mapper} = $args{-map_coords} 
203
205
    if exists $args{-map_coords} && ref($args{-map_coords}) eq 'CODE';
204
 
  $self->{smart_features}    = $args{-smart_features} if exists $args{-smart_features};
 
206
 
 
207
  $self->smart_features($args{-smart_features})       if exists $args{-smart_features};
205
208
  $self->{safe}              = $args{-safe}           if exists $args{-safe};
206
209
 
207
210
  # call with
230
233
 
231
234
=over 4
232
235
 
233
 
=item ($rendered,$panel) = $features-E<gt>render([$panel])
 
236
=item ($rendered,$panel) = $features-E<gt>render([$panel, $position_to_insert, $options, $max_bump, $max_label, $selector])
234
237
 
235
238
Render features in the data set onto the indicated
236
239
Bio::Graphics::Panel.  If no panel is specified, creates one.
237
240
 
 
241
All arguments are optional.
 
242
 
 
243
$panel is a Bio::Graphics::Panel that has previously been created and
 
244
configured.
 
245
 
 
246
$position_to_insert indicates the position at which to start inserting
 
247
new tracks. The last current track on the panel is assumed.
 
248
 
 
249
$options is a scalar used to control automatic expansion of the
 
250
tracks. 0=auto, 1=compact, 2=expanded, 3=expand and label,
 
251
4=hyperexpand, 5=hyperexpand and label.
 
252
 
 
253
$max_bump and $max_label indicate the maximum number of features
 
254
before bumping and labeling are turned off.
 
255
 
 
256
$selector is a code ref that can be used to filter which features to
 
257
render. It receives a feature and should return true to include the
 
258
feature and false to exclude it.
 
259
 
238
260
In a scalar context returns the number of tracks rendered.  In a list
239
 
context, returns a two-element list containing the number of features
240
 
rendered and the panel.  Use this form if you want the panel created
241
 
for you.
 
261
context, returns a three-element list containing the number of
 
262
features rendered, the created panel, and a list of all the track
 
263
objects created.
242
264
 
243
265
=back
244
266
 
249
271
sub render {
250
272
  my $self = shift;
251
273
  my $panel = shift;
252
 
  my ($position_to_insert,$options,$max_bump,$max_label) = @_;
 
274
  my ($position_to_insert,$options,$max_bump,$max_label,$selector) = @_;
253
275
 
254
276
  $panel ||= $self->new_panel;
255
277
 
256
278
  # count up number of tracks inserted
257
 
  my $tracks = 0;
 
279
  my @tracks;
258
280
  my $color;
259
281
  my %types = map {$_=>1} $self->configured_types;
260
282
 
282
304
  }
283
305
 
284
306
  for my $type (@configured_types,@unconfigured_types) {
285
 
    my $features = $self->features($type);
 
307
    next if defined $selector && !$selector->($self,$type);
 
308
    next unless length $type > 0; # avoid empty ''
 
309
    my $f = $self->features($type);
 
310
    my @features = grep {$self->{visible}{$_} || $_->type eq 'group'} @$f;
 
311
    next unless @features;  # suppress tracks for features that don't appear
 
312
    my $features = \@features;
 
313
 
286
314
    my @auto_bump;
287
315
    push @auto_bump,(-bump  => @$features < $max_bump)  if defined $max_bump;
288
316
    push @auto_bump,(-label => @$features < $max_label) if defined $max_label;
290
318
    my @config = ( -glyph   => 'segments',         # really generic
291
319
                   -bgcolor => $COLORS[$color++ % @COLORS],
292
320
                   -label   => 1,
 
321
                   -description => 1,
293
322
                   -key     => $type,
294
323
                   @auto_bump,
295
324
                   @base_config,         # global
297
326
                   @override,
298
327
                 );
299
328
    if (defined($position_to_insert)) {
300
 
      $panel->insert_track($position_to_insert++,$features,@config);
 
329
      push @tracks,$panel->insert_track($position_to_insert++,$features,@config);
301
330
    } else {
302
 
      $panel->add_track($features,@config);
 
331
      push @tracks,$panel->add_track($features,@config);
303
332
    }
304
 
    $tracks++;
305
333
  }
306
 
  return wantarray ? ($tracks,$panel) : $tracks;
 
334
  return wantarray ? (scalar(@tracks),$panel,\@tracks) : scalar @tracks;
307
335
}
308
336
 
309
337
sub _stat {
350
378
 
351
379
sub parse_argv {
352
380
  my $self = shift;
 
381
  $self->init_parse;
353
382
 
354
 
  $self->init_parse;
 
383
  local $/ = "\n";
355
384
  while (<>) {
356
385
    chomp;
357
386
    $self->parse_line($_);
362
391
sub parse_file {
363
392
  my $self = shift;
364
393
  my $fh   = shift or return;
 
394
 
365
395
  $self->_stat($fh);
 
396
  $self->init_parse;
366
397
 
367
 
  $self->init_parse;
 
398
  local $/ = "\n";
368
399
  while (<$fh>) {
369
400
    chomp;
370
401
    $self->parse_line($_) || last;
388
419
  local $_ = shift;
389
420
 
390
421
  s/\015//g;  # get rid of carriage returns left over by MS-DOS/Windows systems
 
422
  s/\s+$//;   # get rid of trailing whitespace
391
423
 
392
424
  # capture GFF header
393
425
  if (/^\#\#gff-version\s+(\d+)/) {
396
428
    return 1;
397
429
  }
398
430
 
399
 
  # skip on blank lines and comments
400
 
  return 1 if /^\s*[\#]/;
 
431
  # remove comments (but rescue hex-code colors)
 
432
  s/\s*\#.+$// unless /\s*\#[0-9A-Fa-f]{6}\b/;
 
433
 
 
434
  # skip on blank lines
 
435
  return 1 if /^\s*$/;
401
436
 
402
437
  # abort if we see a >FASTA line
403
438
  return 0 if /^>/;
404
439
 
405
 
  if (/^\s+(.+)/ && $self->{current_tag}) { # continuation line
 
440
  if (/^\s+(.+)/ && $self->{current_tag}) { # configuration continuation line
406
441
    my $value = $1;
407
442
    my $cc = $self->{current_config} ||= 'general';       # in case no configuration named
408
443
    $self->{config}{$cc}{$self->{current_tag}} .= ' ' . $value;
435
470
    return 1;
436
471
  }
437
472
 
 
473
  undef $self->{current_tag};
 
474
 
438
475
  # parse data lines
439
 
  my @tokens = eval { shellwords($_||'') };
 
476
  my @tokens = shellwords($_);
440
477
  unshift @tokens,'' if /^\s+/;
441
478
 
442
479
  # close any open group
443
 
  if (length $tokens[0] > 0 && $self->{group}) {
 
480
  if ($self->{group} && $self->{grouptype} && $tokens[0] && length $tokens[0] > 0) {
444
481
    push @{$self->{features}{$self->{grouptype}}},$self->{group};
445
482
    undef $self->{group};
446
483
    undef $self->{grouptype};
457
494
 
458
495
  my($ref,$type,$name,$strand,$bounds,$description,$url,$score,%attributes);
459
496
 
460
 
  if (@tokens >= 8) { # conventional GFF file
 
497
  my @parts;
 
498
 
 
499
  # conventional GFF file, with check for numeric start/end
 
500
  if (@tokens >= 8 && $tokens[3]=~ /^-?\d+$/ && $tokens[4]=~ /^-?\d+$/) {
 
501
    require Bio::DB::GFF unless Bio::DB::GFF->can('split_group');
461
502
    my ($r,$source,$method,$start,$stop,$scor,$s,$phase,@rest) = @tokens;
 
503
    # sanity checks
462
504
    my $group = join ' ',@rest;
463
505
    $type   = defined $source && $source ne '.' ? join(':',$method,$source) : $method;
464
 
    $bounds = join '..',$start,$stop;
 
506
    #$bounds = join '..',$start,$stop;
 
507
    @parts   = ([$start,$stop]);
465
508
    $strand = $s;
466
509
    if ($group) {
467
510
      my ($notes,@notes);
470
513
        my ($key,$value) = @$_;
471
514
        if ($value =~ m!^(http|ftp)://!) { 
472
515
          $url = $_ 
473
 
        } elsif ($key=~/note/i) { 
474
 
          push @notes,$value;
 
516
        } else {
 
517
          push @notes,"$key=$value";
475
518
        }
476
519
      }
477
 
      $description = join '; ',@notes if @notes;
 
520
      $description = join '; ',map {_escape($_)} @notes if @notes;
478
521
      $score       = $scor if defined $scor && $scor ne '.';
479
522
    }
480
523
    $name ||= $self->{group}->display_id if $self->{group};
500
543
  }
501
544
  $self->{refs}{$ref}++ if defined $ref;
502
545
 
503
 
  my @parts = map { [/(-?\d+)(?:-|\.\.)(-?\d+)/]} split /(?:,| )\s*/,$bounds;
 
546
  @parts = map { [/(-?\d+)(?:-|\.\.)(-?\d+)/]} split /(?:,| )\s*/,$bounds
 
547
    if $bounds && !@parts;
504
548
 
505
549
  foreach (@parts) { # max and min calculation, sigh...
506
 
    $self->{min} = $_->[0] if !defined $self->{min} || $_->[0] < $self->{min};
507
 
    $self->{max} = $_->[1] if !defined $self->{max} || $_->[1] > $self->{max};
 
550
    $self->{min} = $_->[0] if defined $_->[0] && defined $self->{min} ? ($_->[0] < $self->{min}) : 1;
 
551
    $self->{max} = $_->[1] if defined $_->[1] && defined $self->{max} ? ($_->[1] > $self->{max}) : 1;
508
552
  }
509
553
 
 
554
  my $visible = 1;
 
555
 
510
556
  if ($self->{coordinate_mapper} && $ref) {
511
 
    ($ref,@parts) = $self->{coordinate_mapper}->($ref,@parts);
512
 
    return 1 unless $ref;
 
557
    my @remapped = $self->{coordinate_mapper}->($ref,@parts);
 
558
    ($ref,@parts) = @remapped if @remapped;
 
559
    $visible   = @remapped;
 
560
    return 1 if !$visible && $self->{feature_count} > MAX_REMAP;
513
561
  }
514
562
 
515
563
  $type = '' unless defined $type;
516
564
  $name = '' unless defined $name;
517
565
 
 
566
  # if strand is not explicitly given in file, we infer it
 
567
  # from the order of start and end coordinates
 
568
  # (this is to deal with confusing documentation, actually)
 
569
  unless (defined $strand) {
 
570
    foreach (@parts) {
 
571
      if (defined $_ && ref($_) eq 'ARRAY' && defined $_->[0] && defined $_->[1]) {
 
572
        $strand           ||= $_->[0] <= $_->[1] ? '+' : '-';
 
573
        ($_->[0],$_->[1])   = ($_->[1],$_->[0]) if $_->[0] > $_->[1];
 
574
      }
 
575
    }
 
576
  }
 
577
 
518
578
  # attribute handling
519
 
  if (defined $description && $description =~ /\w+=\w+/) { # attribute line
 
579
  if (defined $description && $description =~ /\w+=\S+/) { # attribute line
520
580
    my @attributes = split /;\s*/,$description;
521
581
    foreach (@attributes) {
522
582
      my ($name,$value) = split /=/,$_,2;
548
608
    $feature->add_segment(map {
549
609
      _make_feature($name,$type,$strand,$description,$ref,\%attributes,$url,$score,[$_])
550
610
    }  @parts);
 
611
    $self->{visible}{$feature}++  if $visible;
551
612
  }
552
613
 
553
614
  else {
559
620
      $self->{group}->add_segment($feature);
560
621
    } else {
561
622
      push @{$self->{features}{$type}},$feature;  # for speed; should use add_feature() instead
 
623
      $self->{visible}{$feature}++  if $visible;
 
624
      $self->{feature_count}++;
562
625
    }
563
626
  }
564
627
 
573
636
  @_;
574
637
}
575
638
 
 
639
sub _escape {
 
640
  my $toencode = shift;
 
641
  $toencode =~ s/([^a-zA-Z0-9_.=-])/uc sprintf("%%%02x",ord($1))/eg;
 
642
  $toencode;
 
643
}
 
644
 
576
645
sub _make_feature {
577
646
  my ($name,$type,$strand,$description,$ref,$attributes,$url,$score,$parts) = @_;
578
647
  my @coordinates = @$parts > 1 ? (-segments => $parts) : (-start=>$parts->[0][0],-end=>$parts->[0][1]);
606
675
sub add_feature {
607
676
  my $self = shift;
608
677
  my ($feature,$type) = @_;
 
678
  $feature->configurator($self) if $self->smart_features;
609
679
  $type = $feature->primary_tag unless defined $type;
 
680
  $self->{visible}{$feature}++;
 
681
  $self->{feature_count}++;
610
682
  push @{$self->{features}{$type}},$feature;
611
683
}
612
684
 
705
777
 
706
778
sub setting {
707
779
  my $self = shift;
 
780
  if (@_ > 2) {
 
781
    $self->{config}->{$_[0]}{$_[1]} = $_[2];
 
782
  }
708
783
  if ($self->safe) {
709
784
     $self->code_setting(@_);
710
785
  } else {
715
790
# return configuration information
716
791
# arguments are ($type) => returns tags for type
717
792
#               ($type=>$tag) => returns values of tag on type
 
793
#               ($type=>$tag,$value) => sets value of tag
718
794
sub _setting {
719
795
  my $self = shift;
720
796
  my $config = $self->{config} or return;
721
797
  return keys %{$config} unless @_;
722
 
  return keys %{$config->{$_[0]}} if @_ == 1;
723
 
  return $config->{$_[0]}{$_[1]}  if @_ > 1;
 
798
  return keys %{$config->{$_[0]}}        if @_ == 1;
 
799
  return $config->{$_[0]}{$_[1]}         if @_ == 2 && exists $config->{$_[0]};
 
800
  return $config->{$_[0]}{$_[1]} = $_[2] if @_ > 2;
 
801
  return;
724
802
}
725
803
 
726
804
 
750
828
    my $package         = $self->base2package;
751
829
    my $codestring      = "\\&${package}\:\:${subroutine_name}";
752
830
    my $coderef         = eval $codestring;
753
 
    warn $@ if $@;
 
831
    $self->_callback_complain($section,$option) if $@;
754
832
    $self->set($section,$option,$coderef);
755
833
    return $coderef;
756
834
  }
757
 
  elsif ($setting =~ /^sub\s*\{/) {
758
 
    my $coderef   = eval $setting;
759
 
    warn $@ if $@;
 
835
  elsif ($setting =~ /^sub\s*(\(\$\$\))*\s*\{/) {
 
836
    my $package         = $self->base2package;
 
837
    my $coderef         = eval "package $package; $setting";
 
838
    $self->_callback_complain($section,$option) if $@;
760
839
    $self->set($section,$option,$coderef);
761
840
    return $coderef;
762
841
  } else {
764
843
  }
765
844
}
766
845
 
 
846
sub _callback_complain {
 
847
  my $self    = shift;
 
848
  my ($section,$option) = @_;
 
849
  carp "An error occurred while evaluating the callback at section='$section', option='$option':\n   => $@";
 
850
}
 
851
 
767
852
=over 4
768
853
 
769
854
=item $flag = $features-E<gt>safe([$flag]);
912
997
# return features
913
998
sub features {
914
999
  my $self = shift;
915
 
  my ($types,$iterator,@rest) = defined($_[0] && $_[0]=~/^-/) ? rearrange([['TYPE','TYPES']],@_) : (\@_);
 
1000
  my ($types,$iterator,@rest) = defined($_[0] && $_[0]=~/^-/)
 
1001
    ? rearrange([['TYPE','TYPES']],@_) : (\@_);
916
1002
  $types = [$types] if $types && !ref($types);
917
1003
  my @types = ($types && @$types) ? @$types : $self->types;
918
1004
  my @features = map {@{$self->{features}{$_}}} @types;
970
1056
  $self->features(@args);
971
1057
}
972
1058
 
 
1059
=head2 get_feature_by_name
 
1060
 
 
1061
 Usage   : $db->get_feature_by_name(-name => $name)
 
1062
 Function: fetch features by their name
 
1063
 Returns : a list of Bio::DB::GFF::Feature objects
 
1064
 Args    : the name of the desired feature
 
1065
 Status  : public
 
1066
 
 
1067
This method can be used to fetch a named feature from the file.
 
1068
 
 
1069
The full syntax is as follows.  Features can be filtered by
 
1070
their reference, start and end positions
 
1071
 
 
1072
  @f = $db->get_feature_by_name(-name  => $name,
 
1073
                                -ref   => $sequence_name,
 
1074
                                -start => $start,
 
1075
                                -end   => $end);
 
1076
 
 
1077
This method may return zero, one, or several Bio::Graphics::Feature
 
1078
objects.
 
1079
 
 
1080
=cut
 
1081
 
 
1082
sub get_feature_by_name {
 
1083
   my $self = shift;
 
1084
   my ($name,$ref,$start,$end) = rearrange(['NAME','REF','START','END'],@_);
 
1085
   my $match = <<'END';
 
1086
sub {
 
1087
        my $f = shift;
 
1088
END
 
1089
   if (defined $name) {
 
1090
      if ($name =~ /[\?\*]/) {  # regexp
 
1091
        $name =  quotemeta($name);
 
1092
        $name =~ s/\\\?/.?/g;
 
1093
        $name =~ s/\\\*/.*/g;
 
1094
        $match .= "     return unless \$f->display_name =~ /$name/i;\n";
 
1095
      } else {
 
1096
        $match .= "     return unless \$f->display_name eq '$name';\n";
 
1097
      }
 
1098
   }
 
1099
 
 
1100
   if (defined $ref) {
 
1101
      $match .= "     return unless \$f->ref eq '$ref';\n";
 
1102
   }
 
1103
   if (defined $start && $start =~ /^-?\d+$/) {
 
1104
      $match .= "     return unless \$f->stop >= $start;\n";
 
1105
   }
 
1106
   if (defined $end && $end =~ /^-?\d+$/) {
 
1107
      $match .= "     return unless \$f->start <= $end;\n";
 
1108
   }
 
1109
   $match .= "     return 1;\n}";
 
1110
 
 
1111
   my $match_sub = eval $match;
 
1112
   unless ($match_sub) {
 
1113
     warn $@;
 
1114
     return;
 
1115
   }
 
1116
 
 
1117
   return grep {$match_sub->($_)} $self->features;
 
1118
}
 
1119
 
 
1120
=head2 search_notes
 
1121
 
 
1122
 Title   : search_notes
 
1123
 Usage   : @search_results = $db->search_notes("full text search string",$limit)
 
1124
 Function: Search the notes for a text string
 
1125
 Returns : array of results
 
1126
 Args    : full text search string, and an optional row limit
 
1127
 Status  : public
 
1128
 
 
1129
Each row of the returned array is a arrayref containing the following fields:
 
1130
 
 
1131
  column 1     Display name of the feature
 
1132
  column 2     The text of the note
 
1133
  column 3     A relevance score.
 
1134
 
 
1135
=cut
 
1136
 
 
1137
sub search_notes {
 
1138
  my $self = shift;
 
1139
  my ($search_string,$limit) = @_;
 
1140
 
 
1141
  $search_string =~ tr/*?//d;
 
1142
 
 
1143
  my @results;
 
1144
  my $search = join '|',map {quotemeta($_)} $search_string =~ /(\S+)/g;
 
1145
 
 
1146
  for my $feature ($self->features) {
 
1147
    next unless $feature->{attributes};
 
1148
    my @attributes = $feature->all_tags;
 
1149
    my @values     = map {$feature->each_tag_value} @attributes;
 
1150
    push @values,$feature->notes        if $feature->notes;
 
1151
    push @values,$feature->display_name if $feature->display_name;
 
1152
    next unless @values;
 
1153
    my $value      = "@values";
 
1154
    my $matches    = 0;
 
1155
    my $note;
 
1156
    my @hits = $value =~ /($search)/ig;
 
1157
    $note ||= $value if @hits;
 
1158
    $matches += @hits;
 
1159
    next unless $matches;
 
1160
 
 
1161
    my $relevance = 10 * $matches;
 
1162
    push @results,[$feature,$note,$relevance];
 
1163
    last if @results >= $limit;
 
1164
  }
 
1165
 
 
1166
  @results;
 
1167
}
 
1168
 
 
1169
 
973
1170
=head2 get_feature_stream(), top_SeqFeatures(), all_SeqFeatures()
974
1171
 
975
1172
Provided for compatibility with older BioPerl and/or Bio::DB::GFF
1030
1227
  $s->{features}    = {};
1031
1228
  $s->{config}      = {};
1032
1229
  $s->{gff_version} = 0;
 
1230
  $s->{feature_count}=0; 
1033
1231
}
1034
1232
 
1035
1233
sub finish_parse {
1055
1253
  my $init_code = $self->_setting(general => 'init_code') or return;
1056
1254
  my $code = "package $package; $init_code; 1;";
1057
1255
  eval $code;
1058
 
  warn $@ if $@;
 
1256
  $self->_callback_complain(general=>'init_code') if $@;
1059
1257
}
1060
1258
 
1061
1259
sub base2package {
1067
1265
 
1068
1266
sub split_group {
1069
1267
  my $self = shift;
1070
 
  return Bio::DB::GFF->split_group(shift, $self->{gff_version} > 2);
 
1268
  my $gff = $self->{gff} ||= Bio::DB::GFF->new(-adaptor=>'memory');
 
1269
  return $gff->split_group(shift, $self->{gff_version} > 2);
1071
1270
}
1072
1271
 
1073
1272
# create a panel if needed
1096
1295
  my $new_segment = Bio::Graphics::Feature->new(-start=>$start,-stop=>$stop);
1097
1296
  my $panel = Bio::Graphics::Panel->new(-segment   => $new_segment,
1098
1297
                                        -width     => $width,
1099
 
                                        -key_style => 'between');
 
1298
                                        -key_style => 'between',
 
1299
                                        $self->style('general'));
1100
1300
  $panel;
1101
1301
}
1102
1302
 
1143
1343
sub feature2label {
1144
1344
  my $self = shift;
1145
1345
  my $feature = shift;
1146
 
  my $type  = eval {$feature->type} || $feature->primary_tag or return;
 
1346
  my $type  = $feature->primary_tag or return;
1147
1347
  (my $basetype = $type) =~ s/:.+$//;
1148
1348
  my @labels = $self->type2label($type);
1149
1349
  @labels = $self->type2label($basetype) unless @labels;
1153
1353
 
1154
1354
=over 4
1155
1355
 
1156
 
=item $link = $features-E<gt>make_link($feature)
 
1356
=item $link = $features-E<gt>link_pattern($linkrule,$feature,$panel)
1157
1357
 
1158
1358
Given a feature, tries to generate a URL to link out from it.  This
1159
1359
uses the 'link' option, if one is present.  This method is a
1163
1363
 
1164
1364
=cut
1165
1365
 
1166
 
sub make_link {
 
1366
sub link_pattern {
1167
1367
  my $self     = shift;
1168
 
  my $feature  = shift;
1169
 
  for my $label ($self->feature2label($feature)) {
1170
 
    my $link     = $self->setting($label,'link');
1171
 
    $link        = $self->setting(general=>'link') unless defined $link;
1172
 
    next unless $link;
1173
 
    return $self->link_pattern($link,$feature);
 
1368
  my ($linkrule,$feature,$panel) = @_;
 
1369
 
 
1370
  $panel ||= 'Bio::Graphics::Panel';
 
1371
 
 
1372
  if (ref($linkrule) && ref($linkrule) eq 'CODE') {
 
1373
    my $val = eval {$linkrule->($feature,$panel)};
 
1374
    $self->_callback_complain(none=>"linkrule for $feature") if $@;
 
1375
    return $val;
1174
1376
  }
1175
 
  return;
1176
 
}
1177
1377
 
1178
 
sub link_pattern {
1179
 
  my $self = shift;
1180
 
  my ($pattern,$feature,$panel) = @_;
1181
1378
  require CGI unless defined &CGI::escape;
1182
1379
  my $n;
1183
 
  $pattern =~ s/\$(\w+)/
 
1380
  $linkrule ||= ''; # prevent uninit warning
 
1381
  $linkrule =~ s/\$(\w+)/
1184
1382
    CGI::escape(
1185
 
    $1 eq 'ref'              ? ($n = $feature->location->seq_id) && "$n"
1186
 
      : $1 eq 'name'         ? ($n = $feature->display_name) && "$n"  # workaround broken CGI.pm
 
1383
    $1 eq 'ref'              ? (($n = $feature->location->seq_id) && "$n") || ''
 
1384
      : $1 eq 'name'         ? (($n = $feature->display_name) && "$n")     || ''
1187
1385
      : $1 eq 'class'        ? eval {$feature->class}  || ''
1188
 
      : $1 eq 'type'         ? eval {$feature->method} || $feature->primary_tag
1189
 
      : $1 eq 'method'       ? eval {$feature->method} || $feature->primary_tag
1190
 
      : $1 eq 'source'       ? eval {$feature->source} || $feature->source_tag
1191
 
      : $1 eq 'start'        ? $feature->start
1192
 
      : $1 eq 'end'          ? $feature->end
1193
 
      : $1 eq 'stop'         ? $feature->end
1194
 
      : $1 eq 'segstart'     ? $panel->start
1195
 
      : $1 eq 'segend'       ? $panel->end
 
1386
      : $1 eq 'type'         ? eval {$feature->method} || $feature->primary_tag || ''
 
1387
      : $1 eq 'method'       ? eval {$feature->method} || $feature->primary_tag || ''
 
1388
      : $1 eq 'source'       ? eval {$feature->source} || $feature->source_tag  || ''
 
1389
      : $1 eq 'start'        ? $feature->start || ''
 
1390
      : $1 eq 'end'          ? $feature->end   || ''
 
1391
      : $1 eq 'stop'         ? $feature->end   || ''
 
1392
      : $1 eq 'segstart'     ? $panel->start   || ''
 
1393
      : $1 eq 'segend'       ? $panel->end     || ''
1196
1394
      : $1 eq 'description'  ? eval {join '',$feature->notes} || ''
1197
 
      : $1 eq 'id'           ? $feature->feature_id
 
1395
      : $1 eq 'id'           ? $feature->feature_id || ''
1198
1396
      : $1
1199
 
               )
1200
 
       /exg;
1201
 
  return $pattern;
 
1397
       )
 
1398
        /exg;
 
1399
  return $linkrule;
 
1400
}
 
1401
 
 
1402
sub make_link {
 
1403
  my $self             = shift;
 
1404
  my ($feature,$panel) = @_;
 
1405
 
 
1406
  for my $label ($self->feature2label($feature)) {
 
1407
    my $linkrule     = $self->setting($label,'link');
 
1408
    $linkrule        = $self->setting(general=>'link') unless defined $linkrule;
 
1409
    return $self->link_pattern($linkrule,$feature,$panel);
 
1410
  }
 
1411
}
 
1412
 
 
1413
sub make_title {
 
1414
  my $self = shift;
 
1415
  my $feature = shift;
 
1416
 
 
1417
  for my $label ($self->feature2label($feature)) {
 
1418
    my $linkrule     = $self->setting($label,'title');
 
1419
    $linkrule        ||= $self->setting(general=>'title');
 
1420
    next unless $linkrule;
 
1421
    return $self->link_pattern($linkrule,$feature);
 
1422
  }
 
1423
 
 
1424
  my $method  = eval {$feature->method} || $feature->primary_tag;
 
1425
  my $seqid   = $feature->can('seq_id')      ? $feature->seq_id : $feature->location->seq_id;
 
1426
  my $title = eval {
 
1427
    if ($feature->can('target') && (my $target = $feature->target)) {
 
1428
      join (' ',
 
1429
            $method,
 
1430
            (defined $seqid ? "$seqid:" : '').
 
1431
            $feature->start."..".$feature->end,
 
1432
            $feature->target.':'.
 
1433
            $feature->target->start."..".$feature->target->end);
 
1434
    } else {
 
1435
      join(' ',
 
1436
           $method,
 
1437
           $feature->can('display_name') ? $feature->display_name : $feature->info,
 
1438
           (defined $seqid ? "$seqid:" : '').
 
1439
           ($feature->start||'?')."..".($feature->end||'?')
 
1440
          );
 
1441
    }
 
1442
  };
 
1443
  warn $@ if $@;
 
1444
  $title;
1202
1445
}
1203
1446
 
1204
1447
# given a feature type, return its label(s)