0
|
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
|