Mercurial > repos > mahtabm > ensembl
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 |