File Coverage

blib/lib/Bio/Phylo/Treedrawer.pm
Criterion Covered Total %
statement 193 311 62.0
branch 38 86 44.1
condition 17 55 30.9
subroutine 47 61 77.0
pod 38 41 92.6
total 333 554 60.1


line stmt bran cond sub pod time code
1             package Bio::Phylo::Treedrawer;
2 3     3   12954 use strict;
  3         5  
  3         72  
3 3     3   13 use warnings;
  3         5  
  3         61  
4 3     3   13 use Bio::Phylo::Util::Logger;
  3         6  
  3         99  
5 3     3   14 use Bio::Phylo::Util::Exceptions 'throw';
  3         5  
  3         110  
6 3     3   14 use Bio::Phylo::Util::CONSTANT qw'_TREE_ /looks_like/ _PI_';
  3         5  
  3         9523  
7              
8             my @fields = qw(
9             WIDTH
10             BRANCH_WIDTH
11             HEIGHT
12             MODE
13             SHAPE
14             PADDING
15             NODE_RADIUS
16             TIP_RADIUS
17             TEXT_HORIZ_OFFSET
18             TEXT_VERT_OFFSET
19             TEXT_WIDTH
20             TREE
21             _SCALEX
22             _SCALEY
23             SCALE
24             FORMAT
25             COLLAPSED_CLADE_WIDTH
26             CLADE_LABEL_WIDTH
27             PIE_COLORS;
28             );
29              
30             my $PI = _PI_;
31             my $tips = 0.000_000_000_000_01;
32             my $logger = Bio::Phylo::Util::Logger->new;
33              
34             =head1 NAME
35              
36             Bio::Phylo::Treedrawer - Visualizer of tree shapes
37              
38             =head1 SYNOPSIS
39              
40             use Bio::Phylo::IO 'parse';
41             use Bio::Phylo::Treedrawer;
42              
43             my $string = '((A:1,B:2)n1:3,C:4)n2:0;';
44             my $tree = parse( -format => 'newick', -string => $string )->first;
45              
46             my $treedrawer = Bio::Phylo::Treedrawer->new(
47             -width => 800,
48             -height => 600,
49             -shape => 'CURVY', # curvogram
50             -mode => 'PHYLO', # phylogram
51             -format => 'SVG'
52             );
53              
54             $treedrawer->set_scale_options(
55             -width => '100%',
56             -major => '10%', # major cross hatch interval
57             -minor => '2%', # minor cross hatch interval
58             -label => 'MYA',
59             );
60              
61             $treedrawer->set_tree($tree);
62             print $treedrawer->draw;
63              
64             =head1 DESCRIPTION
65              
66             This module prepares a tree object for drawing (calculating coordinates for
67             nodes) and calls the appropriate format-specific drawer.
68              
69             =head1 METHODS
70              
71             =head2 CONSTRUCTOR
72              
73             =over
74              
75             =item new()
76              
77             Treedrawer constructor.
78              
79             Type : Constructor
80             Title : new
81             Usage : my $treedrawer = Bio::Phylo::Treedrawer->new(
82             %args
83             );
84             Function: Initializes a Bio::Phylo::Treedrawer object.
85             Alias :
86             Returns : A Bio::Phylo::Treedrawer object.
87             Args : none.
88              
89             =cut
90              
91             sub new {
92 3     3 1 218 my $class = shift;
93 3         43 my $self = {
94             'WIDTH' => 500,
95             'HEIGHT' => 500,
96             'MODE' => 'PHYLO',
97             'SHAPE' => 'CURVY',
98             'PADDING' => 50,
99             'NODE_RADIUS' => 0,
100             'TIP_RADIUS' => 0,
101             'TEXT_HORIZ_OFFSET' => 6,
102             'TEXT_VERT_OFFSET' => 4,
103             'TEXT_WIDTH' => 150,
104             'TREE' => undef,
105             '_SCALEX' => 1,
106             '_SCALEY' => 1,
107             'FORMAT' => 'Svg',
108             'SCALE' => undef,
109             'BRANCH_WIDTH' => 1,
110             'COLLAPSED_CLADE_WIDTH' => 6,
111             'CLADE_LABEL_WIDTH' => 36,
112             'PIE_COLORS' => {},
113             };
114 3         8 bless $self, $class;
115 3 100       11 if (@_) {
116 2         10 my %opts = looks_like_hash @_;
117 2         8 for my $key ( keys %opts ) {
118 11         25 my $mutator = lc $key;
119 11         37 $mutator =~ s/^-/set_/;
120 11         46 $self->$mutator( $opts{$key} );
121             }
122             }
123 3         14 return $self;
124             }
125              
126             sub _cascading_setter {
127 2     2   7 my ( $self, $value ) = @_;
128 2         12 my ( $package, $filename, $line, $subroutine ) = caller(1);
129 2         13 $subroutine =~ s/.*://;
130 2         13 $logger->debug($subroutine);
131 2 50       7 if ( my $tree = $self->get_tree ) {
132 0 0       0 if ( $tree->can($subroutine) ) {
133 0         0 $tree->$subroutine($value);
134             }
135             }
136 2         8 $subroutine =~ s/^set_//;
137 2         6 $self->{ uc $subroutine } = $value;
138 2         6 return $self;
139             }
140              
141             sub _cascading_getter {
142 51     51   100 my ( $self, $invocant ) = @_;
143 51         252 my ( $package, $filename, $line, $subroutine ) = caller(1);
144 51         248 $subroutine =~ s/.*://;
145 51         204 $logger->debug($subroutine);
146 51 100       114 if ( $invocant ) {
147            
148             # The general idea is that there are certain properties that can potentially be
149             # set globally (i.e. in this package) or at the level of the object it applies
150             # to. For example, maybe we want to set the node radius globally here, or maybe
151             # we want to set it on the node. The idea, here, is then that we might first
152             # check to see if the values are set on $invocant, and if not, return the global
153             # value. The way this used to be done was by calling ->can(), however, because of
154             # the way in which method calls are handled by the Draw*Role classes, we can't
155             # do that.
156             #if ( $invocant->can($subroutine) ) {
157 31         209 my $value = $invocant->$subroutine();
158 31 50       83 if ( defined $value ) {
159 0         0 return $value;
160             }
161             #}
162             }
163 51         186 $subroutine =~ s/^get_//;
164 51         265 return $self->{ uc $subroutine };
165             }
166              
167             =back
168              
169             =head2 MUTATORS
170              
171             =over
172              
173             =item set_format()
174              
175             Sets image format.
176              
177             Type : Mutator
178             Title : set_format
179             Usage : $treedrawer->set_format('Svg');
180             Function: Sets the drawer submodule.
181             Returns :
182             Args : Name of an image format
183              
184             =cut
185              
186             sub set_format {
187 3     3 1 9 my ( $self, $format ) = @_;
188 3         10 $format = ucfirst( lc($format) );
189 3 50       15 if ( looks_like_class __PACKAGE__ . '::' . $format ) {
190 3         13 $self->{'FORMAT'} = $format;
191 3         18 return $self;
192             }
193             else {
194 0         0 throw 'BadFormat' => "'$format' is not a valid image format";
195             }
196             }
197              
198             =item set_width()
199              
200             Sets image width.
201              
202             Type : Mutator
203             Title : set_width
204             Usage : $treedrawer->set_width(1000);
205             Function: sets the width of the drawer canvas.
206             Returns :
207             Args : Integer width in pixels.
208              
209             =cut
210              
211             sub set_width {
212 3     3 1 14 my ( $self, $width ) = @_;
213 3 50 33     13 if ( looks_like_number $width && $width > 0 ) {
214 3         12 $self->{'WIDTH'} = $width;
215             }
216             else {
217 0         0 throw 'BadNumber' => "'$width' is not a valid image width";
218             }
219 3         16 return $self;
220             }
221              
222             =item set_height()
223              
224             Sets image height.
225              
226             Type : Mutator
227             Title : set_height
228             Usage : $treedrawer->set_height(1000);
229             Function: sets the height of the canvas.
230             Returns :
231             Args : Integer height in pixels.
232              
233             =cut
234              
235             sub set_height {
236 3     3 1 9 my ( $self, $height ) = @_;
237 3 50 33     15 if ( looks_like_number $height && $height > 0 ) {
238 3         14 $self->{'HEIGHT'} = $height;
239             }
240             else {
241 0         0 throw 'BadNumber' => "'$height' is not a valid image height";
242             }
243 3         8 return $self;
244             }
245              
246             =item set_mode()
247              
248             Sets tree drawing mode.
249              
250             Type : Mutator
251             Title : set_mode
252             Usage : $treedrawer->set_mode('clado');
253             Function: Sets the tree mode, i.e. cladogram
254             or phylogram.
255             Returns : Invocant.
256             Args : String, [clado|phylo]
257              
258             =cut
259              
260             sub set_mode {
261 3     3 1 13 my ( $self, $mode ) = @_;
262 3 50       17 if ( $mode =~ m/^(?:clado|phylo)$/i ) {
263 3         14 $self->{'MODE'} = uc $mode;
264             }
265             else {
266 0         0 throw 'BadFormat' => "'$mode' is not a valid drawing mode";
267             }
268 3         8 return $self;
269             }
270              
271             =item set_shape()
272              
273             Sets tree drawing shape.
274              
275             Type : Mutator
276             Title : set_shape
277             Usage : $treedrawer->set_shape('rect');
278             Function: Sets the tree shape, i.e.
279             rectangular, diagonal, curvy or radial.
280             Returns : Invocant.
281             Args : String, [rect|diag|curvy|radial]
282              
283             =cut
284              
285             sub set_shape {
286 3     3 1 8 my ( $self, $shape ) = @_;
287 3 50       20 if ( $shape =~ m/^(?:rect|diag|curvy|radial|unrooted)/i ) {
288 3         8 $self->{'SHAPE'} = uc $shape;
289             }
290             else {
291 0         0 throw 'BadFormat' => "'$shape' is not a valid drawing shape";
292             }
293 3         61 return $self;
294             }
295              
296             =item set_padding()
297              
298             Sets image padding.
299              
300             Type : Mutator
301             Title : set_padding
302             Usage : $treedrawer->set_padding(100);
303             Function: Sets the canvas padding.
304             Returns :
305             Args : Integer value in pixels.
306              
307             =cut
308              
309             sub set_padding {
310 1     1 1 4 my ( $self, $padding ) = @_;
311 1 50 33     3 if ( looks_like_number $padding && $padding > 0 ) {
312 1         3 $self->{'PADDING'} = $padding;
313             }
314             else {
315 0         0 throw 'BadNumber' => "'$padding' is not a valid padding value";
316             }
317 1         4 return $self;
318             }
319              
320             =item set_text_horiz_offset()
321              
322             Sets text horizontal offset.
323              
324             Type : Mutator
325             Title : set_text_horiz_offset
326             Usage : $treedrawer->set_text_horiz_offset(5);
327             Function: Sets the distance between
328             tips and text, in pixels.
329             Returns :
330             Args : Integer value in pixels.
331              
332             =cut
333              
334             sub set_text_horiz_offset {
335 1     1 1 3 my ( $self, $offset ) = @_;
336 1 50       4 if ( looks_like_number $offset ) {
337 1         4 $self->{'TEXT_HORIZ_OFFSET'} = $offset;
338             }
339             else {
340 0         0 throw 'BadNumber' =>
341             "'$offset' is not a valid text horizontal offset value";
342             }
343 1         3 return $self;
344             }
345              
346             =item set_text_vert_offset()
347              
348             Sets text vertical offset.
349              
350             Type : Mutator
351             Title : set_text_vert_offset
352             Usage : $treedrawer->set_text_vert_offset(3);
353             Function: Sets the text baseline
354             relative to the tips, in pixels.
355             Returns :
356             Args : Integer value in pixels.
357              
358             =cut
359              
360             sub set_text_vert_offset {
361 1     1 1 3 my ( $self, $offset ) = @_;
362 1 50       4 if ( looks_like_number $offset ) {
363 1         2 $self->{'TEXT_VERT_OFFSET'} = $offset;
364             }
365             else {
366 0         0 throw 'BadNumber' =>
367             "'$offset' is not a valid text vertical offset value";
368             }
369 1         4 return $self;
370             }
371              
372             =item set_text_width()
373              
374             Sets text width.
375              
376             Type : Mutator
377             Title : set_text_width
378             Usage : $treedrawer->set_text_width(150);
379             Function: Sets the canvas width for
380             terminal taxon names.
381             Returns :
382             Args : Integer value in pixels.
383              
384             =cut
385              
386             sub set_text_width {
387 1     1 1 3 my ( $self, $width ) = @_;
388 1 50 33     3 if ( looks_like_number $width && $width > 0 ) {
389 1         3 $self->{'TEXT_WIDTH'} = $width;
390             }
391             else {
392 0         0 throw 'BadNumber' => "'$width' is not a valid text width value";
393             }
394 1         4 return $self;
395             }
396              
397             =item set_tree()
398              
399             Sets tree to draw.
400              
401             Type : Mutator
402             Title : set_tree
403             Usage : $treedrawer->set_tree($tree);
404             Function: Sets the Bio::Phylo::Forest::Tree
405             object to unparse.
406             Returns :
407             Args : A Bio::Phylo::Forest::Tree object.
408              
409             =cut
410              
411             sub set_tree {
412 3     3 1 16 my ( $self, $tree ) = @_;
413 3 50       14 if ( looks_like_object $tree, _TREE_ ) {
414 3         37 $self->{'TREE'} = $tree->negative_to_zero;
415 3         36 my $root = $tree->get_root;
416 3 50       11 if ( my $length = $root->get_branch_length ) {
417 0         0 $logger->warn("Collapsing root branch length of $length");
418 0         0 $root->set_branch_length(0);
419             }
420             }
421 3         12 return $self;
422             }
423              
424             =item set_scale_options()
425              
426             Sets time scale options.
427              
428             Type : Mutator
429             Title : set_scale_options
430             Usage : $treedrawer->set_scale_options(
431             -width => 400,
432             -major => '10%', # major cross hatch interval
433             -minor => '2%', # minor cross hatch interval
434             -blocks => '10%', # alternating blocks in light-gray
435             -label => 'MYA',
436             -reverse => 1, # tips are 0
437             -tmpl => '%d', # sprintf template for major cross hatch numbers
438             -font => {
439             -face => 'Verdana',
440             -size => 11,
441             }
442             );
443             Function: Sets the options for time (distance) scale
444             Returns :
445             Args :
446             -width => 400,
447             -major => '10%', # major cross hatch interval
448             -minor => '2%', # minor cross hatch interval
449             -blocks => '10%', # alternating blocks in light-gray
450             -label => 'MYA',
451             -reverse => 1, # tips are 0
452             -tmpl => '%d', # sprintf template for major cross hatch numbers
453             -font => {
454             -face => 'Verdana',
455             -size => 11,
456             }
457              
458             =cut
459              
460             sub set_scale_options {
461 2     2 1 12 my $self = shift;
462 2 50 33     19 if ( ( @_ && !scalar @_ % 2 ) || ( scalar @_ == 1 && ref $_[0] eq 'HASH' ) ) {
      0        
      33        
463 2         4 my %o; # %options
464 2 50 33     10 if ( scalar @_ == 1 && ref $_[0] eq 'HASH' ) {
465 0         0 %o = %{ $_[0] };
  0         0  
466             }
467             else {
468 2         10 %o = looks_like_hash @_;
469             }
470            
471             # copy verbatim
472 2         8 $self->{'SCALE'}->{'-label'} = $o{'-label'};
473 2         5 $self->{'SCALE'}->{'-units'} = $o{'-units'};
474 2         11 $self->{'SCALE'}->{'-reverse'} = $o{'-reverse'};
475 2         5 $self->{'SCALE'}->{'-font'} = $o{'-font'};
476 2         4 $self->{'SCALE'}->{'-tmpl'} = $o{'-tmpl'};
477 2         5 $self->{'SCALE'}->{'-blocks'} = $o{'-blocks'};
478            
479             # set scale width, either pixels or relative to tree
480 2 50 33     7 if ( looks_like_number $o{'-width'} or $o{'-width'} =~ m/^\d+%$/ ) {
481 2         6 $self->{'SCALE'}->{'-width'} = $o{'-width'};
482             }
483             else {
484 0         0 throw 'BadArgs' => "\"$o{'-width'}\" is invalid for '-width'";
485             }
486            
487             # set major tick mark distances
488 2 50 33     9 if ( looks_like_number $o{'-major'} or $o{'-major'} =~ m/^\d+%$/ ) {
489 2         8 $self->{'SCALE'}->{'-major'} = $o{'-major'};
490             }
491             else {
492 0         0 throw 'BadArgs' => "\"$o{'-major'}\" is invalid for '-major'";
493             }
494            
495             # set minor tick mark distances
496 2 50 33     7 if ( looks_like_number $o{'-minor'} or $o{'-minor'} =~ m/^\d+%$/ ) {
497 2         8 $self->{'SCALE'}->{'-minor'} = $o{'-minor'};
498             }
499             else {
500 0         0 throw 'BadArgs' => "\"$o{'-minor'}\" is invalid for '-minor'";
501             }
502             }
503             else {
504 0         0 throw 'OddHash' => 'Odd number of elements in hash assignment';
505             }
506 2         8 return $self;
507             }
508              
509             =item set_pie_colors
510              
511             Sets a hash reference whose keys are (unique) names for the different segments in a
512             likelihood pie chart, and whose values are color codes.
513              
514             Type : Mutator
515             Title : set_pie_colors
516             Usage : $treedrawer->set_pie_colors({ 'p1' => 'red', 'p2' => 'blue' });
517             Function: sets likelihood pie colors
518             Returns :
519             Args : HASH
520              
521             =cut
522              
523             sub set_pie_colors {
524 0     0 1 0 my ( $self, $hash ) = @_;
525 0 0       0 if ( ref($hash) eq 'HASH' ) {
526 0         0 $self->{'PIE_COLORS'} = $hash;
527             }
528             else {
529 0         0 throw 'BadArgs' => "Not a hash reference!";
530             }
531 0         0 return $self;
532             }
533              
534             =back
535              
536             =head2 CASCADING MUTATORS
537              
538             =over
539              
540             =item set_branch_width()
541              
542             Sets branch width.
543              
544             Type : Mutator
545             Title : set_branch_width
546             Usage : $treedrawer->set_branch_width(1);
547             Function: sets the width of branch lines
548             Returns :
549             Args : Integer width in pixels.
550              
551             =cut
552              
553             sub set_branch_width {
554 0     0 1 0 my ( $self, $width ) = @_;
555 0 0 0     0 if ( looks_like_number $width && $width > 0 ) {
556 0         0 $self->_cascading_setter($width);
557             }
558             else {
559 0         0 throw 'BadNumber' => "'$width' is not a valid branch width";
560             }
561 0         0 return $self;
562             }
563              
564             =item set_node_radius()
565              
566             Sets node radius.
567              
568             Type : Mutator
569             Title : set_node_radius
570             Usage : $treedrawer->set_node_radius(20);
571             Function: Sets the node radius in pixels.
572             Returns :
573             Args : Integer value in pixels.
574              
575             =cut
576              
577             sub set_node_radius {
578 2     2 1 5 my ( $self, $radius ) = @_;
579 2 50 33     7 if ( looks_like_number $radius && $radius >= 0 ) {
580 2         8 $self->_cascading_setter($radius);
581             }
582             else {
583 0         0 throw 'BadNumber' => "'$radius' is not a valid node radius value";
584             }
585 2         7 return $self;
586             }
587              
588             =item set_collapsed_clade_width()
589              
590             Sets collapsed clade width.
591              
592             Type : Mutator
593             Title : set_collapsed_clade_width
594             Usage : $treedrawer->set_collapsed_clade_width(6);
595             Function: sets the width of collapsed clade triangles relative to uncollapsed tips
596             Returns :
597             Args : Positive number
598              
599             =cut
600              
601             sub set_collapsed_clade_width {
602 0     0 1 0 my ( $self, $width ) = @_;
603 0 0 0     0 if ( looks_like_number $width && $width > 0 ) {
604 0         0 $self->_cascading_setter($width);
605             }
606             else {
607 0         0 throw 'BadNumber' => "'$width' is not a valid image width";
608             }
609 0         0 return $self;
610             }
611              
612             =item set_clade_label_width
613              
614             Sets clade label width, i.e. the spacing between nested clade annotations
615              
616             Type : Mutator
617             Title : set_clade_label_width
618             Usage : $treedrawer->set_clade_label_width(6);
619             Function: sets the spacing between nested clade annotations
620             Returns :
621             Args : Positive number
622              
623             =cut
624              
625             sub set_clade_label_width {
626 0     0 1 0 my ( $self, $width ) = @_;
627 0 0 0     0 if ( looks_like_number $width && $width >= 0 ) {
628 0         0 $self->{'CLADE_LABEL_WIDTH'} = $width;
629             }
630             else {
631 0         0 throw 'BadNumber' => "'$width' is not a valid clade label width value";
632             }
633 0         0 return $self;
634             }
635              
636             =item set_tip_radius()
637              
638             Sets tip radius.
639              
640             Type : Mutator
641             Title : set_tip_radius
642             Usage : $treedrawer->set_tip_radius(20);
643             Function: Sets the tip radius in pixels.
644             Returns :
645             Args : Integer value in pixels.
646              
647             =cut
648              
649             sub set_tip_radius {
650 0     0 1 0 my ( $self, $radius ) = @_;
651 0 0 0     0 if ( looks_like_number $radius && $radius >= 0 ) {
652 0         0 $self->_cascading_setter($radius);
653             }
654             else {
655 0         0 throw 'BadNumber' => "'$radius' is not a valid tip radius value";
656             }
657 0         0 return $self;
658             }
659              
660             =back
661              
662             =head2 ACCESSORS
663              
664             =over
665              
666             =item get_format()
667              
668             Gets image format.
669              
670             Type : Accessor
671             Title : get_format
672             Usage : my $format = $treedrawer->get_format;
673             Function: Gets the image format.
674             Returns :
675             Args : None.
676              
677             =cut
678              
679 3     3 1 24 sub get_format { shift->{'FORMAT'} }
680              
681             =item get_format_mime()
682              
683             Gets image format as MIME type.
684              
685             Type : Accessor
686             Title : get_format_mime
687             Usage : print "Content-type: ", $treedrawer->get_format_mime, "\n\n";
688             Function: Gets the image format as MIME type.
689             Returns :
690             Args : None.
691              
692             =cut
693              
694             sub get_format_mime {
695 0     0 1 0 my $self = shift;
696 0         0 my %mapping = (
697             'canvas' => 'text/html',
698             'gif' => 'image/gif',
699             'jpeg' => 'image/jpeg',
700             'pdf' => 'application/pdf',
701             'png' => 'image/png',
702             'processing' => 'text/plain',
703             'svg' => 'image/svg+xml',
704             'swf' => 'application/x-shockwave-flash',
705             );
706 0         0 return $mapping{ lc $self->get_format };
707             }
708              
709             =item get_width()
710              
711             Gets image width.
712              
713             Type : Accessor
714             Title : get_width
715             Usage : my $width = $treedrawer->get_width;
716             Function: Gets the width of the drawer canvas.
717             Returns :
718             Args : None.
719              
720             =cut
721              
722 6     6 1 25 sub get_width { shift->{'WIDTH'} }
723              
724             =item get_height()
725              
726             Gets image height.
727              
728             Type : Accessor
729             Title : get_height
730             Usage : my $height = $treedrawer->get_height;
731             Function: Gets the height of the canvas.
732             Returns :
733             Args : None.
734              
735             =cut
736              
737 13     13 1 91 sub get_height { shift->{'HEIGHT'} }
738              
739             =item get_mode()
740              
741             Gets tree drawing mode.
742              
743             Type : Accessor
744             Title : get_mode
745             Usage : my $mode = $treedrawer->get_mode('clado');
746             Function: Gets the tree mode, i.e. cladogram or phylogram.
747             Returns :
748             Args : None.
749              
750             =cut
751              
752 3     3 1 19 sub get_mode { shift->{'MODE'} }
753              
754             =item get_shape()
755              
756             Gets tree drawing shape.
757              
758             Type : Accessor
759             Title : get_shape
760             Usage : my $shape = $treedrawer->get_shape;
761             Function: Gets the tree shape, i.e. rectangular,
762             diagonal, curvy or radial.
763             Returns :
764             Args : None.
765              
766             =cut
767              
768 17     17 1 62 sub get_shape { shift->{'SHAPE'} }
769              
770             =item get_padding()
771              
772             Gets image padding.
773              
774             Type : Accessor
775             Title : get_padding
776             Usage : my $padding = $treedrawer->get_padding;
777             Function: Gets the canvas padding.
778             Returns :
779             Args : None.
780              
781             =cut
782              
783 3     3 1 8 sub get_padding { shift->{'PADDING'} }
784              
785             =item get_text_horiz_offset()
786              
787             Gets text horizontal offset.
788              
789             Type : Accessor
790             Title : get_text_horiz_offset
791             Usage : my $text_horiz_offset =
792             $treedrawer->get_text_horiz_offset;
793             Function: Gets the distance between
794             tips and text, in pixels.
795             Returns : SCALAR
796             Args : None.
797              
798             =cut
799              
800 17     17 1 83 sub get_text_horiz_offset { shift->{'TEXT_HORIZ_OFFSET'} }
801              
802             =item get_text_vert_offset()
803              
804             Gets text vertical offset.
805              
806             Type : Accessor
807             Title : get_text_vert_offset
808             Usage : my $text_vert_offset =
809             $treedrawer->get_text_vert_offset;
810             Function: Gets the text baseline relative
811             to the tips, in pixels.
812             Returns :
813             Args : None.
814              
815             =cut
816              
817 14     14 1 104 sub get_text_vert_offset { shift->{'TEXT_VERT_OFFSET'} }
818              
819             =item get_text_width()
820              
821             Gets text width.
822              
823             Type : Accessor
824             Title : get_text_width
825             Usage : my $textwidth =
826             $treedrawer->get_text_width;
827             Function: Returns the canvas width
828             for terminal taxon names.
829             Returns :
830             Args : None.
831              
832             =cut
833              
834 3     3 1 11 sub get_text_width { shift->{'TEXT_WIDTH'} }
835              
836             =item get_tree()
837              
838             Gets tree to draw.
839              
840             Type : Accessor
841             Title : get_tree
842             Usage : my $tree = $treedrawer->get_tree;
843             Function: Returns the Bio::Phylo::Forest::Tree
844             object to unparse.
845             Returns : A Bio::Phylo::Forest::Tree object.
846             Args : None.
847              
848             =cut
849              
850 31     31 1 103 sub get_tree { shift->{'TREE'} }
851              
852             =item get_scale_options()
853              
854             Gets time scale option.
855              
856             Type : Accessor
857             Title : get_scale_options
858             Usage : my %options = %{
859             $treedrawer->get_scale_options
860             };
861             Function: Returns the time/distance
862             scale options.
863             Returns : A hash ref.
864             Args : None.
865              
866             =cut
867              
868 3     3 1 20 sub get_scale_options { shift->{'SCALE'} }
869              
870             =item get_pie_colors
871              
872             Gets a hash reference whose keys are (unique) names for the different segments in a
873             likelihood pie chart, and whose values are color codes.
874              
875             Type : Accessor
876             Title : get_pie_colors
877             Usage : my %h = %{ $treedrawer->get_pie_colors() };
878             Function: gets likelihood pie colors
879             Returns :
880             Args : None
881              
882             =cut
883              
884 2     2 1 9 sub get_pie_colors { shift->{'PIE_COLORS'} }
885              
886             =item get_clade_label_width
887              
888             Gets clade label width, i.e. the spacing between nested clade annotations
889              
890             Type : Mutator
891             Title : get_clade_label_width
892             Usage : my $width = $treedrawer->get_clade_label_width();
893             Function: gets the spacing between nested clade annotations
894             Returns :
895             Args : None
896              
897             =cut
898              
899 0     0 1 0 sub get_clade_label_width { shift->{'CLADE_LABEL_WIDTH'} }
900              
901             =back
902              
903             =head2 CASCADING ACCESSORS
904              
905             =over
906              
907             =item get_branch_width()
908              
909             Gets branch width.
910              
911             Type : Accessor
912             Title : get_branch_width
913             Usage : my $w = $treedrawer->get_branch_width();
914             Function: gets the width of branch lines
915             Returns :
916             Args : Integer width in pixels.
917              
918             =cut
919              
920             sub get_branch_width {
921 14     14 1 25 my $self = shift;
922 14         39 return $self->_cascading_getter(@_);
923             }
924              
925             =item get_collapsed_clade_width()
926              
927             Gets collapsed clade width.
928              
929             Type : Mutator
930             Title : get_collapsed_clade_width
931             Usage : $w = $treedrawer->get_collapsed_clade_width();
932             Function: gets the width of collapsed clade triangles relative to uncollapsed tips
933             Returns : Positive number
934             Args : None
935              
936             =cut
937              
938             sub get_collapsed_clade_width {
939 3     3 1 5 my $self = shift;
940 3         14 return $self->_cascading_getter(@_);
941             }
942              
943             =item get_node_radius()
944              
945             Gets node radius.
946              
947             Type : Accessor
948             Title : get_node_radius
949             Usage : my $node_radius = $treedrawer->get_node_radius;
950             Function: Gets the node radius in pixels.
951             Returns : SCALAR
952             Args : None.
953              
954             =cut
955              
956             sub get_node_radius {
957 14     14 1 24 my $self = shift;
958 14         41 return $self->_cascading_getter(@_);
959             }
960              
961             =item get_tip_radius()
962              
963             Gets tip radius.
964              
965             Type : Accessor
966             Title : get_tip_radius
967             Usage : my $tip_radius = $treedrawer->get_tip_radius;
968             Function: Gets the tip radius in pixels.
969             Returns : SCALAR
970             Args : None.
971              
972             =cut
973              
974             sub get_tip_radius {
975 20     20 1 35 my $self = shift;
976 20         57 return $self->_cascading_getter(@_);
977             }
978              
979             =begin comment
980              
981             Type : Internal method.
982             Title : _set_scalex
983             Usage : $treedrawer->_set_scalex($scalex);
984             Function:
985             Returns :
986             Args :
987              
988             =end comment
989              
990             =cut
991              
992             sub _set_scalex {
993 3     3   6 my $self = shift;
994 3 50       11 if ( looks_like_number $_[0] ) {
995 3         8 $self->{'_SCALEX'} = $_[0];
996             }
997             else {
998 0         0 throw 'BadNumber' => "\"$_[0]\" is not a valid number value";
999             }
1000 3         9 return $self;
1001             }
1002 0     0   0 sub _get_scalex { shift->{'_SCALEX'} }
1003              
1004             =begin comment
1005              
1006             Type : Internal method.
1007             Title : _set_scaley
1008             Usage : $treedrawer->_set_scaley($scaley);
1009             Function:
1010             Returns :
1011             Args :
1012              
1013             =end comment
1014              
1015             =cut
1016              
1017             sub _set_scaley {
1018 3     3   7 my $self = shift;
1019 3 50       14 if ( looks_like_number $_[0] ) {
1020 3         11 $self->{'_SCALEY'} = $_[0];
1021             }
1022             else {
1023 0         0 throw 'BadNumber' => "\"$_[0]\" is not a valid integer value";
1024             }
1025 3         8 return $self;
1026             }
1027 0     0   0 sub _get_scaley { shift->{'_SCALEY'} }
1028              
1029             =back
1030              
1031             =head2 TREE DRAWING
1032              
1033             =over
1034              
1035             =item draw()
1036              
1037             Creates tree drawing.
1038              
1039             Type : Unparsers
1040             Title : draw
1041             Usage : my $drawing = $treedrawer->draw;
1042             Function: Unparses a Bio::Phylo::Forest::Tree
1043             object into a drawing.
1044             Returns : SCALAR
1045             Args :
1046              
1047             =cut
1048              
1049             sub draw {
1050 3     3 1 15 my $self = shift;
1051 3 50       11 if ( !$self->get_tree ) {
1052 0         0 throw 'BadArgs' => "Can't draw an undefined tree";
1053             }
1054 3         11 my $root = $self->get_tree->get_root;
1055              
1056             # Reset the stored data in the tree
1057 3         14 $self->_reset_internal($root);
1058 3         11 $logger->debug("going to compute coordinates");
1059 3         14 $self->compute_coordinates;
1060 3         22 $logger->debug("going to render figure");
1061 3         13 return $self->render;
1062             }
1063              
1064             sub compute_coordinates {
1065 3     3 0 8 my $self = shift;
1066 3 50       23 if ( $self->get_shape =~ m/(?:radial|unrooted)/i ) {
1067 0         0 $self->_compute_unrooted_coordinates;
1068             }
1069             else {
1070 3         12 $self->_compute_rooted_coordinates;
1071             }
1072 3         7 return $self;
1073             }
1074              
1075             sub polar_to_cartesian {
1076 0     0 0 0 my ( $self, $radius, $angleInDegrees ) = @_;
1077 0         0 my $angleInRadians = $angleInDegrees * $PI / 180.0;
1078 0         0 my $x = $radius * cos($angleInRadians);
1079 0         0 my $y = $radius * sin($angleInRadians);
1080 0         0 return $x, $y;
1081             }
1082              
1083             sub cartesian_to_polar {
1084 0     0 0 0 my ( $self, $x, $y ) = @_;
1085 0         0 my $angleInDegrees = atan2( $y, $x ) / $PI * 180;
1086 0         0 my $radius = sqrt( $y ** 2 + $x ** 2 );
1087 0         0 return $radius, $angleInDegrees;
1088             }
1089              
1090             sub _compute_unrooted_coordinates {
1091 0     0   0 my $self = shift;
1092 0         0 my $tre = $self->get_tree;
1093 0 0       0 my $phy = $self->get_mode =~ /^p/i ? $tre->is_cladogram ? 0 : 1 : 0; # phylogram?
    0          
1094            
1095             # compute unscaled rotation, depth and tip count
1096 0         0 my ( %unscaled_rotation, %depth );
1097 0         0 my ( $total_tips, $total_depth ) = ( 0, 0 );
1098            
1099             $tre->visit_depth_first(
1100             # process tips first
1101             '-no_daughter' => sub {
1102 0     0   0 my $node = shift;
1103 0         0 my $id = $node->get_id;
1104 0         0 ( $unscaled_rotation{$id}, $depth{$id} ) = ( $total_tips, 0 );
1105 0         0 $total_tips++;
1106             },
1107            
1108             # then process internals
1109             '-post_daughter' => sub {
1110 0     0   0 my $node = shift;
1111 0         0 my $id = $node->get_id;
1112            
1113             # get deepest child and average rotation
1114 0         0 my @child = @{ $node->get_children };
  0         0  
1115 0         0 my ( $unscaled_rotation, $depth ) = ( 0, 0 );
1116 0         0 for my $c ( @child ) {
1117 0         0 my $cid = $c->get_id;
1118 0         0 my $c_depth = $depth{$cid};
1119 0         0 $unscaled_rotation += $unscaled_rotation{$cid};
1120 0 0       0 $depth = $c_depth if $c_depth > $depth;
1121             }
1122            
1123             # increment depth
1124 0 0       0 if ( $phy ) {
1125 0         0 my @mapped = map { $_->get_branch_length } @child;
  0         0  
1126 0         0 my ($tallest) = sort { $b <=> $a } @mapped;
  0         0  
1127 0         0 $depth += $tallest;
1128             }
1129             else {
1130 0         0 $depth++;
1131             }
1132            
1133             # update rotation
1134 0         0 $unscaled_rotation /= scalar(@child);
1135            
1136             # check to see if current depth is overal deepest
1137 0 0       0 $total_depth = $depth if $depth > $total_depth;
1138            
1139             # store results
1140 0         0 ( $unscaled_rotation{$id}, $depth{$id} ) =
1141             ( $unscaled_rotation, $depth );
1142             },
1143 0         0 );
1144              
1145             # root, exactly centered on the canvas
1146 0         0 my $center_x = $self->get_width / 2;
1147 0         0 my $center_y = $self->get_height / 2;
1148            
1149 0         0 my $horiz_offset = $self->get_text_horiz_offset;
1150 0         0 my $text_width = $self->get_text_width;
1151 0         0 my $padding = $self->get_padding;
1152 0         0 my $range = $center_x - ( $horiz_offset + $text_width + $padding );
1153            
1154             # cladogram branch length
1155 0         0 $self->_set_scalex( $range / $total_depth );
1156 0         0 $self->_set_scaley( $range / $total_depth );
1157            
1158 0         0 for my $n ( @{ $tre->get_entities } ) {
  0         0  
1159 0 0       0 if ( $n->is_root ) {
1160 0         0 $n->set_x( $center_x );
1161 0         0 $n->set_y( $center_y );
1162             }
1163             else {
1164 0         0 my $id = $n->get_id;
1165 0         0 my ( $unscaled_rotation, $depth ) = ( $unscaled_rotation{$id}, $depth{$id} );
1166 0         0 my $radius = $self->_get_scalex * ( $depth - $total_depth ) * -1;
1167 0         0 my $rotation = $unscaled_rotation / $total_tips * 360;
1168 0         0 my ( $x, $y ) = $self->polar_to_cartesian( $radius, $rotation );
1169 0         0 $n->set_x( $x + $center_x );
1170 0         0 $n->set_y( $y + $center_y );
1171 0         0 $n->set_rotation( $rotation );
1172 0         0 $n->set_generic( 'radius' => $radius );
1173             }
1174             }
1175             }
1176              
1177             sub _compute_rooted_coordinates {
1178 3     3   7 my $td = shift;
1179 3         8 my $tree = $td->get_tree;
1180 3 100       11 my $phylo = $td->get_mode =~ /^p/i ? 1 : 0; # phylogram or cladogram
1181 3         9 my $padding = $td->get_padding;
1182 3         11 my $width = $td->get_width - ( $td->get_text_width + ( $padding * 2 ) );
1183 3         20 my $height = $td->get_height - ( $padding * 2 );
1184 3         12 my $cladew = $td->get_collapsed_clade_width;
1185 3         11 my ( $tip_counter, $tallest_tip ) = ( 0, 0 );
1186             $tree->visit_depth_first(
1187            
1188             # preprocess each node
1189             '-pre' => sub {
1190 17     17   28 my $node = shift;
1191 17 100       36 if ( my $parent = $node->get_parent ) {
1192 14   100     72 my $parent_x = $parent->get_x || 0;
1193 14 100 50     51 my $x = $phylo ? $node->get_branch_length || 0 : 1;
1194 14         76 $node->set_x( $x + $parent_x );
1195             }
1196             else {
1197 3         15 $node->set_x(0); # root
1198             }
1199             },
1200            
1201             # process this only on tips
1202             '-no_daughter' => sub {
1203 10     10   18 my $node = shift;
1204 10         50 $node->set_y( $tip_counter++ );
1205 10         55 my $x = $node->get_x;
1206 10 100       43 $tallest_tip = $x if $x > $tallest_tip;
1207             },
1208            
1209             # process this only on internal nodes
1210             '-post_daughter' => sub {
1211 7     7   11 my $node = shift;
1212 7         16 my ( $child_count, $child_y ) = ( 0, 0 );
1213 7         11 for my $child ( @{ $node->get_children } ) {
  7         17  
1214 14         23 $child_count++;
1215 14         85 $child_y += $child->get_y;
1216             }
1217 7         41 $node->set_y( $child_y / $child_count );
1218             },
1219 3         79 );
1220             $tree->visit(
1221             sub {
1222 17     17   23 my $node = shift;
1223 17 50       37 if ( not $tallest_tip ) {
1224 0         0 throw 'BadArgs' => "This tree has no branch lengths, can't draw a phylogram";
1225             }
1226 17         70 $node->set_x( $padding + $node->get_x * ( $width / $tallest_tip ) );
1227 17         78 $node->set_y( $padding + $node->get_y * ( $height / $tip_counter ) );
1228 17 100 100     77 if ( !$phylo && $node->is_terminal ) {
1229 3         21 $node->set_x( $padding + $tallest_tip * ( $width / $tallest_tip ) );
1230             }
1231             }
1232 3         81 );
1233 3         32 $td->_set_scaley( $height / $tip_counter );
1234 3         15 $td->_set_scalex( $width / $tallest_tip );
1235             }
1236              
1237             =item render()
1238              
1239             Renders tree based on pre-computed node coordinates. You would typically use
1240             this method if you have passed a Bio::Phylo::Forest::DrawTree on which you
1241             have already calculated the node coordinates separately.
1242              
1243             Type : Unparsers
1244             Title : render
1245             Usage : my $drawing = $treedrawer->render;
1246             Function: Unparses a Bio::Phylo::Forest::DrawTree
1247             object into a drawing.
1248             Returns : SCALAR
1249             Args :
1250              
1251             =cut
1252              
1253             sub render {
1254 3     3 1 8 my $self = shift;
1255 3         12 my $library =
1256             looks_like_class __PACKAGE__ . '::' . ucfirst( lc( $self->get_format ) );
1257 3         16 my $drawer = $library->_new(
1258             '-tree' => $self->get_tree,
1259             '-drawer' => $self
1260             );
1261 3         18 return $drawer->_draw;
1262             }
1263              
1264             =begin comment
1265              
1266             Type : Internal method.
1267             Title : _reset_internal
1268             Usage : $treedrawer->_reset_internal;
1269             Function: resets the set_generic values stored by Treedrawer, this must be
1270             called at the start of each draw command or weird results are obtained!
1271             Returns : nothing
1272             Args : treedrawer, node being processed
1273              
1274             =end comment
1275              
1276             =cut
1277              
1278             sub _reset_internal {
1279 17     17   29 my ( $self, $node ) = @_;
1280 17         36 my $tree = $self->get_tree;
1281 17         161 $node->set_x(undef);
1282 17         102 $node->set_y(undef);
1283 17         151 my $children = $node->get_children;
1284 17         47 for my $child (@$children) {
1285 14         36 _reset_internal( $self, $child );
1286             }
1287             }
1288              
1289             =back
1290              
1291             =head1 SEE ALSO
1292              
1293             There is a mailing list at L<https://groups.google.com/forum/#!forum/bio-phylo>
1294             for any user or developer questions and discussions.
1295              
1296             =over
1297              
1298             =item L<Bio::Phylo>
1299              
1300             The L<Bio::Phylo::Treedrawer> object inherits from the L<Bio::Phylo> object.
1301             Look there for more methods applicable to the treedrawer object.
1302              
1303             =item L<Bio::Phylo::Manual>
1304              
1305             Also see the manual: L<Bio::Phylo::Manual> and L<http://rutgervos.blogspot.com>.
1306              
1307             =back
1308              
1309             =head1 CITATION
1310              
1311             If you use Bio::Phylo in published research, please cite it:
1312              
1313             B<Rutger A Vos>, B<Jason Caravas>, B<Klaas Hartmann>, B<Mark A Jensen>
1314             and B<Chase Miller>, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
1315             I<BMC Bioinformatics> B<12>:63.
1316             L<http://dx.doi.org/10.1186/1471-2105-12-63>
1317              
1318             =cut
1319              
1320             1;