A toolkit for working with phylogenetic data.
v0.20.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
tickmarks.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 
35 #include <algorithm>
36 #include <cassert>
37 #include <cmath>
38 #include <stdexcept>
39 
40 namespace genesis {
41 namespace utils {
42 
43 // ================================================================================================
44 // Tickmarks
45 // ================================================================================================
46 
47 double Tickmarks::step_size( double interval_size, size_t target_steps )
48 {
49  // Adapted from
50  // http://stackoverflow.com/questions/361681/algorithm-for-nice-grid-line-intervals-on-a-graph
51 
52  if( target_steps == 0 ) {
53  throw std::invalid_argument( "Cannot calculate tickmark step size for 0 steps." );
54  }
55  if( interval_size <= 0.0 ) {
56  throw std::invalid_argument( "Cannot calculate tickmark step size for negative intervals." );
57  }
58 
59  // Calculate an initial guess at step size.
60  auto const step_guess = interval_size / static_cast<double>( target_steps );
61 
62  // Get the magnitude of the step size.
63  auto const mag = std::floor( std::log10( step_guess ));
64  auto const mag_pow = std::pow( 10, mag );
65 
66  // Calculate most significant digit (MSD) of the new step size.
67  auto mag_msd = static_cast<int>( step_guess / mag_pow + 0.5 );
68 
69  // Promote the MSD to either 1, 2, 5 or 10.
70  if( mag_msd > 5 ) {
71  mag_msd = 10;
72  } else if( mag_msd > 2 ) {
73  mag_msd = 5;
74  } else if( mag_msd > 1 ) {
75  mag_msd = 2;
76  } else {
77  assert( mag_msd == 1 );
78  }
79 
80  return static_cast<double>( mag_msd ) * mag_pow;
81 }
82 
83 std::vector<double> Tickmarks::linear_ticks( double min, double max, size_t target_steps )
84 {
85  // Prepare result.
86  auto res = std::vector<double>();
87 
88  if( max < min ) {
89  throw std::runtime_error( "Cannot calcualte scale with max < min." );
90  }
91 
92  // The case of 0 target steps can happen for example in SvgPalette.
93  // In that case, we only output min and max if needed, but not any inner tickmarks.
94  if( target_steps > 0 ) {
95  // Get step size.
96  auto interval_size = max - min;
97  auto step_sz = step_size( interval_size, target_steps );
98 
99  // Calculate first tick position, so that it is the largest multiple of the step size
100  // that is below the min.
101  auto tick = step_sz * std::floor( min / step_sz );
102 
103  // Determine whether we want to start before or after the min.
104  if( ! undershoot_at_min ) {
105  tick += step_sz;
106  }
107 
108  // Add ticks to the list.
109  while( tick <= max ) {
110  res.push_back( tick );
111  tick += step_sz;
112  }
113 
114  // Determine whether we want to stop before or after the max.
115  if( overshoot_at_max ) {
116  res.push_back( tick );
117  }
118  }
119 
120  // Add min and max if needed.
121  if( include_min ) {
122  res.push_back( min );
123  }
124  if( include_max ) {
125  res.push_back( max );
126  }
127 
128  // Cleanup duplicate entries and those that are close by. We do not need ticks that are
129  // too close to each other.
130  // It is easier to do this than to check for duplicate entries in each addition step...
131  std::sort( res.begin(), res.end() );
132  auto uniq_end = std::unique( res.begin(), res.end(), [&]( double lhs, double rhs ){
133  return almost_equal_relative( lhs, rhs, relative_epsilon );
134  });
135  res.erase( uniq_end, res.end() );
136 
137  return res;
138 }
139 
140 std::vector<Tickmarks::LabeledTick> Tickmarks::linear_labels(
141  double min, double max, size_t target_steps
142 ) {
143  auto res = std::vector<LabeledTick>();
144 
145  auto ticks = linear_ticks( min, max, target_steps );
146  auto range = max - min;
147 
148  for( auto const& tick : ticks ) {
149  auto rel_pos = ( tick - min ) / range;
150  res.emplace_back( rel_pos, tick );
151  }
152  return res;
153 }
154 
155 std::vector<Tickmarks::LabeledTick> Tickmarks::logarithmic_labels( double min, double max, double base )
156 {
157  auto res = std::vector<LabeledTick>();
158 
159  if( min <= 0.0 ) {
160  throw std::runtime_error( "Cannot calculate logarithmic scale for negative values." );
161  }
162  if( min >= max ) {
163  throw std::runtime_error( "Cannot calcualte scale with min >= max." );
164  }
165  if( base < 0.0 ) {
166  throw std::runtime_error( "Cannot calcualte logarithmic scale with negative base." );
167  }
168 
169  // Start at a power below min.
170  double exp_i = std::floor( std::log( min ) / std::log( base ) );
171 
172  // Determine whether we want to start before or after the min.
173  if( ! undershoot_at_min ) {
174  exp_i += 1.0;
175  }
176 
177  // Constants.
178  auto const lg_min = std::log( min ) / std::log( base );
179  auto const lg_max = std::log( max ) / std::log( base );
180 
181  // Either stop at max or do one more loop if we want to overshoot.
182  auto const lim = lg_max + ( overshoot_at_max ? 1.0 : 0.0 );
183 
184  while( exp_i <= lim ) {
185  auto const rel_pos = ( exp_i - lg_min ) / ( lg_max - lg_min );
186  res.emplace_back( rel_pos, std::pow( base, exp_i ) );
187 
188  // Next order of magnitude.
189  exp_i += 1.0;
190  }
191 
192  // Add min and max if needed.
193  if( include_min ) {
194  res.emplace_back( 0.0, min );
195  }
196  if( include_max ) {
197  res.emplace_back( 1.0, max );
198  }
199 
200  // Cleanup duplicate entries and those that are close by. We do not need ticks that are
201  // too close to each other.
202  // It is easier to do this than to check for duplicate entries in each addition step...
203  std::sort( res.begin(), res.end(), []( LabeledTick const& lhs, LabeledTick const& rhs ){
204  return lhs.relative_position < rhs.relative_position;
205  });
206  auto uniq_end = std::unique( res.begin(), res.end(),
207  [&]( LabeledTick const& lhs, LabeledTick const& rhs ){
208  return almost_equal_relative(
209  lhs.relative_position,
210  rhs.relative_position,
212  );
213  }
214  );
215  res.erase( uniq_end, res.end() );
216 
217  return res;
218 }
219 
220 } // namespace utils
221 } // namespace genesis
std::vector< double > linear_ticks(double min, double max, size_t target_steps)
Calculate a set of ticks that linearily span from min to max in approximately target_steps many steps...
Definition: tickmarks.cpp:83
bool undershoot_at_min
Should the lowest value in the resulting list of ticks be below the provided min value (true) or not ...
Definition: tickmarks.hpp:147
double relative_epsilon
Relative epsilon used to exclude two tickmarks that are too close to each other.
Definition: tickmarks.hpp:158
static double step_size(double interval_size, size_t target_steps)
Calculate a step size that fills the interval_size in approximately target_steps many steps...
Definition: tickmarks.cpp:47
bool overshoot_at_max
Should the highest value in the resulting list of ticks be above the provided max value (true) or not...
Definition: tickmarks.hpp:153
std::vector< LabeledTick > linear_labels(double min, double max, size_t target_steps)
Return a set of labels with relative positions between min and max, where the labels correspond to th...
Definition: tickmarks.cpp:140
bool include_max
Should the provided max value be included in the resulting list of ticks or not.
Definition: tickmarks.hpp:141
bool include_min
Should the provided min value be included in the resulting list of ticks or not.
Definition: tickmarks.hpp:136
std::vector< LabeledTick > logarithmic_labels(double min, double max, double base=10.0)
Return a set of labels with relative positions between min and max, where the labels correspond to lo...
Definition: tickmarks.cpp:155
bool almost_equal_relative(double lhs, double rhs, double max_rel_diff=std::numeric_limits< double >::epsilon())
Check whether two doubles are almost equal, using a relative epsilon to compare them.
Definition: common.hpp:108