~ubuntu-branches/ubuntu/trusty/bioperl/trusty-proposed

« back to all changes in this revision

Viewing changes to Bio/Graphics/Glyph.pm

  • Committer: Bazaar Package Importer
  • Author(s): Charles Plessy
  • Date: 2009-03-10 07:19:11 UTC
  • mfrom: (1.2.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20090310071911-fukqzw54pyb1f0bd
Tags: 1.6.0-2
* Removed patch system (not used):
  - removed instuctions in debian/rules;
  - removed quilt from Build-Depends in debian/control.
* Re-enabled tests:
  - uncommented test command in debian/rules;
  - uncommented previously missing build-dependencies in debian/control.
  - Re-enabled tests and uncommented build-dependencies accordingly.
* Removed libmodule-build-perl and libtest-harness-perl from
  Build-Depends-Indep (provided by perl-modules).
* Better cleaning of empty directories using find -type d -empty -delete
  instead of rmdir in debian/rules (LP: #324001).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
package Bio::Graphics::Glyph;
2
 
 
3
 
# $Id: Glyph.pm,v 1.113.4.9 2006/11/29 02:38:33 lstein Exp $
4
 
 
5
 
use strict;
6
 
use Carp 'croak','cluck';
7
 
use constant BUMP_SPACING => 2; # vertical distance between bumped glyphs
8
 
use Bio::Root::Version;
9
 
 
10
 
use base qw(Bio::Root::Root);
11
 
 
12
 
my %LAYOUT_COUNT;
13
 
 
14
 
# the CM1 and CM2 constants control the size of the hash used to
15
 
# detect collisions.
16
 
use constant CM1 => 200; # big bin, x axis
17
 
use constant CM2 => 50;  # big bin, y axis
18
 
use constant CM3 => 50;  # small bin, x axis
19
 
use constant CM4 => 50;  # small bin, y axis
20
 
use constant DEBUG => 0;
21
 
 
22
 
use constant QUILL_INTERVAL => 8;  # number of pixels between Jim Kent style intron "quills"
23
 
 
24
 
# a bumpable graphical object that has bumpable graphical subparts
25
 
 
26
 
# args:  -feature => $feature_object (may contain subsequences)
27
 
#        -factory => $factory_object (called to create glyphs for subsequences)
28
 
# In this scheme, the factory decides based on stylesheet information what glyph to
29
 
# draw and what configurations options to us. This allows for heterogeneous tracks.
30
 
sub new {
31
 
  my $class = shift;
32
 
  my %arg = @_;
33
 
 
34
 
  my $feature = $arg{-feature} or $class->throw("No feature $class");
35
 
  my $factory = $arg{-factory} || $class->default_factory;
36
 
  my $level   = $arg{-level} || 0;
37
 
  my $flip    = $arg{-flip};
38
 
 
39
 
  my $self = bless {},$class;
40
 
  $self->{feature} = $feature;
41
 
  $self->{factory} = $factory;
42
 
  $self->{level}   = $level;
43
 
  $self->{flip}++  if $flip;
44
 
  $self->{top} = 0;
45
 
 
46
 
  my $panel = $factory->panel;
47
 
  my $p_start = $panel->start;
48
 
  my $p_end   = $panel->end;
49
 
 
50
 
  my @subfeatures;
51
 
  my @subglyphs;
52
 
 
53
 
  warn $self if DEBUG;
54
 
  warn $feature if DEBUG;
55
 
 
56
 
  @subfeatures         = $self->subfeat($feature);
57
 
 
58
 
  if ($self->option('ignore_sub_part')) {
59
 
    my @tmparray;
60
 
    foreach (@subfeatures) {
61
 
      my $type = $_->method;
62
 
 
63
 
      my @ignore_list = split /\s+/, $self->option('ignore_sub_part');
64
 
      my $ignore_str  = join('|', @ignore_list);
65
 
 
66
 
      unless ($type =~ /$ignore_str/) {
67
 
        push @tmparray, $_;
68
 
      }
69
 
    }
70
 
    @subfeatures = @tmparray;
71
 
  }
72
 
 
73
 
  my @visible_subfeatures = grep {$p_start <= $_->end && $p_end >= $_->start} @subfeatures;
74
 
 
75
 
  $self->feature_has_subparts(@subfeatures>0);
76
 
 
77
 
  if (@visible_subfeatures) {
78
 
    # dynamic glyph resolution
79
 
    @subglyphs = map { $_->[0] }
80
 
          sort { $a->[1] <=> $b->[1] }
81
 
            map { [$_, $_->left ] }
82
 
              $factory->make_glyph($level+1,@visible_subfeatures);
83
 
    $self->{parts}   = \@subglyphs;
84
 
  }
85
 
 
86
 
  my ($start,$stop) = ($self->start, $self->stop);
87
 
  if (defined $start && defined $stop && $start ne '') {  # more paranoia
88
 
    ($start,$stop) = ($stop,$start) if $start > $stop;  # sheer paranoia
89
 
    # the +1 here is critical for allowing features to meet nicely at nucleotide resolution
90
 
    my ($left,$right) = $factory->map_pt($start,$stop+1);
91
 
    $self->{left}    = $left;
92
 
    $self->{width}   = $right - $left + 1;
93
 
  }
94
 
 
95
 
  if (@subglyphs) {
96
 
      my $l            = $subglyphs[0]->left;
97
 
      # this clashes with the pad_left calculation and is unecessary
98
 
      # $self->{left}    = $l if !defined($self->{left}) || $l < $self->{left};
99
 
      my $right        = (
100
 
                          sort { $b<=>$a } 
101
 
                          map {$_->right} @subglyphs)[0];
102
 
      my $w            = $right - $self->{left} + 1;
103
 
      # this clashes with the pad_right calculation and is unecessary
104
 
      # $self->{width}   = $w if !defined($self->{width}) || $w > $self->{width};
105
 
  }
106
 
 
107
 
  $self->{point} = $arg{-point} ? $self->height : undef;
108
 
 
109
 
  return $self;
110
 
}
111
 
 
112
 
sub parts      {
113
 
  my $self = shift;
114
 
  return unless $self->{parts};
115
 
  return wantarray ? @{$self->{parts}} : $self->{parts};
116
 
}
117
 
 
118
 
# this is different than parts(). parts() will return subglyphs
119
 
# that are contained within the current viewing range. feature_has_subparts()
120
 
# will return true if the feature has any subparts, even if they are off the
121
 
# screen.
122
 
sub feature_has_subparts {
123
 
  my $self = shift;
124
 
 
125
 
  return $self->{feature_has_subparts} = shift if @_;
126
 
  return 0 if $self->maxdepth == 0;
127
 
  my $feature = $self->feature;
128
 
  return 1 if $feature->can('compound') && $feature->compound;
129
 
  return $self->{feature_has_subparts};
130
 
}
131
 
 
132
 
sub feature { shift->{feature} }
133
 
sub factory { shift->{factory} }
134
 
sub panel   { shift->factory->panel }
135
 
sub point   { shift->{point}   }
136
 
sub scale   { shift->factory->scale }
137
 
sub flip    {
138
 
  my $self      = shift;
139
 
  my $d         = $self->{flip};
140
 
  $self->{flip} = shift if @_;
141
 
  $d;
142
 
}
143
 
sub start   {
144
 
  my $self = shift;
145
 
  return $self->{start} if exists $self->{start};
146
 
  if ($self->{flip}) {
147
 
    $self->{start} = defined $self->{feature}->end
148
 
                     ? $self->panel->end + 1 - $self->{feature}->end
149
 
                     : 0;
150
 
  } else {
151
 
    $self->{start} = defined $self->{feature}->start
152
 
                     ? $self->{feature}->start
153
 
                     : $self->panel->offset - 1
154
 
  }
155
 
 
156
 
  return $self->{start};
157
 
}
158
 
 
159
 
sub stop    {
160
 
  my $self = shift;
161
 
  return $self->{stop} if exists $self->{stop};
162
 
  if ($self->{flip}) {
163
 
    $self->{stop} = defined $self->{feature}->start 
164
 
      ? $self->panel->end + 1 - $self->{feature}->start
165
 
      : $self->panel->offset - 1;
166
 
  } else {
167
 
    $self->{stop} = defined $self->{feature}->end
168
 
      ?  $self->{feature}->end
169
 
      : $self->panel->offset+$self->panel->length+1;
170
 
  }
171
 
 
172
 
  return $self->{stop}
173
 
}
174
 
sub end     { shift->stop }
175
 
sub length { my $self = shift; $self->stop - $self->start };
176
 
sub score {
177
 
    my $self = shift;
178
 
    return $self->{score} if exists $self->{score};
179
 
    return $self->{score} = ($self->{feature}->score || 0);
180
 
}
181
 
sub strand {
182
 
    my $self = shift;
183
 
    return $self->{strand} if exists $self->{strand};
184
 
    return $self->{strand} = ($self->{feature}->strand || 0);
185
 
}
186
 
sub map_pt  { shift->{factory}->map_pt(@_) }
187
 
sub map_no_trunc { shift->{factory}->map_no_trunc(@_) }
188
 
 
189
 
# add a feature (or array ref of features) to the list
190
 
sub add_feature {
191
 
  my $self       = shift;
192
 
  my $factory    = $self->factory;
193
 
 
194
 
  for my $feature (@_) {
195
 
    if (ref $feature eq 'ARRAY') {
196
 
      $self->add_group(@$feature);
197
 
    } else {
198
 
      warn $factory if DEBUG;
199
 
      push @{$self->{parts}},$factory->make_glyph(0,$feature);
200
 
    }
201
 
  }
202
 
}
203
 
 
204
 
# link a set of features together so that they bump as a group
205
 
sub add_group {
206
 
  my $self = shift;
207
 
  my @features = ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_;
208
 
  my $f    = Bio::Graphics::Feature->new(
209
 
                                         -segments=>\@features,
210
 
                                         -type => 'group',
211
 
                                        );
212
 
  $self->add_feature($f);
213
 
  $f;
214
 
}
215
 
 
216
 
sub top {
217
 
  my $self = shift;
218
 
  my $g = $self->{top};
219
 
  $self->{top} = shift if @_;
220
 
  $g;
221
 
}
222
 
sub left {
223
 
  my $self = shift;
224
 
  return $self->{left} - $self->pad_left;
225
 
}
226
 
sub right {
227
 
  my $self = shift;
228
 
  return $self->left + $self->layout_width - 1;
229
 
}
230
 
sub bottom {
231
 
  my $self = shift;
232
 
  $self->top + $self->layout_height - 1;
233
 
}
234
 
sub height {
235
 
  my $self = shift;
236
 
  return $self->{height} if exists $self->{height};
237
 
  my $baseheight = $self->option('height');  # what the factory says
238
 
  return $self->{height} = $baseheight;
239
 
}
240
 
sub width {
241
 
  my $self = shift;
242
 
  my $g = $self->{width};
243
 
  $self->{width} = shift if @_;
244
 
  $g;
245
 
}
246
 
sub layout_height {
247
 
  my $self = shift;
248
 
  return $self->layout;
249
 
}
250
 
sub layout_width {
251
 
  my $self = shift;
252
 
  return $self->width + $self->pad_left + $self->pad_right;
253
 
}
254
 
 
255
 
# returns the rectangle that surrounds the physical part of the
256
 
# glyph, excluding labels and other "extra" stuff
257
 
sub calculate_boundaries {return shift->bounds(@_);}
258
 
 
259
 
sub bounds {
260
 
  my $self = shift;
261
 
  my ($dx,$dy) = @_;
262
 
  $dx += 0; $dy += 0;
263
 
  ($dx + $self->{left},
264
 
   $dy + $self->top    + $self->pad_top,
265
 
   $dx + $self->{left} + $self->{width} - 1,
266
 
   $dy + $self->bottom - $self->pad_bottom);
267
 
}
268
 
 
269
 
sub box {
270
 
  my $self = shift;
271
 
  my @result = ($self->left,$self->top,$self->right,$self->bottom);
272
 
  return @result;
273
 
}
274
 
 
275
 
sub unfilled_box {
276
 
  my $self = shift;
277
 
  my $gd   = shift;
278
 
  my ($x1,$y1,$x2,$y2,$fg,$bg,$lw) = @_;
279
 
  $lw = $self->linewidth;
280
 
 
281
 
  unless ($fg) {
282
 
      $fg ||= $self->fgcolor;
283
 
  $fg = $self->set_pen($lw,$fg) if $lw > 1;
284
 
  }
285
 
 
286
 
  unless ($bg) {
287
 
      $bg ||= $self->bgcolor;
288
 
      $bg = $self->set_pen($lw,$bg) if $lw > 1;
289
 
  }
290
 
 
291
 
  # draw a box
292
 
  $gd->rectangle($x1,$y1,$x2,$y2,$fg);
293
 
 
294
 
  # if the left end is off the end, then cover over
295
 
  # the leftmost line
296
 
  my ($width) = $gd->getBounds;
297
 
 
298
 
  $gd->line($x1,$y1+$lw,$x1,$y2-$lw,$bg)
299
 
    if $x1 < $self->panel->pad_left;
300
 
 
301
 
  $gd->line($x2,$y1+$lw,$x2,$y2-$lw,$bg)
302
 
    if $x2 > $width - $self->panel->pad_right;
303
 
}
304
 
 
305
 
# return boxes surrounding each part
306
 
sub boxes {
307
 
  my $self = shift;
308
 
 
309
 
  my ($left,$top,$parent) = @_;
310
 
  $top  += 0; $left += 0;
311
 
  my @result;
312
 
 
313
 
  $self->layout;
314
 
  $parent         ||= $self;
315
 
  my $subparts = $self->box_subparts || 0;
316
 
 
317
 
  for my $part ($self->parts) {
318
 
    my $type = $part->feature->primary_tag || '';
319
 
    if ($type eq 'group' or $subparts > $part->level) {
320
 
      push @result,$part->boxes($left,$top+$self->top+$self->pad_top,$parent);
321
 
      next if $type eq 'group';
322
 
    }
323
 
    my ($x1,$y1,$x2,$y2) = $part->box;
324
 
    $x2++ if $x1==$x2;
325
 
    push @result,[$part->feature,
326
 
                  $left + $x1,$top+$self->top+$self->pad_top+$y1,
327
 
                  $left + $x2,$top+$self->top+$self->pad_top+$y2,
328
 
                  $parent];
329
 
  }
330
 
 
331
 
  return wantarray ? @result : \@result;
332
 
}
333
 
 
334
 
sub box_subparts {
335
 
  my $self = shift;
336
 
  return $self->{box_subparts} if exists $self->{box_subparts};
337
 
  return $self->{box_subparts} = $self->_box_subparts;
338
 
}
339
 
 
340
 
sub _box_subparts { shift->option('box_subparts') }
341
 
 
342
 
# this should be overridden for labels, etc.
343
 
# allows glyph to make itself thicker or thinner depending on
344
 
# domain-specific knowledge
345
 
sub pad_top {
346
 
  my $self = shift;
347
 
  return 0;
348
 
}
349
 
sub pad_bottom {
350
 
  my $self = shift;
351
 
  return 0;
352
 
}
353
 
sub pad_left {
354
 
  my $self = shift;
355
 
  my @parts = $self->parts or return 0;
356
 
  my $max = 0;
357
 
  foreach (@parts) {
358
 
    my $pl = $_->pad_left;
359
 
    $max = $pl if $max < $pl;
360
 
  }
361
 
  $max;
362
 
}
363
 
sub pad_right {
364
 
  my $self = shift;
365
 
  my @parts = $self->parts or return 0;
366
 
  my $max = 0;
367
 
  foreach (@parts) {
368
 
    my $pr = $_->pad_right;
369
 
    $max = $pr if $max < $pr;
370
 
  }
371
 
  $max;
372
 
}
373
 
 
374
 
# move relative to parent
375
 
sub move {
376
 
  my $self = shift;
377
 
  my ($dx,$dy) = @_;
378
 
  $self->{left} += $dx;
379
 
  $self->{top}  += $dy;
380
 
 
381
 
  # because the feature parts use *absolute* not relative addressing
382
 
  # we need to move each of the parts horizontally, but not vertically
383
 
  $_->move($dx,0) foreach $self->parts;
384
 
}
385
 
 
386
 
# get an option
387
 
sub option {
388
 
  my $self = shift;
389
 
  my $option_name = shift;
390
 
  my @args = ($option_name,@{$self}{qw(partno total_parts)});
391
 
  my $factory = $self->{factory} or return;
392
 
  return $factory->option($self,@args)
393
 
}
394
 
 
395
 
# get an option that might be a code reference
396
 
sub code_option {
397
 
  my $self = shift;
398
 
  my $option_name = shift;
399
 
  my $factory = $self->factory or return;
400
 
  $factory->get_option($option_name);
401
 
}
402
 
 
403
 
# set an option globally
404
 
sub configure {
405
 
  my $self = shift;
406
 
  my $factory = $self->factory;
407
 
  my $option_map = $factory->option_map;
408
 
  while (@_) {
409
 
    my $option_name  = shift;
410
 
    my $option_value = shift;
411
 
    ($option_name = lc $option_name) =~ s/^-//;
412
 
    $option_map->{$option_name} = $option_value;
413
 
  }
414
 
}
415
 
 
416
 
# some common options
417
 
sub color {
418
 
  my $self = shift;
419
 
  my $color = shift;
420
 
  my $index = $self->option($color);
421
 
  # turn into a color index
422
 
  return $self->factory->translate_color($index) if defined $index;
423
 
  return 0;
424
 
}
425
 
 
426
 
sub connector {
427
 
  return shift->option('connector',@_);
428
 
}
429
 
 
430
 
# return value:
431
 
#              0    no bumping
432
 
#              +1   bump down
433
 
#              -1   bump up
434
 
#              +2   simple bump down
435
 
#              -2   simple bump up
436
 
sub bump {
437
 
  my $self = shift;
438
 
  return $self->option('bump');
439
 
}
440
 
 
441
 
# control horizontal and vertical collision control
442
 
sub hbumppad {
443
 
  my $self = shift;
444
 
  return $self->{_hbumppad} if exists $self->{_hbumppad};
445
 
  return $self->{_hbumppad}= $self->option('hbumppad');
446
 
}
447
 
 
448
 
# we also look for the "color" option for Ace::Graphics compatibility
449
 
sub fgcolor {
450
 
  my $self  = shift;
451
 
  my $index   = $self->option('color') || $self->option('fgcolor');
452
 
  $index = 'black' unless defined $index;
453
 
  $self->factory->translate_color($index);
454
 
}
455
 
 
456
 
#add for compatibility
457
 
sub fillcolor {
458
 
    my $self = shift;
459
 
    return $self->bgcolor;
460
 
}
461
 
 
462
 
# we also look for the "background-color" option for Ace::Graphics compatibility
463
 
sub bgcolor {
464
 
  my $self = shift;
465
 
  my $bgcolor = $self->option('bgcolor');
466
 
  my $index = defined $bgcolor ? $bgcolor : $self->option('fillcolor');
467
 
  $index = 'white' unless defined $index;
468
 
  $self->factory->translate_color($index);
469
 
}
470
 
 
471
 
sub getfont {
472
 
  my $self    = shift;
473
 
  my $option  = shift || 'font';
474
 
  my $default = shift;
475
 
 
476
 
  my $font = $self->option($option) || $default;
477
 
  return unless $font;
478
 
 
479
 
  my $img_class = $self->image_class;
480
 
 
481
 
  unless (UNIVERSAL::isa($font,$img_class . '::Font')) {
482
 
    my $ref    = {
483
 
                  gdTinyFont       => $img_class->gdTinyFont(),
484
 
                  gdSmallFont      => $img_class->gdSmallFont(),
485
 
                  gdMediumBoldFont => $img_class->gdMediumBoldFont(),
486
 
                  gdLargeFont      => $img_class->gdLargeFont(),
487
 
                  gdGiantFont      => $img_class->gdGiantFont(),
488
 
                 };
489
 
 
490
 
    my $gdfont = $ref->{$font};
491
 
    $self->configure($option => $gdfont);
492
 
    return $gdfont;
493
 
  }
494
 
  return $font;
495
 
}
496
 
 
497
 
sub font {
498
 
  my $self = shift;
499
 
  return $self->getfont('font','gdSmallFont');
500
 
}
501
 
 
502
 
sub fontcolor {
503
 
  my $self = shift;
504
 
  my $fontcolor = $self->color('fontcolor');
505
 
  return defined $fontcolor ? $fontcolor : $self->fgcolor;
506
 
}
507
 
sub font2color {
508
 
  my $self = shift;
509
 
  my $font2color = $self->color('font2color');
510
 
  return defined $font2color ? $font2color : $self->fgcolor;
511
 
}
512
 
sub tkcolor { # "track color"
513
 
  my $self = shift;
514
 
  $self->option('tkcolor') or return;
515
 
  return $self->color('tkcolor')
516
 
}
517
 
sub connector_color {
518
 
  my $self = shift;
519
 
  $self->color('connector_color') || $self->fgcolor;
520
 
}
521
 
 
522
 
sub image_class { shift->{factory}->{panel}->{image_class}; }
523
 
sub polygon_package { shift->{factory}->{panel}->{polygon_package}; }
524
 
 
525
 
sub layout_sort {
526
 
    my $self = shift;
527
 
    my $sortfunc;
528
 
 
529
 
    my $opt = $self->code_option("sort_order");
530
 
 
531
 
    if (!$opt) {
532
 
       $sortfunc = sub { $a->left <=> $b->left };
533
 
    } elsif (ref $opt eq 'CODE') {
534
 
      $self->throw('sort_order subroutines must use the $$ prototype') unless prototype($opt) eq '$$';
535
 
      $sortfunc = $opt;
536
 
    } elsif ($opt =~ /^sub\s+\{/o) {
537
 
       $sortfunc = eval $opt;
538
 
    } else {
539
 
       # build $sortfunc for ourselves:
540
 
       my @sortbys = split(/\s*\|\s*/o, $opt);
541
 
       $sortfunc = 'sub { ';
542
 
       my $sawleft = 0;
543
 
 
544
 
       # not sure I can make this schwartzian transformed
545
 
       for my $sortby (@sortbys) {
546
 
         if ($sortby eq "left" || $sortby eq "default") {
547
 
           $sortfunc .= '($a->left <=> $b->left) || ';
548
 
           $sawleft++;
549
 
         } elsif ($sortby eq "right") {
550
 
           $sortfunc .= '($a->right <=> $b->right) || ';
551
 
         } elsif ($sortby eq "low_score") {
552
 
           $sortfunc .= '($a->score <=> $b->score) || ';
553
 
         } elsif ($sortby eq "high_score") {
554
 
           $sortfunc .= '($b->score <=> $a->score) || ';
555
 
         } elsif ($sortby eq "longest") {
556
 
           $sortfunc .= '(($b->length) <=> ($a->length)) || ';
557
 
         } elsif ($sortby eq "shortest") {
558
 
           $sortfunc .= '(($a->length) <=> ($b->length)) || ';
559
 
         } elsif ($sortby eq "strand") {
560
 
           $sortfunc .= '($b->strand <=> $a->strand) || ';
561
 
         } elsif ($sortby eq "name") {
562
 
           $sortfunc .= '($a->feature->display_name cmp $b->feature->display_name) || ';
563
 
         }
564
 
       }
565
 
       unless ($sawleft) {
566
 
           $sortfunc .= ' ($a->left <=> $b->left) ';
567
 
       } else {
568
 
           $sortfunc .= ' 0';
569
 
       }
570
 
       $sortfunc .= '}';
571
 
       $sortfunc = eval $sortfunc;
572
 
    }
573
 
 
574
 
    # cache this
575
 
    # $self->factory->set_option(sort_order => $sortfunc);
576
 
 
577
 
    my @things = sort $sortfunc @_;
578
 
    return @things;
579
 
}
580
 
 
581
 
# handle collision detection
582
 
sub layout {
583
 
  my $self = shift;
584
 
  return $self->{layout_height} if exists $self->{layout_height};
585
 
 
586
 
  my @parts = $self->parts;
587
 
  return $self->{layout_height} = $self->height + $self->pad_top + $self->pad_bottom unless @parts;
588
 
 
589
 
  my $bump_direction = $self->bump;
590
 
  my $bump_limit = $self->option('bump_limit') || -1;
591
 
 
592
 
  $_->layout foreach @parts;  # recursively lay out
593
 
 
594
 
  # no bumping requested, or only one part here
595
 
  if (@parts == 1 || !$bump_direction) {
596
 
    my $highest = 0;
597
 
    foreach (@parts) {
598
 
      my $height = $_->layout_height;
599
 
      $highest   = $height > $highest ? $height : $highest;
600
 
    }
601
 
    return $self->{layout_height} = $highest + $self->pad_top + $self->pad_bottom;
602
 
  }
603
 
 
604
 
  my (%bin1,%bin2);
605
 
  my $limit = 0;
606
 
 
607
 
  for my $g ($self->layout_sort(@parts)) {
608
 
 
609
 
    my $height = $g->{layout_height};
610
 
 
611
 
    # Simple +/- 2 bumping.  Every feature gets its very own line
612
 
    if (abs($bump_direction) >= 2) {
613
 
      $g->move(0,$limit);
614
 
      $limit += $height + BUMP_SPACING if $bump_direction > 0;
615
 
      $limit -= $height + BUMP_SPACING if $bump_direction < 0;
616
 
      next;
617
 
    }
618
 
 
619
 
    # we get here for +/- 1 bumping
620
 
    my $pos = 0;
621
 
    my $bumplevel = 0;
622
 
    my $left   = $g->left;
623
 
    my $right  = $g->right;
624
 
 
625
 
    while (1) {
626
 
 
627
 
      # stop bumping if we've gone too far down
628
 
      if ($bump_limit > 0 && $bumplevel++ >= $bump_limit) {
629
 
        $g->{overbumped}++;  # this flag can be used to suppress label and description
630
 
        foreach ($g->parts) {
631
 
          $_->{overbumped}++;
632
 
        }
633
 
        last;
634
 
      }
635
 
 
636
 
      # look for collisions
637
 
      my $bottom = $pos + $height;
638
 
      $self->collides(\%bin1,CM1,CM2,$left,$pos,$right,$bottom) or last;
639
 
      my $collision = $self->collides(\%bin2,CM3,CM4,$left,$pos,$right,$bottom) or last;
640
 
 
641
 
      if ($bump_direction > 0) {
642
 
        $pos += $collision->[3]-$collision->[1] + BUMP_SPACING;    # collision, so bump
643
 
      } else {
644
 
        $pos -= BUMP_SPACING;
645
 
      }
646
 
 
647
 
      $pos++ if $pos % 2; # correct for GD rounding errors
648
 
    }
649
 
 
650
 
    $g->move(0,$pos);
651
 
    $self->add_collision(\%bin1,CM1,CM2,$left,$g->top,$right,$g->bottom);
652
 
    $self->add_collision(\%bin2,CM3,CM4,$left,$g->top,$right,$g->bottom);
653
 
  }
654
 
 
655
 
  # If -1 bumping was allowed, then normalize so that the top glyph is at zero
656
 
  if ($bump_direction < 0) {
657
 
    my $topmost;
658
 
    foreach (@parts) {
659
 
      my $top  = $_->top;
660
 
      $topmost = $top if !defined($topmost) or $top < $topmost;
661
 
    }
662
 
    my $offset = - $topmost;
663
 
    $_->move(0,$offset) foreach @parts;
664
 
  }
665
 
 
666
 
  # find new height
667
 
  my $bottom = 0;
668
 
  foreach (@parts) {
669
 
    $bottom = $_->bottom if $_->bottom > $bottom;
670
 
  }
671
 
  # return $self->{layout_height} = $self->pad_bottom + $self->pad_top + $bottom - $self->top  + 1;
672
 
  return $self->{layout_height} = $bottom + $self->pad_top + $self->pad_bottom;
673
 
}
674
 
 
675
 
# the $%occupied structure is a hash of {left,top} = [left,top,right,bottom]
676
 
sub collides {
677
 
  my $self = shift;
678
 
  my ($occupied,$cm1,$cm2,$left,$top,$right,$bottom) = @_;
679
 
  my @keys = $self->_collision_keys($cm1,$cm2,$left,$top,$right,$bottom);
680
 
  my $hspacing = $self->hbumppad || 0;
681
 
  my $collides = 0;
682
 
  for my $k (@keys) {
683
 
    next unless exists $occupied->{$k};
684
 
    for my $bounds (@{$occupied->{$k}}) {
685
 
      my ($l,$t,$r,$b) = @$bounds;
686
 
      next unless $right+$hspacing >= $l and $left-$hspacing <= $r 
687
 
        and $bottom >= $t and $top <= $b;
688
 
      $collides = $bounds;
689
 
      last;
690
 
    }
691
 
  }
692
 
  $collides;
693
 
}
694
 
 
695
 
sub add_collision {
696
 
  my $self = shift;
697
 
  my ($occupied,$cm1,$cm2,$left,$top,$right,$bottom) = @_;
698
 
  my $value = [$left,$top,$right+2,$bottom];
699
 
  my @keys = $self->_collision_keys($cm1,$cm2,@$value);
700
 
  push @{$occupied->{$_}},$value foreach @keys;
701
 
}
702
 
 
703
 
sub _collision_keys {
704
 
  my $self = shift;
705
 
  my ($binx,$biny,$left,$top,$right,$bottom) = @_;
706
 
  my @keys;
707
 
  my $bin_left   = int($left/$binx);
708
 
  my $bin_right  = int($right/$binx);
709
 
  my $bin_top    = int($top/$biny);
710
 
  my $bin_bottom = int($bottom/$biny);
711
 
  for (my $x=$bin_left;$x<=$bin_right; $x++) {
712
 
    for (my $y=$bin_top;$y<=$bin_bottom; $y++) {
713
 
      push @keys,join(',',$x,$y);
714
 
    }
715
 
  }
716
 
  @keys;
717
 
}
718
 
 
719
 
sub draw {
720
 
  my $self = shift;
721
 
  my $gd = shift;
722
 
  my ($left,$top,$partno,$total_parts) = @_;
723
 
 
724
 
  my $connector = $self->connector;
725
 
 
726
 
  if (my @parts = $self->parts) {
727
 
 
728
 
    # invoke sorter if user wants to sort always and we haven't already sorted
729
 
    # during bumping.
730
 
    @parts = $self->layout_sort(@parts) if !$self->bump && $self->option('always_sort');
731
 
 
732
 
    my $x = $left;
733
 
    my $y = $top  + $self->top + $self->pad_top;
734
 
 
735
 
    $self->draw_connectors($gd,$x,$y) if $connector && $connector ne 'none';
736
 
 
737
 
    my $last_x;
738
 
    for (my $i=0; $i<@parts; $i++) {
739
 
      # lie just a little bit to avoid lines overlapping and make the picture prettier
740
 
      my $fake_x = $x;
741
 
      $fake_x-- if defined $last_x && $parts[$i]->left - $last_x == 1;
742
 
      $parts[$i]->draw($gd,$fake_x,$y,$i,scalar(@parts));
743
 
      $last_x = $parts[$i]->right;
744
 
    }
745
 
  }
746
 
 
747
 
  else {  # no part
748
 
    $self->draw_connectors($gd,$left,$top)
749
 
      if $connector && $connector ne 'none'; # && $self->{level} == 0;
750
 
    $self->draw_component($gd,$left,$top,$partno,$total_parts) unless $self->feature_has_subparts;
751
 
  }
752
 
 
753
 
}
754
 
 
755
 
# the "level" is the level of testing of the glyph
756
 
# groups are level -1, top level glyphs are level 0, subcomponents are level 1 and so forth.
757
 
sub level {
758
 
  shift->{level};
759
 
}
760
 
 
761
 
sub draw_connectors {
762
 
  my $self = shift;
763
 
 
764
 
  return if $self->{overbumped};
765
 
  my $gd = shift;
766
 
  my ($dx,$dy) = @_;
767
 
  my @parts = sort { $a->left <=> $b->left } $self->parts;
768
 
  for (my $i = 0; $i < @parts-1; $i++) {
769
 
    # don't let connectors double-back on themselves
770
 
    next if ($parts[$i]->bounds)[2] > ($parts[$i+1]->bounds)[0];
771
 
    $self->_connector($gd,$dx,$dy,$parts[$i]->bounds,$parts[$i+1]->bounds);
772
 
  }
773
 
 
774
 
  # extra connectors going off ends
775
 
  if (@parts) {
776
 
    my($x1,$y1,$x2,$y2) = $self->bounds(0,0);
777
 
    my($xl,$xt,$xr,$xb) = $parts[0]->bounds;
778
 
    $self->_connector($gd,$dx,$dy,$x1,$xt,$x1,$xb,$xl,$xt,$xr,$xb)      if $x1 < $xl;
779
 
    my ($xl2,$xt2,$xr2,$xb2) = $parts[-1]->bounds;
780
 
 
781
 
    my $feature = $self->feature;
782
 
    my @p       = map {$_->feature} @parts;
783
 
    $self->_connector($gd,$dx,$dy,$parts[-1]->bounds,$x2,$xt2,$x2,$xb2) if $x2 > $xr2;
784
 
  } else {
785
 
    my ($x1,$y1,$x2,$y2) = $self->bounds($dx,$dy);
786
 
    $self->draw_connector($gd,$y1,$y2,$x1,$y1,$y2,$x2);
787
 
  }
788
 
 
789
 
}
790
 
 
791
 
# return true if this feature should be highlited
792
 
sub hilite_color {
793
 
  my $self         = shift;
794
 
  return     if $self->level; # only highlite top level glyphs
795
 
  my $index   = $self->option('hilite') or return;
796
 
  $self->factory->translate_color($index);
797
 
}
798
 
 
799
 
sub draw_highlight {
800
 
  my $self              = shift;
801
 
  my ($gd,$left,$top)   = @_;
802
 
  my $color  = $self->hilite_color or return;
803
 
  my @bounds = $self->bounds;
804
 
  $gd->filledRectangle($bounds[0]+$left - 3,
805
 
                       $bounds[1]+$top  - 3,
806
 
                       $bounds[2]+$left + 3,
807
 
                       $bounds[3]+$top  + 3,
808
 
                       $color);
809
 
}
810
 
 
811
 
sub _connector {
812
 
  my $self = shift;
813
 
  my ($gd,
814
 
      $dx,$dy,
815
 
      $xl,$xt,$xr,$xb,
816
 
      $yl,$yt,$yr,$yb) = @_;
817
 
  my $left   = $dx + $xr;
818
 
  my $right  = $dx + $yl;
819
 
  my $top1     = $dy + $xt;
820
 
  my $bottom1  = $dy + $xb;
821
 
  my $top2     = $dy + $yt;
822
 
  my $bottom2  = $dy + $yb;
823
 
 
824
 
  # restore this comment if you don't like the group dash working
825
 
  # its way backwards.
826
 
  return if $right-$left < 1 && !$self->isa('Bio::Graphics::Glyph::group');
827
 
 
828
 
  $self->draw_connector($gd,
829
 
                        $top1,$bottom1,$left,
830
 
                        $top2,$bottom2,$right,
831
 
                       );
832
 
}
833
 
 
834
 
sub draw_connector {
835
 
  my $self   = shift;
836
 
  my $gd     = shift;
837
 
 
838
 
  my $color          = $self->connector_color;
839
 
  my $connector_type = $self->connector or return;
840
 
 
841
 
  if ($connector_type eq 'hat') {
842
 
    $self->draw_hat_connector($gd,$color,@_);
843
 
  } elsif ($connector_type eq 'solid') {
844
 
    $self->draw_solid_connector($gd,$color,@_);
845
 
  } elsif ($connector_type eq 'dashed') {
846
 
    $self->draw_dashed_connector($gd,$color,@_);
847
 
  } elsif ($connector_type eq 'quill') {
848
 
    $self->draw_quill_connector($gd,$color,@_);
849
 
  } elsif ($connector_type eq 'crossed') {
850
 
    $self->draw_crossed_connector($gd,$color,@_);
851
 
  } else {
852
 
    ; # draw nothing
853
 
  }
854
 
}
855
 
 
856
 
sub draw_hat_connector {
857
 
  my $self = shift;
858
 
  my $gd   = shift;
859
 
  my $color = shift;
860
 
  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
861
 
 
862
 
  cluck "gd object is $gd" unless ref $gd;
863
 
 
864
 
  my $center1  = ($top1 + $bottom1)/2;
865
 
  my $quarter1 = $top1 + ($bottom1-$top1)/4;
866
 
  my $center2  = ($top2 + $bottom2)/2;
867
 
  my $quarter2 = $top2 + ($bottom2-$top2)/4;
868
 
 
869
 
  if ($center1 != $center2) {
870
 
    $self->draw_solid_connector($gd,$color,@_);
871
 
    return;
872
 
  }
873
 
 
874
 
  if ($right - $left > 4) {  # room for the inverted "V"
875
 
      my $middle = $left + int(($right - $left)/2);
876
 
      $gd->line($left,$center1,$middle,$top1,$color);
877
 
      $gd->line($middle,$top1,$right-1,$center1,$color);
878
 
    } elsif ($right-$left > 1) { # no room, just connect
879
 
      $gd->line($left,$quarter1,$right-1,$quarter1,$color);
880
 
    }
881
 
 
882
 
}
883
 
 
884
 
sub draw_solid_connector {
885
 
  my $self = shift;
886
 
  my $gd   = shift;
887
 
  my $color = shift;
888
 
  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
889
 
 
890
 
  my $center1  = ($top1 + $bottom1)/2;
891
 
  my $center2  = ($top2 + $bottom2)/2;
892
 
 
893
 
  $gd->line($left,$center1,$right,$center2,$color);
894
 
}
895
 
 
896
 
sub draw_dashed_connector {
897
 
  my $self = shift;
898
 
  my $gd   = shift;
899
 
  my $color = shift;
900
 
  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
901
 
 
902
 
  my $center1  = ($top1 + $bottom1)/2;
903
 
  my $center2  = ($top2 + $bottom2)/2;
904
 
  my $image_class   = $self->panel->image_class;
905
 
  my $gdTransparent = $image_class->gdTransparent;
906
 
  my $gdStyled      = $image_class->gdStyled;
907
 
  $gd->setStyle($color,$color,$gdTransparent,$gdTransparent);
908
 
  $gd->line($left,$center1,$right,$center2,$gdStyled);
909
 
}
910
 
 
911
 
sub draw_quill_connector {
912
 
  my $self = shift;
913
 
  my $gd   = shift;
914
 
  my $color = shift;
915
 
  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
916
 
 
917
 
  my $center1  = ($top1 + $bottom1)/2;
918
 
  my $center2  = ($top2 + $bottom2)/2;
919
 
 
920
 
  $gd->line($left,$center1,$right,$center2,$color);
921
 
  my $direction = $self->feature->strand;
922
 
  return unless $direction;
923
 
  $direction *= -1 if $self->{flip};
924
 
 
925
 
  if ($direction > 0) {
926
 
    my $start = $left+4;
927
 
    my $end   = $right-1;
928
 
    for (my $position=$start; $position <= $end; $position += QUILL_INTERVAL) {
929
 
      $gd->line($position,$center1,$position-2,$center1-2,$color);
930
 
      $gd->line($position,$center1,$position-2,$center1+2,$color);
931
 
    }
932
 
  } else {
933
 
    my $start = $left+1;
934
 
    my $end   = $right-4;
935
 
    for (my $position=$start; $position <= $end; $position += QUILL_INTERVAL) {
936
 
      $gd->line($position,$center1,$position+2,$center1-2,$color);
937
 
      $gd->line($position,$center1,$position+2,$center1+2,$color);
938
 
    }
939
 
  }
940
 
}
941
 
 
942
 
sub draw_crossed_connector {
943
 
  my $self = shift;
944
 
  my $gd = shift;
945
 
  my $color = shift;
946
 
  my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
947
 
 
948
 
  #Draw the horizontal line
949
 
  my $center1  = ($top1 + $bottom1)/2;
950
 
  my $center2  = ($top2 + $bottom2)/2;
951
 
 
952
 
  $gd->line($left,$center1,$right,$center2,$color);
953
 
 
954
 
  #Extra validations
955
 
  ($left, $right)   = ($right, $left)   if ($right < $left);
956
 
  ($top1, $bottom1) = ($bottom1, $top1) if ($bottom1 < $top1);
957
 
  ($top2, $bottom2) = ($bottom2, $top2) if ($bottom2 < $top2);
958
 
 
959
 
  #Draw the "X"
960
 
  my $middle = int(($right - $left) / 2) + $left;
961
 
  my $midLen = int(($bottom1 - $top1) / 2);
962
 
 
963
 
  $gd->line($middle-$midLen,$top1,   $middle+$midLen,$bottom2,$color);
964
 
  $gd->line($middle-$midLen,$bottom1,$middle+$midLen,$top2,$color);
965
 
}
966
 
 
967
 
sub filled_box {
968
 
  my $self = shift;
969
 
  my $gd = shift;
970
 
  my ($x1,$y1,$x2,$y2,$bg,$fg,$lw) = @_;
971
 
 
972
 
  $bg ||= $self->bgcolor;
973
 
  $fg ||= $self->fgcolor;
974
 
  $lw ||= $self->option('linewidth') || 1;
975
 
 
976
 
  $gd->filledRectangle($x1,$y1,$x2,$y2,$bg);
977
 
  $fg = $self->set_pen($lw,$fg) if $lw > 1;
978
 
 
979
 
  # draw a box
980
 
  $gd->rectangle($x1,$y1,$x2,$y2,$fg);
981
 
 
982
 
  # if the left end is off the end, then cover over
983
 
  # the leftmost line
984
 
  my ($width) = $gd->getBounds;
985
 
 
986
 
  $bg = $self->set_pen($lw,$bg) if $lw > 1;
987
 
 
988
 
  $gd->line($x1,$y1+$lw,$x1,$y2-$lw,$bg)
989
 
    if $x1 < $self->panel->pad_left;
990
 
 
991
 
  $gd->line($x2,$y1+$lw,$x2,$y2-$lw,$bg)
992
 
    if $x2 > $width - $self->panel->pad_right;
993
 
}
994
 
 
995
 
sub filled_oval {
996
 
  my $self = shift;
997
 
  my $gd = shift;
998
 
  my ($x1,$y1,$x2,$y2,$bg,$fg,$lw) = @_;
999
 
  my $cx = ($x1+$x2)/2;
1000
 
  my $cy = ($y1+$y2)/2;
1001
 
 
1002
 
  $fg ||= $self->fgcolor;
1003
 
  $bg ||= $self->bgcolor;
1004
 
  $lw ||= $self->linewidth;
1005
 
 
1006
 
  $fg = $self->set_pen($lw) if $lw > 1;
1007
 
 
1008
 
  # Maintain backwards compatability with gd 1.8.4
1009
 
  # which does not support the ellipse methods.
1010
 
  # can() method fails with GD::SVG...
1011
 
  if ($gd->can('ellipse') || $gd =~ /SVG/ ) {
1012
 
    $gd->filledEllipse($cx,$cy,$x2-$x1,$y2-$y1,$bg);
1013
 
    # Draw the edge around the ellipse
1014
 
    $gd->ellipse($cx,$cy,$x2-$x1,$y2-$y1,$fg);
1015
 
  } else {
1016
 
    $gd->arc($cx,$cy,$x2-$x1,$y2-$y1,0,360,$fg);
1017
 
    $gd->fillToBorder($cx,$cy,$fg,$bg);
1018
 
  }
1019
 
}
1020
 
 
1021
 
sub oval {
1022
 
  my $self = shift;
1023
 
  my $gd = shift;
1024
 
  my ($x1,$y1,$x2,$y2) = @_;
1025
 
  my $cx = ($x1+$x2)/2;
1026
 
  my $cy = ($y1+$y2)/2;
1027
 
 
1028
 
  my $fg = $self->fgcolor;
1029
 
  my $linewidth = $self->linewidth;
1030
 
  $fg = $self->set_pen($linewidth) if $linewidth > 1;
1031
 
 
1032
 
  # Maintain backwards compatability with gd 1.8.4 which does not
1033
 
  # support the ellipse method.
1034
 
  if ($gd->can('ellipse') || $gd =~ /SVG/ ) {
1035
 
    $gd->ellipse($cx,$cy,$x2-$x1,$y2-$y1,$fg);
1036
 
  } else {
1037
 
    $gd->arc($cx,$cy,$x2-$x1,$y2-$y1,0,360,$fg);
1038
 
  }
1039
 
}
1040
 
 
1041
 
sub filled_arrow {
1042
 
  my $self = shift;
1043
 
  my $gd   = shift;
1044
 
  my $orientation = shift;
1045
 
  my ($x1,$y1,$x2,$y2,$fg,$bg)  = @_;
1046
 
 
1047
 
  $orientation *= -1 if $self->{flip};
1048
 
 
1049
 
  my ($width) = $gd->getBounds;
1050
 
  my $indent = $y2-$y1 < $x2-$x1 ? $y2-$y1 : ($x2-$x1)/2;
1051
 
 
1052
 
  return $self->filled_box($gd,@_)
1053
 
    if ($orientation == 0)
1054
 
      or ($x1 < 0 && $orientation < 0)
1055
 
        or ($x2 > $width && $orientation > 0)
1056
 
          or ($indent <= 0)
1057
 
            or ($x2 - $x1 < 3);
1058
 
 
1059
 
  $fg   ||= $self->fgcolor;
1060
 
  $bg   ||= $self->bgcolor;
1061
 
  my $pkg  = $self->polygon_package;
1062
 
  my $poly = $pkg->new();
1063
 
  if ($orientation >= 0) {
1064
 
    $poly->addPt($x1,$y1);
1065
 
    $poly->addPt($x2-$indent,$y1);
1066
 
    $poly->addPt($x2,($y2+$y1)/2);
1067
 
    $poly->addPt($x2-$indent,$y2);
1068
 
    $poly->addPt($x1,$y2);
1069
 
  } else {
1070
 
    $poly->addPt($x2,$y1);
1071
 
    $poly->addPt($x2,$y2);
1072
 
    $poly->addPt($x1+$indent,$y2);
1073
 
    $poly->addPt($x1,($y2+$y1)/2);
1074
 
    $poly->addPt($x1+$indent,$y1);
1075
 
  }
1076
 
  $gd->filledPolygon($poly,$bg);
1077
 
  $gd->polygon($poly,$fg);
1078
 
 
1079
 
  # blunt it a bit if off the end
1080
 
  # good idea - but isn't inuitive
1081
 
  # if ($orientation >= 0 && $x2 > $width - $self->panel->pad_right) {
1082
 
  # $gd->filledRectangle($x2-3,$y1,$x2,$y2,$self->panel->bgcolor);
1083
 
  #}
1084
 
}
1085
 
 
1086
 
sub linewidth {
1087
 
  shift->option('linewidth') || 1;
1088
 
}
1089
 
 
1090
 
sub fill {
1091
 
  my $self = shift;
1092
 
  my $gd   = shift;
1093
 
  my ($x1,$y1,$x2,$y2) = @_;
1094
 
  if ( ($x2-$x1) >= 2 && ($y2-$y1) >= 2 ) {
1095
 
    $gd->fill($x1+1,$y1+1,$self->bgcolor);
1096
 
  }
1097
 
}
1098
 
sub set_pen {
1099
 
  my $self = shift;
1100
 
  my ($linewidth,$color) = @_;
1101
 
  $linewidth ||= $self->linewidth;
1102
 
  $color     ||= $self->fgcolor;
1103
 
  return $color unless $linewidth > 1;
1104
 
  $self->panel->set_pen($linewidth,$color);
1105
 
}
1106
 
 
1107
 
sub draw_component {
1108
 
  my $self = shift;
1109
 
  my ($gd,$left,$top,$partno,$total_parts) = @_;
1110
 
  my($x1,$y1,$x2,$y2) = $self->bounds($left,$top);
1111
 
 
1112
 
  # clipping
1113
 
  my $panel = $self->panel;
1114
 
  return unless $x2 >= $panel->left and $x1 <= $panel->right;
1115
 
 
1116
 
  if ($self->option('strand_arrow') || $self->option('stranded')) {
1117
 
    $self->filled_arrow($gd,$self->feature->strand,
1118
 
                        $x1, $y1,
1119
 
                        $x2, $y2)
1120
 
  } else {
1121
 
    $self->filled_box($gd,
1122
 
                      $x1, $y1,
1123
 
                      $x2, $y2)
1124
 
  }
1125
 
}
1126
 
 
1127
 
 
1128
 
sub no_subparts {
1129
 
  return shift->option('no_subparts');
1130
 
}
1131
 
 
1132
 
sub maxdepth {
1133
 
  my $self = shift;
1134
 
 
1135
 
  my $maxdepth =  $self->option('maxdepth');
1136
 
  return $maxdepth if defined $maxdepth;
1137
 
 
1138
 
  # $feature->compound is an artefact from aggregators. Sadly, an aggregated feature can miss
1139
 
  # parts that are out of the query range - this is a horrible mis-feature. Aggregated features have
1140
 
  # a compound flag to hack around this.
1141
 
  my $feature = $self->feature;
1142
 
  return 1 if $feature->can('compound') && $feature->compound;
1143
 
 
1144
 
  return;
1145
 
}
1146
 
 
1147
 
sub exceeds_depth {
1148
 
  my $self = shift;
1149
 
  my $max_depth     = $self->maxdepth;
1150
 
  return unless defined $max_depth;
1151
 
 
1152
 
  my $current_depth = $self->level || 0;
1153
 
  return $current_depth >= $max_depth;
1154
 
}
1155
 
 
1156
 
# memoize _subfeat -- it's a bottleneck with segments
1157
 
sub subfeat {
1158
 
  my $self    = shift;
1159
 
  my $feature = shift;
1160
 
 
1161
 
  return $self->_subfeat($feature) unless ref $self;  # protect against class invocation
1162
 
 
1163
 
  return if $self->level == 0 && $self->no_subparts;
1164
 
  return if $self->exceeds_depth;
1165
 
 
1166
 
  return @{$self->{cached_subfeat}{$feature}} if exists $self->{cached_subfeat}{$feature};
1167
 
  my @ss = $self->_subfeat($feature);
1168
 
  $self->{cached_subfeat}{$feature} = \@ss;
1169
 
  @ss;
1170
 
}
1171
 
 
1172
 
sub _subfeat {
1173
 
  my $class   = shift;
1174
 
  my $feature = shift;
1175
 
 
1176
 
  return $feature->segments     if $feature->can('segments');
1177
 
 
1178
 
  my @split = eval { my $id   = $feature->location->seq_id;
1179
 
                     my @subs = $feature->location->sub_Location;
1180
 
                     grep {$id eq $_->seq_id} @subs;
1181
 
                   };
1182
 
 
1183
 
  return @split if @split;
1184
 
 
1185
 
  # Either the APIs have changed, or I got confused at some point...
1186
 
  return $feature->get_SeqFeatures         if $feature->can('get_SeqFeatures');
1187
 
  return $feature->sub_SeqFeature          if $feature->can('sub_SeqFeature');
1188
 
  return;
1189
 
}
1190
 
 
1191
 
# synthesize a key glyph
1192
 
sub keyglyph {
1193
 
  my $self = shift;
1194
 
  my $feature = $self->make_key_feature;
1195
 
  my $factory = $self->factory->clone;
1196
 
  $factory->set_option(label       => 1);
1197
 
  $factory->set_option(description => 0);
1198
 
  $factory->set_option(bump  => 0);
1199
 
  $factory->set_option(connector  => 'solid');
1200
 
  return $factory->make_glyph(0,$feature);
1201
 
}
1202
 
 
1203
 
# synthesize a key glyph
1204
 
sub make_key_feature {
1205
 
  my $self = shift;
1206
 
 
1207
 
  my $scale = 1/$self->scale;  # base pairs/pixel
1208
 
 
1209
 
  # one segments, at pixels 0->80
1210
 
  my $offset = $self->panel->offset;
1211
 
 
1212
 
  my $feature =
1213
 
    Bio::Graphics::Feature->new(-start =>0 * $scale +$offset,
1214
 
                                -end   =>80*$scale+$offset,
1215
 
                                -name => $self->make_key_name(),
1216
 
                                -strand => '+1');
1217
 
  return $feature;
1218
 
}
1219
 
 
1220
 
sub make_key_name {
1221
 
  my $self = shift;
1222
 
 
1223
 
  # breaking encapsulation - this should be handled by the panel
1224
 
  my $key      = $self->option('key') || '';
1225
 
  return $key unless $self->panel->add_category_labels;
1226
 
 
1227
 
  my $category = $self->option('category');
1228
 
  my $name     = defined $category ? "$key ($category)" : $key;
1229
 
  return $name;
1230
 
}
1231
 
 
1232
 
sub all_callbacks {
1233
 
  my $self = shift;
1234
 
  return $self->{all_callbacks} if exists $self->{all_callbacks}; # memoize
1235
 
  return $self->{all_callbacks} = $self->_all_callbacks;
1236
 
}
1237
 
 
1238
 
sub _all_callbacks {
1239
 
  my $self = shift;
1240
 
  my $track_level = $self->option('all_callbacks');
1241
 
  return $track_level if defined $track_level;
1242
 
  return $self->panel->all_callbacks;
1243
 
}
1244
 
 
1245
 
sub subpart_callbacks {
1246
 
  my $self = shift;
1247
 
  return $self->{subpart_callbacks} if exists $self->{subpart_callbacks}; # memoize
1248
 
  return $self->{subpart_callbacks} = $self->_subpart_callbacks;
1249
 
}
1250
 
 
1251
 
sub _subpart_callbacks {
1252
 
  my $self = shift;
1253
 
  return 1 if $self->all_callbacks;
1254
 
  my $do_subparts = $self->option('subpart_callbacks');
1255
 
  return $self->{level} == 0 || ($self->{level} > 0 && $do_subparts);
1256
 
}
1257
 
 
1258
 
sub default_factory {
1259
 
  croak "no default factory implemented";
1260
 
}
1261
 
 
1262
 
sub finished {
1263
 
  my $self = shift;
1264
 
  delete $self->{factory};
1265
 
  foreach (@{$self->{parts} || []}) {
1266
 
    $_->finished;
1267
 
  }
1268
 
  delete $self->{parts};
1269
 
}
1270
 
 
1271
 
1;
1272
 
 
1273
 
__END__
1274
 
 
1275
 
=head1 NAME
1276
 
 
1277
 
Bio::Graphics::Glyph - Base class for Bio::Graphics::Glyph objects
1278
 
 
1279
 
=head1 SYNOPSIS
1280
 
 
1281
 
See L<Bio::Graphics::Panel>.
1282
 
 
1283
 
=head1 DESCRIPTION
1284
 
 
1285
 
Bio::Graphics::Glyph is the base class for all glyph objects.  Each
1286
 
glyph is a wrapper around an Bio:SeqFeatureI object, knows how to
1287
 
render itself on an Bio::Graphics::Panel, and has a variety of
1288
 
configuration variables.
1289
 
 
1290
 
End developers will not ordinarily work directly with
1291
 
Bio::Graphics::Glyph objects, but with Bio::Graphics::Glyph::generic
1292
 
and its subclasses.  Similarly, most glyph developers will want to
1293
 
subclass from Bio::Graphics::Glyph::generic because the latter
1294
 
provides labeling and arrow-drawing facilities.
1295
 
 
1296
 
=head1 METHODS
1297
 
 
1298
 
This section describes the class and object methods for
1299
 
Bio::Graphics::Glyph.
1300
 
 
1301
 
=head2 CONSTRUCTORS
1302
 
 
1303
 
Bio::Graphics::Glyph objects are constructed automatically by an
1304
 
Bio::Graphics::Glyph::Factory, and are not usually created by
1305
 
end-developer code.
1306
 
 
1307
 
=over 4
1308
 
 
1309
 
=item $glyph = Bio::Graphics::Glyph-E<gt>new(-feature=E<gt>$feature,-factory=E<gt>$factory)
1310
 
 
1311
 
Given a sequence feature, creates an Bio::Graphics::Glyph object to
1312
 
display it.  The B<-feature> argument points to the Bio:SeqFeatureI
1313
 
object to display, and B<-factory> indicates an
1314
 
Bio::Graphics::Glyph::Factory object from which the glyph will fetch
1315
 
all its run-time configuration information.  Factories are created and
1316
 
manipulated by the Bio::Graphics::Panel object.
1317
 
 
1318
 
A standard set of options are recognized.  See L<OPTIONS>.
1319
 
 
1320
 
=back
1321
 
 
1322
 
=head2 OBJECT METHODS
1323
 
 
1324
 
Once a glyph is created, it responds to a large number of methods.  In
1325
 
this section, these methods are grouped into related categories.
1326
 
 
1327
 
Retrieving glyph context:
1328
 
 
1329
 
=over 4
1330
 
 
1331
 
=item $factory = $glyph-E<gt>factory
1332
 
 
1333
 
Get the Bio::Graphics::Glyph::Factory associated with this object.
1334
 
This cannot be changed once it is set.
1335
 
 
1336
 
=item $panel = $glyph-E<gt>panel
1337
 
 
1338
 
Get the Bio::Graphics::Panel associated with this object.  This cannot
1339
 
be changed once it is set.
1340
 
 
1341
 
=item $feature = $glyph-E<gt>feature
1342
 
 
1343
 
Get the sequence feature associated with this object.  This cannot be
1344
 
changed once it is set.
1345
 
 
1346
 
=item $feature = $glyph-E<gt>add_feature(@features)
1347
 
 
1348
 
Add the list of features to the glyph, creating subparts.  This is
1349
 
most common done with the track glyph returned by
1350
 
Ace::Graphics::Panel-E<gt>add_track().
1351
 
 
1352
 
=item $feature = $glyph-E<gt>add_group(@features)
1353
 
 
1354
 
This is similar to add_feature(), but the list of features is treated
1355
 
as a group and can be configured as a set.
1356
 
 
1357
 
=item $glyph-E<gt>finished
1358
 
 
1359
 
When you are finished with a glyph, you can call its finished() method
1360
 
in order to break cycles that would otherwise cause memory leaks.
1361
 
finished() is typically only used by the Panel object.
1362
 
 
1363
 
=back
1364
 
 
1365
 
Retrieving glyph options:
1366
 
 
1367
 
=over 4
1368
 
 
1369
 
=item $fgcolor = $glyph-E<gt>fgcolor
1370
 
 
1371
 
=item $bgcolor = $glyph-E<gt>bgcolor
1372
 
 
1373
 
=item $fontcolor = $glyph-E<gt>fontcolor
1374
 
 
1375
 
=item $fontcolor = $glyph-E<gt>font2color
1376
 
 
1377
 
=item $fillcolor = $glyph-E<gt>fillcolor
1378
 
 
1379
 
These methods return the configured foreground, background, font,
1380
 
alternative font, and fill colors for the glyph in the form of a
1381
 
GD::Image color index.
1382
 
 
1383
 
=item $color = $glyph-E<gt>tkcolor
1384
 
 
1385
 
This method returns a color to be used to flood-fill the entire glyph
1386
 
before drawing (currently used by the "track" glyph).
1387
 
 
1388
 
=item $width = $glyph-E<gt>width([$newwidth])
1389
 
 
1390
 
Return the width of the glyph, not including left or right padding.
1391
 
This is ordinarily set internally based on the size of the feature and
1392
 
the scale of the panel.
1393
 
 
1394
 
=item $width = $glyph-E<gt>layout_width
1395
 
 
1396
 
Returns the width of the glyph including left and right padding.
1397
 
 
1398
 
=item $width = $glyph-E<gt>height
1399
 
 
1400
 
Returns the height of the glyph, not including the top or bottom
1401
 
padding.  This is calculated from the "height" option and cannot be
1402
 
changed.
1403
 
 
1404
 
 
1405
 
=item $font = $glyph-E<gt>font
1406
 
 
1407
 
Return the font for the glyph.
1408
 
 
1409
 
=item $option = $glyph-E<gt>option($option)
1410
 
 
1411
 
Return the value of the indicated option.
1412
 
 
1413
 
=item $index = $glyph-E<gt>color($color)
1414
 
 
1415
 
Given a symbolic or #RRGGBB-form color name, returns its GD index.
1416
 
 
1417
 
=item $level = $glyph-E<gt>level
1418
 
 
1419
 
The "level" is the nesting level of the glyph.
1420
 
Groups are level -1, top level glyphs are level 0,
1421
 
subparts (e.g. exons) are level 1 and so forth.
1422
 
 
1423
 
=back
1424
 
 
1425
 
Setting an option:
1426
 
 
1427
 
=over 4
1428
 
 
1429
 
=item $glyph-E<gt>configure(-name=E<gt>$value)
1430
 
 
1431
 
You may change a glyph option after it is created using set_option().
1432
 
This is most commonly used to configure track glyphs.
1433
 
 
1434
 
=back
1435
 
 
1436
 
Retrieving information about the sequence:
1437
 
 
1438
 
=over 4
1439
 
 
1440
 
=item $start = $glyph-E<gt>start
1441
 
 
1442
 
=item $end   = $glyph-E<gt>end
1443
 
 
1444
 
These methods return the start and end of the glyph in base pair
1445
 
units.
1446
 
 
1447
 
=item $offset = $glyph-E<gt>offset
1448
 
 
1449
 
Returns the offset of the segment (the base pair at the far left of
1450
 
the image).
1451
 
 
1452
 
=item $length = $glyph-E<gt>length
1453
 
 
1454
 
Returns the length of the sequence segment.
1455
 
 
1456
 
=back
1457
 
 
1458
 
 
1459
 
Retrieving formatting information:
1460
 
 
1461
 
=over 4
1462
 
 
1463
 
=item $top = $glyph-E<gt>top
1464
 
 
1465
 
=item $left = $glyph-E<gt>left
1466
 
 
1467
 
=item $bottom = $glyph-E<gt>bottom
1468
 
 
1469
 
=item $right = $glyph-E<gt>right
1470
 
 
1471
 
These methods return the top, left, bottom and right of the glyph in
1472
 
pixel coordinates.
1473
 
 
1474
 
=item $height = $glyph-E<gt>height
1475
 
 
1476
 
Returns the height of the glyph.  This may be somewhat larger or
1477
 
smaller than the height suggested by the GlyphFactory, depending on
1478
 
the type of the glyph.
1479
 
 
1480
 
=item $scale = $glyph-E<gt>scale
1481
 
 
1482
 
Get the scale for the glyph in pixels/bp.
1483
 
 
1484
 
=item $height = $glyph-E<gt>labelheight
1485
 
 
1486
 
Return the height of the label, if any.
1487
 
 
1488
 
=item $label = $glyph-E<gt>label
1489
 
 
1490
 
Return a human-readable label for the glyph.
1491
 
 
1492
 
=back
1493
 
 
1494
 
These methods are called by Bio::Graphics::Track during the layout
1495
 
process:
1496
 
 
1497
 
=over 4
1498
 
 
1499
 
=item $glyph-E<gt>move($dx,$dy)
1500
 
 
1501
 
Move the glyph in pixel coordinates by the indicated delta-x and
1502
 
delta-y values.
1503
 
 
1504
 
=item ($x1,$y1,$x2,$y2) = $glyph-E<gt>box
1505
 
 
1506
 
Return the current position of the glyph.
1507
 
 
1508
 
=back
1509
 
 
1510
 
These methods are intended to be overridden in subclasses:
1511
 
 
1512
 
=over 4
1513
 
 
1514
 
=item $glyph-E<gt>calculate_height
1515
 
 
1516
 
Calculate the height of the glyph.
1517
 
 
1518
 
=item $glyph-E<gt>calculate_left
1519
 
 
1520
 
Calculate the left side of the glyph.
1521
 
 
1522
 
=item $glyph-E<gt>calculate_right
1523
 
 
1524
 
Calculate the right side of the glyph.
1525
 
 
1526
 
=item $glyph-E<gt>draw($gd,$left,$top)
1527
 
 
1528
 
Optionally offset the glyph by the indicated amount and draw it onto
1529
 
the GD::Image object.
1530
 
 
1531
 
=item $glyph-E<gt>draw_label($gd,$left,$top)
1532
 
 
1533
 
Draw the label for the glyph onto the provided GD::Image object,
1534
 
optionally offsetting by the amounts indicated in $left and $right.
1535
 
 
1536
 
=item $glyph-E<gt>maxdepth()
1537
 
 
1538
 
This returns the maximum number of levels of feature subparts that the
1539
 
glyph will recurse through. For example, returning 0 indicates that
1540
 
the glyph will only draw the top-level feature. Returning 1 indicates
1541
 
that it will only draw the top-level feature and one level of
1542
 
subfeatures. Returning 2 will descend down two levels. Overriding this
1543
 
method will speed up rendering by avoiding creating of a bunch of
1544
 
subglyphs that will never be drawn.
1545
 
 
1546
 
The default behavior is to return undef (unlimited levels of descent)
1547
 
unless the -maxdepth option is passed, in which case this number is
1548
 
returned.
1549
 
 
1550
 
Note that Bio::Graphics::Glyph::generic overrides maxdepth() to return
1551
 
0, meaning no descent into subparts will be performed.
1552
 
 
1553
 
=back
1554
 
 
1555
 
These methods are useful utility routines:
1556
 
 
1557
 
=over 4
1558
 
 
1559
 
=item $pixels = $glyph-E<gt>map_pt($bases);
1560
 
 
1561
 
Map the indicated base position, given in base pair units, into
1562
 
pixels, using the current scale and glyph position.
1563
 
 
1564
 
=item $glyph-E<gt>filled_box($gd,$x1,$y1,$x2,$y2)
1565
 
 
1566
 
Draw a filled rectangle with the appropriate foreground and fill
1567
 
colors, and pen width onto the GD::Image object given by $gd, using
1568
 
the provided rectangle coordinates.
1569
 
 
1570
 
=item $glyph-E<gt>filled_oval($gd,$x1,$y1,$x2,$y2)
1571
 
 
1572
 
As above, but draws an oval inscribed on the rectangle.
1573
 
 
1574
 
=item $glyph-E<gt>exceeds_depth
1575
 
 
1576
 
Returns true if descending into another level of subfeatures will
1577
 
exceed the value returned by maxdepth().
1578
 
 
1579
 
=back
1580
 
 
1581
 
=head2 OPTIONS
1582
 
 
1583
 
The following options are standard among all Glyphs.  See individual
1584
 
glyph pages for more options.
1585
 
 
1586
 
  Option      Description                      Default
1587
 
  ------      -----------                      -------
1588
 
 
1589
 
  -fgcolor      Foreground color               black
1590
 
 
1591
 
  -outlinecolor Synonym for -fgcolor
1592
 
 
1593
 
  -bgcolor      Background color               turquoise
1594
 
 
1595
 
  -fillcolor    Synonym for -bgcolor
1596
 
 
1597
 
  -linewidth    Line width                     1
1598
 
 
1599
 
  -height       Height of glyph                10
1600
 
 
1601
 
  -font         Glyph font                     gdSmallFont
1602
 
 
1603
 
  -connector    Connector type                 undef (false)
1604
 
 
1605
 
  -connector_color
1606
 
                Connector color                black
1607
 
 
1608
 
  -strand_arrow Whether to indicate            undef (false)
1609
 
                 strandedness
1610
 
 
1611
 
  -label        Whether to draw a label        undef (false)
1612
 
 
1613
 
  -description  Whether to draw a description  undef (false)
1614
 
 
1615
 
  -no_subparts  Set to true to prevent         undef (false)
1616
 
                drawing of the subparts
1617
 
                of a feature.
1618
 
 
1619
 
  -ignore_sub_part Give the types/methods of   undef
1620
 
                subparts to ignore (as a 
1621
 
                space delimited list).
1622
 
 
1623
 
  -maxdepth     Specifies the maximum number   undef (unlimited) 
1624
 
                child-generations to decend
1625
 
                when getting subfeatures
1626
 
 
1627
 
  -sort_order   Specify layout sort order      "default"
1628
 
 
1629
 
  -always_sort  Sort even when bumping is off  undef (false)
1630
 
 
1631
 
  -bump_limit   Maximum number of levels to bump undef (unlimited)
1632
 
 
1633
 
  -hilite       Highlight color                undef (no color)
1634
 
 
1635
 
  -link, -title, -target
1636
 
               These options are used when creating imagemaps
1637
 
               for display on the web.  See L<Bio::Graphics::Panel/"Creating Imagemaps">.
1638
 
 
1639
 
 
1640
 
For glyphs that consist of multiple segments, the B<-connector> option
1641
 
controls what's drawn between the segments.  The default is undef (no
1642
 
connector).  Options include:
1643
 
 
1644
 
   "hat"     an upward-angling conector
1645
 
   "solid"   a straight horizontal connector
1646
 
   "quill"   a decorated line with small arrows indicating strandedness
1647
 
             (like the UCSC Genome Browser uses)
1648
 
   "dashed"  a horizontal dashed line.
1649
 
   "crossed" a straight horizontal connector with an "X" on it
1650
 
              (Can be used when segments are not yet validated
1651
 
               by some internal experiments...)
1652
 
 
1653
 
The B<-connector_color> option controls the color of the connector, if
1654
 
any.
1655
 
 
1656
 
The label is printed above the glyph.  You may pass an anonymous
1657
 
subroutine to B<-label>, in which case the subroutine will be invoked
1658
 
with the feature as its single argument.  and is expected to return
1659
 
the string to use as the description.  If you provide the numeric
1660
 
value "1" to B<-description>, the description will be read off the
1661
 
feature's seqname(), info() and primary_tag() methods will be called
1662
 
until a suitable name is found.  To create a label with the
1663
 
text "1", pass the string "1 ".  (A 1 followed by a space).
1664
 
 
1665
 
The description is printed below the glyph.  You may pass an anonymous
1666
 
subroutine to B<-description>, in which case the subroutine will be
1667
 
invoked with the feature as its single argument and is expected to
1668
 
return the string to use as the description.  If you provide the
1669
 
numeric value "1" to B<-description>, the description will be read off
1670
 
the feature's source_tag() method.  To create a description with the
1671
 
text "1", pass the string "1 ".  (A 1 followed by a space).
1672
 
 
1673
 
In the case of ACEDB Ace::Sequence feature objects, the feature's
1674
 
info(), Brief_identification() and Locus() methods will be called to
1675
 
create a suitable description.
1676
 
 
1677
 
The B<-strand_arrow> option, if true, requests that the glyph indicate
1678
 
which strand it is on, usually by drawing an arrowhead.  Not all
1679
 
glyphs will respond to this request.  For historical reasons,
1680
 
B<-stranded> is a synonym for this option.
1681
 
 
1682
 
B<sort_order>: By default, features are drawn with a layout based only on the
1683
 
position of the feature, assuring a maximal "packing" of the glyphs
1684
 
when bumped.  In some cases, however, it makes sense to display the
1685
 
glyphs sorted by score or some other comparison, e.g. such that more
1686
 
"important" features are nearer the top of the display, stacked above
1687
 
less important features.  The -sort_order option allows a few
1688
 
different built-in values for changing the default sort order (which
1689
 
is by "left" position): "low_score" (or "high_score") will cause
1690
 
features to be sorted from lowest to highest score (or vice versa).
1691
 
"left" (or "default") and "right" values will cause features to be
1692
 
sorted by their position in the sequence.  "longer" (or "shorter")
1693
 
will cause the longest (or shortest) features to be sorted first, and
1694
 
"strand" will cause the features to be sorted by strand: "+1"
1695
 
(forward) then "0" (unknown, or NA) then "-1" (reverse).
1696
 
 
1697
 
In all cases, the "left" position will be used to break any ties.  To
1698
 
break ties using another field, options may be strung together using a
1699
 
"|" character; e.g. "strand|low_score|right" would cause the features
1700
 
to be sorted first by strand, then score (lowest to highest), then by
1701
 
"right" position in the sequence.
1702
 
 
1703
 
Finally, a subroutine coderef with a $$ prototype can be provided.  It
1704
 
will receive two B<glyph> as arguments and should return -1, 0 or 1
1705
 
(see Perl's sort() function for more information).  For example, to
1706
 
sort a set of database search hits by bits (stored in the features'
1707
 
"score" fields), scaled by the log of the alignment length (with
1708
 
"start" position breaking any ties):
1709
 
 
1710
 
  sort_order = sub ($$) {
1711
 
    my ($glyph1,$glyph2) = @_;
1712
 
    my $a = $glyph1->feature;
1713
 
    my $b = $glyph2->feature;
1714
 
    ( $b->score/log($b->length)
1715
 
          <=>
1716
 
      $a->score/log($a->length) )
1717
 
          ||
1718
 
    ( $a->start <=> $b->start )
1719
 
  }
1720
 
 
1721
 
It is important to remember to use the $$ prototype as shown in the
1722
 
example.  Otherwise Bio::Graphics will quit with an exception. The
1723
 
arguments are subclasses of Bio::Graphics::Glyph, not the features
1724
 
themselves.  While glyphs implement some, but not all, of the feature
1725
 
methods, to be safe call the two glyphs' feature() methods in order to
1726
 
convert them into the actual features.
1727
 
 
1728
 
The '-always_sort' option, if true, will sort features even if bumping
1729
 
is turned off.  This is useful if you would like overlapping features
1730
 
to stack in a particular order.  Features towards the end of the list
1731
 
will overlay those towards the beginning of the sort order.
1732
 
 
1733
 
The B<-hilite> option draws a colored box behind each feature using the
1734
 
indicated color. Typically you will pass it a code ref that returns a
1735
 
color name.  For example:
1736
 
 
1737
 
  -hilite => sub { my $name = shift->display_name; 
1738
 
                   return 'yellow' if $name =~ /XYZ/ }
1739
 
 
1740
 
The B<-no_subparts> option will prevent the glyph from searching its
1741
 
feature for subfeatures. This may enhance performance if you know in
1742
 
advance that none of your features contain subfeatures.
1743
 
 
1744
 
=head1 SUBCLASSING Bio::Graphics::Glyph
1745
 
 
1746
 
By convention, subclasses are all lower-case.  Begin each subclass
1747
 
with a preamble like this one:
1748
 
 
1749
 
 package Bio::Graphics::Glyph::crossbox;
1750
 
 
1751
 
 use strict;
1752
 
 use base qw(Bio::Graphics::Glyph);
1753
 
 
1754
 
Then override the methods you need to.  Typically, just the draw()
1755
 
method will need to be overridden.  However, if you need additional
1756
 
room in the glyph, you may override calculate_height(),
1757
 
calculate_left() and calculate_right().  Do not directly override
1758
 
height(), left() and right(), as their purpose is to cache the values
1759
 
returned by their calculating cousins in order to avoid time-consuming
1760
 
recalculation.
1761
 
 
1762
 
A simple draw() method looks like this:
1763
 
 
1764
 
 sub draw {
1765
 
  my $self = shift;
1766
 
  $self->SUPER::draw(@_);
1767
 
  my $gd = shift;
1768
 
 
1769
 
  # and draw a cross through the box
1770
 
  my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_);
1771
 
  my $fg = $self->fgcolor;
1772
 
  $gd->line($x1,$y1,$x2,$y2,$fg);
1773
 
  $gd->line($x1,$y2,$x2,$y1,$fg);
1774
 
 }
1775
 
 
1776
 
This subclass draws a simple box with two lines criss-crossed through
1777
 
it.  We first call our inherited draw() method to generate the filled
1778
 
box and label.  We then call calculate_boundaries() to return the
1779
 
coordinates of the glyph, disregarding any extra space taken by
1780
 
labels.  We call fgcolor() to return the desired foreground color, and
1781
 
then call $gd-E<gt>line() twice to generate the criss-cross.
1782
 
 
1783
 
For more complex draw() methods, see Bio::Graphics::Glyph::transcript
1784
 
and Bio::Graphics::Glyph::segments.
1785
 
 
1786
 
Please avoid using a specific image class (via "use GD" for example)
1787
 
within your glyph package. Instead, rely on the image package passed
1788
 
to the draw() method. This approach allows for future expansion of
1789
 
supported image classes without requiring glyph redesign. If you need
1790
 
access to the specific image classes such as Polygon, Image, or Font,
1791
 
generate them like such:
1792
 
 
1793
 
 sub draw {
1794
 
  my $self = shift;
1795
 
  my $image_class = shift;
1796
 
 
1797
 
  my $polygon_package = $self->polygon_package->new()
1798
 
  ...
1799
 
  }
1800
 
 
1801
 
=head1 BUGS
1802
 
 
1803
 
Please report them.
1804
 
 
1805
 
=head1 SEE ALSO
1806
 
 
1807
 
L<Bio::DB::GFF::Feature>,
1808
 
L<Ace::Sequence>,
1809
 
L<Bio::Graphics::Panel>,
1810
 
L<Bio::Graphics::Track>,
1811
 
L<Bio::Graphics::Glyph::anchored_arrow>,
1812
 
L<Bio::Graphics::Glyph::arrow>,
1813
 
L<Bio::Graphics::Glyph::box>,
1814
 
L<Bio::Graphics::Glyph::dna>,
1815
 
L<Bio::Graphics::Glyph::graded_segments>,
1816
 
L<Bio::Graphics::Glyph::primers>,
1817
 
L<Bio::Graphics::Glyph::segments>,
1818
 
L<Bio::Graphics::Glyph::toomany>,
1819
 
L<Bio::Graphics::Glyph::transcript>,
1820
 
L<Bio::Graphics::Glyph::transcript2>,
1821
 
L<Bio::Graphics::Glyph::wormbase_transcript>
1822
 
L<Bio::Graphics::Glyph::xyplot>
1823
 
L<Bio::Graphics::Glyph::whiskerplot>
1824
 
 
1825
 
=head1 AUTHOR
1826
 
 
1827
 
Lincoln Stein E<lt>lstein@cshl.orgE<gt>
1828
 
 
1829
 
Copyright (c) 2001 Cold Spring Harbor Laboratory
1830
 
 
1831
 
This library is free software; you can redistribute it and/or modify
1832
 
it under the same terms as Perl itself.  See DISCLAIMER.txt for
1833
 
disclaimers of warranty.
1834
 
 
1835
 
=cut