A toolkit for working with phylogenetic data.
v0.19.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
circular_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 
36 
37 #include <algorithm>
38 #include <assert.h>
39 #include <cmath>
40 #include <stdexcept>
41 
42 namespace genesis {
43 namespace tree {
44 
45 // =================================================================================================
46 // Settings
47 // =================================================================================================
48 
49 CircularLayout& CircularLayout::radius( double const value )
50 {
51  radius_ = value;
52  return *this;
53 }
54 
55 double CircularLayout::radius() const
56 {
57  return radius_;
58 }
59 
60 // =================================================================================================
61 // Virtual Functions
62 // =================================================================================================
63 
64 utils::SvgDocument CircularLayout::to_svg_document_() const
65 {
66  using namespace utils;
67  SvgDocument doc;
68  SvgGroup tree_lines;
69  SvgGroup taxa_names;
70  SvgGroup edge_shapes;
71  SvgGroup node_shapes;
72 
73  // If the radius was not set, use automatic:
74  // minimum of 50, and grow with tree size.
75  auto radius = radius_;
76  if( radius <= 0.0 ) {
77  radius = std::max( 50.0, static_cast<double>( tree().node_count() ));
78  }
79 
80  size_t max_text_len = 0;
81 
82  for( auto const& node_it : tree().nodes() ) {
83  auto const& node = *node_it;
84 
85  auto const& node_data = node.data<LayoutNodeData>();
86  auto const& prnt_data = tree().node_at( node_data.parent_index ).data<LayoutNodeData>();
87 
88  auto const node_spreading = 2.0 * utils::PI * node_data.spreading;
89  auto const prnt_spreading = 2.0 * utils::PI * prnt_data.spreading;
90 
91  auto const node_x = node_data.distance * radius * cos( node_spreading );
92  auto const node_y = node_data.distance * radius * sin( node_spreading );
93 
94  // Get the edge between the node and its parent.
95  auto edge_ptr = edge_between( node, tree().node_at( node_data.parent_index ) );
96 
97  // If there is an edge (i.e., we are not at the root), draw lines between the nodes.
98  if( edge_ptr ) {
99  auto const& edge_data = edge_ptr->data<LayoutEdgeData>();
100 
101  // Get line strokes
102  auto spreading_stroke = edge_data.spreading_stroke;
103  auto distance_stroke = edge_data.distance_stroke;
104  spreading_stroke.line_cap = utils::SvgStroke::LineCap::kRound;
105  distance_stroke.line_cap = utils::SvgStroke::LineCap::kRound;
106 
107  // Calculate circular spreading
108  auto start_a = prnt_spreading;
109  auto end_a = node_spreading;
110  if( prnt_spreading > node_spreading ) {
111  std::swap( start_a, end_a );
112  }
113 
114  // Calculate linear distance
115  auto const dist_start_x = prnt_data.distance * radius * cos( node_spreading );
116  auto const dist_start_y = prnt_data.distance * radius * sin( node_spreading );
117 
118  // Draw lines
119  tree_lines << SvgPath(
120  { svg_arc( 0, 0, prnt_data.distance * radius, start_a, end_a ) },
121  spreading_stroke,
122  SvgFill( SvgFill::Type::kNone )
123  );
124  tree_lines << SvgLine(
125  dist_start_x, dist_start_y,
126  node_x, node_y,
127  distance_stroke
128  );
129 
130  // If there is an edge shape, draw it to the middle of the edge
131  if( ! edge_data.shape.empty() ) {
132  auto const shape_x = ( dist_start_x + node_x ) / 2.0;
133  auto const shape_y = ( dist_start_y + node_y ) / 2.0;
134 
135  auto es = edge_data.shape;
136  es.transform.append( SvgTransform::Translate( shape_x, shape_y ));
137  edge_shapes << std::move( es );
138  }
139 
140  } else {
141 
142  // If there is no edge, it must be the root.
143  assert( node.is_root() );
144  }
145 
146  // If the node has a name, print it.
147  if( node_data.name != "" ) {
148  // auto label = SvgText( node_data.name, SvgPoint( node_data.x + 5, node_data.y ) );
149  // label.dy = "0.4em";
150 
151  auto label = text_template();
152  label.text = node_data.name;
153  label.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
154 
155  // Move label to tip node.
156  label.transform.append( SvgTransform::Translate(
157  ( node_data.distance * radius + 10 ) * cos( node_spreading ),
158  ( node_data.distance * radius + 10 ) * sin( node_spreading )
159  ));
160 
161  // Rotate label so that its orientation is correct.
162  // Caveat: here, we use the spreading [0, 1] value directly.
163  if( node_data.spreading > 0.25 && node_data.spreading <= 0.75 ) {
164  // Left hemisphere.
165  label.anchor = SvgText::Anchor::kEnd;
166  label.transform.append( SvgTransform::Rotate( 360 * node_data.spreading + 180 ));
167  } else {
168  // Right hemisphere.
169  label.transform.append( SvgTransform::Rotate( 360 * node_data.spreading ));
170  }
171 
172  taxa_names << std::move( label );
173  max_text_len = std::max( max_text_len, node_data.name.size() );
174  }
175 
176  // If there is a node shape, draw it.
177  if( ! node_data.shape.empty() ) {
178  auto ns = node_data.shape;
179  ns.transform.append( SvgTransform::Translate( node_x, node_y ));
180  node_shapes << std::move( ns );
181  }
182  }
183 
184  // Make sure that the drawing is done from outside to inside,
185  // so that the overlapping parts look nice.
186  tree_lines.reverse();
187 
188  // Set the margins according to longest label.
189  auto const marg = std::max( 30.0, max_text_len * text_template().font.size );
190  doc.margin = SvgMargin( marg );
191 
192  // We are sure that we won't use the groups again, so let's move them!
193  doc << std::move( tree_lines );
194  if( ! taxa_names.empty() ) {
195  doc << std::move( taxa_names );
196  }
197  if( ! edge_shapes.empty() ) {
198  doc << std::move( edge_shapes );
199  }
200  if( ! node_shapes.empty() ) {
201  doc << std::move( node_shapes );
202  }
203  return doc;
204 }
205 
206 } // namespace tree
207 } // namespace genesis
void swap(SequenceSet &lhs, SequenceSet &rhs)
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.
constexpr double PI
Make the world go round.
Definition: common.hpp:49
Tree const & tree() const
Definition: layout_base.cpp:69
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.
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()