A toolkit for working with phylogenetic data.
v0.19.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
rectangular_layout.cpp
Go to the documentation of this file.
1 /*
2  Genesis - A toolkit for working with phylogenetic data.
3  Copyright (C) 2014-2018 Lucas Czech and HITS gGmbH
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 
18  Contact:
19  Lucas Czech <lucas.czech@h-its.org>
20  Exelixis Lab, Heidelberg Institute for Theoretical Studies
21  Schloss-Wolfsbrunnenweg 35, D-69118 Heidelberg, Germany
22 */
23 
32 
34 
35 #include <algorithm>
36 #include <assert.h>
37 #include <stdexcept>
38 
39 namespace genesis {
40 namespace tree {
41 
42 // =================================================================================================
43 // Settings
44 // =================================================================================================
45 
47 {
48  width_ = value;
49  return *this;
50 }
51 
53 {
54  return width_;
55 }
56 
58 {
59  height_ = value;
60  return *this;
61 }
62 
64 {
65  return height_;
66 }
67 
68 // =================================================================================================
69 // Virtual Functions
70 // =================================================================================================
71 
72 utils::SvgDocument RectangularLayout::to_svg_document_() const
73 {
74  using namespace utils;
75  SvgDocument doc;
76  SvgGroup tree_lines;
77  SvgGroup taxa_names;
78  SvgGroup edge_shapes;
79  SvgGroup node_shapes;
80 
81  // If no width and/or height is set, use automatic ones:
82  // The height is at least 100, or depends on the node count, so that it scales well.
83  // The factor of six is chosen based on the default svg font on our test system.
84  // Might need to look into this for other systems.
85  // Also, circular trees use node count without a factor as the default radius.
86  // Because circumference is 2*pi*r, our factor of 6 is close to 2*pi,
87  // which makes the font spacing similar for circular and rectangular trees ;-)
88  // Furthermore, width is chosen to be half the height,
89  // which usually is a good aspect ratio for tree figures.
90  auto width = width_;
91  auto height = height_;
92  if( height <= 0.0 ) {
93  height = std::max( 100.0, 6.0 * static_cast<double>( tree().node_count() ));
94  }
95  if( width <= 0.0 ) {
96  width = height / 2.0;
97  }
98 
99  size_t max_text_len = 0;
100 
101  for( auto const& node_it : tree().nodes() ) {
102  auto const& node = *node_it;
103 
104  auto const& node_data = node.data<LayoutNodeData>();
105  auto const& prnt_data = tree().node_at( node_data.parent_index ).data<LayoutNodeData>();
106 
107  auto const node_x = node_data.distance * width;
108  auto const node_y = node_data.spreading * height;
109 
110  // Get the edge between the node and its parent.
111  auto edge_ptr = edge_between( node, tree().node_at( node_data.parent_index ) );
112 
113  // If there is an edge (i.e., we are not at the root), draw lines between the nodes.
114  if( edge_ptr ) {
115  auto const& edge_data = edge_ptr->data<LayoutEdgeData>();
116 
117  // Get line strokes
118  auto spreading_stroke = edge_data.spreading_stroke;
119  auto distance_stroke = edge_data.distance_stroke;
120  spreading_stroke.line_cap = utils::SvgStroke::LineCap::kSquare;
121  distance_stroke.line_cap = utils::SvgStroke::LineCap::kButt;
122 
123  // Calculate linear distance
124  auto const dist_start_x = prnt_data.distance * width;
125  auto const dist_start_y = node_y;
126 
127  // Draw lines
128  tree_lines << SvgLine(
129  prnt_data.distance * width, prnt_data.spreading * height,
130  dist_start_x, dist_start_y,
131  spreading_stroke
132  );
133  tree_lines << SvgLine(
134  dist_start_x, dist_start_y,
135  node_x, node_y,
136  distance_stroke
137  );
138 
139  // If there is an edge shape, draw it to the middle of the edge
140  if( ! edge_data.shape.empty() ) {
141  auto const shape_x = ( dist_start_x + node_x ) / 2.0;
142  auto const shape_y = ( dist_start_y + node_y ) / 2.0;
143 
144  auto es = edge_data.shape;
145  es.transform.append( SvgTransform::Translate( shape_x, shape_y ));
146  edge_shapes << std::move( es );
147  }
148 
149  } else {
150 
151  // If there is no edge, it must be the root.
152  assert( node.is_root() );
153  }
154 
155  // If the node has a name, print it.
156  if( node_data.name != "" ) {
157  // auto label = SvgText(
158  // node_data.name,
159  // SvgPoint( node_x + 5, node_y )
160  // );
161  // label.dy = "0.4em";
162 
163  auto label = text_template();
164  label.text = node_data.name;
165  label.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
166 
167  // Move label to tip node.
168  label.transform.append( SvgTransform::Translate( node_x + 5, node_y ));
169  taxa_names << std::move( label );
170  max_text_len = std::max( max_text_len, node_data.name.size() );
171  }
172 
173  // If there is a node shape, draw it.
174  if( ! node_data.shape.empty() ) {
175  auto ns = node_data.shape;
176  ns.transform.append( SvgTransform::Translate( node_x, node_y ));
177  node_shapes << std::move( ns );
178  }
179  }
180 
181  // Make sure that the drawing is done from outside to inside,
182  // so that the overlapping parts look nice.
183  tree_lines.reverse();
184 
185  // Set the margins according to longest label.
186  auto const marg_a = std::max( 20.0, text_template().font.size );
187  auto const marg_r = std::max( 25.0, max_text_len * text_template().font.size );
188  doc.margin = SvgMargin( marg_a, marg_r, marg_a, marg_a );
189 
190  // We are sure that we won't use the groups again, so let's move them!
191  doc << std::move( tree_lines );
192  if( ! taxa_names.empty() ) {
193  doc << std::move( taxa_names );
194  }
195  if( ! edge_shapes.empty() ) {
196  doc << std::move( edge_shapes );
197  }
198  if( ! node_shapes.empty() ) {
199  doc << std::move( node_shapes );
200  }
201  return doc;
202 }
203 
204 } // namespace tree
205 } // namespace genesis
TreeNode & node_at(size_t index)
Return the TreeNode at a certain index.
Definition: tree/tree.cpp:304
Tree operator functions.
TreeEdge * edge_between(TreeNode &lhs, TreeNode &rhs)
Return the TreeEdge between two TreeNode&s, if they are neighbours, or nullptr otherwise.
Tree const & tree() const
Definition: layout_base.cpp:69
size_t node_count(Tree const &tree)
Return the number of Nodes of a Tree. Same as Tree::node_count().
NodeDataType & data()
Definition: node.hpp:108
utils::SvgText & text_template()