A toolkit for working with phylogenetic data.
v0.22.1
rectangular_layout.cpp
Go to the documentation of this file.
1 /*
2  Genesis - A toolkit for working with phylogenetic data.
3  Copyright (C) 2014-2019 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 <cassert>
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_lines;
79  SvgGroup taxa_names;
80  SvgGroup edge_shapes;
81  SvgGroup node_shapes;
82 
83  // If no width and/or height is set, use automatic ones:
84  // The height is at least 100, or depends on the node count, so that it scales well.
85  // The factor of six is chosen based on the default svg font on our test system.
86  // Might need to look into this for other systems.
87  // Also, circular trees use node count without a factor as the default radius.
88  // Because circumference is 2*pi*r, our factor of 6 is close to 2*pi,
89  // which makes the font spacing similar for circular and rectangular trees ;-)
90  // Furthermore, width is chosen to be half the height,
91  // which usually is a good aspect ratio for tree figures.
92  auto width = width_;
93  auto height = height_;
94  if( height <= 0.0 ) {
95  height = std::max( 100.0, 6.0 * static_cast<double>( tree().node_count() ));
96  }
97  if( width <= 0.0 ) {
98  width = height / 2.0;
99  }
100 
101  size_t max_text_len = 0;
102 
103  for( auto const& node : tree().nodes() ) {
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  // In the following, we will draw the label and the spacer (if labels shall be aligned).
157  // As aligning chances the x dist of the label, we store it here first, change if needed,
158  // and later use it for positioning the label text.
159  auto label_dist = node_x;
160 
161  // If we want to align all labels, adjust the distance to the max,
162  // and draw a line from the node to there. This line is also drawn if there is no label,
163  // which is what we want. Users will have to explicitly set an empty line if they don't
164  // want one. This makes sure that we can also draw these lines for inner nodes, which
165  // might be needed in some scenarious.
166  if( align_labels() ) {
167  label_dist = width + extra_spacer();
168 
169  auto label_path = SvgPath( node_data.spacer_stroke, SvgFill( SvgFill::Type::kNone ));
170  label_path.elements.push_back(
171  "M " + std::to_string( node_x ) + " " + std::to_string( node_y )
172  );
173  label_path.elements.push_back(
174  "L " + std::to_string( label_dist ) + " " + std::to_string( node_y )
175  );
176  taxa_lines << label_path;
177  // taxa_lines << SvgLine(
178  // node_x, node_y,
179  // label_dist, node_y,
180  // node_data.spacer_stroke
181  // );
182  }
183 
184  // If the node has a name, print it.
185  if( node_data.name != "" ) {
186  // auto label = SvgText(
187  // node_data.name,
188  // SvgPoint( node_x + 5, node_y )
189  // );
190  // label.dy = "0.4em";
191 
192  auto label = text_template();
193  label.text = node_data.name;
194  label.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
195 
196  // Move label to tip node.
197  label.transform.append( SvgTransform::Translate( label_dist + 5, node_y ));
198  taxa_names << std::move( label );
199  max_text_len = std::max( max_text_len, node_data.name.size() );
200  }
201 
202  // If there is a node shape, draw it.
203  if( ! node_data.shape.empty() ) {
204  auto ns = node_data.shape;
205  ns.transform.append( SvgTransform::Translate( node_x, node_y ));
206  node_shapes << std::move( ns );
207  }
208  }
209 
210  // Make sure that the drawing is done from outside to inside,
211  // so that the overlapping parts look nice.
212  tree_lines.reverse();
213 
214  // Set the margins according to longest label.
215  auto const marg_a = std::max( 20.0, text_template().font.size );
216  auto const marg_r = std::max( 25.0, max_text_len * text_template().font.size );
217  doc.margin = SvgMargin( marg_a, marg_r, marg_a, marg_a );
218 
219  // We are sure that we won't use the groups again, so let's move them!
220  doc << std::move( tree_lines );
221  if( ! taxa_lines.empty() ) {
222  doc << std::move( taxa_lines );
223  }
224  if( ! taxa_names.empty() ) {
225  doc << std::move( taxa_names );
226  }
227  if( ! edge_shapes.empty() ) {
228  doc << std::move( edge_shapes );
229  }
230  if( ! node_shapes.empty() ) {
231  doc << std::move( node_shapes );
232  }
233  return doc;
234 }
235 
236 } // namespace tree
237 } // namespace genesis
double distance
Distance of the node to the root.
bool is_root(TreeLink const &link)
Return whether the link belongs to the root node of its Tree.
double extra_spacer() const
Tree operator functions.
size_t node_count(Tree const &tree)
Return the number of Nodes of a Tree. Same as Tree::node_count().
Container namespace for all symbols of genesis in order to keep them separate when used as a library...
std::string to_string(T const &v)
Return a string representation of a given value.
Definition: string.hpp:384
TreeEdge * edge_between(TreeNode &lhs, TreeNode &rhs)
Return the TreeEdge between two TreeNode&s, if they are neighbours, or nullptr otherwise.
Data class for LayoutTreeEdges.
TreeNode & node_at(size_t index)
Return the TreeNode at a certain index.
Definition: tree/tree.hpp:220
utils::SvgStroke spreading_stroke
Tree const & tree() const
Definition: layout_base.cpp:70
double spreading
Position of the node along the second axis.
NodeDataType & data()
Definition: node.hpp:169
Data class for LayoutTreeNodes.
Definition: layout_tree.hpp:80
utils::SvgText & text_template()