comparison variant_effect_predictor/Bio/Graphics/Glyph.pm @ 0:1f6dce3d34e0

Uploaded
author mahtabm
date Thu, 11 Apr 2013 02:01:53 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:1f6dce3d34e0
1 package Bio::Graphics::Glyph;
2 use GD;
3
4 use strict;
5 use Carp 'croak';
6 use constant BUMP_SPACING => 2; # vertical distance between bumped glyphs
7
8
9 my %LAYOUT_COUNT;
10
11 # the CM1 and CM2 constants control the size of the hash used to
12 # detect collisions.
13 use constant CM1 => 200; # big bin, x axis
14 use constant CM2 => 50; # big bin, y axis
15 use constant CM3 => 50; # small bin, x axis
16 use constant CM4 => 50; # small bin, y axis
17
18 use constant QUILL_INTERVAL => 8; # number of pixels between Jim Kent style intron "quills"
19
20 # a bumpable graphical object that has bumpable graphical subparts
21
22 # args: -feature => $feature_object (may contain subsequences)
23 # -factory => $factory_object (called to create glyphs for subsequences)
24 # In this scheme, the factory decides based on stylesheet information what glyph to
25 # draw and what configurations options to us. This allows for heterogeneous tracks.
26 sub new {
27 my $class = shift;
28 my %arg = @_;
29
30 my $feature = $arg{-feature} or die "No feature";
31 my $factory = $arg{-factory} || $class->default_factory;
32 my $level = $arg{-level} || 0;
33 my $flip = $arg{-flip};
34
35 my $self = bless {},$class;
36 $self->{feature} = $feature;
37 $self->{factory} = $factory;
38 $self->{level} = $level;
39 $self->{flip}++ if $flip;
40 $self->{top} = 0;
41
42 my @subglyphs;
43 my @subfeatures = $self->subseq($feature);
44
45 if (@subfeatures) {
46
47 # dynamic glyph resolution
48 @subglyphs = map { $_->[0] }
49 sort { $a->[1] <=> $b->[1] }
50 map { [$_, $_->left ] }
51 $factory->make_glyph($level+1,@subfeatures);
52
53 $self->{parts} = \@subglyphs;
54 }
55
56 my ($start,$stop) = ($self->start, $self->stop);
57 if (defined $start && defined $stop) {
58 ($start,$stop) = ($stop,$start) if $start > $stop; # sheer paranoia
59 # the +1 here is critical for allowing features to meet nicely at nucleotide resolution
60 my ($left,$right) = $factory->map_pt($start,$stop+1);
61 $self->{left} = $left;
62 $self->{width} = $right - $left + 1;
63 }
64 if (@subglyphs) {
65 my $l = $subglyphs[0]->left;
66 $self->{left} = $l if !defined($self->{left}) || $l < $self->{left};
67 my $right = (
68 sort { $b<=>$a }
69 map {$_->right} @subglyphs)[0];
70 my $w = $right - $self->{left} + 1;
71 $self->{width} = $w if !defined($self->{width}) || $w > $self->{width};
72 }
73
74 $self->{point} = $arg{-point} ? $self->height : undef;
75 #Handle glyphs that don't actually fill their space, but merely mark a point.
76 #They need to have their collision bounds altered. We will (for now)
77 #hard code them to be in the center of their feature.
78 # note: this didn't actually seem to work properly, all features were aligned on
79 # their right edges. It works to do it in individual point-like glyphs such as triangle.
80 # if($self->option('point')){
81 # my ($left,$right) = $factory->map_pt($self->start,$self->stop);
82 # my $center = int(($left+$right)/2 + 0.5);
83
84 # $self->{width} = $self->height;
85 # $self->{left} = $center - ($self->{width});
86 # $self->{right} = $center + ($self->{width});
87 # }
88
89 return $self;
90 }
91
92 sub parts {
93 my $self = shift;
94 return unless $self->{parts};
95 return wantarray ? @{$self->{parts}} : $self->{parts};
96 }
97
98 sub feature { shift->{feature} }
99 sub factory { shift->{factory} }
100 sub panel { shift->factory->panel }
101 sub point { shift->{point} }
102 sub scale { shift->factory->scale }
103 sub start {
104 my $self = shift;
105 return $self->{start} if exists $self->{start};
106 $self->{start} = $self->{flip} ? $self->panel->end + 1 - $self->{feature}->end : $self->{feature}->start;
107
108 # handle the case of features whose endpoints are undef
109 # (this happens with wormbase clones where one or more clone end is not defined)
110 # in this case, we set the start to one minus the beginning of the panel
111 $self->{start} = $self->panel->offset - 1 unless defined $self->{start};
112
113 return $self->{start};
114 }
115 sub stop {
116 my $self = shift;
117 return $self->{stop} if exists $self->{stop};
118 $self->{stop} = $self->{flip} ? $self->panel->end + 1 - $self->{feature}->start : $self->{feature}->end;
119
120 # handle the case of features whose endpoints are undef
121 # (this happens with wormbase clones where one or more clone end is not defined)
122 # in this case, we set the start to one plus the end of the panel
123 $self->{stop} = $self->panel->offset + $self->panel->length + 1 unless defined $self->{stop};
124
125 return $self->{stop}
126 }
127 sub end { shift->stop }
128 sub length { my $self = shift; $self->stop - $self->start };
129 sub score {
130 my $self = shift;
131 return $self->{score} if exists $self->{score};
132 return $self->{score} = ($self->{feature}->score || 0);
133 }
134 sub strand {
135 my $self = shift;
136 return $self->{strand} if exists $self->{strand};
137 return $self->{strand} = ($self->{feature}->strand || 0);
138 }
139 sub map_pt { shift->{factory}->map_pt(@_) }
140 sub map_no_trunc { shift->{factory}->map_no_trunc(@_) }
141
142 # add a feature (or array ref of features) to the list
143 sub add_feature {
144 my $self = shift;
145 my $factory = $self->factory;
146 for my $feature (@_) {
147 if (ref $feature eq 'ARRAY') {
148 $self->add_group(@$feature);
149 } else {
150 push @{$self->{parts}},$factory->make_glyph(0,$feature);
151 }
152 }
153 }
154
155 # link a set of features together so that they bump as a group
156 sub add_group {
157 my $self = shift;
158 my @features = ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_;
159 my $f = Bio::Graphics::Feature->new(
160 -segments=>\@features,
161 -type => 'group'
162 );
163 $self->add_feature($f);
164 }
165
166 sub top {
167 my $self = shift;
168 my $g = $self->{top};
169 $self->{top} = shift if @_;
170 $g;
171 }
172 sub left {
173 my $self = shift;
174 return $self->{left} - $self->pad_left;
175 }
176 sub right {
177 my $self = shift;
178 return $self->left + $self->layout_width - 1;
179 }
180 sub bottom {
181 my $self = shift;
182 $self->top + $self->layout_height - 1;
183 }
184 sub height {
185 my $self = shift;
186 return $self->{height} if exists $self->{height};
187 my $baseheight = $self->option('height'); # what the factory says
188 return $self->{height} = $baseheight;
189 }
190 sub width {
191 my $self = shift;
192 my $g = $self->{width};
193 $self->{width} = shift if @_;
194 $g;
195 }
196 sub layout_height {
197 my $self = shift;
198 return $self->layout;
199 }
200 sub layout_width {
201 my $self = shift;
202 return $self->width + $self->pad_left + $self->pad_right;
203 }
204
205 # returns the rectangle that surrounds the physical part of the
206 # glyph, excluding labels and other "extra" stuff
207 sub calculate_boundaries {return shift->bounds(@_);}
208
209 sub bounds {
210 my $self = shift;
211 my ($dx,$dy) = @_;
212 $dx += 0; $dy += 0;
213 ($dx + $self->{left},
214 $dy + $self->top + $self->pad_top,
215 $dx + $self->{left} + $self->{width} - 1,
216 $dy + $self->bottom - $self->pad_bottom);
217 }
218
219
220 sub box {
221 my $self = shift;
222 return ($self->left,$self->top,$self->right,$self->bottom);
223 }
224
225
226 sub unfilled_box {
227 my $self = shift;
228 my $gd = shift;
229 my ($x1,$y1,$x2,$y2,$fg,$bg) = @_;
230
231 my $linewidth = $self->option('linewidth') || 1;
232
233 unless ($fg) {
234 $fg ||= $self->fgcolor;
235 $fg = $self->set_pen($linewidth,$fg) if $linewidth > 1;
236 }
237
238 unless ($bg) {
239 $bg ||= $self->bgcolor;
240 $bg = $self->set_pen($linewidth,$bg) if $linewidth > 1;
241 }
242
243 # draw a box
244 $gd->rectangle($x1,$y1,$x2,$y2,$fg);
245
246 # if the left end is off the end, then cover over
247 # the leftmost line
248 my ($width) = $gd->getBounds;
249
250 $gd->line($x1,$y1+$linewidth,$x1,$y2-$linewidth,$bg)
251 if $x1 < $self->panel->pad_left;
252
253 $gd->line($x2,$y1+$linewidth,$x2,$y2-$linewidth,$bg)
254 if $x2 > $width - $self->panel->pad_right;
255 }
256
257
258 # return boxes surrounding each part
259 sub boxes {
260 my $self = shift;
261 my ($left,$top) = @_;
262 $top += 0; $left += 0;
263 my @result;
264
265 $self->layout;
266 my @parts = $self->parts;
267 @parts = $self if !@parts && $self->option('box_subparts') && $self->level>0;
268
269 for my $part ($self->parts) {
270 if (eval{$part->feature->primary_tag} eq 'group' or
271 ($part->level == 0 && $self->option('box_subparts'))) {
272 push @result,$part->boxes($left+$self->left+$self->pad_left,$top+$self->top+$self->pad_top);
273 } else {
274 my ($x1,$y1,$x2,$y2) = $part->box;
275 push @result,[$part->feature,$x1,$top+$self->top+$self->pad_top+$y1,
276 $x2,$top+$self->top+$self->pad_top+$y2];
277 }
278 }
279 return wantarray ? @result : \@result;
280 }
281
282 # this should be overridden for labels, etc.
283 # allows glyph to make itself thicker or thinner depending on
284 # domain-specific knowledge
285 sub pad_top {
286 my $self = shift;
287 return 0;
288 }
289 sub pad_bottom {
290 my $self = shift;
291 return 0;
292 }
293 sub pad_left {
294 my $self = shift;
295 return 0;
296 }
297 sub pad_right {
298 my $self = shift;
299 # this shouldn't be necessary
300 my @parts = $self->parts or return 0;
301 my $max = 0;
302 foreach (@parts) {
303 my $pr = $_->pad_right;
304 $max = $pr if $max < $pr;
305 }
306 $max;
307 }
308
309 # move relative to parent
310 sub move {
311 my $self = shift;
312 my ($dx,$dy) = @_;
313 $self->{left} += $dx;
314 $self->{top} += $dy;
315
316 # because the feature parts use *absolute* not relative addressing
317 # we need to move each of the parts horizontally, but not vertically
318 $_->move($dx,0) foreach $self->parts;
319 }
320
321 # get an option
322 sub option {
323 my $self = shift;
324 my $option_name = shift;
325 my $factory = $self->factory;
326 return unless $factory;
327 $factory->option($self,$option_name,@{$self}{qw(partno total_parts)});
328 }
329
330 # set an option globally
331 sub configure {
332 my $self = shift;
333 my $factory = $self->factory;
334 my $option_map = $factory->option_map;
335 while (@_) {
336 my $option_name = shift;
337 my $option_value = shift;
338 ($option_name = lc $option_name) =~ s/^-//;
339 $option_map->{$option_name} = $option_value;
340 }
341 }
342
343 # some common options
344 sub color {
345 my $self = shift;
346 my $color = shift;
347 my $index = $self->option($color);
348 # turn into a color index
349 return $self->factory->translate_color($index) if defined $index;
350 return 0;
351 }
352
353 sub connector {
354 return shift->option('connector',@_);
355 }
356
357 # return value:
358 # 0 no bumping
359 # +1 bump down
360 # -1 bump up
361 sub bump {
362 my $self = shift;
363 return $self->option('bump');
364 }
365
366 # we also look for the "color" option for Ace::Graphics compatibility
367 sub fgcolor {
368 my $self = shift;
369 my $color = $self->option('fgcolor');
370 my $index = defined $color ? $color : $self->option('color');
371 $index = 'black' unless defined $index;
372 $self->factory->translate_color($index);
373 }
374
375 #add for compatibility
376 sub fillcolor {
377 my $self = shift;
378 return $self->bgcolor;
379 }
380
381 # we also look for the "background-color" option for Ace::Graphics compatibility
382 sub bgcolor {
383 my $self = shift;
384 my $bgcolor = $self->option('bgcolor');
385 my $index = defined $bgcolor ? $bgcolor : $self->option('fillcolor');
386 $index = 'white' unless defined $index;
387 $self->factory->translate_color($index);
388 }
389 sub font {
390 my $self = shift;
391 my $font = $self->option('font');
392 unless (UNIVERSAL::isa($font,'GD::Font')) {
393 my $ref = {
394 gdTinyFont => gdTinyFont,
395 gdSmallFont => gdSmallFont,
396 gdMediumBoldFont => gdMediumBoldFont,
397 gdLargeFont => gdLargeFont,
398 gdGiantFont => gdGiantFont};
399 my $gdfont = $ref->{$font} || $font;
400 $self->configure(font=>$gdfont);
401 return $gdfont;
402 }
403 return $font;
404 }
405 sub fontcolor {
406 my $self = shift;
407 my $fontcolor = $self->color('fontcolor');
408 return defined $fontcolor ? $fontcolor : $self->fgcolor;
409 }
410 sub font2color {
411 my $self = shift;
412 my $font2color = $self->color('font2color');
413 return defined $font2color ? $font2color : $self->fgcolor;
414 }
415 sub tkcolor { # "track color"
416 my $self = shift;
417 $self->option('tkcolor') or return;
418 return $self->color('tkcolor')
419 }
420 sub connector_color {
421 my $self = shift;
422 $self->color('connector_color') || $self->fgcolor;
423 }
424
425 sub layout_sort {
426
427 my $self = shift;
428 my $sortfunc;
429
430 my $opt = $self->option("sort_order");
431 if (!$opt) {
432 $sortfunc = eval 'sub { $a->left <=> $b->left }';
433 } elsif (ref $opt eq 'CODE') {
434 $sortfunc = $opt;
435 } elsif ($opt =~ /^sub\s+\{/o) {
436 $sortfunc = eval $opt;
437 } else {
438 # build $sortfunc for ourselves:
439 my @sortbys = split(/\s*\|\s*/o, $opt);
440 $sortfunc = 'sub { ';
441 my $sawleft = 0;
442
443 # not sure I can make this schwartzian transfored
444 for my $sortby (@sortbys) {
445 if ($sortby eq "left" || $sortby eq "default") {
446 $sortfunc .= '($a->left <=> $b->left) || ';
447 $sawleft++;
448 } elsif ($sortby eq "right") {
449 $sortfunc .= '($a->right <=> $b->right) || ';
450 } elsif ($sortby eq "low_score") {
451 $sortfunc .= '($a->score <=> $b->score) || ';
452 } elsif ($sortby eq "high_score") {
453 $sortfunc .= '($b->score <=> $a->score) || ';
454 } elsif ($sortby eq "longest") {
455 $sortfunc .= '(($b->length) <=> ($a->length)) || ';
456 } elsif ($sortby eq "shortest") {
457 $sortfunc .= '(($a->length) <=> ($b->length)) || ';
458 } elsif ($sortby eq "strand") {
459 $sortfunc .= '($b->strand <=> $a->strand) || ';
460 } elsif ($sortby eq "name") {
461 $sortfunc .= '($a->feature->display_name cmp $b->feature->display_name) || ';
462 }
463 }
464 unless ($sawleft) {
465 $sortfunc .= ' ($a->left <=> $b->left) ';
466 } else {
467 $sortfunc .= ' 0';
468 }
469 $sortfunc .= '}';
470 $sortfunc = eval $sortfunc;
471 }
472
473 # cache this
474 # $self->factory->set_option(sort_order => $sortfunc);
475
476 return sort $sortfunc @_;
477 }
478
479 # handle collision detection
480 sub layout {
481 my $self = shift;
482 return $self->{layout_height} if exists $self->{layout_height};
483
484 my @parts = $self->parts;
485 return $self->{layout_height}
486 = $self->height + $self->pad_top + $self->pad_bottom unless @parts;
487
488 my $bump_direction = $self->bump;
489 my $bump_limit = $self->option('bump_limit') || -1;
490
491 $_->layout foreach @parts; # recursively lay out
492
493 # no bumping requested, or only one part here
494 if (@parts == 1 || !$bump_direction) {
495 my $highest = 0;
496 foreach (@parts) {
497 my $height = $_->layout_height;
498 $highest = $height > $highest ? $height : $highest;
499 }
500 return $self->{layout_height} = $highest + $self->pad_top + $self->pad_bottom;
501 }
502
503 my (%bin1,%bin2);
504 for my $g ($self->layout_sort(@parts)) {
505
506 my $pos = 0;
507 my $bumplevel = 0;
508 my $left = $g->left;
509 my $right = $g->right;
510 my $height = $g->{layout_height};
511
512 while (1) {
513
514 # stop bumping if we've gone too far down
515 if ($bump_limit > 0 && $bumplevel++ >= $bump_limit) {
516 $g->{overbumped}++; # this flag can be used to suppress label and description
517 foreach ($g->parts) {
518 $_->{overbumped}++;
519 }
520 last;
521 }
522
523 # look for collisions
524 my $bottom = $pos + $height;
525 $self->collides(\%bin1,CM1,CM2,$left,$pos,$right,$bottom) or last;
526 my $collision = $self->collides(\%bin2,CM3,CM4,$left,$pos,$right,$bottom) or last;
527
528 if ($bump_direction > 0) {
529 $pos += $collision->[3]-$collision->[1] + BUMP_SPACING; # collision, so bump
530
531 } else {
532 $pos -= BUMP_SPACING;
533 }
534
535 }
536
537 $g->move(0,$pos);
538 $self->add_collision(\%bin1,CM1,CM2,$left,$g->top,$right,$g->bottom);
539 $self->add_collision(\%bin2,CM3,CM4,$left,$g->top,$right,$g->bottom);
540 }
541
542 # If -1 bumping was allowed, then normalize so that the top glyph is at zero
543 if ($bump_direction < 0) {
544 my $topmost;
545 foreach (@parts) {
546 my $top = $_->top;
547 $topmost = $top if !defined($topmost) or $top < $topmost;
548 }
549 my $offset = - $topmost;
550 $_->move(0,$offset) foreach @parts;
551 }
552
553 # find new height
554 my $bottom = 0;
555 foreach (@parts) {
556 $bottom = $_->bottom if $_->bottom > $bottom;
557 }
558 return $self->{layout_height} = $self->pad_bottom + $self->pad_top + $bottom - $self->top + 1;
559 }
560
561 # the $%occupied structure is a hash of {left,top} = [left,top,right,bottom]
562 sub collides {
563 my $self = shift;
564 my ($occupied,$cm1,$cm2,$left,$top,$right,$bottom) = @_;
565 my @keys = $self->_collision_keys($cm1,$cm2,$left,$top,$right,$bottom);
566 my $collides = 0;
567 for my $k (@keys) {
568 next unless exists $occupied->{$k};
569 for my $bounds (@{$occupied->{$k}}) {
570 my ($l,$t,$r,$b) = @$bounds;
571 next unless $right >= $l and $left <= $r and $bottom >= $t and $top <= $b;
572 $collides = $bounds;
573 last;
574 }
575 }
576 $collides;
577 }
578
579 sub add_collision {
580 my $self = shift;
581 my ($occupied,$cm1,$cm2,$left,$top,$right,$bottom) = @_;
582 my $value = [$left,$top,$right+2,$bottom];
583 my @keys = $self->_collision_keys($cm1,$cm2,@$value);
584 push @{$occupied->{$_}},$value foreach @keys;
585 }
586
587 sub _collision_keys {
588 my $self = shift;
589 my ($binx,$biny,$left,$top,$right,$bottom) = @_;
590 my @keys;
591 my $bin_left = int($left/$binx);
592 my $bin_right = int($right/$binx);
593 my $bin_top = int($top/$biny);
594 my $bin_bottom = int($bottom/$biny);
595 for (my $x=$bin_left;$x<=$bin_right; $x++) {
596 for (my $y=$bin_top;$y<=$bin_bottom; $y++) {
597 push @keys,join(',',$x,$y);
598 }
599 }
600 @keys;
601 }
602
603 sub draw {
604 my $self = shift;
605 my $gd = shift;
606 my ($left,$top,$partno,$total_parts) = @_;
607
608 local($self->{partno},$self->{total_parts});
609 @{$self}{qw(partno total_parts)} = ($partno,$total_parts);
610
611 my $connector = $self->connector;
612 if (my @parts = $self->parts) {
613
614 # invoke sorter if use wants to sort always and we haven't already sorted
615 # during bumping.
616 @parts = $self->layout_sort(@parts) if !$self->bump && $self->option('always_sort');
617
618 my $x = $left;
619 my $y = $top + $self->top + $self->pad_top;
620 $self->draw_connectors($gd,$x,$y) if $connector && $connector ne 'none';
621
622 my $last_x;
623 for (my $i=0; $i<@parts; $i++) {
624 # lie just a little bit to avoid lines overlapping and
625 # make the picture prettier
626 my $fake_x = $x;
627 $fake_x-- if defined $last_x && $parts[$i]->left - $last_x == 1;
628 $parts[$i]->draw($gd,$fake_x,$y,$i,scalar(@parts));
629 $last_x = $parts[$i]->right;
630 }
631 }
632
633 else { # no part
634 $self->draw_connectors($gd,$left,$top)
635 if $connector && $connector ne 'none' && $self->{level} == 0;
636 $self->draw_component($gd,$left,$top);
637 }
638 }
639
640 # the "level" is the level of testing of the glyph
641 # groups are level -1, top level glyphs are level 0, subcomponents are level 1 and so forth.
642 sub level {
643 shift->{level};
644 }
645
646 sub draw_connectors {
647 my $self = shift;
648 return if $self->{overbumped};
649 my $gd = shift;
650 my ($dx,$dy) = @_;
651 my @parts = sort { $a->left <=> $b->left } $self->parts;
652 for (my $i = 0; $i < @parts-1; $i++) {
653 $self->_connector($gd,$dx,$dy,$parts[$i]->bounds,$parts[$i+1]->bounds);
654 }
655
656 # extra connectors going off ends
657 if (@parts) {
658 my($x1,$y1,$x2,$y2) = $self->bounds(0,0);
659 my($xl,$xt,$xr,$xb) = $parts[0]->bounds;
660 $self->_connector($gd,$dx,$dy,$x1,$xt,$x1,$xb,$xl,$xt,$xr,$xb) if $x1 < $xl;
661 my ($xl2,$xt2,$xr2,$xb2) = $parts[-1]->bounds;
662 $self->_connector($gd,$dx,$dy,$parts[-1]->bounds,$x2,$xt2,$x2,$xb2) if $x2 > $xr;
663 }
664
665 }
666
667 sub _connector {
668 my $self = shift;
669 my ($gd,
670 $dx,$dy,
671 $xl,$xt,$xr,$xb,
672 $yl,$yt,$yr,$yb) = @_;
673 my $left = $dx + $xr;
674 my $right = $dx + $yl;
675 my $top1 = $dy + $xt;
676 my $bottom1 = $dy + $xb;
677 my $top2 = $dy + $yt;
678 my $bottom2 = $dy + $yb;
679 # restore this comment if you don't like the group dash working
680 # its way backwards.
681 return if $right-$left < 1 && !$self->isa('Bio::Graphics::Glyph::group');
682
683 $self->draw_connector($gd,
684 $top1,$bottom1,$left,
685 $top2,$bottom2,$right,
686 );
687 }
688
689 sub draw_connector {
690 my $self = shift;
691 my $gd = shift;
692
693 my $color = $self->connector_color;
694 my $connector_type = $self->connector or return;
695
696 if ($connector_type eq 'hat') {
697 $self->draw_hat_connector($gd,$color,@_);
698 } elsif ($connector_type eq 'solid') {
699 $self->draw_solid_connector($gd,$color,@_);
700 } elsif ($connector_type eq 'dashed') {
701 $self->draw_dashed_connector($gd,$color,@_);
702 } elsif ($connector_type eq 'quill') {
703 $self->draw_quill_connector($gd,$color,@_);
704 } else {
705 ; # draw nothing
706 }
707 }
708
709 sub draw_hat_connector {
710 my $self = shift;
711 my $gd = shift;
712 my $color = shift;
713 my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
714
715 my $center1 = ($top1 + $bottom1)/2;
716 my $quarter1 = $top1 + ($bottom1-$top1)/4;
717 my $center2 = ($top2 + $bottom2)/2;
718 my $quarter2 = $top2 + ($bottom2-$top2)/4;
719
720 if ($center1 != $center2) {
721 $self->draw_solid_connector($gd,$color,@_);
722 return;
723 }
724
725 if ($right - $left > 4) { # room for the inverted "V"
726 my $middle = $left + int(($right - $left)/2);
727 $gd->line($left,$center1,$middle,$top1,$color);
728 $gd->line($middle,$top1,$right-1,$center1,$color);
729 } elsif ($right-$left > 1) { # no room, just connect
730 $gd->line($left,$quarter1,$right-1,$quarter1,$color);
731 }
732
733 }
734
735 sub draw_solid_connector {
736 my $self = shift;
737 my $gd = shift;
738 my $color = shift;
739 my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
740
741 my $center1 = ($top1 + $bottom1)/2;
742 my $center2 = ($top2 + $bottom2)/2;
743
744 $gd->line($left,$center1,$right,$center2,$color);
745 }
746
747 sub draw_dashed_connector {
748 my $self = shift;
749 my $gd = shift;
750 my $color = shift;
751 my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
752
753 my $center1 = ($top1 + $bottom1)/2;
754 my $center2 = ($top2 + $bottom2)/2;
755
756 $gd->setStyle($color,$color,gdTransparent,gdTransparent,);
757 $gd->line($left,$center1,$right,$center2,gdStyled);
758 }
759
760 sub draw_quill_connector {
761 my $self = shift;
762 my $gd = shift;
763 my $color = shift;
764 my ($top1,$bottom1,$left,$top2,$bottom2,$right) = @_;
765
766 my $center1 = ($top1 + $bottom1)/2;
767 my $center2 = ($top2 + $bottom2)/2;
768
769 $gd->line($left,$center1,$right,$center2,$color);
770 my $direction = $self->feature->strand;
771 return unless $direction;
772
773 if ($direction > 0) {
774 my $start = $left+4;
775 my $end = $right-1;
776 for (my $position=$start; $position <= $end; $position += QUILL_INTERVAL) {
777 $gd->line($position,$center1,$position-2,$center1-2,$color);
778 $gd->line($position,$center1,$position-2,$center1+2,$color);
779 }
780 } else {
781 my $start = $left+1;
782 my $end = $right-4;
783 for (my $position=$start; $position <= $end; $position += QUILL_INTERVAL) {
784 $gd->line($position,$center1,$position+2,$center1-2,$color);
785 $gd->line($position,$center1,$position+2,$center1+2,$color);
786 }
787 }
788 }
789
790 sub filled_box {
791 my $self = shift;
792 my $gd = shift;
793 my ($x1,$y1,$x2,$y2,$bg,$fg) = @_;
794
795 $bg ||= $self->bgcolor;
796 $fg ||= $self->fgcolor;
797 my $linewidth = $self->option('linewidth') || 1;
798
799 $gd->filledRectangle($x1,$y1,$x2,$y2,$bg);
800
801 $fg = $self->set_pen($linewidth,$fg) if $linewidth > 1;
802
803 # draw a box
804 $gd->rectangle($x1,$y1,$x2,$y2,$fg);
805
806 # if the left end is off the end, then cover over
807 # the leftmost line
808 my ($width) = $gd->getBounds;
809
810 $bg = $self->set_pen($linewidth,$bg) if $linewidth > 1;
811
812 $gd->line($x1,$y1+$linewidth,$x1,$y2-$linewidth,$bg)
813 if $x1 < $self->panel->pad_left;
814
815 $gd->line($x2,$y1+$linewidth,$x2,$y2-$linewidth,$bg)
816 if $x2 > $width - $self->panel->pad_right;
817 }
818
819 sub filled_oval {
820 my $self = shift;
821 my $gd = shift;
822 my ($x1,$y1,$x2,$y2,$bg,$fg) = @_;
823 my $cx = ($x1+$x2)/2;
824 my $cy = ($y1+$y2)/2;
825
826 $fg ||= $self->fgcolor;
827 $bg ||= $self->bgcolor;
828 my $linewidth = $self->linewidth;
829
830 $fg = $self->set_pen($linewidth) if $linewidth > 1;
831 $gd->arc($cx,$cy,$x2-$x1,$y2-$y1,0,360,$fg);
832
833 # and fill it
834 $gd->fill($cx,$cy,$bg);
835 }
836
837 sub oval {
838 my $self = shift;
839 my $gd = shift;
840 my ($x1,$y1,$x2,$y2) = @_;
841 my $cx = ($x1+$x2)/2;
842 my $cy = ($y1+$y2)/2;
843
844 my $fg = $self->fgcolor;
845 my $linewidth = $self->linewidth;
846
847 $fg = $self->set_pen($linewidth) if $linewidth > 1;
848 $gd->arc($cx,$cy,$x2-$x1,$y2-$y1,0,360,$fg);
849 }
850
851 sub filled_arrow {
852 my $self = shift;
853 my $gd = shift;
854 my $orientation = shift;
855 $orientation *= -1 if $self->{flip};
856
857 my ($x1,$y1,$x2,$y2) = @_;
858
859 my ($width) = $gd->getBounds;
860 my $indent = $y2-$y1 < $x2-$x1 ? $y2-$y1 : ($x2-$x1)/2;
861
862 return $self->filled_box($gd,@_)
863 if ($orientation == 0)
864 or ($x1 < 0 && $orientation < 0)
865 or ($x2 > $width && $orientation > 0)
866 or ($indent <= 0)
867 or ($x2 - $x1 < 3);
868
869 my $fg = $self->fgcolor;
870 if ($orientation >= 0) {
871 $gd->line($x1,$y1,$x2-$indent,$y1,$fg);
872 $gd->line($x2-$indent,$y1,$x2,($y2+$y1)/2,$fg);
873 $gd->line($x2,($y2+$y1)/2,$x2-$indent,$y2,$fg);
874 $gd->line($x2-$indent,$y2,$x1,$y2,$fg);
875 $gd->line($x1,$y2,$x1,$y1,$fg);
876 my $left = $self->panel->left > $x1 ? $self->panel->left : $x1;
877 $gd->fillToBorder($left+1,($y1+$y2)/2,$fg,$self->bgcolor);
878 } else {
879 $gd->line($x1,($y2+$y1)/2,$x1+$indent,$y1,$fg);
880 $gd->line($x1+$indent,$y1,$x2,$y1,$fg);
881 $gd->line($x2,$y2,$x1+$indent,$y2,$fg);
882 $gd->line($x1+$indent,$y2,$x1,($y1+$y2)/2,$fg);
883 $gd->line($x2,$y1,$x2,$y2,$fg);
884 my $right = $self->panel->right < $x2 ? $self->panel->right : $x2;
885 $gd->fillToBorder($right-1,($y1+$y2)/2,$fg,$self->bgcolor);
886 }
887 }
888
889 sub linewidth {
890 shift->option('linewidth') || 1;
891 }
892
893 sub fill {
894 my $self = shift;
895 my $gd = shift;
896 my ($x1,$y1,$x2,$y2) = @_;
897 if ( ($x2-$x1) >= 2 && ($y2-$y1) >= 2 ) {
898 $gd->fill($x1+1,$y1+1,$self->bgcolor);
899 }
900 }
901 sub set_pen {
902 my $self = shift;
903 my ($linewidth,$color) = @_;
904 $linewidth ||= $self->linewidth;
905 $color ||= $self->fgcolor;
906 return $color unless $linewidth > 1;
907 $self->panel->set_pen($linewidth,$color);
908 }
909
910 sub draw_component {
911 my $self = shift;
912 my $gd = shift;
913 my($x1,$y1,$x2,$y2) = $self->bounds(@_);
914
915 # clipping
916 my $panel = $self->panel;
917 return unless $x2 >= $panel->left and $x1 <= $panel->right;
918
919 if ($self->option('strand_arrow') || $self->option('stranded')) {
920 $self->filled_arrow($gd,$self->feature->strand,
921 $x1, $y1,
922 $x2, $y2)
923 } else {
924 $self->filled_box($gd,
925 $x1, $y1,
926 $x2, $y2)
927 }
928 }
929
930 # memoize _subseq -- it's a bottleneck with segments
931 sub subseq {
932 my $self = shift;
933 my $feature = shift;
934 return $self->_subseq($feature) unless ref $self;
935 return @{$self->{cached_subseq}{$feature}} if $self->{cached_subseq}{$feature};
936 my @ss = $self->_subseq($feature);
937 $self->{cached_subseq}{$feature} = \@ss;
938 @ss;
939 }
940
941 sub _subseq {
942 my $class = shift;
943 my $feature = shift;
944 return $feature->merged_segments if $feature->can('merged_segments');
945 return $feature->segments if $feature->can('segments');
946 my @split = eval { my $id = $feature->location->seq_id;
947 my @subs = $feature->location->sub_Location;
948 grep {$id eq $_->seq_id} @subs};
949 return @split if @split;
950 return $feature->sub_SeqFeature if $feature->can('sub_SeqFeature');
951 return;
952 }
953
954 # synthesize a key glyph
955 sub keyglyph {
956 my $self = shift;
957 my $feature = $self->make_key_feature;
958 my $factory = $self->factory->clone;
959 $factory->set_option(label => 1);
960 $factory->set_option(description => 0);
961 $factory->set_option(bump => 0);
962 $factory->set_option(connector => 'solid');
963 return $factory->make_glyph(0,$feature);
964 }
965
966 # synthesize a key glyph
967 sub make_key_feature {
968 my $self = shift;
969
970 my $scale = 1/$self->scale; # base pairs/pixel
971
972 # one segments, at pixels 0->80
973 my $offset = $self->panel->offset;
974
975
976 my $feature =
977 Bio::Graphics::Feature->new(-start =>0 * $scale +$offset,
978 -end =>80*$scale+$offset,
979 -name => $self->option('key'),
980 -strand => '+1');
981 return $feature;
982 }
983
984 sub all_callbacks {
985 my $self = shift;
986 my $track_level = $self->option('all_callbacks');
987 return $track_level if defined $track_level;
988 return $self->panel->all_callbacks;
989 }
990
991 sub default_factory {
992 croak "no default factory implemented";
993 }
994
995 1;
996
997 __END__
998
999 =head1 NAME
1000
1001 Bio::Graphics::Glyph - Base class for Bio::Graphics::Glyph objects
1002
1003 =head1 SYNOPSIS
1004
1005 See L<Bio::Graphics::Panel>.
1006
1007 =head1 DESCRIPTION
1008
1009 Bio::Graphics::Glyph is the base class for all glyph objects. Each
1010 glyph is a wrapper around an Bio:SeqFeatureI object, knows how to
1011 render itself on an Bio::Graphics::Panel, and has a variety of
1012 configuration variables.
1013
1014 End developers will not ordinarily work directly with
1015 Bio::Graphics::Glyph objects, but with Bio::Graphics::Glyph::generic
1016 and its subclasses. Similarly, most glyph developers will want to
1017 subclass from Bio::Graphics::Glyph::generic because the latter
1018 provides labeling and arrow-drawing facilities.
1019
1020 =head1 METHODS
1021
1022 This section describes the class and object methods for
1023 Bio::Graphics::Glyph.
1024
1025 =head2 CONSTRUCTORS
1026
1027 Bio::Graphics::Glyph objects are constructed automatically by an
1028 Bio::Graphics::Glyph::Factory, and are not usually created by
1029 end-developer code.
1030
1031 =over 4
1032
1033 =item $glyph = Bio::Graphics::Glyph-E<gt>new(-feature=E<gt>$feature,-factory=E<gt>$factory)
1034
1035 Given a sequence feature, creates an Bio::Graphics::Glyph object to
1036 display it. The B<-feature> argument points to the Bio:SeqFeatureI
1037 object to display, and B<-factory> indicates an
1038 Bio::Graphics::Glyph::Factory object from which the glyph will fetch
1039 all its run-time configuration information. Factories are created and
1040 manipulated by the Bio::Graphics::Panel object.
1041
1042 A standard set of options are recognized. See L<OPTIONS>.
1043
1044 =back
1045
1046 =head2 OBJECT METHODS
1047
1048 Once a glyph is created, it responds to a large number of methods. In
1049 this section, these methods are grouped into related categories.
1050
1051 Retrieving glyph context:
1052
1053 =over 4
1054
1055 =item $factory = $glyph-E<gt>factory
1056
1057 Get the Bio::Graphics::Glyph::Factory associated with this object.
1058 This cannot be changed once it is set.
1059
1060 =item $panel = $glyph-E<gt>panel
1061
1062 Get the Bio::Graphics::Panel associated with this object. This cannot
1063 be changed once it is set.
1064
1065 =item $feature = $glyph-E<gt>feature
1066
1067 Get the sequence feature associated with this object. This cannot be
1068 changed once it is set.
1069
1070 =item $feature = $glyph-E<gt>add_feature(@features)
1071
1072 Add the list of features to the glyph, creating subparts. This is
1073 most common done with the track glyph returned by
1074 Ace::Graphics::Panel-E<gt>add_track().
1075
1076 =item $feature = $glyph-E<gt>add_group(@features)
1077
1078 This is similar to add_feature(), but the list of features is treated
1079 as a group and can be configured as a set.
1080
1081 =back
1082
1083 Retrieving glyph options:
1084
1085 =over 4
1086
1087 =item $fgcolor = $glyph-E<gt>fgcolor
1088
1089 =item $bgcolor = $glyph-E<gt>bgcolor
1090
1091 =item $fontcolor = $glyph-E<gt>fontcolor
1092
1093 =item $fontcolor = $glyph-E<gt>font2color
1094
1095 =item $fillcolor = $glyph-E<gt>fillcolor
1096
1097 These methods return the configured foreground, background, font,
1098 alternative font, and fill colors for the glyph in the form of a
1099 GD::Image color index.
1100
1101 =item $color = $glyph-E<gt>tkcolor
1102
1103 This method returns a color to be used to flood-fill the entire glyph
1104 before drawing (currently used by the "track" glyph).
1105
1106 =item $width = $glyph-E<gt>width([$newwidth])
1107
1108 Return the width of the glyph, not including left or right padding.
1109 This is ordinarily set internally based on the size of the feature and
1110 the scale of the panel.
1111
1112 =item $width = $glyph-E<gt>layout_width
1113
1114 Returns the width of the glyph including left and right padding.
1115
1116 =item $width = $glyph-E<gt>height
1117
1118 Returns the height of the glyph, not including the top or bottom
1119 padding. This is calculated from the "height" option and cannot be
1120 changed.
1121
1122
1123 =item $font = $glyph-E<gt>font
1124
1125 Return the font for the glyph.
1126
1127 =item $option = $glyph-E<gt>option($option)
1128
1129 Return the value of the indicated option.
1130
1131 =item $index = $glyph-E<gt>color($color)
1132
1133 Given a symbolic or #RRGGBB-form color name, returns its GD index.
1134
1135 =item $level = $glyph-E<gt>level
1136
1137 The "level" is the nesting level of the glyph.
1138 Groups are level -1, top level glyphs are level 0,
1139 subparts (e.g. exons) are level 1 and so forth.
1140
1141 =back
1142
1143 Setting an option:
1144
1145 =over 4
1146
1147 =item $glyph-E<gt>configure(-name=E<gt>$value)
1148
1149 You may change a glyph option after it is created using set_option().
1150 This is most commonly used to configure track glyphs.
1151
1152 =back
1153
1154 Retrieving information about the sequence:
1155
1156 =over 4
1157
1158 =item $start = $glyph-E<gt>start
1159
1160 =item $end = $glyph-E<gt>end
1161
1162 These methods return the start and end of the glyph in base pair
1163 units.
1164
1165 =item $offset = $glyph-E<gt>offset
1166
1167 Returns the offset of the segment (the base pair at the far left of
1168 the image).
1169
1170 =item $length = $glyph-E<gt>length
1171
1172 Returns the length of the sequence segment.
1173
1174 =back
1175
1176
1177 Retrieving formatting information:
1178
1179 =over 4
1180
1181 =item $top = $glyph-E<gt>top
1182
1183 =item $left = $glyph-E<gt>left
1184
1185 =item $bottom = $glyph-E<gt>bottom
1186
1187 =item $right = $glyph-E<gt>right
1188
1189 These methods return the top, left, bottom and right of the glyph in
1190 pixel coordinates.
1191
1192 =item $height = $glyph-E<gt>height
1193
1194 Returns the height of the glyph. This may be somewhat larger or
1195 smaller than the height suggested by the GlyphFactory, depending on
1196 the type of the glyph.
1197
1198 =item $scale = $glyph-E<gt>scale
1199
1200 Get the scale for the glyph in pixels/bp.
1201
1202 =item $height = $glyph-E<gt>labelheight
1203
1204 Return the height of the label, if any.
1205
1206 =item $label = $glyph-E<gt>label
1207
1208 Return a human-readable label for the glyph.
1209
1210 =back
1211
1212 These methods are called by Bio::Graphics::Track during the layout
1213 process:
1214
1215 =over 4
1216
1217 =item $glyph-E<gt>move($dx,$dy)
1218
1219 Move the glyph in pixel coordinates by the indicated delta-x and
1220 delta-y values.
1221
1222 =item ($x1,$y1,$x2,$y2) = $glyph-E<gt>box
1223
1224 Return the current position of the glyph.
1225
1226 =back
1227
1228 These methods are intended to be overridden in subclasses:
1229
1230 =over 4
1231
1232 =item $glyph-E<gt>calculate_height
1233
1234 Calculate the height of the glyph.
1235
1236 =item $glyph-E<gt>calculate_left
1237
1238 Calculate the left side of the glyph.
1239
1240 =item $glyph-E<gt>calculate_right
1241
1242 Calculate the right side of the glyph.
1243
1244 =item $glyph-E<gt>draw($gd,$left,$top)
1245
1246 Optionally offset the glyph by the indicated amount and draw it onto
1247 the GD::Image object.
1248
1249
1250 =item $glyph-E<gt>draw_label($gd,$left,$top)
1251
1252 Draw the label for the glyph onto the provided GD::Image object,
1253 optionally offsetting by the amounts indicated in $left and $right.
1254
1255 =back
1256
1257 These methods are useful utility routines:
1258
1259 =over 4
1260
1261 =item $pixels = $glyph-E<gt>map_pt($bases);
1262
1263 Map the indicated base position, given in base pair units, into
1264 pixels, using the current scale and glyph position.
1265
1266 =item $glyph-E<gt>filled_box($gd,$x1,$y1,$x2,$y2)
1267
1268 Draw a filled rectangle with the appropriate foreground and fill
1269 colors, and pen width onto the GD::Image object given by $gd, using
1270 the provided rectangle coordinates.
1271
1272 =item $glyph-E<gt>filled_oval($gd,$x1,$y1,$x2,$y2)
1273
1274 As above, but draws an oval inscribed on the rectangle.
1275
1276 =back
1277
1278 =head2 OPTIONS
1279
1280 The following options are standard among all Glyphs. See individual
1281 glyph pages for more options.
1282
1283 Option Description Default
1284 ------ ----------- -------
1285
1286 -fgcolor Foreground color black
1287
1288 -outlinecolor Synonym for -fgcolor
1289
1290 -bgcolor Background color turquoise
1291
1292 -fillcolor Synonym for -bgcolor
1293
1294 -linewidth Line width 1
1295
1296 -height Height of glyph 10
1297
1298 -font Glyph font gdSmallFont
1299
1300 -connector Connector type undef (false)
1301
1302 -connector_color
1303 Connector color black
1304
1305 -strand_arrow Whether to indicate undef (false)
1306 strandedness
1307
1308 -label Whether to draw a label undef (false)
1309
1310 -description Whether to draw a description undef (false)
1311
1312 -sort_order Specify layout sort order "default"
1313
1314 -always_sort Sort even when bumping is off undef (false)
1315
1316 -bump_limit Maximum number of levels to bump undef (unlimited)
1317
1318 For glyphs that consist of multiple segments, the B<-connector> option
1319 controls what's drawn between the segments. The default is undef (no
1320 connector). Options include:
1321
1322 "hat" an upward-angling conector
1323 "solid" a straight horizontal connector
1324 "quill" a decorated line with small arrows indicating strandedness
1325 (like the UCSC Genome Browser uses)
1326 "dashed" a horizontal dashed line.
1327
1328 The B<-connector_color> option controls the color of the connector, if
1329 any.
1330
1331 The label is printed above the glyph. You may pass an anonymous
1332 subroutine to B<-label>, in which case the subroutine will be invoked
1333 with the feature as its single argument. and is expected to return
1334 the string to use as the description. If you provide the numeric
1335 value "1" to B<-description>, the description will be read off the
1336 feature's seqname(), info() and primary_tag() methods will be called
1337 until a suitable name is found. To create a label with the
1338 text "1", pass the string "1 ". (A 1 followed by a space).
1339
1340 The description is printed below the glyph. You may pass an anonymous
1341 subroutine to B<-description>, in which case the subroutine will be
1342 invoked with the feature as its single argument and is expected to
1343 return the string to use as the description. If you provide the
1344 numeric value "1" to B<-description>, the description will be read off
1345 the feature's source_tag() method. To create a description with the
1346 text "1", pass the string "1 ". (A 1 followed by a space).
1347
1348 In the case of ACEDB Ace::Sequence feature objects, the feature's
1349 info(), Brief_identification() and Locus() methods will be called to
1350 create a suitable description.
1351
1352 The B<-strand_arrow> option, if true, requests that the glyph indicate
1353 which strand it is on, usually by drawing an arrowhead. Not all
1354 glyphs will respond to this request. For historical reasons,
1355 B<-stranded> is a synonym for this option.
1356
1357 By default, features are drawn with a layout based only on the
1358 position of the feature, assuring a maximal "packing" of the glyphs
1359 when bumped. In some cases, however, it makes sense to display the
1360 glyphs sorted by score or some other comparison, e.g. such that more
1361 "important" features are nearer the top of the display, stacked above
1362 less important features. The -sort_order option allows a few
1363 different built-in values for changing the default sort order (which
1364 is by "left" position): "low_score" (or "high_score") will cause
1365 features to be sorted from lowest to highest score (or vice versa).
1366 "left" (or "default") and "right" values will cause features to be
1367 sorted by their position in the sequence. "longer" (or "shorter")
1368 will cause the longest (or shortest) features to be sorted first, and
1369 "strand" will cause the features to be sorted by strand: "+1"
1370 (forward) then "0" (unknown, or NA) then "-1" (reverse). Lastly,
1371 "name" will sort features alphabetically by their display_name()
1372 attribute.
1373
1374 In all cases, the "left" position will be used to break any ties. To
1375 break ties using another field, options may be strung together using a
1376 "|" character; e.g. "strand|low_score|right" would cause the features
1377 to be sorted first by strand, then score (lowest to highest), then by
1378 "right" position in the sequence. Finally, a subroutine coderef can
1379 be provided, which should expect to receive two feature objects (via
1380 the special sort variables $a and $b), and should return -1, 0 or 1
1381 (see Perl's sort() function for more information); this subroutine
1382 will be used without further modification for sorting. For example,
1383 to sort a set of database search hits by bits (stored in the features'
1384 "score" fields), scaled by the log of the alignment length (with
1385 "left" position breaking any ties):
1386
1387 sort_order = sub { ( $b->score/log($b->length)
1388 <=>
1389 $a->score/log($a->length) )
1390 ||
1391 ( $a->start <=> $b->start )
1392 }
1393
1394 The -always_sort option, if true, will sort features even if bumping
1395 is turned off. This is useful if you would like overlapping features
1396 to stack in a particular order. Features towards the end of the list
1397 will overlay those towards the beginning of the sort order.
1398
1399 =head1 SUBCLASSING Bio::Graphics::Glyph
1400
1401 By convention, subclasses are all lower-case. Begin each subclass
1402 with a preamble like this one:
1403
1404 package Bio::Graphics::Glyph::crossbox;
1405
1406 use strict;
1407 use vars '@ISA';
1408 @ISA = 'Bio::Graphics::Glyph';
1409
1410 Then override the methods you need to. Typically, just the draw()
1411 method will need to be overridden. However, if you need additional
1412 room in the glyph, you may override calculate_height(),
1413 calculate_left() and calculate_right(). Do not directly override
1414 height(), left() and right(), as their purpose is to cache the values
1415 returned by their calculating cousins in order to avoid time-consuming
1416 recalculation.
1417
1418 A simple draw() method looks like this:
1419
1420 sub draw {
1421 my $self = shift;
1422 $self->SUPER::draw(@_);
1423 my $gd = shift;
1424
1425 # and draw a cross through the box
1426 my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries(@_);
1427 my $fg = $self->fgcolor;
1428 $gd->line($x1,$y1,$x2,$y2,$fg);
1429 $gd->line($x1,$y2,$x2,$y1,$fg);
1430 }
1431
1432 This subclass draws a simple box with two lines criss-crossed through
1433 it. We first call our inherited draw() method to generate the filled
1434 box and label. We then call calculate_boundaries() to return the
1435 coordinates of the glyph, disregarding any extra space taken by
1436 labels. We call fgcolor() to return the desired foreground color, and
1437 then call $gd-E<gt>line() twice to generate the criss-cross.
1438
1439 For more complex draw() methods, see Bio::Graphics::Glyph::transcript
1440 and Bio::Graphics::Glyph::segments.
1441
1442 =head1 BUGS
1443
1444 Please report them.
1445
1446 =head1 SEE ALSO
1447
1448 L<Bio::DB::GFF::Feature>,
1449 L<Ace::Sequence>,
1450 L<Bio::Graphics::Panel>,
1451 L<Bio::Graphics::Track>,
1452 L<Bio::Graphics::Glyph::anchored_arrow>,
1453 L<Bio::Graphics::Glyph::arrow>,
1454 L<Bio::Graphics::Glyph::box>,
1455 L<Bio::Graphics::Glyph::dna>,
1456 L<Bio::Graphics::Glyph::graded_segments>,
1457 L<Bio::Graphics::Glyph::primers>,
1458 L<Bio::Graphics::Glyph::segments>,
1459 L<Bio::Graphics::Glyph::toomany>,
1460 L<Bio::Graphics::Glyph::transcript>,
1461 L<Bio::Graphics::Glyph::transcript2>,
1462 L<Bio::Graphics::Glyph::wormbase_transcript>
1463
1464 =head1 AUTHOR
1465
1466 Lincoln Stein E<lt>lstein@cshl.orgE<gt>
1467
1468 Copyright (c) 2001 Cold Spring Harbor Laboratory
1469
1470 This library is free software; you can redistribute it and/or modify
1471 it under the same terms as Perl itself. See DISCLAIMER.txt for
1472 disclaimers of warranty.
1473
1474 =cut