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