A library for working with phylogenetic and population genetic data.
v0.27.0
circular_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 
37 
38 #include <algorithm>
39 #include <cassert>
40 #include <cmath>
41 #include <stdexcept>
42 
43 namespace genesis {
44 namespace tree {
45 
46 // =================================================================================================
47 // Settings
48 // =================================================================================================
49 
50 CircularLayout& CircularLayout::radius( double const value )
51 {
52  radius_ = value;
53  return *this;
54 }
55 
56 double CircularLayout::radius() const
57 {
58  return radius_;
59 }
60 
61 // =================================================================================================
62 // Virtual Functions
63 // =================================================================================================
64 
65 utils::SvgDocument CircularLayout::to_svg_document_() const
66 {
67  using namespace utils;
68  SvgDocument doc;
69  SvgGroup tree_lines;
70  SvgGroup taxa_lines;
71  SvgGroup taxa_names;
72  SvgGroup edge_shapes;
73  SvgGroup node_shapes;
74 
75  // If the radius was not set, use automatic:
76  // minimum of 50, and grow with tree size.
77  auto radius = radius_;
78  if( radius <= 0.0 ) {
79  radius = std::max( 50.0, static_cast<double>( tree().node_count() ));
80  }
81 
82  // The spreading of nodes is in [ 0.0, 1.0 ]. This would mean that the first and the last
83  // node end up at the same position in the circle. Fix this by re-scaling so that an
84  // interval is added at the end. That is, we set a maximum spread < 1.0, that rescales
85  // the actual calculated spreads of the nodes.
86  double node_count = 0;
87  assert( ! tree().empty() );
88  switch( inner_node_spreading() ) {
90  node_count = static_cast<double>( leaf_node_count( tree() ) - 1 );
91  break;
92  }
94  node_count = static_cast<double>( tree().node_count() - 2 );
95  break;
96  }
98  node_count = static_cast<double>( tree().node_count() - 1 );
99  break;
100  }
101  default: {
102  assert( false );
103  }
104  }
105  double const max_spreading = node_count / ( node_count + 1 );
106 
107  size_t max_text_len = 0;
108 
109  for( auto const& node : tree().nodes() ) {
110 
111  auto const& node_data = node.data<LayoutNodeData>();
112  auto const& prnt_data = tree().node_at( node_data.parent_index ).data<LayoutNodeData>();
113 
114  auto const node_spreading = 2.0 * utils::PI * node_data.spreading * max_spreading;
115  auto const prnt_spreading = 2.0 * utils::PI * prnt_data.spreading * max_spreading;
116 
117  auto const node_x = node_data.distance * radius * cos( node_spreading );
118  auto const node_y = node_data.distance * radius * sin( node_spreading );
119 
120  // Get the edge between the node and its parent.
121  auto edge_ptr = edge_between( node, tree().node_at( node_data.parent_index ) );
122 
123  // If there is an edge (i.e., we are not at the root), draw lines between the nodes.
124  if( edge_ptr ) {
125  auto const& edge_data = edge_ptr->data<LayoutEdgeData>();
126 
127  // Get line strokes
128  auto spreading_stroke = edge_data.spreading_stroke;
129  auto distance_stroke = edge_data.distance_stroke;
130  spreading_stroke.line_cap = utils::SvgStroke::LineCap::kRound;
131  distance_stroke.line_cap = utils::SvgStroke::LineCap::kRound;
132 
133  // Calculate circular spreading
134  auto start_a = prnt_spreading;
135  auto end_a = node_spreading;
136  if( prnt_spreading > node_spreading ) {
137  std::swap( start_a, end_a );
138  }
139 
140  // Calculate linear distance
141  auto const dist_start_x = prnt_data.distance * radius * cos( node_spreading );
142  auto const dist_start_y = prnt_data.distance * radius * sin( node_spreading );
143 
144  // Draw lines
145  tree_lines << SvgPath(
146  { svg_arc( 0, 0, prnt_data.distance * radius, start_a, end_a ) },
147  spreading_stroke,
148  SvgFill( SvgFill::Type::kNone )
149  );
150  tree_lines << SvgLine(
151  dist_start_x, dist_start_y,
152  node_x, node_y,
153  distance_stroke
154  );
155 
156  // If there is an edge shape, draw it to the middle of the edge
157  if( ! edge_data.shape.empty() ) {
158  auto const shape_x = ( dist_start_x + node_x ) / 2.0;
159  auto const shape_y = ( dist_start_y + node_y ) / 2.0;
160 
161  auto es = edge_data.shape;
162  es.transform.append( SvgTransform::Translate( shape_x, shape_y ));
163  edge_shapes << std::move( es );
164  }
165 
166  } else {
167 
168  // If there is no edge, it must be the root.
169  assert( is_root( node ));
170  }
171 
172  // In the following, we will draw the label and the spacer (if labels shall be aligned).
173  // As aligning chances the x dist of the label, we store it here first, change if needed,
174  // and later use it for positioning the label text.
175  auto label_dist = node_data.distance * radius;
176 
177  // If we want to align all labels, adjust the distance to the max,
178  // and draw a line from the node to there. This line is also drawn if there is no label,
179  // which is what we want. Users will have to explicitly set an empty line if they don't
180  // want one. This makes sure that we can also draw these lines for inner nodes, which
181  // might be needed in some scenarious.
182  if( align_labels() ) {
183  label_dist = radius + extra_spacer();
184 
185  taxa_lines << SvgLine(
186  node_x, node_y,
187  label_dist * cos( node_spreading ),
188  label_dist * sin( node_spreading ),
189  node_data.spacer_stroke
190  );
191  }
192 
193  // If the node has a name, print it.
194  if( node_data.name != "" ) {
195  // auto label = SvgText( node_data.name, SvgPoint( node_data.x + 5, node_data.y ) );
196  // label.dy = "0.4em";
197 
198  auto label = text_template();
199  label.text = node_data.name;
200  label.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
201 
202  // Move label to tip node.
203  label.transform.append( SvgTransform::Translate(
204  ( label_dist + 10 ) * cos( node_spreading ),
205  ( label_dist + 10 ) * sin( node_spreading )
206  ));
207 
208  // Rotate label so that its orientation is correct.
209  // Caveat: here, we use the spreading [0, 1] value directly.
210  if(
211  node_data.spreading * max_spreading > 0.25 &&
212  node_data.spreading * max_spreading <= 0.75
213  ) {
214  // Left hemisphere.
215  label.anchor = SvgText::Anchor::kEnd;
216  label.transform.append( SvgTransform::Rotate(
217  360 * node_data.spreading * max_spreading + 180
218  ));
219  } else {
220  // Right hemisphere.
221  label.transform.append( SvgTransform::Rotate(
222  360 * node_data.spreading * max_spreading
223  ));
224  }
225 
226  taxa_names << std::move( label );
227  max_text_len = std::max( max_text_len, node_data.name.size() );
228  }
229 
230  // If there is a node shape, draw it.
231  if( ! node_data.shape.empty() ) {
232  auto ns = node_data.shape;
233  ns.transform.append( SvgTransform::Translate( node_x, node_y ));
234  node_shapes << std::move( ns );
235  }
236  }
237 
238  // Make sure that the drawing is done from outside to inside,
239  // so that the overlapping parts look nice.
240  tree_lines.reverse();
241 
242  // Set the margins according to longest label.
243  auto const marg = std::max( 30.0, max_text_len * text_template().font.size );
244  doc.margin = SvgMargin( marg );
245 
246  // We are sure that we won't use the groups again, so let's move them!
247  doc << std::move( tree_lines );
248  if( ! taxa_lines.empty() ) {
249  doc << std::move( taxa_lines );
250  }
251  if( ! taxa_names.empty() ) {
252  doc << std::move( taxa_names );
253  }
254  if( ! edge_shapes.empty() ) {
255  doc << std::move( edge_shapes );
256  }
257  if( ! node_shapes.empty() ) {
258  doc << std::move( node_shapes );
259  }
260  return doc;
261 }
262 
263 } // namespace tree
264 } // namespace genesis
genesis::placement::swap
void swap(Sample &lhs, Sample &rhs)
Definition: sample.cpp:104
genesis::tree::leaf_node_count
size_t leaf_node_count(Tree const &tree)
Count the number of leaf Nodes of a Tree.
Definition: tree/function/functions.cpp:168
genesis::tree::Tree::node_count
size_t node_count() const
Return the number of TreeNodes of the Tree.
Definition: tree/tree.hpp:264
genesis::utils::SvgStroke::LineCap::kRound
@ kRound
circular_layout.hpp
genesis::tree::LayoutSpreading::kLeafNodesOnly
@ kLeafNodesOnly
genesis::tree::LayoutBase::extra_spacer
double extra_spacer() const
Definition: layout_base.cpp:533
common.hpp
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:65
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:261
genesis::tree::LayoutSpreading::kAllNodes
@ kAllNodes
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::LayoutBase::inner_node_spreading
LayoutSpreading inner_node_spreading() const
Definition: layout_base.cpp:513
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
genesis::utils::PI
constexpr double PI
Make the world go round.
Definition: common.hpp:55
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::Tree::nodes
utils::Range< IteratorNodes > nodes()
Definition: tree/tree.hpp:418
genesis::tree::LayoutBase::text_template
utils::SvgText & text_template()
Definition: layout_base.cpp:543
genesis::utils::SvgDocument
Definition: svg/document.hpp:49
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::CircularLayout::radius
double radius() const
Definition: circular_layout.cpp:56
genesis::tree::LayoutSpreading::kAllNodesButRoot
@ kAllNodesButRoot
genesis::utils::svg_arc
std::string svg_arc(double center_x, double center_y, double radius, double start_angle, double end_angle)
Create an arc to use in an SvgPath.
Definition: utils/formats/svg/helper.hpp:240
genesis::tree::CircularLayout
Definition: circular_layout.hpp:47