comparison variant_effect_predictor/Bio/Graphics/Panel.pm @ 0:2bc9b66ada89 draft default tip

Uploaded
author mahtabm
date Thu, 11 Apr 2013 06:29:17 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:2bc9b66ada89
1 package Bio::Graphics::Panel;
2
3 use strict;
4 use Bio::Graphics::Glyph::Factory;
5 use Bio::Graphics::Feature;
6 use GD;;
7
8
9 use constant KEYLABELFONT => gdMediumBoldFont;
10 use constant KEYSPACING => 5; # extra space between key columns
11 use constant KEYPADTOP => 5; # extra padding before the key starts
12 use constant KEYCOLOR => 'wheat';
13 use constant KEYSTYLE => 'bottom';
14 use constant KEYALIGN => 'left';
15 use constant GRIDCOLOR => 'lightcyan';
16 use constant MISSING_TRACK_COLOR =>'gray';
17
18 my %COLORS; # translation table for symbolic color names to RGB triple
19
20 # Create a new panel of a given width and height, and add lists of features
21 # one by one
22 sub new {
23 my $class = shift;
24 my %options = @_;
25
26 $class->read_colors() unless %COLORS;
27
28 my $length = $options{-length} || 0;
29 my $offset = $options{-offset} || 0;
30 my $spacing = $options{-spacing} || 5;
31 my $bgcolor = $options{-bgcolor} || 0;
32 my $keyfont = $options{-key_font} || KEYLABELFONT;
33 my $keycolor = $options{-key_color} || KEYCOLOR;
34 my $keyspacing = $options{-key_spacing} || KEYSPACING;
35 my $keystyle = $options{-key_style} || KEYSTYLE;
36 my $keyalign = $options{-key_align} || KEYALIGN;
37 my $allcallbacks = $options{-all_callbacks} || 0;
38 my $gridcolor = $options{-gridcolor} || GRIDCOLOR;
39 my $grid = $options{-grid} || 0;
40 my $flip = $options{-flip} || 0;
41 my $empty_track_style = $options{-empty_tracks} || 'key';
42 my $truecolor = $options{-truecolor} || 0;
43
44 if (my $seg = $options{-segment}) {
45 $offset = eval {$seg->start-1} || 0;
46 $length = $seg->length;
47 }
48
49 $offset ||= $options{-start}-1 if defined $options{-start};
50 $length ||= $options{-stop}-$options{-start}+1
51 if defined $options{-start} && defined $options{-stop};
52
53 return bless {
54 tracks => [],
55 width => $options{-width} || 600,
56 pad_top => $options{-pad_top}||0,
57 pad_bottom => $options{-pad_bottom}||0,
58 pad_left => $options{-pad_left}||0,
59 pad_right => $options{-pad_right}||0,
60 length => $length,
61 offset => $offset,
62 gridcolor => $gridcolor,
63 grid => $grid,
64 bgcolor => $bgcolor,
65 height => 0, # AUTO
66 spacing => $spacing,
67 key_font => $keyfont,
68 key_color => $keycolor,
69 key_spacing => $keyspacing,
70 key_style => $keystyle,
71 key_align => $keyalign,
72 all_callbacks => $allcallbacks,
73 truecolor => $truecolor,
74 flip => $flip,
75 empty_track_style => $empty_track_style,
76 },$class;
77 }
78
79 sub pad_left {
80 my $self = shift;
81 my $g = $self->{pad_left};
82 $self->{pad_left} = shift if @_;
83 $g;
84 }
85 sub pad_right {
86 my $self = shift;
87 my $g = $self->{pad_right};
88 $self->{pad_right} = shift if @_;
89 $g;
90 }
91 sub pad_top {
92 my $self = shift;
93 my $g = $self->{pad_top};
94 $self->{pad_top} = shift if @_;
95 $g;
96 }
97 sub pad_bottom {
98 my $self = shift;
99 my $g = $self->{pad_bottom};
100 $self->{pad_bottom} = shift if @_;
101 $g;
102 }
103
104 sub flip {
105 my $self = shift;
106 my $g = $self->{flip};
107 $self->{flip} = shift if @_;
108 $g;
109 }
110
111 # values of empty_track_style are:
112 # "suppress" -- suppress empty tracks entirely (default)
113 # "key" -- show just the key in "between" mode
114 # "line" -- draw a thin grey line
115 # "dashed" -- draw a dashed line
116 sub empty_track_style {
117 my $self = shift;
118 my $g = $self->{empty_track_style};
119 $self->{empty_track_style} = shift if @_;
120 $g;
121 }
122
123 sub key_style {
124 my $self = shift;
125 my $g = $self->{key_style};
126 $self->{key_style} = shift if @_;
127 $g;
128 }
129
130 # public routine for mapping from a base pair
131 # location to pixel coordinates
132 sub location2pixel {
133 my $self = shift;
134 my $end = $self->end + 1;
135 my @coords = $self->{flip} ? map { $end-$_ } @_ : @_;
136 $self->map_pt(@coords);
137 }
138
139 # numerous direct calls into array used here for performance considerations
140 sub map_pt {
141 my $self = shift;
142 my $offset = $self->{offset};
143 my $scale = $self->{scale} || $self->scale;
144 my $pl = $self->{pad_left};
145 my $pr = $self->{width} - $self->{pad_right};
146 my $flip = $self->{flip};
147 my $length = $self->{length};
148 my @result;
149 foreach (@_) {
150 my $val = $flip ? int (0.5 + $pr - ($length - ($_- 1)) * $scale) : int (0.5 + $pl + ($_-$offset-1) * $scale);
151 $val = $pl-1 if $val < $pl;
152 $val = $pr+1 if $val > $pr;
153 push @result,$val;
154 }
155 @result;
156 }
157
158 sub map_no_trunc {
159 my $self = shift;
160 my $offset = $self->{offset};
161 my $scale = $self->scale;
162 my $pl = $self->{pad_left};
163 my $pr = $self->{width} - $self->{pad_right};
164 my $flip = $self->{flip};
165 my $length = $self->{length};
166 my $end = $offset+$length;
167 my @result;
168 foreach (@_) {
169 my $val = $flip ? int (0.5 + $pl + ($end - ($_- 1)) * $scale) : int (0.5 + $pl + ($_-$offset-1) * $scale);
170 push @result,$val;
171 }
172 @result;
173 }
174
175 sub scale {
176 my $self = shift;
177 $self->{scale} ||= ($self->{width}-$self->pad_left-$self->pad_right)/($self->length);
178 }
179
180 sub start { shift->{offset}+1}
181 sub end { $_[0]->start + $_[0]->{length}-1}
182
183 sub offset { shift->{offset} }
184 sub width {
185 my $self = shift;
186 my $d = $self->{width};
187 $self->{width} = shift if @_;
188 $d;
189 # $d + $self->pad_left + $self->pad_right;
190 }
191
192 sub left {
193 my $self = shift;
194 $self->pad_left;
195 }
196 sub right {
197 my $self = shift;
198 $self->width - $self->pad_right;
199 }
200
201 sub spacing {
202 my $self = shift;
203 my $d = $self->{spacing};
204 $self->{spacing} = shift if @_;
205 $d;
206 }
207
208 sub key_spacing {
209 my $self = shift;
210 my $d = $self->{key_spacing};
211 $self->{key_spacing} = shift if @_;
212 $d;
213 }
214
215 sub length {
216 my $self = shift;
217 my $d = $self->{length};
218 if (@_) {
219 my $l = shift;
220 $l = $l->length if ref($l) && $l->can('length');
221 $self->{length} = $l;
222 }
223 $d;
224 }
225
226 sub gridcolor {shift->{gridcolor}}
227
228 sub all_callbacks { shift->{all_callbacks} }
229
230 sub add_track {
231 my $self = shift;
232 $self->_do_add_track(scalar(@{$self->{tracks}}),@_);
233 }
234
235 sub unshift_track {
236 my $self = shift;
237 $self->_do_add_track(0,@_);
238 }
239
240 sub insert_track {
241 my $self = shift;
242 my $position = shift;
243 $self->_do_add_track($position,@_);
244 }
245
246
247 # create a feature and factory pair
248 # see Factory.pm for the format of the options
249 # The thing returned is actually a generic Glyph
250 sub _do_add_track {
251 my $self = shift;
252 my $position = shift;
253
254 # due to indecision, we accept features
255 # and/or glyph types in the first two arguments
256 my ($features,$glyph_name) = ([],undef);
257 while ( @_ && $_[0] !~ /^-/) {
258 my $arg = shift;
259 $features = $arg and next if ref($arg);
260 $glyph_name = $arg and next unless ref($arg);
261 }
262
263 my %args = @_;
264 my ($map,$ss,%options);
265
266 foreach (keys %args) {
267 (my $canonical = lc $_) =~ s/^-//;
268 if ($canonical eq 'glyph') {
269 $map = $args{$_};
270 delete $args{$_};
271 } elsif ($canonical eq 'stylesheet') {
272 $ss = $args{$_};
273 delete $args{$_};
274 } else {
275 $options{$canonical} = $args{$_};
276 }
277 }
278
279 $glyph_name = $map if defined $map;
280 $glyph_name ||= 'generic';
281
282 local $^W = 0; # uninitialized variable warnings under 5.00503
283
284 my $panel_map =
285 ref($map) eq 'CODE' ? sub {
286 my $feature = shift;
287 return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'track' };
288 return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'group' };
289 return $map->($feature);
290 }
291 : ref($map) eq 'HASH' ? sub {
292 my $feature = shift;
293 return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'track' };
294 return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'group' };
295 return eval {$map->{$feature->primary_tag}} || 'generic';
296 }
297 : sub {
298 my $feature = shift;
299 return 'track' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'track' };
300 return 'group' if eval { defined $feature->primary_tag && $feature->primary_tag eq 'group' };
301 return $glyph_name;
302 };
303
304 $self->_add_track($position,$features,-map=>$panel_map,-stylesheet=>$ss,-options=>\%options);
305 }
306
307 sub _add_track {
308 my $self = shift;
309 my ($position,$features,@options) = @_;
310
311 # build the list of features into a Bio::Graphics::Feature object
312 $features = [$features] unless ref $features eq 'ARRAY';
313
314 # optional middle-level glyph is the group
315 foreach my $f (grep {ref $_ eq 'ARRAY'} @$features) {
316 next unless ref $f eq 'ARRAY';
317 $f = Bio::Graphics::Feature->new(
318 -segments=>$f,
319 -type => 'group'
320 );
321 }
322
323 # top-level glyph is the track
324 my $feature = Bio::Graphics::Feature->new(
325 -segments=>$features,
326 -start => $self->offset+1,
327 -stop => $self->offset+$self->length,
328 -type => 'track'
329 );
330
331 my $factory = Bio::Graphics::Glyph::Factory->new($self,@options);
332 my $track = $factory->make_glyph(-1,$feature);
333
334 splice(@{$self->{tracks}},$position,0,$track);
335 return $track;
336 }
337
338 sub height {
339 my $self = shift;
340 my $spacing = $self->spacing;
341 my $key_height = $self->format_key;
342 my $empty_track_style = $self->empty_track_style;
343 my $key_style = $self->key_style;
344 my $bottom_key = $key_style eq 'bottom';
345 my $between_key = $key_style eq 'between';
346 my $draw_empty = $empty_track_style =~ /^(line|dashed)$/;
347 my $keyheight = $self->{key_font}->height;
348 my $height = 0;
349 for my $track (@{$self->{tracks}}) {
350 my $draw_between = $between_key && $track->option('key');
351 my $has_parts = $track->parts;
352 next if !$has_parts && ($empty_track_style eq 'suppress'
353 or $empty_track_style eq 'key' && $bottom_key);
354 $height += $keyheight if $draw_between;
355 $height += $self->spacing;
356 $height += $track->layout_height;
357 }
358
359 # get rid of spacing under last track
360 $height -= $self->spacing unless $bottom_key;
361 return $height + $key_height + $self->pad_top + $self->pad_bottom;
362 }
363
364 sub gd {
365 my $self = shift;
366 my $existing_gd = shift;
367
368 local $^W = 0; # can't track down the uninitialized variable warning
369
370 return $self->{gd} if $self->{gd};
371
372 my $width = $self->width;
373 my $height = $self->height;
374
375 my $gd = $existing_gd || GD::Image->new($width,$height,
376 ($self->{truecolor} && GD::Image->can('isTrueColor') ? 1 : ())
377 );
378
379 my %translation_table;
380 for my $name ('white','black',keys %COLORS) {
381 my $idx = $gd->colorAllocate(@{$COLORS{$name}});
382 $translation_table{$name} = $idx;
383 }
384
385 $self->{translations} = \%translation_table;
386 $self->{gd} = $gd;
387 if ($self->bgcolor) {
388 $gd->fill(0,0,$self->bgcolor);
389 } elsif (eval {$gd->isTrueColor}) {
390 $gd->fill(0,0,$translation_table{'white'});
391 }
392
393 my $pl = $self->pad_left;
394 my $pt = $self->pad_top;
395 my $offset = $pt;
396 my $keyheight = $self->{key_font}->height;
397 my $bottom_key = $self->{key_style} eq 'bottom';
398 my $between_key = $self->{key_style} eq 'between';
399 my $left_key = $self->{key_style} eq 'left';
400 my $right_key = $self->{key_style} eq 'right';
401 my $empty_track_style = $self->empty_track_style;
402 my $spacing = $self->spacing;
403
404 # we draw in two steps, once for background of tracks, and once for
405 # the contents. This allows the grid to sit on top of the track background.
406 for my $track (@{$self->{tracks}}) {
407 my $draw_between = $between_key && $track->option('key');
408 next if !$track->parts && ($empty_track_style eq 'suppress'
409 or $empty_track_style eq 'key' && $bottom_key);
410 $gd->filledRectangle($pl,
411 $offset,
412 $width-$self->pad_right,
413 $offset+$track->layout_height
414 + ($between_key ? $self->{key_font}->height : 0),
415 $track->tkcolor)
416 if defined $track->tkcolor;
417 $offset += $keyheight if $draw_between;
418 $offset += $track->layout_height + $spacing;
419 }
420
421 $self->draw_grid($gd) if $self->{grid};
422
423 $offset = $pt;
424 for my $track (@{$self->{tracks}}) {
425 my $draw_between = $between_key && $track->option('key');
426 my $has_parts = $track->parts;
427 next if !$has_parts && ($empty_track_style eq 'suppress'
428 or $empty_track_style eq 'key' && $bottom_key);
429
430 if ($draw_between) {
431 $offset += $self->draw_between_key($gd,$track,$offset);
432 }
433
434 elsif ($self->{key_style} =~ /^(left|right)$/) {
435 $self->draw_side_key($gd,$track,$offset,$self->{key_style});
436 }
437
438 $self->draw_empty($gd,$offset,$empty_track_style)
439 if !$has_parts && $empty_track_style=~/^(line|dashed)$/;
440
441 $track->draw($gd,0,$offset,0,1);
442 $self->track_position($track,$offset);
443 $offset += $track->layout_height + $spacing;
444 }
445
446
447 $self->draw_bottom_key($gd,$pl,$offset) if $self->{key_style} eq 'bottom';
448
449 return $self->{gd} = $gd;
450 }
451
452 sub boxes {
453 my $self = shift;
454 my @boxes;
455 my $offset = 0;
456
457 my $pl = $self->pad_left;
458 my $pt = $self->pad_top;
459 my $between_key = $self->{key_style} eq 'between';
460 my $bottom_key = $self->{key_style} eq 'bottom';
461 my $empty_track_style = $self->empty_track_style;
462 my $keyheight = $self->{key_font}->height;
463 my $spacing = $self->spacing;
464
465 for my $track (@{$self->{tracks}}) {
466 my $draw_between = $between_key && $track->option('key');
467 next if !$track->parts && ($empty_track_style eq 'suppress'
468 or $empty_track_style eq 'key' && $bottom_key);
469 $offset += $keyheight if $draw_between;
470 my $boxes = $track->boxes(0,$offset+$pt);
471 $self->track_position($track,$offset);
472 push @boxes,@$boxes;
473 $offset += $track->layout_height + $self->spacing;
474 }
475 return wantarray ? @boxes : \@boxes;
476 }
477
478 sub track_position {
479 my $self = shift;
480 my $track = shift;
481 my $d = $self->{_track_position}{$track};
482 $self->{_track_position}{$track} = shift if @_;
483 $d;
484 }
485
486 # draw the keys -- between
487 sub draw_between_key {
488 my $self = shift;
489 my ($gd,$track,$offset) = @_;
490 my $key = $track->option('key') or return 0;
491 my $x = $self->{key_align} eq 'center' ? $self->width - (CORE::length($key) * $self->{key_font}->width)/2
492 : $self->{key_align} eq 'right' ? $self->width - CORE::length($key)
493 : $self->pad_left;
494 $gd->string($self->{key_font},$x,$offset,$key,1);
495 return $self->{key_font}->height;
496 }
497
498 # draw the keys -- left or right side
499 sub draw_side_key {
500 my $self = shift;
501 my ($gd,$track,$offset,$side) = @_;
502 my $key = $track->option('key') or return;
503 my $pos = $side eq 'left' ? $self->pad_left - $self->{key_font}->width * CORE::length($key)-3
504 : $self->width - $self->pad_right+3;
505 $gd->string($self->{key_font},$pos,$offset,$key,1);
506 }
507
508 # draw the keys -- bottom
509 sub draw_bottom_key {
510 my $self = shift;
511 my ($gd,$left,$top) = @_;
512 my $key_glyphs = $self->{key_glyphs} or return;
513
514 my $color = $self->translate_color($self->{key_color});
515 $gd->filledRectangle($left,$top,$self->width - $self->pad_right,$self->height-$self->pad_bottom,$color);
516 $gd->string($self->{key_font},$left,KEYPADTOP+$top,"KEY:",1);
517 $top += $self->{key_font}->height + KEYPADTOP;
518
519 $_->draw($gd,$left,$top) foreach @$key_glyphs;
520 }
521
522 # Format the key section, and return its height
523 sub format_key {
524 my $self = shift;
525 return 0 unless $self->key_style eq 'bottom';
526
527 return $self->{key_height} if defined $self->{key_height};
528
529 my $suppress = $self->{empty_track_style} eq 'suppress';
530 my $between = $self->{key_style} eq 'between';
531
532 if ($between) {
533 my @key_tracks = $suppress
534 ? grep {$_->option('key') && $_->parts} @{$self->{tracks}}
535 : grep {$_->option('key')} @{$self->{tracks}};
536 return $self->{key_height} = @key_tracks * $self->{key_font}->height;
537 }
538
539 elsif ($self->{key_style} eq 'bottom') {
540
541 my ($height,$width) = (0,0);
542 my %tracks;
543 my @glyphs;
544
545 # determine how many glyphs become part of the key
546 # and their max size
547 for my $track (@{$self->{tracks}}) {
548
549 next unless $track->option('key');
550 next if $suppress && !$track->parts;
551
552 my $glyph;
553 if (my @parts = $track->parts) {
554 $glyph = $parts[0]->keyglyph;
555 } else {
556 my $t = Bio::Graphics::Feature->new(-segments=>
557 [Bio::Graphics::Feature->new(-start => $self->offset,
558 -stop => $self->offset+$self->length)]);
559 my $g = $track->factory->make_glyph(0,$t);
560 $glyph = $g->keyglyph;
561 }
562 next unless $glyph;
563
564
565 $tracks{$track} = $glyph;
566 my ($h,$w) = ($glyph->layout_height,
567 $glyph->layout_width);
568 $height = $h if $h > $height;
569 $width = $w if $w > $width;
570 push @glyphs,$glyph;
571
572 }
573
574 $width += $self->key_spacing;
575
576 # no key glyphs, no key
577 return $self->{key_height} = 0 unless @glyphs;
578
579 # now height and width hold the largest glyph, and $glyph_count
580 # contains the number of glyphs. We will format them into a
581 # box that is roughly 3 height/4 width (golden mean)
582 my $rows = 0;
583 my $cols = 0;
584 my $maxwidth = $self->width - $self->pad_left - $self->pad_right;
585 while (++$rows) {
586 $cols = @glyphs / $rows;
587 $cols = int ($cols+1) if $cols =~ /\./; # round upward for fractions
588 my $total_width = $cols * $width;
589 my $total_height = $rows * $width;
590 last if $total_width < $maxwidth;
591 }
592
593 # move glyphs into row-major format
594 my $spacing = $self->key_spacing;
595 my $i = 0;
596 for (my $c = 0; $c < $cols; $c++) {
597 for (my $r = 0; $r < $rows; $r++) {
598 my $x = $c * ($width + $spacing);
599 my $y = $r * ($height + $spacing);
600 next unless defined $glyphs[$i];
601 $glyphs[$i]->move($x,$y);
602 $i++;
603 }
604 }
605
606 $self->{key_glyphs} = \@glyphs; # remember our key glyphs
607 # remember our key height
608 return $self->{key_height} =
609 ($height+$spacing) * $rows + $self->{key_font}->height +KEYPADTOP;
610 }
611
612 else { # no known key style, neither "between" nor "bottom"
613 return $self->{key_height} = 0;
614 }
615 }
616
617 sub draw_empty {
618 my $self = shift;
619 my ($gd,$offset,$style) = @_;
620 $offset += $self->spacing/2;
621 my $left = $self->pad_left;
622 my $right = $self->width-$self->pad_right;
623 my $color = $self->translate_color(MISSING_TRACK_COLOR);
624 if ($style eq 'dashed') {
625 $gd->setStyle($color,$color,gdTransparent,gdTransparent);
626 $gd->line($left,$offset,$right,$offset,gdStyled);
627 } else {
628 $gd->line($left,$offset,$right,$offset,$color);
629 }
630 $offset;
631 }
632
633 # draw a grid
634 sub draw_grid {
635 my $self = shift;
636 my $gd = shift;
637
638 my $gridcolor = $self->translate_color($self->{gridcolor});
639 my @positions;
640 if (ref $self->{grid} eq 'ARRAY') {
641 @positions = @{$self->{grid}};
642 } else {
643 my ($major,$minor) = $self->ticks;
644 my $first_tick = $minor * int(0.5 + $self->start/$minor);
645 for (my $i = $first_tick; $i < $self->end; $i += $minor) {
646 push @positions,$i;
647 }
648 }
649 my $pl = $self->pad_left;
650 my $pt = $self->pad_top;
651 my $pb = $self->height - $self->pad_bottom;
652 local $self->{flip} = 0;
653 for my $tick (@positions) {
654 my ($pos) = $self->map_pt($tick);
655 $gd->line($pos,$pt,$pos,$pb,$gridcolor);
656 }
657 }
658
659 # calculate major and minor ticks, given a start position
660 sub ticks {
661 my $self = shift;
662 my ($length,$minwidth) = @_;
663
664 $length = $self->{length} unless defined $length;
665 $minwidth = gdSmallFont->width*7 unless defined $minwidth;
666
667 my ($major,$minor);
668
669 # figure out tick mark scale
670 # we want no more than 1 major tick mark every 40 pixels
671 # and enough room for the labels
672 my $scale = $self->scale;
673
674 my $interval = 1;
675
676 while (1) {
677 my $pixels = $interval * $scale;
678 last if $pixels >= $minwidth;
679 $interval *= 10;
680 }
681
682 # to make sure a major tick shows up somewhere in the first half
683 #
684 $interval *= .5 if ($interval > 0.5*$length);
685
686 return ($interval,$interval/10);
687 }
688
689 # reverse of translate(); given index, return rgb triplet
690 sub rgb {
691 my $self = shift;
692 my $idx = shift;
693 my $gd = $self->{gd} or return;
694 return $gd->rgb($idx);
695 }
696
697 sub translate_color {
698 my $self = shift;
699 my @colors = @_;
700 if (@colors == 3) {
701 my $gd = $self->gd or return 1;
702 return $self->colorClosest($gd,@colors);
703 }
704 elsif ($colors[0] =~ /^\#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) {
705 my $gd = $self->gd or return 1;
706 my ($r,$g,$b) = (hex($1),hex($2),hex($3));
707 return $self->colorClosest($gd,$r,$g,$b);
708 }
709 else {
710 my $color = $colors[0];
711 my $table = $self->{translations} or return 1;
712 return defined $table->{$color} ? $table->{$color} : 1;
713 }
714 }
715
716 # workaround for bad GD
717 sub colorClosest {
718 my ($self,$gd,@c) = @_;
719 return $self->{closestcache}{"@c"} if exists $self->{closestcache}{"@c"};
720 return $self->{closestcache}{"@c"} = $gd->colorClosest(@c) if $GD::VERSION < 2.04;
721 my ($value,$index);
722 for (keys %COLORS) {
723 my ($r,$g,$b) = @{$COLORS{$_}};
724 my $dist = ($r-$c[0])**2 + ($g-$c[1])**2 + ($b-$c[2])**2;
725 ($value,$index) = ($dist,$_) if !defined($value) || $dist < $value;
726 }
727 return $self->{closestcache}{"@c"} = $self->{translations}{$index};
728 }
729
730 sub bgcolor {
731 my $self = shift;
732 return unless $self->{bgcolor};
733 $self->translate_color($self->{bgcolor});
734 }
735
736 sub set_pen {
737 my $self = shift;
738 my ($linewidth,$color) = @_;
739 return $self->{pens}{$linewidth,$color} if $self->{pens}{$linewidth,$color};
740
741 my $pen = $self->{pens}{$linewidth} = GD::Image->new($linewidth,$linewidth);
742 my @rgb = $self->rgb($color);
743 my $bg = $pen->colorAllocate(255,255,255);
744 my $fg = $pen->colorAllocate(@rgb);
745 $pen->fill(0,0,$fg);
746 $self->{gd}->setBrush($pen);
747 return gdBrushed;
748 }
749
750 sub png {
751 my $gd = shift->gd;
752 $gd->png;
753 }
754
755 sub read_colors {
756 my $class = shift;
757 while (<DATA>) {
758 chomp;
759 last if /^__END__/;
760 my ($name,$r,$g,$b) = split /\s+/;
761 $COLORS{$name} = [hex $r,hex $g,hex $b];
762 }
763 }
764
765 sub color_name_to_rgb {
766 my $class = shift;
767 my $color_name = shift;
768 $class->read_colors() unless %COLORS;
769 return unless $COLORS{$color_name};
770 return wantarray ? @{$COLORS{$color_name}}
771 : $COLORS{$color_name};
772 }
773
774 sub color_names {
775 my $class = shift;
776 $class->read_colors unless %COLORS;
777 return wantarray ? keys %COLORS : [keys %COLORS];
778 }
779
780 1;
781
782 __DATA__
783 white FF FF FF
784 black 00 00 00
785 aliceblue F0 F8 FF
786 antiquewhite FA EB D7
787 aqua 00 FF FF
788 aquamarine 7F FF D4
789 azure F0 FF FF
790 beige F5 F5 DC
791 bisque FF E4 C4
792 blanchedalmond FF EB CD
793 blue 00 00 FF
794 blueviolet 8A 2B E2
795 brown A5 2A 2A
796 burlywood DE B8 87
797 cadetblue 5F 9E A0
798 chartreuse 7F FF 00
799 chocolate D2 69 1E
800 coral FF 7F 50
801 cornflowerblue 64 95 ED
802 cornsilk FF F8 DC
803 crimson DC 14 3C
804 cyan 00 FF FF
805 darkblue 00 00 8B
806 darkcyan 00 8B 8B
807 darkgoldenrod B8 86 0B
808 darkgray A9 A9 A9
809 darkgreen 00 64 00
810 darkkhaki BD B7 6B
811 darkmagenta 8B 00 8B
812 darkolivegreen 55 6B 2F
813 darkorange FF 8C 00
814 darkorchid 99 32 CC
815 darkred 8B 00 00
816 darksalmon E9 96 7A
817 darkseagreen 8F BC 8F
818 darkslateblue 48 3D 8B
819 darkslategray 2F 4F 4F
820 darkturquoise 00 CE D1
821 darkviolet 94 00 D3
822 deeppink FF 14 100
823 deepskyblue 00 BF FF
824 dimgray 69 69 69
825 dodgerblue 1E 90 FF
826 firebrick B2 22 22
827 floralwhite FF FA F0
828 forestgreen 22 8B 22
829 fuchsia FF 00 FF
830 gainsboro DC DC DC
831 ghostwhite F8 F8 FF
832 gold FF D7 00
833 goldenrod DA A5 20
834 gray 80 80 80
835 green 00 80 00
836 greenyellow AD FF 2F
837 honeydew F0 FF F0
838 hotpink FF 69 B4
839 indianred CD 5C 5C
840 indigo 4B 00 82
841 ivory FF FF F0
842 khaki F0 E6 8C
843 lavender E6 E6 FA
844 lavenderblush FF F0 F5
845 lawngreen 7C FC 00
846 lemonchiffon FF FA CD
847 lightblue AD D8 E6
848 lightcoral F0 80 80
849 lightcyan E0 FF FF
850 lightgoldenrodyellow FA FA D2
851 lightgreen 90 EE 90
852 lightgrey D3 D3 D3
853 lightpink FF B6 C1
854 lightsalmon FF A0 7A
855 lightseagreen 20 B2 AA
856 lightskyblue 87 CE FA
857 lightslategray 77 88 99
858 lightsteelblue B0 C4 DE
859 lightyellow FF FF E0
860 lime 00 FF 00
861 limegreen 32 CD 32
862 linen FA F0 E6
863 magenta FF 00 FF
864 maroon 80 00 00
865 mediumaquamarine 66 CD AA
866 mediumblue 00 00 CD
867 mediumorchid BA 55 D3
868 mediumpurple 100 70 DB
869 mediumseagreen 3C B3 71
870 mediumslateblue 7B 68 EE
871 mediumspringgreen 00 FA 9A
872 mediumturquoise 48 D1 CC
873 mediumvioletred C7 15 85
874 midnightblue 19 19 70
875 mintcream F5 FF FA
876 mistyrose FF E4 E1
877 moccasin FF E4 B5
878 navajowhite FF DE AD
879 navy 00 00 80
880 oldlace FD F5 E6
881 olive 80 80 00
882 olivedrab 6B 8E 23
883 orange FF A5 00
884 orangered FF 45 00
885 orchid DA 70 D6
886 palegoldenrod EE E8 AA
887 palegreen 98 FB 98
888 paleturquoise AF EE EE
889 palevioletred DB 70 100
890 papayawhip FF EF D5
891 peachpuff FF DA B9
892 peru CD 85 3F
893 pink FF C0 CB
894 plum DD A0 DD
895 powderblue B0 E0 E6
896 purple 80 00 80
897 red FF 00 00
898 rosybrown BC 8F 8F
899 royalblue 41 69 E1
900 saddlebrown 8B 45 13
901 salmon FA 80 72
902 sandybrown F4 A4 60
903 seagreen 2E 8B 57
904 seashell FF F5 EE
905 sienna A0 52 2D
906 silver C0 C0 C0
907 skyblue 87 CE EB
908 slateblue 6A 5A CD
909 slategray 70 80 90
910 snow FF FA FA
911 springgreen 00 FF 7F
912 steelblue 46 82 B4
913 tan D2 B4 8C
914 teal 00 80 80
915 thistle D8 BF D8
916 tomato FF 63 47
917 turquoise 40 E0 D0
918 violet EE 82 EE
919 wheat F5 DE B3
920 whitesmoke F5 F5 F5
921 yellow FF FF 00
922 yellowgreen 9A CD 32
923 gradient1 00 ff 00
924 gradient2 0a ff 00
925 gradient3 14 ff 00
926 gradient4 1e ff 00
927 gradient5 28 ff 00
928 gradient6 32 ff 00
929 gradient7 3d ff 00
930 gradient8 47 ff 00
931 gradient9 51 ff 00
932 gradient10 5b ff 00
933 gradient11 65 ff 00
934 gradient12 70 ff 00
935 gradient13 7a ff 00
936 gradient14 84 ff 00
937 gradient15 8e ff 00
938 gradient16 99 ff 00
939 gradient17 a3 ff 00
940 gradient18 ad ff 00
941 gradient19 b7 ff 00
942 gradient20 c1 ff 00
943 gradient21 cc ff 00
944 gradient22 d6 ff 00
945 gradient23 e0 ff 00
946 gradient24 ea ff 00
947 gradient25 f4 ff 00
948 gradient26 ff ff 00
949 gradient27 ff f4 00
950 gradient28 ff ea 00
951 gradient29 ff e0 00
952 gradient30 ff d6 00
953 gradient31 ff cc 00
954 gradient32 ff c1 00
955 gradient33 ff b7 00
956 gradient34 ff ad 00
957 gradient35 ff a3 00
958 gradient36 ff 99 00
959 gradient37 ff 8e 00
960 gradient38 ff 84 00
961 gradient39 ff 7a 00
962 gradient40 ff 70 00
963 gradient41 ff 65 00
964 gradient42 ff 5b 00
965 gradient43 ff 51 00
966 gradient44 ff 47 00
967 gradient45 ff 3d 00
968 gradient46 ff 32 00
969 gradient47 ff 28 00
970 gradient48 ff 1e 00
971 gradient49 ff 14 00
972 gradient50 ff 0a 00
973 __END__
974
975 =head1 NAME
976
977 Bio::Graphics::Panel - Generate GD images of Bio::Seq objects
978
979 =head1 SYNOPSIS
980
981 # This script parses a GenBank or EMBL file named on the command
982 # line and produces a PNG rendering of it. Call it like this:
983 # render.pl my_file.embl | display -
984
985 use strict;
986 use Bio::Graphics;
987 use Bio::SeqIO;
988
989 my $file = shift or die "provide a sequence file as the argument";
990 my $io = Bio::SeqIO->new(-file=>$file) or die "could not create Bio::SeqIO";
991 my $seq = $io->next_seq or die "could not find a sequence in the file";
992
993 my @features = $seq->all_SeqFeatures;
994
995 # sort features by their primary tags
996 my %sorted_features;
997 for my $f (@features) {
998 my $tag = $f->primary_tag;
999 push @{$sorted_features{$tag}},$f;
1000 }
1001
1002 my $panel = Bio::Graphics::Panel->new(
1003 -length => $seq->length,
1004 -key_style => 'between',
1005 -width => 800,
1006 -pad_left => 10,
1007 -pad_right => 10,
1008 );
1009 $panel->add_track( arrow => Bio::SeqFeature::Generic->new(-start=>1,
1010 -end=>$seq->length),
1011 -bump => 0,
1012 -double=>1,
1013 -tick => 2);
1014 $panel->add_track(generic => Bio::SeqFeature::Generic->new(-start=>1,
1015 -end=>$seq->length),
1016 -glyph => 'generic',
1017 -bgcolor => 'blue',
1018 -label => 1,
1019 );
1020
1021 # general case
1022 my @colors = qw(cyan orange blue purple green chartreuse magenta yellow aqua);
1023 my $idx = 0;
1024 for my $tag (sort keys %sorted_features) {
1025 my $features = $sorted_features{$tag};
1026 $panel->add_track($features,
1027 -glyph => 'generic',
1028 -bgcolor => $colors[$idx++ % @colors],
1029 -fgcolor => 'black',
1030 -font2color => 'red',
1031 -key => "${tag}s",
1032 -bump => +1,
1033 -height => 8,
1034 -label => 1,
1035 -description => 1,
1036 );
1037 }
1038
1039 print $panel->png;
1040 exit 0;
1041
1042 =head1 DESCRIPTION
1043
1044 The Bio::Graphics::Panel class provides drawing and formatting
1045 services for any object that implements the Bio::SeqFeatureI
1046 interface, including Ace::Sequence::Feature and Das::Segment::Feature
1047 objects. It can be used to draw sequence annotations, physical
1048 (contig) maps, or any other type of map in which a set of discrete
1049 ranges need to be laid out on the number line.
1050
1051 The module supports a drawing style in which each type of feature
1052 occupies a discrete "track" that spans the width of the display. Each
1053 track will have its own distinctive "glyph", a configurable graphical
1054 representation of the feature.
1055
1056 The module also supports a more flexible style in which several
1057 different feature types and their associated glyphs can occupy the
1058 same track. The choice of glyph is under run-time control.
1059
1060 Semantic zooming (for instance, changing the type of glyph depending
1061 on the density of features) is supported by a callback system for
1062 configuration variables. The module has built-in support for Bio::Das
1063 stylesheets, and stylesheet-driven configuration can be intermixed
1064 with semantic zooming, if desired.
1065
1066 You can add a key to the generated image using either of two key
1067 styles. One style places the key captions at the top of each track.
1068 The other style generates a graphical key at the bottom of the image.
1069
1070 Note that this modules depends on GD.
1071
1072 =head1 METHODS
1073
1074 This section describes the class and object methods for
1075 Bio::Graphics::Panel.
1076
1077 Typically you will begin by creating a new Bio::Graphics::Panel
1078 object, passing it the desired width of the image to generate and an
1079 origin and length describing the coordinate range to display. The
1080 Bio::Graphics::Panel-E<gt>new() method has may configuration variables
1081 that allow you to control the appearance of the image.
1082
1083 You will then call add_track() one or more times to add sets of
1084 related features to the picture. add_track() places a new horizontal
1085 track on the image, and is likewise highly configurable. When you
1086 have added all the features you desire, you may call png() to convert
1087 the image into a PNG-format image, or boxes() to return coordinate
1088 information that can be used to create an imagemap.
1089
1090 =head2 CONSTRUCTORS
1091
1092 new() is the constructor for Bio::Graphics::Panel:
1093
1094 =over 4
1095
1096 =item $panel = Bio::Graphics::Panel-E<gt>new(@options)
1097
1098 The new() method creates a new panel object. The options are
1099 a set of tag/value pairs as follows:
1100
1101 Option Value Default
1102 ------ ----- -------
1103
1104 -offset Base pair to place at extreme left none
1105 of image, in zero-based coordinates
1106
1107 -length Length of sequence segment, in bp none
1108
1109 -start Start of range, in 1-based none
1110 coordinates.
1111
1112 -stop Stop of range, in 1-based none
1113 coordinates.
1114
1115 -segment A Bio::SeqI or Das::Segment none
1116 object, used to derive sequence
1117 range if not otherwise specified.
1118
1119 -width Desired width of image, in pixels 600
1120
1121 -spacing Spacing between tracks, in pixels 5
1122
1123 -pad_top Additional whitespace between top 0
1124 of image and contents, in pixels
1125
1126 -pad_bottom Additional whitespace between top 0
1127 of image and bottom, in pixels
1128
1129 -pad_left Additional whitespace between left 0
1130 of image and contents, in pixels
1131
1132 -pad_right Additional whitespace between right 0
1133 of image and bottom, in pixels
1134
1135 -bgcolor Background color for the panel as a white
1136 whole
1137
1138 -key_color Background color for the key printed wheat
1139 at bottom of panel (if any)
1140
1141 -key_spacing Spacing between key glyphs in the 10
1142 key printed at bottom of panel
1143 (if any)
1144
1145 -key_font Font to use in printed key gdMediumBoldFont
1146 captions.
1147
1148 -key_style Whether to print key at bottom of none
1149 panel ("bottom"), between each
1150 track ("between"), to the left of
1151 each track ("left"), to the right
1152 of each track ("right") or
1153 not at all ("none").
1154
1155 -empty_tracks What to do when a track is empty. suppress
1156 Options are to suppress the track
1157 completely ("suppress"), to show just
1158 the key in "between" mode ("key"),
1159 to draw a thin grey line ("line"),
1160 or to draw a dashed line ("dashed").
1161
1162 -flip flip the drawing coordinates left false
1163 to right, so that lower coordinates
1164 are to the right. This can be
1165 useful for drawing (-) strand
1166 features.
1167
1168 -all_callbacks Whether to invoke callbacks on false
1169 the automatic "track" and "group"
1170 glyphs.
1171
1172 -grid Whether to draw a vertical grid in false
1173 the background. Pass a scalar true
1174 value to have a grid drawn at
1175 regular intervals (corresponding
1176 to the minor ticks of the arrow
1177 glyph). Pass an array reference
1178 to draw the grid at the specified
1179 positions.
1180
1181 -gridcolor Color of the grid lightcyan
1182
1183
1184 Typically you will pass new() an object that implements the
1185 Bio::RangeI interface, providing a length() method, from which the
1186 panel will derive its scale.
1187
1188 $panel = Bio::Graphics::Panel->new(-segment => $sequence,
1189 -width => 800);
1190
1191 new() will return undef in case of an error.
1192
1193 Note that if you use the "left" or "right" key styles, you are
1194 responsible for allocating sufficient -pad_left or -pad_right room for
1195 the labels to appear. The necessary width is the number of characters
1196 in the longest key times the font width (gdMediumBoldFont by default)
1197 plus 3 pixels of internal padding. The simplest way to calculate this
1198 is to iterate over the possible track labels, find the largest one,
1199 and then to compute its width using the formula:
1200
1201 $width = gdMediumBoldFont->width * length($longest_key) +3;
1202
1203 =back
1204
1205 =head2 OBJECT METHODS
1206
1207 =over 4
1208
1209 =item $track = $panel-E<gt>add_track($glyph,$features,@options)
1210
1211 The add_track() method adds a new track to the image.
1212
1213 Tracks are horizontal bands which span the entire width of the panel.
1214 Each track contains a number of graphical elements called "glyphs",
1215 corresponding to a sequence feature.
1216
1217 There are a large number of glyph types. By default, each track will
1218 be homogeneous on a single glyph type, but you can mix several glyph
1219 types on the same track by providing a code reference to the -glyph
1220 argument. Other options passed to add_track() control the color and
1221 size of the glyphs, whether they are allowed to overlap, and other
1222 formatting attributes. The height of a track is determined from its
1223 contents and cannot be directly influenced.
1224
1225 The first two arguments are the glyph name and an array reference
1226 containing the list of features to display. The order of the
1227 arguments is irrelevant, allowing either of these idioms:
1228
1229 $panel->add_track(arrow => \@features);
1230 $panel->add_track(\@features => 'arrow');
1231
1232
1233 The glyph name indicates how each feature is to be rendered. A
1234 variety of glyphs are available, and the number is growing. You may
1235 omit the glyph name entirely by providing a B<-glyph> argument among
1236 @options, as described below.
1237
1238 Currently, the following glyphs are available:
1239
1240 Name Description
1241 ---- -----------
1242
1243 anchored_arrow
1244 a span with vertical bases |---------|. If one or
1245 the other end of the feature is off-screen, the base
1246 will be replaced by an arrow.
1247
1248 arrow An arrow; can be unidirectional or bidirectional.
1249 It is also capable of displaying a scale with
1250 major and minor tickmarks, and can be oriented
1251 horizontally or vertically.
1252
1253 cds Draws CDS features, using the phase information to
1254 show the reading frame usage. At high magnifications
1255 draws the protein translation.
1256
1257 crossbox A box with a big "X" inside it.
1258
1259 diamond A diamond, useful for point features like SNPs.
1260
1261 dna At high magnification draws the DNA sequence. At
1262 low magnifications draws the GC content.
1263
1264 dot A circle, useful for point features like SNPs, stop
1265 codons, or promoter elements.
1266
1267 ellipse An oval.
1268
1269 extending_arrow
1270 Similar to arrow, but a dotted line indicates when the
1271 feature extends beyond the end of the canvas.
1272
1273 generic A filled rectangle, nondirectional.
1274
1275 graded_segments
1276 Similar to segments, but the intensity of the color
1277 is proportional to the score of the feature. This
1278 is used for showing the intensity of blast hits or
1279 other alignment features.
1280
1281 group A group of related features connected by a dashed line.
1282 This is used internally by Panel.
1283
1284 heterogeneous_segments
1285 Like segments, but you can use the source field of the feature
1286 to change the color of each segment.
1287
1288 line A simple line.
1289
1290 pinsertion A triangle designed to look like an insertion location
1291 (e.g. a transposon insertion).
1292
1293 processed_transcript multi-purpose representation of a spliced mRNA, including
1294 positions of UTRs
1295
1296 primers Two inward pointing arrows connected by a line.
1297 Used for STSs.
1298
1299 redgreen_box A box that changes from green->yellow->red as the score
1300 of the feature increases from 0.0 to 1.0. Useful for
1301 representing microarray results.
1302
1303 rndrect A round-cornered rectangle.
1304
1305 segments A set of filled rectangles connected by solid lines.
1306 Used for interrupted features, such as gapped
1307 alignments.
1308
1309 ruler_arrow An arrow with major and minor tick marks and interval
1310 labels.
1311
1312 toomany Tries to show many features as a cloud. Not very successful.
1313
1314 track A group of related features not connected by a line.
1315 This is used internally by Panel.
1316
1317 transcript Similar to segments, but the connecting line is
1318 a "hat" shape, and the direction of transcription
1319 is indicated by a small arrow.
1320
1321 transcript2 Similar to transcript, but the direction of
1322 transcription is indicated by a terminal exon
1323 in the shape of an arrow.
1324
1325 translation 1, 2 and 3-frame translations. At low magnifications,
1326 can be configured to show start and stop codon locations.
1327 At high magnifications, shows the multi-frame protein
1328 translation.
1329
1330 triangle A triangle whose width and orientation can be altered.
1331
1332 xyplot Histograms and other graphs plotted against the genome.
1333
1334 If the glyph name is omitted from add_track(), the "generic" glyph
1335 will be used by default. To get more information about a glyph, run
1336 perldoc on "Bio::Graphics::Glyph::glyphname", replacing "glyphname"
1337 with the name of the glyph you are interested in.
1338
1339 The @options array is a list of name/value pairs that control the
1340 attributes of the track. Some options are interpretered directly by
1341 the track. Others are passed down to the individual glyphs (see
1342 L<"GLYPH OPTIONS">). The following options are track-specific:
1343
1344 Option Description Default
1345 ------ ----------- -------
1346
1347 -tkcolor Track color white
1348
1349 -glyph Glyph class to use. "generic"
1350
1351 -stylesheet Bio::Das::Stylesheet to none
1352 use to generate glyph
1353 classes and options.
1354
1355 B<-tkcolor> controls the background color of the track as a whole.
1356
1357 B<-glyph> controls the glyph type. If present, it supersedes the
1358 glyph name given in the first or second argument to add_track(). The
1359 value of B<-glyph> may be a constant string, a hash reference, or a
1360 code reference. In the case of a constant string, that string will be
1361 used as the class name for all generated glyphs. If a hash reference
1362 is passed, then the feature's primary_tag() will be used as the key to
1363 the hash, and the value, if any, used to generate the glyph type. If
1364 a code reference is passed, then this callback will be passed each
1365 feature in turn as its single argument. The callback is expected to
1366 examine the feature and return a glyph name as its single result.
1367
1368 Example:
1369
1370 $panel->add_track(\@exons,
1371 -glyph => sub { my $feature = shift;
1372 $feature->source_tag eq 'curated'
1373 ? 'ellipse' : 'generic'; }
1374 );
1375
1376 The B<-stylesheet> argument is used to pass a Bio::Das stylesheet
1377 object to the panel. This stylesheet will be called to determine both
1378 the glyph and the glyph options. If both a stylesheet and direct
1379 options are provided, the latter take precedence.
1380
1381 If successful, add_track() returns an Bio::Graphics::Glyph object.
1382 You can use this object to add additional features or to control the
1383 appearance of the track with greater detail, or just ignore it.
1384 Tracks are added in order from the top of the image to the bottom. To
1385 add tracks to the top of the image, use unshift_track().
1386
1387 B<Adding groups of features:> It is not uncommon to add a group of
1388 features which are logically connected, such as the 5' and 3' ends of
1389 EST reads. To group features into sets that remain on the same
1390 horizontal position and bump together, pass the sets as an anonymous
1391 array. For example:
1392
1393 $panel->add_track(segments => [[$abc_5,$abc_3],
1394 [$xxx_5,$xxx_3],
1395 [$yyy_5,$yyy_3]]
1396 );
1397
1398 Typical usage is:
1399
1400 $panel->add_track( transcript => \@genes,
1401 -fillcolor => 'green',
1402 -fgcolor => 'black',
1403 -bump => +1,
1404 -height => 10,
1405 -label => 1);
1406
1407 =item $track = unshift_track($glyph,$features,@options)
1408
1409 unshift_track() works like add_track(), except that the new track is
1410 added to the top of the image rather than the bottom.
1411
1412 =item $gd = $panel-E<gt>gd([$gd])
1413
1414 The gd() method lays out the image and returns a GD::Image object
1415 containing it. You may then call the GD::Image object's png() or
1416 jpeg() methods to get the image data.
1417
1418 Optionally, you may pass gd() a preexisting GD::Image object that you
1419 wish to draw on top of. If you do so, you should call the width() and
1420 height() methods first to ensure that the image has sufficient
1421 dimensions.
1422
1423 =item $png = $panel-E<gt>png
1424
1425 The png() method returns the image as a PNG-format drawing, without
1426 the intermediate step of returning a GD::Image object.
1427
1428 =item $boxes = $panel-E<gt>boxes
1429
1430 =item @boxes = $panel-E<gt>boxes
1431
1432 The boxes() method returns the coordinates of each glyph, useful for
1433 constructing an image map. In a scalar context, boxes() returns an
1434 array ref. In an list context, the method returns the array directly.
1435
1436 Each member of the list is an anonymous array of the following format:
1437
1438 [ $feature, $x1, $y1, $x2, $y2 ]
1439
1440 The first element is the feature object; either an
1441 Ace::Sequence::Feature, a Das::Segment::Feature, or another Bioperl
1442 Bio::SeqFeatureI object. The coordinates are the topleft and
1443 bottomright corners of the glyph, including any space allocated for
1444 labels.
1445
1446 =item $position = $panel-E<gt>track_position($track)
1447
1448 After calling gd() or boxes(), you can learn the resulting Y
1449 coordinate of a track by calling track_position() with the value
1450 returned by add_track() or unshift_track(). This will return undef if
1451 called before gd() or boxes() or with an invalid track.
1452
1453 =item @pixel_coords = $panel-E<gt>location2pixel(@feature_coords)
1454
1455 Public routine to map feature coordinates (in base pairs) into pixel
1456 coordinates relative to the left-hand edge of the picture.
1457
1458 =back
1459
1460 =head1 GLYPH OPTIONS
1461
1462 Each glyph has its own specialized subset of options, but
1463 some are shared by all glyphs:
1464
1465 Option Description Default
1466 ------ ----------- -------
1467
1468 -fgcolor Foreground color black
1469
1470 -bgcolor Background color turquoise
1471
1472 -linewidth Width of lines drawn by 1
1473 glyph
1474
1475 -height Height of glyph 10
1476
1477 -font Glyph font gdSmallFont
1478
1479 -fontcolor Primary font color black
1480
1481 -font2color Secondary font color turquoise
1482
1483 -label Whether to draw a label false
1484
1485 -description Whether to draw a false
1486 description
1487
1488 -bump Bump direction 0
1489
1490 -sort_order Specify layout sort order "default"
1491
1492 -bump_limit Maximum number of levels undef (unlimited)
1493 to bump
1494
1495 -strand_arrow Whether to indicate undef (false)
1496 strandedness
1497
1498 -stranded Synonym for -strand_arrow undef (false)
1499
1500 -connector Type of connector to none
1501 use to connect related
1502 features. Options are
1503 "solid," "hat", "dashed",
1504 "quill" and "none".
1505
1506 -key Description of track for undef
1507 use in key.
1508
1509 -all_callbacks Whether to invoke undef
1510 callbacks for autogenerated
1511 "track" and "group" glyphs
1512
1513 -box_subparts Return boxes around feature false
1514 subparts rather than around the
1515 feature itself.
1516
1517
1518 B<Specifying colors:> Colors can be expressed in either of two ways:
1519 as symbolic names such as "cyan" and as HTML-style #RRGGBB triples.
1520 The symbolic names are the 140 colors defined in the Netscape/Internet
1521 Explorer color cube, and can be retrieved using the
1522 Bio::Graphics::Panel-E<gt>color_names() method.
1523
1524 B<Foreground color:> The -fgcolor option controls the foreground
1525 color, including the edges of boxes and the like.
1526
1527 B<Background color:> The -bgcolor option controls the background used
1528 for filled boxes and other "solid" glyphs. The foreground color
1529 controls the color of lines and strings. The -tkcolor argument
1530 controls the background color of the entire track.
1531
1532 B<Track color:> The -tkcolor option used to specify the background of
1533 the entire track.
1534
1535 B<Font color:> The -fontcolor option controls the color of primary
1536 text, such as labels
1537
1538 B<Secondary Font color:> The -font2color option controls the color of
1539 secondary text, such as descriptions.
1540
1541 B<Labels:> The -label argument controls whether or not the ID of the
1542 feature should be printed next to the feature. It is accepted by all
1543 glyphs. By default, the label is printed just above the glyph and
1544 left aligned with it.
1545
1546 -label can be a constant string or a code reference. Values can be
1547 any of:
1548
1549 -label value Description
1550 ------------ -----------
1551
1552 0 Don't draw a label
1553 1 Calculate a label based on primary tag of sequence
1554 "a string" Use "a string" as the label
1555 code ref Invoke the code reference to compute the label
1556
1557 A known bug with this naming scheme is that you can't label a feature
1558 with the string "1". To work around this, use "1 " (note the terminal
1559 space).
1560
1561 B<Descriptions:> The -description argument controls whether or not a
1562 brief description of the feature should be printed next to it. By
1563 default, the description is printed just below the glyph and
1564 left-aligned with it. A value of 0 will suppress the description. A
1565 value of 1 will call the source_tag() method of the feature. A code
1566 reference will be invoked to calculate the description on the fly.
1567 Anything else will be treated as a string and used verbatim.
1568
1569 B<Connectors:> A glyph can contain subglyphs, recursively. The top
1570 level glyph is the track, which contains one or more groups, which
1571 contain features, which contain subfeatures, and so forth. By
1572 default, the "group" glyph draws dotted lines between each of its
1573 subglyphs, the "segment" glyph draws a solid line between each of its
1574 subglyphs, and the "transcript" and "transcript2" glyphs draw
1575 hat-shaped lines between their subglyphs. All other glyphs do not
1576 connect their components. You can override this behavior by providing
1577 a -connector option, to explicitly set the type of connector. Valid
1578 options are:
1579
1580
1581 "hat" an upward-angling conector
1582 "solid" a straight horizontal connector
1583 "quill" a decorated line with small arrows indicating strandedness
1584 (like the UCSC Genome Browser uses)
1585 "dashed" a horizontal dashed line.
1586
1587 The B<-connector_color> option controls the color of the connector, if
1588 any.
1589
1590 B<Collision control:> The -bump argument controls what happens when
1591 glyphs collide. By default, they will simply overlap (value 0). A
1592 -bump value of +1 will cause overlapping glyphs to bump downwards
1593 until there is room for them. A -bump value of -1 will cause
1594 overlapping glyphs to bump upwards. The bump argument can also be a
1595 code reference; see below.
1596
1597 B<Keys:> The -key argument declares that the track is to be shown in a
1598 key appended to the bottom of the image. The key contains a picture
1599 of a glyph and a label describing what the glyph means. The label is
1600 specified in the argument to -key.
1601
1602 B<box_subparts:> Ordinarily, when you invoke the boxes() methods to
1603 retrieve the rectangles surrounding the glyphs (which you need to do
1604 to create clickable imagemaps, for example), the rectangles will
1605 surround the top level features. If you wish for the rectangles to
1606 surround subpieces of the glyph, such as the exons in a transcript,
1607 set box_subparts to a true value.
1608
1609 B<strand_arrow:> If set to true, some glyphs will indicate their
1610 strandedness, usually by drawing an arrow. For this to work, the
1611 Bio::SeqFeature must have a strand of +1 or -1. The glyph will ignore
1612 this directive if the underlying feature has a strand of zero or
1613 undef.
1614
1615 B<sort_order>: By default, features are drawn with a layout based only on the
1616 position of the feature, assuring a maximal "packing" of the glyphs
1617 when bumped. In some cases, however, it makes sense to display the
1618 glyphs sorted by score or some other comparison, e.g. such that more
1619 "important" features are nearer the top of the display, stacked above
1620 less important features. The -sort_order option allows a few
1621 different built-in values for changing the default sort order (which
1622 is by "left" position): "low_score" (or "high_score") will cause
1623 features to be sorted from lowest to highest score (or vice versa).
1624 "left" (or "default") and "right" values will cause features to be
1625 sorted by their position in the sequence. "longer" (or "shorter")
1626 will cause the longest (or shortest) features to be sorted first, and
1627 "strand" will cause the features to be sorted by strand: "+1"
1628 (forward) then "0" (unknown, or NA) then "-1" (reverse).
1629
1630 In all cases, the "left" position will be used to break any ties. To
1631 break ties using another field, options may be strung together using a
1632 "|" character; e.g. "strand|low_score|right" would cause the features
1633 to be sorted first by strand, then score (lowest to highest), then by
1634 "right" position in the sequence. Finally, a subroutine coderef can
1635 be provided, which should expect to receive two feature objects (via
1636 the special sort variables $a and $b), and should return -1, 0 or 1
1637 (see Perl's sort() function for more information); this subroutine
1638 will be used without further modification for sorting. For example,
1639 to sort a set of database search hits by bits (stored in the features'
1640 "score" fields), scaled by the log of the alignment length (with
1641 "left" position breaking any ties):
1642
1643 sort_order = sub { ( $b->score/log($b->length)
1644 <=>
1645 $a->score/log($a->length) )
1646 ||
1647 ( $a->start <=> $b->start )
1648 }
1649
1650 B<bump_limit>: When bumping is chosen, colliding features will
1651 ordinarily move upward or downward without limit. When many features
1652 collide, this can lead to excessively high images. You can limit the
1653 number of levels that features will bump by providing a numeric
1654 B<bump_limit> option.
1655
1656 =head2 Options and Callbacks
1657
1658 Instead of providing a constant value to an option, you may subsitute
1659 a code reference. This code reference will be called every time the
1660 panel needs to configure a glyph. The callback will be called with
1661 three arguments like this:
1662
1663 sub callback {
1664 my ($feature,$option_name,$part_no,$total_parts,$glyph) = @_;
1665 # do something which results in $option_value being set
1666 return $option_value;
1667 }
1668
1669 The five arguments are C<$feature>, a reference to the IO::SeqFeatureI
1670 object, C<$option_name>, the name of the option to configure,
1671 C<$part_no>, an integer index indicating which subpart of the feature
1672 is being drawn, C<$total_parts>, an integer indicating the total
1673 number of subfeatures in the feature, and finally C<$glyph>, the Glyph
1674 object itself. The latter fields are useful in the case of treating
1675 the first or last subfeature differently, such as using a different
1676 color for the terminal exon of a gene. Usually you will only need to
1677 examine the first argument. This example shows a callback examining
1678 the score() attribute of a feature (possibly a BLAST hit) and return
1679 the color "red" for high-scoring features, and "green" for low-scoring
1680 features:
1681
1682 sub callback {
1683 my $feature = shift;
1684 if ($feature->score > 90) {
1685 return 'red';
1686 else {
1687 return 'green';
1688 }
1689 }
1690
1691 The callback should return a string indicating the desired value of
1692 the option. To tell the panel to use the default value for this
1693 option, return the string "*default*".
1694
1695 When you install a callback for a feature that contains subparts, the
1696 callback will be invoked first for the top-level feature, and then for
1697 each of its subparts (recursively). You should make sure to examine
1698 the feature's type to determine whether the option is appropriate.
1699
1700 Some glyphs deliberately disable this recursive feature. The "track",
1701 "group", "transcript", "transcript2" and "segments" glyphs selectively
1702 disable the -bump, -label and -description options. This is to avoid,
1703 for example, a label being attached to each exon in a transcript, or
1704 the various segments of a gapped alignment bumping each other. You
1705 can override this behavior and force your callback to be invoked by
1706 providing add_track() with a true B<-all_callbacks> argument. In this
1707 case, you must be prepared to handle configuring options for the
1708 "group" and "track" glyphs.
1709
1710 In particular, this means that in order to control the -bump option
1711 with a callback, you should specify -all_callbacks=E<gt>1, and turn on
1712 bumping when the callback is in the track or group glyphs.
1713
1714 =head2 ACCESSORS
1715
1716 The following accessor methods provide access to various attributes of
1717 the panel object. Called with no arguments, they each return the
1718 current value of the attribute. Called with a single argument, they
1719 set the attribute and return its previous value.
1720
1721 Note that in most cases you must change attributes prior to invoking
1722 gd(), png() or boxes(). These three methods all invoke an internal
1723 layout() method which places the tracks and the glyphs within them,
1724 and then caches the result.
1725
1726 Accessor Name Description
1727 ------------- -----------
1728
1729 width() Get/set width of panel
1730 spacing() Get/set spacing between tracks
1731 key_spacing() Get/set spacing between keys
1732 length() Get/set length of segment (bp)
1733 flip() Get/set coordinate flipping
1734 pad_top() Get/set top padding
1735 pad_left() Get/set left padding
1736 pad_bottom() Get/set bottom padding
1737 pad_right() Get/set right padding
1738 start() Get the start of the sequence (bp; read only)
1739 end() Get the end of the sequence (bp; read only)
1740 left() Get the left side of the drawing area (pixels; read only)
1741 right() Get the right side of the drawing area (pixels; read only)
1742
1743 =head2 COLOR METHODS
1744
1745 The following methods are used internally, but may be useful for those
1746 implementing new glyph types.
1747
1748 =over 4
1749
1750 =item @names = Bio::Graphics::Panel-E<gt>color_names
1751
1752 Return the symbolic names of the colors recognized by the panel
1753 object. In a scalar context, returns an array reference.
1754
1755 =item ($red,$green,$blue) = Bio::Graphics::Panel-E<gt>color_name_to_rgb($color)
1756
1757 Given a symbolic color name, returns the red, green, blue components
1758 of the color. In a scalar context, returns an array reference to the
1759 rgb triplet. Returns undef for an invalid color name.
1760
1761 =item @rgb = $panel-E<gt>rgb($index)
1762
1763 Given a GD color index (between 0 and 140), returns the RGB triplet
1764 corresponding to this index. This method is only useful within a
1765 glyph's draw() routine, after the panel has allocated a GD::Image and
1766 is populating it.
1767
1768 =item $index = $panel-E<gt>translate_color($color)
1769
1770 Given a color, returns the GD::Image index. The color may be
1771 symbolic, such as "turquoise", or a #RRGGBB triple, as in #F0E0A8.
1772 This method is only useful within a glyph's draw() routine, after the
1773 panel has allocated a GD::Image and is populating it.
1774
1775 =item $panel-E<gt>set_pen($width,$color)
1776
1777 Changes the width and color of the GD drawing pen to the values
1778 indicated. This is called automatically by the GlyphFactory fgcolor()
1779 method. It returns the GD value gdBrushed, which should be used for
1780 drawing.
1781
1782 =back
1783
1784 =head1 BUGS
1785
1786 Please report them.
1787
1788 =head1 SEE ALSO
1789
1790 L<Bio::Graphics::Glyph>,
1791 L<Bio::Graphics::Glyph::arrow>,
1792 L<Bio::Graphics::Glyph::cds>,
1793 L<Bio::Graphics::Glyph::crossbox>,
1794 L<Bio::Graphics::Glyph::diamond>,
1795 L<Bio::Graphics::Glyph::dna>,
1796 L<Bio::Graphics::Glyph::dot>,
1797 L<Bio::Graphics::Glyph::ellipse>,
1798 L<Bio::Graphics::Glyph::extending_arrow>,
1799 L<Bio::Graphics::Glyph::generic>,
1800 L<Bio::Graphics::Glyph::graded_segments>,
1801 L<Bio::Graphics::Glyph::heterogeneous_segments>,
1802 L<Bio::Graphics::Glyph::line>,
1803 L<Bio::Graphics::Glyph::pinsertion>,
1804 L<Bio::Graphics::Glyph::primers>,
1805 L<Bio::Graphics::Glyph::rndrect>,
1806 L<Bio::Graphics::Glyph::segments>,
1807 L<Bio::Graphics::Glyph::redgreen_box>,
1808 L<Bio::Graphics::Glyph::ruler_arrow>,
1809 L<Bio::Graphics::Glyph::toomany>,
1810 L<Bio::Graphics::Glyph::transcript>,
1811 L<Bio::Graphics::Glyph::transcript2>,
1812 L<Bio::Graphics::Glyph::translation>,
1813 L<Bio::Graphics::Glyph::triangle>,
1814 L<Bio::Graphics::Glyph::xyplot>,
1815 L<Bio::SeqI>,
1816 L<Bio::SeqFeatureI>,
1817 L<Bio::Das>,
1818 L<GD>
1819
1820 =head1 AUTHOR
1821
1822 Lincoln Stein E<lt>lstein@cshl.orgE<gt>
1823
1824 Copyright (c) 2001 Cold Spring Harbor Laboratory
1825
1826 This library is free software; you can redistribute it and/or modify
1827 it under the same terms as Perl itself. See DISCLAIMER.txt for
1828 disclaimers of warranty.
1829
1830 =cut
1831