A library for working with phylogenetic and population genetic data.
v0.32.0
color_bar.cpp
Go to the documentation of this file.
1 /*
2  Genesis - A toolkit for working with phylogenetic data.
3  Copyright (C) 2014-2024 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 
40 
51 
52 #include <algorithm>
53 #include <cassert>
54 #include <cstdlib>
55 #include <stdexcept>
56 
57 namespace genesis {
58 namespace utils {
59 
60 // =================================================================================================
61 // Local Helper Functions
62 // =================================================================================================
63 
64 static std::pair<SvgGradientLinear, SvgGroup> make_svg_color_bar_gradient_(
65  SvgColorBarSettings const& settings,
66  ColorMap const& map,
67  ColorNormalization const& norm,
68  std::string const& id
69 ) {
70  // Use a gradient ID with randomness so that we get a different one for each palette.
71  std::string const gradient_id = ( id.empty()
72  ? "PaletteGradient_" + std::to_string( std::rand() )
73  : id
74  );
75 
76  // Depending on the orientation, set gradient points.
77  SvgPoint point_1;
78  SvgPoint point_2;
79  switch( settings.direction ) {
81  point_1 = SvgPoint( 0.0, 1.0 );
82  point_2 = SvgPoint( 0.0, 0.0 );
83  break;
84  }
86  point_1 = SvgPoint( 0.0, 0.0 );
87  point_2 = SvgPoint( 0.0, 1.0 );
88  break;
89  }
91  point_1 = SvgPoint( 0.0, 0.0 );
92  point_2 = SvgPoint( 1.0, 0.0 );
93  break;
94  }
96  point_1 = SvgPoint( 1.0, 0.0 );
97  point_2 = SvgPoint( 0.0, 0.0 );
98  break;
99  }
100  default: {
101  throw std::runtime_error( "Invalid SvgPalette direction." );
102  }
103  }
104 
105  // Get the gradient list depending on the norm type.
106  auto const norm_gradient = color_stops( map, norm );
107 
108  // Fill gradient with the colors, add it to a group as a colored rect.
109  auto grad = SvgGradientLinear( gradient_id, point_1, point_2 );
110  for( auto const& g : norm_gradient ) {
111  if( g.first < 0.0 || g.first > 1.0 ) {
112  throw std::runtime_error( "Color Normalization gradient out of [ 0.0, 1.0 ]" );
113  }
114  grad.add_stop({ g.first, g.second });
115  }
116 
117  // Make group
118  SvgGroup group;
119  group << SvgRect(
120  0.0, 0.0, settings.width, settings.height,
121  SvgStroke( Color( 0.0, 0.0, 0.0 ), settings.line_width ),
122  SvgFill( gradient_id )
123  );
124 
125  return { grad, group };
126 }
127 
128 static std::pair<SvgGradientLinear, SvgGroup> make_svg_color_bar_discrete_(
129  SvgColorBarSettings const& settings,
130  std::map<double, Color> const& stops
131 ) {
132  // Fill a group with colors for the stops.
133  SvgGroup group;
134  for( auto it = stops.begin(); it != stops.end(); ++it ) {
135 
136  // Get and check iterator values.
137  auto const& pos = it->first;
138  auto const& color = it->second;
139  if( !std::isfinite(pos) || pos < 0.0 || pos > 1.0 ) {
140  throw std::runtime_error( "Color Normalization stops out of [ 0.0, 1.0 ]" );
141  }
142 
143  // If we are at the upper bound, we do not need to draw another box.
144  if( pos == 1.0 ) {
145  continue;
146  }
147 
148  // We need the next one for calculating the end of the box.
149  // We init next pos to 1, which is used if there is no 1.0 stop in the stops list,
150  // meaning that the `continue` statement above is never used.
151  // In the other case (there is a stop 1.0), next will always point to a valid element,
152  // so that next pos is set to it.
153  auto next = it;
154  ++next;
155  double next_pos = 1.0;
156  if( next != stops.end() ) {
157  next_pos = next->first;
158  }
159  assert( next_pos > pos );
160 
161  // Calculate the box.
162  double x = 0.0;
163  double y = 0.0;
164  double w = settings.width;
165  double h = settings.height;
166  switch( settings.direction ) {
168  y = settings.height - next_pos * settings.height;
169  h = ( next_pos - pos ) * settings.height;
170  break;
171  }
173  y = pos * settings.height;
174  h = ( next_pos - pos ) * settings.height;
175  break;
176  }
178  x = pos * settings.width;
179  w = ( next_pos - pos ) * settings.width;
180  break;
181  }
183  x = settings.width - next_pos * settings.width;
184  w = ( next_pos - pos ) * settings.width;
185  break;
186  }
187  default: {
188  throw std::runtime_error( "Invalid SvgPalette direction." );
189  }
190  }
191 
192  // Make the box.
193  group << SvgRect(
194  x, y, w, h,
196  SvgFill( color )
197  );
198  }
199 
200  // Add a black line around the bar
201  group << SvgRect(
202  0.0, 0.0, settings.width, settings.height,
203  SvgStroke( Color( 0.0, 0.0, 0.0 ), settings.line_width ),
205  );
206 
207  return { SvgGradientLinear(), group };
208 }
209 
211  SvgColorBarSettings const& settings,
212  ColorMap const& map,
213  ColorNormalization const& norm,
214  SvgGroup& group
215 ) {
216  // Helper function to make a tick mark with line and text
217  // at a relative position [ 0.0, 1.0 ] along the rect.
218  auto make_tick = [&]( double rel_pos, std::string label ){
219  assert( std::isfinite(rel_pos) && 0.0 <= rel_pos && rel_pos <= 1.0 );
220 
221  // Get positions for needed elements.
222  double v = -1.0;
223  double h = -1.0;
224  switch( settings.direction ) {
226  v = settings.height - ( rel_pos * settings.height );
227  break;
228  }
230  v = rel_pos * settings.height;
231  break;
232  }
234  h = rel_pos * settings.width;
235  break;
236  }
238  h = settings.width - ( rel_pos * settings.width );
239  break;
240  }
241  default: {
242  throw std::runtime_error( "Invalid SvgPalette direction." );
243  }
244  }
245 
246  // Set elements.
247  SvgPoint line1_p1;
248  SvgPoint line1_p2;
249  SvgPoint line2_p1;
250  SvgPoint line2_p2;
251  SvgPoint text_p;
252  switch( settings.direction ) {
255  {
256  assert( v > -1.0 );
257  line1_p1 = SvgPoint( 0.0, v );
258  line1_p2 = SvgPoint( settings.width * 0.15, v );
259  line2_p1 = SvgPoint( settings.width * 0.85, v );
260  line2_p2 = SvgPoint( settings.width, v );
261  text_p = SvgPoint( settings.width * 1.05, v );
262  break;
263  }
266  {
267  assert( h > -1.0 );
268  line1_p1 = SvgPoint( h, 0.0 );
269  line1_p2 = SvgPoint( h, settings.height * 0.15 );
270  line2_p1 = SvgPoint( h, settings.height * 0.85 );
271  line2_p2 = SvgPoint( h, settings.height );
272  text_p = SvgPoint( h, settings.height * 1.05 );
273  break;
274  }
275  default: {
276  throw std::runtime_error( "Invalid SvgPalette direction." );
277  }
278  }
279 
280  // Draw lines and text. Lines only for inners, as we already have a box around the scale.
281  if( rel_pos != 0.0 && rel_pos != 1.0 ) {
282  auto const stroke = SvgStroke( Color( 0.0, 0.0, 0.0 ), settings.line_width );
283  group << SvgLine( line1_p1, line1_p2, stroke );
284  group << SvgLine( line2_p1, line2_p2, stroke );
285  }
286  if( settings.with_labels ) {
287  if( rel_pos == 1.0 && map.clip_over() ) {
288  label = "≥ " + label;
289  }
290  if( rel_pos == 0.0 && map.clip_under() ) {
291  label = "≤ " + label;
292  }
293  auto text_s = SvgText( label );
294  text_s.transform.append( SvgTransform::Translate( text_p ));
295  text_s.font.size = settings.text_size;
296  if(
299  ) {
300  text_s.transform.append( SvgTransform::Rotate( 90.0 ));
301  }
302  text_s.dominant_baseline = SvgText::DominantBaseline::kMiddle;
303  // text_s.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
304  // text_s.dy = "0.33em";
305  group << text_s;
306  }
307  };
308 
309  // Make tickmarks and labels.
310  if( settings.with_tickmarks ) {
311  auto const tickmarks = color_tickmarks( norm, settings.num_ticks );
312  for( auto const& tick : tickmarks ) {
313  if( !std::isfinite(tick.first) || tick.first < 0.0 || tick.first > 1.0 ) {
314  throw std::runtime_error( "Color Normalization tickmark out of [ 0.0, 1.0 ]" );
315  }
316  make_tick( tick.first, tick.second );
317  }
318  }
319 }
320 
321 // =================================================================================================
322 // Svg Color Bar
323 // =================================================================================================
324 
325 std::pair<SvgGradientLinear, SvgGroup> make_svg_color_bar(
326  SvgColorBarSettings const& settings,
327  ColorMap const& map,
328  ColorNormalization const& norm,
329  std::string const& id
330 ) {
331 
332  if( map.palette().size() < 2 ) {
333  throw std::runtime_error(
334  "Cannot make SvgPalette with a ColorMap of less than two colors."
335  );
336  }
337  if( ! norm.is_valid() ) {
338  throw std::runtime_error(
339  "Invaid ColorNormalization settings."
340  );
341  }
342 
343  // Prepare result.
344  auto result = std::pair<SvgGradientLinear, SvgGroup>();
345 
346  // We have an ugly special case for boundary norms, where we do not want to display
347  // a gradient, but discrete color bars instead...
348  auto norm_boundary = dynamic_cast<ColorNormalizationBoundary const*>( &norm );
349  if( norm_boundary ) {
350  auto const norm_gradient = color_stops( map, norm );
351  result = make_svg_color_bar_discrete_( settings, norm_gradient );
352  } else {
353  result = make_svg_color_bar_gradient_( settings, map, norm, id );
354  }
355 
356  // Add the tickmarks to the bar/
357  make_svg_color_bar_tickmarks_( settings, map, norm, result.second );
358 
359  return result;
360 }
361 
362 // =================================================================================================
363 // Svg Color List
364 // =================================================================================================
365 
366 void make_svg_color_list_entry_( size_t i, Color const& color, std::string const& label, SvgGroup& group )
367 {
368  group << SvgRect(
369  0.0, i * 15.0, 10.0, 10.0,
371  SvgFill( color )
372  );
373  group << SvgText( label, SvgPoint( 20.0, i * 15.0 + 10.0 ));
374 }
375 
377  ColorMap const& map,
378  std::vector<std::string> const& labels
379 ) {
380  SvgGroup group;
381 
382  for( size_t i = 0; i < labels.size(); ++i ) {
383  make_svg_color_list_entry_( i, map.color(i), labels[i], group );
384  }
385 
386  return group;
387 }
388 
390  std::vector<Color> const& colors,
391  std::vector<std::string> const& labels
392 ) {
393  if( colors.size() != labels.size() ) {
394  throw std::invalid_argument( "List of colors and list of labels have different size." );
395  }
396 
397  SvgGroup group;
398  for( size_t i = 0; i < labels.size(); ++i ) {
399  make_svg_color_list_entry_( i, colors[i], labels[i], group );
400  }
401  return group;
402 }
403 
404 } // namespace utils
405 } // namespace genesis
genesis::utils::SvgColorBarSettings::Direction::kTopToBottom
@ kTopToBottom
genesis::utils::ColorMap
Store a list of colors and offer them as a map for values in range [ 0.0, 1.0 ].
Definition: map.hpp:61
genesis::utils::Color
Definition: color.hpp:47
genesis::utils::color_stops
std::map< double, Color > color_stops(ColorMap const &map, ColorNormalization const &norm)
Definition: helpers.cpp:53
helper.hpp
genesis::utils::make_svg_color_bar
std::pair< SvgGradientLinear, SvgGroup > make_svg_color_bar(SvgColorBarSettings const &settings, ColorMap const &map, ColorNormalization const &norm, std::string const &id)
Definition: color_bar.cpp:325
genesis::utils::SvgTransform::Rotate
Definition: attributes.hpp:298
genesis::utils::SvgColorBarSettings::Direction::kRightToLeft
@ kRightToLeft
genesis::utils::make_svg_color_bar_discrete_
static std::pair< SvgGradientLinear, SvgGroup > make_svg_color_bar_discrete_(SvgColorBarSettings const &settings, std::map< double, Color > const &stops)
Definition: color_bar.cpp:128
genesis::utils::ColorNormalization
Base class for color normalization.
Definition: normalization.hpp:52
genesis::utils::make_svg_color_list_entry_
void make_svg_color_list_entry_(size_t i, Color const &color, std::string const &label, SvgGroup &group)
Definition: color_bar.cpp:366
shapes.hpp
map.hpp
genesis::utils::SvgColorBarSettings::direction
Direction direction
Definition: color_bar.hpp:79
genesis::utils::ColorMap::clip_over
bool clip_over() const
Clip (clamp) values greater than max() to be inside [ min, max ].
Definition: map.hpp:141
genesis::utils::SvgColorBarSettings::with_labels
bool with_labels
Definition: color_bar.hpp:86
helpers.hpp
genesis::utils::SvgLine
Definition: shapes.hpp:50
genesis::utils::SvgColorBarSettings::Direction::kLeftToRight
@ kLeftToRight
genesis::utils::SvgColorBarSettings::height
double height
Definition: color_bar.hpp:82
genesis::utils::SvgColorBarSettings::num_ticks
size_t num_ticks
Definition: color_bar.hpp:88
genesis::utils::color_tickmarks
std::map< double, std::string > color_tickmarks(ColorNormalization const &norm, size_t num_ticks)
Definition: helpers.cpp:157
genesis::population::to_string
std::string to_string(GenomeLocus const &locus)
Definition: function/genome_locus.hpp:52
genesis::utils::SvgPoint
Definition: utils/formats/svg/helper.hpp:57
genesis::utils::SvgStroke
Definition: attributes.hpp:49
string.hpp
Provides some commonly used string utility functions.
genesis::utils::SvgRect
Definition: shapes.hpp:112
genesis::utils::SvgText::DominantBaseline::kMiddle
@ kMiddle
genesis::utils::SvgTransform::Translate
Definition: attributes.hpp:252
genesis::sequence::labels
std::unordered_set< std::string > labels(SequenceSet const &set)
Return a set of all labels of the SequenceSet.
Definition: labels.cpp:64
genesis::utils::ColorNormalization::is_valid
bool is_valid() const
Return whether ranges and other values are correct.
Definition: normalization.hpp:91
genesis::utils::ColorNormalizationBoundary
Color normalization that maps to discrete intervals.
Definition: norm_boundary.hpp:75
genesis::utils::make_svg_color_bar_gradient_
static std::pair< SvgGradientLinear, SvgGroup > make_svg_color_bar_gradient_(SvgColorBarSettings const &settings, ColorMap const &map, ColorNormalization const &norm, std::string const &id)
Definition: color_bar.cpp:64
genesis::utils::SvgStroke::Type::kNone
@ kNone
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
norm_diverging.hpp
functions.hpp
Color operators and functions.
genesis::utils::SvgFill
Definition: attributes.hpp:131
genesis::utils::SvgGradientLinear
Definition: gradient.hpp:102
genesis::utils::SvgColorBarSettings
Definition: color_bar.hpp:59
color.hpp
Header of Color class.
genesis::utils::ColorMap::clip_under
bool clip_under() const
Clip (clamp) values less than min() to be inside [ min, max ].
Definition: map.hpp:131
norm_linear.hpp
color_bar.hpp
genesis::utils::SvgText
Definition: text.hpp:47
genesis::utils::SvgColorBarSettings::Direction::kBottomToTop
@ kBottomToTop
text.hpp
genesis::utils::make_svg_color_bar_tickmarks_
static void make_svg_color_bar_tickmarks_(SvgColorBarSettings const &settings, ColorMap const &map, ColorNormalization const &norm, SvgGroup &group)
Definition: color_bar.cpp:210
tickmarks.hpp
attributes.hpp
norm_boundary.hpp
genesis::utils::ColorMap::palette
ColorMap & palette(std::vector< Color > const &value)
Definition: map.hpp:219
normalization.hpp
genesis::utils::SvgGroup
Definition: group.hpp:50
genesis::utils::SvgColorBarSettings::line_width
double line_width
Definition: color_bar.hpp:83
object.hpp
genesis::utils::make_svg_color_list
SvgGroup make_svg_color_list(ColorMap const &map, std::vector< std::string > const &labels)
Definition: color_bar.cpp:376
genesis::utils::SvgFill::Type::kNone
@ kNone
genesis::utils::ColorMap::size
size_t size() const
Return the size of the map, that is, the number of colors in the list.
Definition: map.hpp:248
genesis::utils::SvgColorBarSettings::with_tickmarks
bool with_tickmarks
Definition: color_bar.hpp:85
norm_logarithmic.hpp
genesis::utils::SvgColorBarSettings::width
double width
Definition: color_bar.hpp:81
genesis::utils::ColorMap::color
Color color(size_t index) const
Return a particular color from the palette, module the palette size.
Definition: map.hpp:259
genesis::utils::SvgColorBarSettings::text_size
double text_size
Definition: color_bar.hpp:89