A toolkit for working with phylogenetic data.
v0.20.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 
35 
36 #include <algorithm>
37 #include <assert.h>
38 #include <stdexcept>
39 
40 namespace genesis {
41 namespace tree {
42 
43 // =================================================================================================
44 // Settings
45 // =================================================================================================
46 
48 {
49  width_ = value;
50  return *this;
51 }
52 
54 {
55  return width_;
56 }
57 
59 {
60  height_ = value;
61  return *this;
62 }
63 
65 {
66  return height_;
67 }
68 
69 // =================================================================================================
70 // Virtual Functions
71 // =================================================================================================
72 
73 utils::SvgDocument RectangularLayout::to_svg_document_() const
74 {
75  using namespace utils;
76  SvgDocument doc;
77  SvgGroup tree_lines;
78  SvgGroup taxa_names;
79  SvgGroup edge_shapes;
80  SvgGroup node_shapes;
81 
82  // If no width and/or height is set, use automatic ones:
83  // The height is at least 100, or depends on the node count, so that it scales well.
84  // The factor of six is chosen based on the default svg font on our test system.
85  // Might need to look into this for other systems.
86  // Also, circular trees use node count without a factor as the default radius.
87  // Because circumference is 2*pi*r, our factor of 6 is close to 2*pi,
88  // which makes the font spacing similar for circular and rectangular trees ;-)
89  // Furthermore, width is chosen to be half the height,
90  // which usually is a good aspect ratio for tree figures.
91  auto width = width_;
92  auto height = height_;
93  if( height <= 0.0 ) {
94  height = std::max( 100.0, 6.0 * static_cast<double>( tree().node_count() ));
95  }
96  if( width <= 0.0 ) {
97  width = height / 2.0;
98  }
99 
100  size_t max_text_len = 0;
101 
102  for( auto const& node_it : tree().nodes() ) {
103  auto const& node = *node_it;
104 
105  auto const& node_data = node.data<LayoutNodeData>();
106  auto const& prnt_data = tree().node_at( node_data.parent_index ).data<LayoutNodeData>();
107 
108  auto const node_x = node_data.distance * width;
109  auto const node_y = node_data.spreading * height;
110 
111  // Get the edge between the node and its parent.
112  auto edge_ptr = edge_between( node, tree().node_at( node_data.parent_index ) );
113 
114  // If there is an edge (i.e., we are not at the root), draw lines between the nodes.
115  if( edge_ptr ) {
116  auto const& edge_data = edge_ptr->data<LayoutEdgeData>();
117 
118  // Get line strokes
119  auto spreading_stroke = edge_data.spreading_stroke;
120  auto distance_stroke = edge_data.distance_stroke;
121  spreading_stroke.line_cap = utils::SvgStroke::LineCap::kSquare;
122  distance_stroke.line_cap = utils::SvgStroke::LineCap::kButt;
123 
124  // Calculate linear distance
125  auto const dist_start_x = prnt_data.distance * width;
126  auto const dist_start_y = node_y;
127 
128  // Draw lines
129  tree_lines << SvgLine(
130  prnt_data.distance * width, prnt_data.spreading * height,
131  dist_start_x, dist_start_y,
132  spreading_stroke
133  );
134  tree_lines << SvgLine(
135  dist_start_x, dist_start_y,
136  node_x, node_y,
137  distance_stroke
138  );
139 
140  // If there is an edge shape, draw it to the middle of the edge
141  if( ! edge_data.shape.empty() ) {
142  auto const shape_x = ( dist_start_x + node_x ) / 2.0;
143  auto const shape_y = ( dist_start_y + node_y ) / 2.0;
144 
145  auto es = edge_data.shape;
146  es.transform.append( SvgTransform::Translate( shape_x, shape_y ));
147  edge_shapes << std::move( es );
148  }
149 
150  } else {
151 
152  // If there is no edge, it must be the root.
153  assert( is_root( node ));
154  }
155 
156  // If the node has a name, print it.
157  if( node_data.name != "" ) {
158  // auto label = SvgText(
159  // node_data.name,
160  // SvgPoint( node_x + 5, node_y )
161  // );
162  // label.dy = "0.4em";
163 
164  auto label = text_template();
165  label.text = node_data.name;
166  label.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
167 
168  // Move label to tip node.
169  label.transform.append( SvgTransform::Translate( node_x + 5, node_y ));
170  taxa_names << std::move( label );
171  max_text_len = std::max( max_text_len, node_data.name.size() );
172  }
173 
174  // If there is a node shape, draw it.
175  if( ! node_data.shape.empty() ) {
176  auto ns = node_data.shape;
177  ns.transform.append( SvgTransform::Translate( node_x, node_y ));
178  node_shapes << std::move( ns );
179  }
180  }
181 
182  // Make sure that the drawing is done from outside to inside,
183  // so that the overlapping parts look nice.
184  tree_lines.reverse();
185 
186  // Set the margins according to longest label.
187  auto const marg_a = std::max( 20.0, text_template().font.size );
188  auto const marg_r = std::max( 25.0, max_text_len * text_template().font.size );
189  doc.margin = SvgMargin( marg_a, marg_r, marg_a, marg_a );
190 
191  // We are sure that we won't use the groups again, so let's move them!
192  doc << std::move( tree_lines );
193  if( ! taxa_names.empty() ) {
194  doc << std::move( taxa_names );
195  }
196  if( ! edge_shapes.empty() ) {
197  doc << std::move( edge_shapes );
198  }
199  if( ! node_shapes.empty() ) {
200  doc << std::move( node_shapes );
201  }
202  return doc;
203 }
204 
205 } // namespace tree
206 } // namespace genesis
TreeNode & node_at(size_t index)
Return the TreeNode at a certain index.
Definition: tree/tree.cpp:304
Tree operator functions.
bool is_root(TreeNode const &node)
Return whether the node is the root of its Tree.
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:152
utils::SvgText & text_template()