A library for working with phylogenetic and population genetic data.
v0.32.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-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 
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  for( auto const& node : tree().nodes() ) {
109 
110  auto const& node_data = node.data<LayoutNodeData>();
111  auto const& prnt_data = tree().node_at( node_data.parent_index ).data<LayoutNodeData>();
112 
113  auto const node_spreading = 2.0 * utils::PI * node_data.spreading * max_spreading;
114  auto const prnt_spreading = 2.0 * utils::PI * prnt_data.spreading * max_spreading;
115 
116  auto const node_x = node_data.distance * radius * cos( node_spreading );
117  auto const node_y = node_data.distance * radius * sin( node_spreading );
118 
119  // Get the edge between the node and its parent.
120  auto edge_ptr = edge_between( node, tree().node_at( node_data.parent_index ) );
121 
122  // If there is an edge (i.e., we are not at the root), draw lines between the nodes.
123  if( edge_ptr ) {
124  auto const& edge_data = edge_ptr->data<LayoutEdgeData>();
125 
126  // Get line strokes
127  auto spreading_stroke = edge_data.spreading_stroke;
128  auto distance_stroke = edge_data.distance_stroke;
129  spreading_stroke.line_cap = utils::SvgStroke::LineCap::kRound;
130  distance_stroke.line_cap = utils::SvgStroke::LineCap::kRound;
131 
132  // Calculate circular spreading
133  auto start_a = prnt_spreading;
134  auto end_a = node_spreading;
135  if( prnt_spreading > node_spreading ) {
136  std::swap( start_a, end_a );
137  }
138 
139  // Calculate linear distance
140  auto const dist_start_x = prnt_data.distance * radius * cos( node_spreading );
141  auto const dist_start_y = prnt_data.distance * radius * sin( node_spreading );
142 
143  // Draw lines
144  tree_lines << SvgPath(
145  { svg_arc( 0, 0, prnt_data.distance * radius, start_a, end_a ) },
146  spreading_stroke,
147  SvgFill( SvgFill::Type::kNone )
148  );
149  tree_lines << SvgLine(
150  dist_start_x, dist_start_y,
151  node_x, node_y,
152  distance_stroke
153  );
154 
155  // If there is an edge shape, draw it to the middle of the edge
156  if( ! edge_data.shape.empty() ) {
157  auto const shape_x = ( dist_start_x + node_x ) / 2.0;
158  auto const shape_y = ( dist_start_y + node_y ) / 2.0;
159 
160  auto es = edge_data.shape;
161  es.transform.append( SvgTransform::Translate( shape_x, shape_y ));
162  edge_shapes << std::move( es );
163  }
164 
165  } else {
166 
167  // If there is no edge, it must be the root.
168  assert( is_root( node ));
169  }
170 
171  // In the following, we will draw the label and the spacer (if labels shall be aligned).
172  // As aligning chances the x dist of the label, we store it here first, change if needed,
173  // and later use it for positioning the label text.
174  auto label_dist = node_data.distance * radius;
175 
176  // If we want to align all labels, adjust the distance to the max,
177  // and draw a line from the node to there. This line is also drawn if there is no label,
178  // which is what we want. Users will have to explicitly set an empty line if they don't
179  // want one. This makes sure that we can also draw these lines for inner nodes, which
180  // might be needed in some scenarious.
181  if( align_labels() ) {
182  label_dist = radius + extra_spacer();
183 
184  taxa_lines << SvgLine(
185  node_x, node_y,
186  label_dist * cos( node_spreading ),
187  label_dist * sin( node_spreading ),
188  node_data.spacer_stroke
189  );
190  }
191 
192  // If the node has a name, print it.
193  if( node_data.name != "" ) {
194  // auto label = SvgText( node_data.name, SvgPoint( node_data.x + 5, node_data.y ) );
195  // label.dy = "0.4em";
196 
197  auto label = text_template();
198  label.text = node_data.name;
199  label.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
200 
201  // Move label to tip node.
202  label.transform.append( SvgTransform::Translate(
203  ( label_dist + 10 ) * cos( node_spreading ),
204  ( label_dist + 10 ) * sin( node_spreading )
205  ));
206 
207  // Rotate label so that its orientation is correct.
208  // Caveat: here, we use the spreading [0, 1] value directly.
209  if(
210  node_data.spreading * max_spreading > 0.25 &&
211  node_data.spreading * max_spreading <= 0.75
212  ) {
213  // Left hemisphere.
214  label.anchor = SvgText::Anchor::kEnd;
215  label.transform.append( SvgTransform::Rotate(
216  360 * node_data.spreading * max_spreading + 180
217  ));
218  } else {
219  // Right hemisphere.
220  label.transform.append( SvgTransform::Rotate(
221  360 * node_data.spreading * max_spreading
222  ));
223  }
224 
225  taxa_names << std::move( label );
226  // max_text_len = std::max( max_text_len, node_data.name.size() );
227  }
228 
229  // If there is a node shape, draw it.
230  if( ! node_data.shape.empty() ) {
231  auto ns = node_data.shape;
232  ns.transform.append( SvgTransform::Translate( node_x, node_y ));
233  node_shapes << std::move( ns );
234  }
235  }
236 
237  // Make sure that the drawing is done from outside to inside,
238  // so that the overlapping parts look nice.
239  tree_lines.reverse();
240 
241  // Set the margins according to longest label.
242  // auto const marg = std::max( 30.0, max_text_len * text_template().font.size );
243  // doc.margin = SvgMargin( marg );
244 
245  // We are sure that we won't use the groups again, so let's move them!
246  doc << std::move( tree_lines );
247  if( ! taxa_lines.empty() ) {
248  doc << std::move( taxa_lines );
249  }
250  if( ! taxa_names.empty() ) {
251  doc << std::move( taxa_names );
252  }
253  if( ! edge_shapes.empty() ) {
254  doc << std::move( edge_shapes );
255  }
256  if( ! node_shapes.empty() ) {
257  doc << std::move( node_shapes );
258  }
259  return doc;
260 }
261 
262 } // namespace tree
263 } // 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:70
genesis::utils::svg_arc
std::string svg_arc(double center_x, double center_y, double radius, double start_angle, double end_angle, bool wedge)
Create an arc to use in an SvgPath.
Definition: utils/formats/svg/helper.cpp:50
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::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: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::CircularLayout::radius
double radius() const
Definition: circular_layout.cpp:56
genesis::tree::LayoutSpreading::kAllNodesButRoot
@ kAllNodesButRoot
genesis::tree::CircularLayout
Definition: circular_layout.hpp:47