Merge pull request #42 from solgenomics/topic/duplicate_image_warning
[cxgn-corelibs.git] / lib / CXGN / Phylo / Layout.pm
blob76ae4ae0c06cf34e5a2aadd2b5418d629eff45df
1 =head1 NAME
3 CXGN::Phylo::Layout - calculates the layout of a tree.
5 =head1 DESCRIPTION
7 The Layout class calculates the layout of a tree.
8 The layout object is then passed to a Renderer object, which
9 will render the tree.
11 These classes are used by the tree object L<CXGN::Phylo::Tree> to layout the tree.
13 The layout can be subclassed to achieve different tree layouts, such as in the examples below (rendering the tree from right to left instead of left to right, etc. The CXGN::Phylo::Tree object can then be made to use the alternate layout classes by using the set_layout() accessor in L<CXGN::Phylo::Tree>. The default layout for a Tree is CXGN::Phylo::Layout, which lays out the tree from left to right (the root is left and the labels are on the right).
15 This can be combined with alternate renderers (L<CXGN::Phylo::Renderer>) to achieve a large number of different tree output options. Note that it will not be possible to combine every Layout with any Renderer.
17 =head1 AUTHOR(S)
19 Lukas Mueller <lam87@cornell.edu>
21 =head1 METHODS
23 This class implements the following methods:
25 =cut
27 package CXGN::Phylo::Layout;
29 =head2 constructor new()
31 Usage: my $lo = CXGN::Phylo::Layout->new($tree);
32 Desc: creates a new layout object
33 Args: a CXGN::Phylo::Tree object
34 Side Effects: lays out the tree object
35 Example:
37 =cut
39 sub new {
40 my $class = shift;
41 my $tree = shift;
42 my $args = {};
43 my $self = bless $args, $class;
45 # initialize object properties
47 $self->set_tree($tree);
48 $self->set_top_margin(10);
49 $self->set_right_margin(10);
50 $self->set_bottom_margin(10);
51 $self->set_left_margin(10);
52 $self->set_label_margin(0);
53 $self->set_vertical_gap(0);
54 $self->set_horizontal_scaling_factor(0);
55 $self->set_image_width(100);
56 $self->set_image_height(100);
57 return $self;
60 =head2 accessors get_tree(), set_tree()
62 Synopsis: my $tree = $layout -> get_tree();
63 $layout -> set_tree($tree);
64 Property: the tree this layout object works on
65 Side effects:
66 Description:
68 =cut
70 sub get_tree {
71 my $self=shift;
72 return $self->{tree};
75 sub set_tree {
76 my $self=shift;
77 $self->{tree}=shift;
80 =head1 Functions controlling the the size and margins
83 =head2 accessors get_image_width(), set_image_width()
85 Synopsis: $lo->set_image_width(400);
86 Property: the width of the final image in pixels
87 Side effects:
88 Description:
90 =cut
92 sub get_image_width {
93 my $self=shift;
94 return $self->{image_width};
97 sub set_image_width {
98 my $self=shift;
99 $self->{image_width}=shift;
103 =head2 accessors get_image_height(), set_image_height()
105 Synopsis: $lo->set_image_height(300);
106 Property: the height of the final image in pixels
107 [integer]
108 Side effects:
109 Description:
111 =cut
113 sub get_image_height {
114 my $self=shift;
115 return $self->{image_height};
118 sub set_image_height {
119 my $self=shift;
120 $self->{image_height}=shift;
123 =head2 accessors get_top_margin(), set_top_margin()
125 Synopsis: $lo->set_top_margin(10);
126 Property: the margin at the top of the image
127 in pixels [integer]
128 Side effects:
129 Description:
131 =cut
133 sub get_top_margin {
134 my $self=shift;
135 return $self->{top_margin};
138 sub set_top_margin {
139 my $self=shift;
140 $self->{top_margin}=shift;
143 =head2 accessors get_left_margin(), set_margin_left()
145 Synopsis: $lo->set_left_margin(20);
146 Property: the margin at the left of the image
147 in pixels, integer.
148 Side effects:
149 Description:
151 =cut
153 sub get_left_margin {
154 my $self=shift;
155 return $self->{left_margin};
158 sub set_left_margin {
159 my $self=shift;
160 $self->{left_margin}=shift;
163 =head2 accessors get_right_margin(), set_right_margin()
165 Synopsis: $lo->set_right_margin(10);
166 Property: the right margin in pixels [integer]
167 Description:
169 =cut
171 sub get_right_margin {
172 my $self=shift;
173 return $self->{right_margin};
176 sub set_right_margin {
177 my $self=shift;
178 $self->{right_margin}=shift;
181 =head2 accessors get_bottom_margin(), set_bottom_margin()
183 Synopsis: $lo->set_bottom_margin(10);
184 Property: the bottom margin in pixels [integer]
185 Side effects:
186 Description:
188 =cut
190 sub get_bottom_margin {
191 my $self=shift;
192 return $self->{bottom_margin};
195 sub set_bottom_margin {
196 my $self=shift;
197 $self->{bottom_margin}=shift;
200 =head2 accessors get_label_margin(), set_label_margin()
202 Synopsis: $lo->set_label_margin(10)
203 Property: the margin between the label and the leaf node
204 in pixels [integer]
205 Side effects:
206 Description:
208 =cut
210 sub get_label_margin {
211 my $self=shift;
212 return $self->{label_margin};
215 sub set_label_margin {
216 my $self=shift;
217 $self->{label_margin}=shift;
218 # print STDERR "in set_label_margin. label margin set to: [", $self->get_label_margin(), "]\n";
222 =head1 Functions and properties used by the layout() function.
224 These should probably not be called directly, unless this class is sub-classed and layout() overridden.
226 =head2 accessors get_vertical_gap(), set_vertical_gap()
228 Synopsis: my $vertical_gap = $lo->get_vertical_gap();
229 Property: the vertical gap between leaf nodes.
230 Side effects:
231 Description:
233 =cut
235 sub get_vertical_gap {
236 my $self=shift;
237 return $self->{vertical_gap};
240 sub set_vertical_gap {
241 my $self=shift;
242 $self->{vertical_gap}=shift;
245 =head2 accessor get_horizontal_scaling_factor(), set_horizontal_scaling_factor()
247 Synopsis: $lo->set_horizontal_scaling_factor(2);
248 Property: the horizontal scaling factor [real]
249 Side effects:
250 Description: determines how the tree will be scaled horizontally,
251 in a conversion from branch length units to pixels.
253 =cut
255 sub get_horizontal_scaling_factor {
256 my $self=shift;
257 return $self->{horizontal_scaling_factor};
260 sub set_horizontal_scaling_factor {
261 my $self=shift;
262 $self->{horizontal_scaling_factor}=shift;
265 =head2 function layout()
267 Usage: $lo->layout()
268 Desc: does the layout calculation
269 Ret: nothing
270 Args: none
271 Side Effects:
272 Example:
274 =cut
276 sub layout {
277 my $self = shift;
279 # if labels are shown, calculate the longest label
280 my $longest_label = 0; # in pixels
281 foreach my $n ($self->get_tree()->get_leaf_list()) {
282 my $label = $n->get_label()->get_shown_name();
283 next if($n->get_hidden or !$n->is_leaf() or $n->get_hide_label()); # in these cases the label will not be shown, so don't consider its length.
284 if (length($label) > $longest_label) {
285 # print STDERR "in layout. new longest label: ", $label, "\n";
286 $longest_label= length($label);
289 #print STDERR "in layout. [", $longest_label, "][", $self->get_tree()->get_renderer()->get_font_width(), "]\n";
290 $self->set_label_margin($longest_label*$self->get_tree()->get_renderer()->get_font_width());
292 # perform some tree calculations
293 # -- get the distances from root, used for horizontal layout
294 my $bltype = "branch_length";
295 #$bltype = "square_root";
296 #$bltype = "proportion_different";
297 #$bltype = "equal";
298 $self->get_tree()->shown_branch_length_transformation_reset($bltype); # branch_length");
299 # $self->get_tree()->get_root()->calculate_distances_from_root(0.0, $bltype);
301 # -- get the leaf list, used for vertical layout
302 $self->get_tree()->get_leaf_list();
304 $self->_layout_horizontal();
305 $self->_layout_vertical();
308 =head2 function _layout_horizontal()
310 Usage: $lo->_layout_horizontal()
311 Desc: helper function for layout, which lays out the tree
312 in the horizontal dimension
313 Ret: nothing
314 Args: none
315 Side Effects: sets layout coordinates in the node objects
316 Example:
318 =cut
320 sub _layout_horizontal {
321 my $self = shift;
323 # calculate the distance of each node to the root
324 # $self->get_tree()->get_root()->calculate_distances_from_root();
325 my $longest = $self->get_tree()->calculate_longest_root_leaf_length();
327 #print STDERR "AAAAA in _layout_horizontal. longest: $longest , image width: ", $self->get_image_width(), "\n";
328 $longest = 1.0 if($longest<=0);
329 $self->get_tree()->set_longest_root_leaf_length($longest);
330 # $self->set_horizontal_scaling_factor($self->get_image_width()/$largest); # is this used??? yes, in renderer
331 $self->set_horizontal_scaling_factor($self->get_image_width()/$longest); # is this used??? yes, in renderer
333 # calculate all the horizontal coordinates for each node recursively
334 $self->recursive_horizontal_coords($self->get_tree()->get_root());
337 =head2 function _layout_vertical()
339 Synopsis: helper function to layout() that lays out the vertical
340 dimension of the tree
341 Arguments: none
342 Returns: nothing
343 Side effects: sets all the node coordinates, starting from the leaf nodes.
344 The leaf nodes are spread evenly across the image height, all
345 other node positions are calculated by recursing through the
346 tree and taking the average of the vertical coordinates of the
347 corresponding children nodes. Takes into account the margins as
348 set by set_top_margin() and set_bottom_margin().
349 Description:
351 =cut
353 sub _layout_vertical {
354 my $self = shift;
356 # get the total leaf count
357 my $total_leaves = $self->get_tree()->get_leaf_count();
358 my $image_height = $self->get_image_height();
359 if ($total_leaves < 1) { return; }
360 my $vertical_gap = ($image_height - $self->get_top_margin() - $self->get_bottom_margin()) / ($total_leaves);
362 # the leaf nodes should be easy.
363 # traverse the tree, find all the leaves, and set the vertical
364 # coordinate in the leaves to an increasing value with step $vertical_gap.
365 my @leaves = $self->get_tree()->get_leaf_list();
366 for (my $i=0; $i<@leaves; $i++) {
367 $leaves[$i]->set_Y($self->get_top_margin()+($i)*$vertical_gap);
370 $self->set_vertical_gap($vertical_gap);
371 # for the remaining nodes, we just take the average of the coords for the
372 # first and the last child.
373 $self->_recursive_vertical_coords($self->get_tree()->get_root());
377 sub _recursive_vertical_coords {
378 my $self = shift;
379 my $node = shift;
381 if ($node->is_leaf() || $node->get_hidden()) { return $node->get_vertical_coord(); }
383 # calculate average over all children
384 # if not is not hidden.
385 my $coord;
386 my @children = $node->get_children();
387 my $total = 0;
388 foreach my $c (@children) {
389 $total += $self->_recursive_vertical_coords($c);
392 $coord = $total/@children;
394 $node -> set_Y($coord);
396 # print STDERR "CHILD: ".$node->get_name()." COORD: ".$node->get_vertical_coord()."\n";
397 return $coord;
401 sub recursive_horizontal_coords {
402 my $self = shift;
403 my $node = shift;
405 # if the tree is undefined or all the branch lengths are zero, or
406 # the root node is hidden, we should not be doing this...
408 #print STDERR "in recursive_horizontal_coords. [", $self->get_tree()->get_longest_root_leaf_length(), "]\n";
409 if ($self->get_tree()->get_longest_root_leaf_length() == 0) { return; }
411 my @children = $node->get_children();
413 # if the node is hidden, don't proceed with the children! (all sub-children
414 # will be hidden too!)
416 if (!$node->get_hidden()) {
417 foreach my $c (@children) {
418 $self->recursive_horizontal_coords($c);
421 # print "dist from root, longest dist from root: " , $node->get_dist_from_root(), " ", $self->get_tree()->get_longest_root_leaf_length(), "\n";
422 my $normalized_dist_from_root = $node->get_dist_from_root()/($self->get_tree()->get_longest_root_leaf_length()); #between 0 and 1.
424 #print STDERR "in rec.horiz.coords. [", $self->get_image_width(), "][", $self->get_left_margin(), "][",
425 #$self->get_right_margin(), "][", $self->get_label_margin(), "]\n";
426 my $available_width = ($self->get_image_width()-$self->get_left_margin()-$self->get_right_margin()-$self->get_label_margin());
427 #print STDERR "in rec.horiz.coords. [", $normalized_dist_from_root, "][", $available_width, "][", $self->get_left_margin(), "]\n";
428 $node->set_X($self->get_left_margin()+$normalized_dist_from_root*$available_width);
429 # print STDERR "HORIZONTAL COORD: ".$node->get_X()."\n";
433 =head1 Package CXGN::Phylo::Layout_left_to_right;
435 This is just a subclass of Layout which does not perform any additional calculations.
437 =cut
439 package CXGN::Phylo::Layout_left_to_right;
441 use base qw/ CXGN::Phylo::Layout /;
444 =head1 Package CXGN::Phylo::Layout_right_to_left
446 Subclass of Layout. It performs the same calculations as layout, and then transforms
447 the resulting coordinates such that the tree will be displayed in a flipped orientation.
449 =cut
451 package CXGN::Phylo::Layout_right_to_left;
453 use base qw/ CXGN::Phylo::Layout /;
455 sub new {
456 my $class = shift;
457 my $self = $class->SUPER::new(@_);
458 return $self;
461 sub layout {
462 my $self = shift;
463 $self->SUPER::layout(@_);
464 $self->flip_coordinates_horizontally($self->get_tree()->get_root());
468 sub flip_coordinates_horizontally {
469 my $self = shift;
470 my $node = shift;
471 my ($x, $y) = ($node->get_X(), $node->get_Y());
472 $node->set_X($self->get_image_width()-$x);
473 $node->set_Y($y);
474 $node->get_label()->align_left();
475 foreach my $c ($node->get_children()) {
476 $self->flip_coordinates_horizontally($c);
481 =head1 Package CXGN::Phylo::Layout_top_to_bottom
483 Subclass of Layout. It performs the same calculations as layout, and then transforms
484 the resulting coordinates such that the tree will be displayed in a flipped orientation.
486 =cut
488 package CXGN::Phylo::Layout_top_to_bottom;
490 use base qw/ CXGN::Phylo::Layout /;
492 sub new {
493 my $class = shift;
494 my $self = $class->SUPER::new(@_);
495 return $self;
498 sub layout {
499 my $self = shift;
500 my ($width, $height) = ($self->get_image_width(),$self->get_image_height());
501 $self->set_image_width($height);
502 $self->set_image_height($width);
503 $self->SUPER::layout(@_);
504 $self->flip_coordinates_90_degrees($self->get_tree()->get_root());
505 $self->set_image_width($width);
506 $self->set_image_height($height);
510 sub flip_coordinates_90_degrees {
511 my $self = shift;
512 my $node = shift;
513 my ($x, $y) = ($node->get_X(), $node->get_Y());
514 $node->set_X($y);
515 $node->set_Y($x);
516 $node->get_label()->align_right();
517 foreach my $c ($node->get_children()) {
518 $self->flip_coordinates_90_degrees($c);
525 =head1 Package CXGN::Phylo::Layout_bottom_to_top
527 Subclass of Layout. It performs the same calculations as layout, and then transforms
528 the resulting coordinates such that the tree will be displayed in a flipped orientation.
530 =cut
532 package CXGN::Phylo::Layout_bottom_to_top;