A library for working with phylogenetic and population genetic data.
v0.32.0
rectangular_layout.cpp
Go to the documentation of this file.
1 /*
2  Genesis - A toolkit for working with phylogenetic data.
3  Copyright (C) 2014-2022 Lucas Czech
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 <lczech@carnegiescience.edu>
20  Department of Plant Biology, Carnegie Institution For Science
21  260 Panama Street, Stanford, CA 94305, USA
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  for( auto const& node : tree().nodes() ) {
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( is_root( node ));
153  }
154 
155  // In the following, we will draw the label and the spacer (if labels shall be aligned).
156  // As aligning chances the x dist of the label, we store it here first, change if needed,
157  // and later use it for positioning the label text.
158  auto label_dist = node_x;
159 
160  // If we want to align all labels, adjust the distance to the max,
161  // and draw a line from the node to there. This line is also drawn if there is no label,
162  // which is what we want. Users will have to explicitly set an empty line if they don't
163  // want one. This makes sure that we can also draw these lines for inner nodes, which
164  // might be needed in some scenarious.
165  if( align_labels() ) {
166  label_dist = width + extra_spacer();
167 
168  auto label_path = SvgPath( node_data.spacer_stroke, SvgFill( SvgFill::Type::kNone ));
169  label_path.elements.push_back(
170  "M " + std::to_string( node_x ) + " " + std::to_string( node_y )
171  );
172  label_path.elements.push_back(
173  "L " + std::to_string( label_dist ) + " " + std::to_string( node_y )
174  );
175  taxa_lines << label_path;
176  // taxa_lines << SvgLine(
177  // node_x, node_y,
178  // label_dist, node_y,
179  // node_data.spacer_stroke
180  // );
181  }
182 
183  // If the node has a name, print it.
184  if( node_data.name != "" ) {
185  // auto label = SvgText(
186  // node_data.name,
187  // SvgPoint( node_x + 5, node_y )
188  // );
189  // label.dy = "0.4em";
190 
191  auto label = text_template();
192  label.text = node_data.name;
193  label.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
194 
195  // Move label to tip node.
196  label.transform.append( SvgTransform::Translate( label_dist + 5, node_y ));
197  taxa_names << std::move( label );
198  // max_text_len = std::max( max_text_len, node_data.name.size() );
199  }
200 
201  // If there is a node shape, draw it.
202  if( ! node_data.shape.empty() ) {
203  auto ns = node_data.shape;
204  ns.transform.append( SvgTransform::Translate( node_x, node_y ));
205  node_shapes << std::move( ns );
206  }
207  }
208 
209  // Make sure that the drawing is done from outside to inside,
210  // so that the overlapping parts look nice.
211  tree_lines.reverse();
212 
213  // Set the margins according to longest label.
214  // auto const marg_a = std::max( 20.0, text_template().font.size );
215  // auto const marg_r = std::max( 25.0, max_text_len * text_template().font.size );
216  // doc.margin = SvgMargin( marg_a, marg_r, marg_a, marg_a );
217 
218  // We are sure that we won't use the groups again, so let's move them!
219  doc << std::move( tree_lines );
220  if( ! taxa_lines.empty() ) {
221  doc << std::move( taxa_lines );
222  }
223  if( ! taxa_names.empty() ) {
224  doc << std::move( taxa_names );
225  }
226  if( ! edge_shapes.empty() ) {
227  doc << std::move( edge_shapes );
228  }
229  if( ! node_shapes.empty() ) {
230  doc << std::move( node_shapes );
231  }
232  return doc;
233 }
234 
235 } // namespace tree
236 } // namespace genesis
genesis::tree::LayoutBase::extra_spacer
double extra_spacer() const
Definition: layout_base.cpp:533
genesis::tree::RectangularLayout
Definition: rectangular_layout.hpp:47
functions.hpp
genesis::tree::Tree::node_at
TreeNode & node_at(size_t index)
Return the TreeNode at a certain index.
Definition: tree/tree.hpp:220
genesis::tree::LayoutBase::tree
Tree const & tree() const
Definition: layout_base.cpp:70
genesis::population::to_string
std::string to_string(GenomeLocus const &locus)
Definition: function/genome_locus.hpp:52
genesis::tree::edge_between
TreeEdge * edge_between(TreeNode &lhs, TreeNode &rhs)
Return the TreeEdge between two TreeNode&s, if they are neighbours, or nullptr otherwise.
Definition: tree/function/operators.cpp:260
genesis::tree::node_count
size_t node_count(Tree const &tree)
Return the number of Nodes of a Tree. Same as Tree::node_count().
Definition: tree/function/functions.cpp:185
genesis::tree::is_root
bool is_root(TreeLink const &link)
Return whether the link belongs to the root node of its Tree.
Definition: tree/function/functions.cpp:89
rectangular_layout.hpp
genesis::tree::RectangularLayout::height
double height() const
Definition: rectangular_layout.cpp:64
genesis
Container namespace for all symbols of genesis in order to keep them separate when used as a library.
Definition: placement/formats/edge_color.cpp:42
operators.hpp
Tree operator functions.
genesis::tree::LayoutBase::text_template
utils::SvgText & text_template()
Definition: layout_base.cpp:543
genesis::utils::SvgDocument
Definition: svg/document.hpp:50
genesis::tree::TreeNode::data
NodeDataType & data()
Definition: tree/tree/node.hpp:203
genesis::tree::LayoutBase::align_labels
bool align_labels() const
Definition: layout_base.cpp:523
genesis::tree::RectangularLayout::width
double width() const
Definition: rectangular_layout.cpp:53