File Coverage

blib/lib/Bio/Phylo/Treedrawer.pm
Criterion Covered Total %
statement 188 306 61.4
branch 38 86 44.1
condition 17 55 30.9
subroutine 46 60 76.6
pod 38 41 92.6
total 327 548 59.6


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