A toolkit for working with phylogenetic data.
v0.24.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-2020 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 
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(),
122  // SvgStroke( SvgStroke::Type::kNone ),
123  SvgFill( gradient_id )
124  );
125 
126  return { grad, group };
127 }
128 
129 static std::pair<SvgGradientLinear, SvgGroup> make_svg_color_bar_discrete_(
130  SvgColorBarSettings const& settings,
131  std::map<double, Color> const& stops
132 ) {
133  // Fill a group with colors for the stops.
134  SvgGroup group;
135  for( auto it = stops.begin(); it != stops.end(); ++it ) {
136 
137  // Get and check iterator values.
138  auto const& pos = it->first;
139  auto const& color = it->second;
140  if( pos < 0.0 || pos > 1.0 ) {
141  throw std::runtime_error( "Color Normalization stops out of [ 0.0, 1.0 ]" );
142  }
143 
144  // If we are at the upper bound, we do not need to draw another box.
145  if( pos == 1.0 ) {
146  continue;
147  }
148 
149  // We need the next one for calculating the end of the box.
150  // We init next pos to 1, which is used if there is no 1.0 stop in the stops list,
151  // meaning that the `continue` statement above is never used.
152  // In the other case (there is a stop 1.0), next will always point to a valid element,
153  // so that next pos is set to it.
154  auto next = it;
155  ++next;
156  double next_pos = 1.0;
157  if( next != stops.end() ) {
158  next_pos = next->first;
159  }
160  assert( next_pos > pos );
161 
162  // Calculate the box.
163  double x = 0.0;
164  double y = 0.0;
165  double w = settings.width;
166  double h = settings.height;
167  switch( settings.direction ) {
169  y = settings.height - next_pos * settings.height;
170  h = ( next_pos - pos ) * settings.height;
171  break;
172  }
174  y = pos * settings.height;
175  h = ( next_pos - pos ) * settings.height;
176  break;
177  }
179  x = pos * settings.width;
180  w = ( next_pos - pos ) * settings.width;
181  break;
182  }
184  x = settings.width - next_pos * settings.width;
185  w = ( next_pos - pos ) * settings.width;
186  break;
187  }
188  default: {
189  throw std::runtime_error( "Invalid SvgPalette direction." );
190  }
191  }
192 
193  // Make the box.
194  group << SvgRect(
195  x, y, w, h,
197  SvgFill( color )
198  );
199  }
200 
201  // Add a black line around the bar
202  group << SvgRect(
203  0.0, 0.0, settings.width, settings.height,
204  SvgStroke( Color( 0.0, 0.0, 0.0 )),
206  );
207 
208  return { SvgGradientLinear(), group };
209 }
210 
212  SvgColorBarSettings const& settings,
213  ColorMap const& map,
214  ColorNormalization const& norm,
215  SvgGroup& group
216 ) {
217  // Helper function to make a tick mark with line and text
218  // at a relative position [ 0.0, 1.0 ] along the rect.
219  auto make_tick = [&]( double rel_pos, std::string label ){
220  assert( 0.0 <= rel_pos && rel_pos <= 1.0 );
221 
222  // Get positions for needed elements.
223  double v = -1.0;
224  double h = -1.0;
225  switch( settings.direction ) {
227  v = settings.height - ( rel_pos * settings.height );
228  break;
229  }
231  v = rel_pos * settings.height;
232  break;
233  }
235  h = rel_pos * settings.width;
236  break;
237  }
239  h = settings.width - ( rel_pos * settings.width );
240  break;
241  }
242  default: {
243  throw std::runtime_error( "Invalid SvgPalette direction." );
244  }
245  }
246 
247  // Set elements.
248  SvgPoint line1_p1;
249  SvgPoint line1_p2;
250  SvgPoint line2_p1;
251  SvgPoint line2_p2;
252  SvgPoint text_p;
253  switch( settings.direction ) {
256  {
257  assert( v > -1.0 );
258  line1_p1 = SvgPoint( 0.0, v );
259  line1_p2 = SvgPoint( settings.width * 0.15, v );
260  line2_p1 = SvgPoint( settings.width * 0.85, v );
261  line2_p2 = SvgPoint( settings.width, v );
262  text_p = SvgPoint( settings.width * 1.05, v );
263  break;
264  }
267  {
268  assert( h > -1.0 );
269  line1_p1 = SvgPoint( h, 0.0 );
270  line1_p2 = SvgPoint( h, settings.height * 0.15 );
271  line2_p1 = SvgPoint( h, settings.height * 0.85 );
272  line2_p2 = SvgPoint( h, settings.height );
273  text_p = SvgPoint( h, settings.height * 1.05 );
274  break;
275  }
276  default: {
277  throw std::runtime_error( "Invalid SvgPalette direction." );
278  }
279  }
280 
281  // Draw lines and text. Lines only for inners, as we already have a box around the scale.
282  if( rel_pos != 0.0 && rel_pos != 1.0 ) {
283  group << SvgLine( line1_p1, line1_p2 );
284  group << SvgLine( line2_p1, line2_p2 );
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(
298  ) {
299  text_s.transform.append( SvgTransform::Rotate( 90.0 ));
300  }
301  // text_s.dominant_baseline = SvgText::DominantBaseline::kMiddle;
302  // text_s.alignment_baseline = SvgText::AlignmentBaseline::kMiddle;
303  // text_s.dy = "0.33em";
304  group << text_s;
305  }
306  };
307 
308  // Make tickmarks and labels.
309  if( settings.with_tickmarks ) {
310  auto const tickmarks = color_tickmarks( norm, settings.num_ticks );
311  for( auto const& tick : tickmarks ) {
312  if( tick.first < 0.0 || tick.first > 1.0 ) {
313  throw std::runtime_error( "Color Normalization tickmark out of [ 0.0, 1.0 ]" );
314  }
315  make_tick( tick.first, tick.second );
316  }
317  }
318 }
319 
320 // =================================================================================================
321 // Svg Color Bar
322 // =================================================================================================
323 
324 std::pair<SvgGradientLinear, SvgGroup> make_svg_color_bar(
325  SvgColorBarSettings const& settings,
326  ColorMap const& map,
327  ColorNormalization const& norm,
328  std::string const& id
329 ) {
330 
331  if( map.palette().size() < 2 ) {
332  throw std::runtime_error(
333  "Cannot make SvgPalette with a ColorMap of less than two colors."
334  );
335  }
336  if( ! norm.is_valid() ) {
337  throw std::runtime_error(
338  "Invaid ColorNormalization settings."
339  );
340  }
341 
342  // Prepare result.
343  auto result = std::pair<SvgGradientLinear, SvgGroup>();
344 
345  // We have an ugly special case for boundary norms, where we do not want to display
346  // a gradient, but discrete color bars instead...
347  auto norm_boundary = dynamic_cast<ColorNormalizationBoundary const*>( &norm );
348  if( norm_boundary ) {
349  auto const norm_gradient = color_stops( map, norm );
350  result = make_svg_color_bar_discrete_( settings, norm_gradient );
351  } else {
352  result = make_svg_color_bar_gradient_( settings, map, norm, id );
353  }
354 
355  // Add the tickmarks to the bar/
356  make_svg_color_bar_tickmarks_( settings, map, norm, result.second );
357 
358  return result;
359 }
360 
361 // =================================================================================================
362 // Svg Color List
363 // =================================================================================================
364 
365 void make_svg_color_list_entry_( size_t i, Color const& color, std::string const& label, SvgGroup& group )
366 {
367  group << SvgRect(
368  0.0, i * 15.0, 10.0, 10.0,
370  SvgFill( color )
371  );
372  group << SvgText( label, SvgPoint( 20.0, i * 15.0 + 10.0 ));
373 }
374 
376  ColorMap const& map,
377  std::vector<std::string> const& labels
378 ) {
379  SvgGroup group;
380 
381  for( size_t i = 0; i < labels.size(); ++i ) {
382  make_svg_color_list_entry_( i, map.color(i), labels[i], group );
383  }
384 
385  return group;
386 }
387 
389  std::vector<Color> const& colors,
390  std::vector<std::string> const& labels
391 ) {
392  if( colors.size() != labels.size() ) {
393  throw std::invalid_argument( "List of colors and list of labels have different size." );
394  }
395 
396  SvgGroup group;
397  for( size_t i = 0; i < labels.size(); ++i ) {
398  make_svg_color_list_entry_( i, colors[i], labels[i], group );
399  }
400  return group;
401 }
402 
403 } // namespace utils
404 } // namespace genesis
Color color(size_t index) const
Return a particular color from the palette, module the palette size.
Definition: map.hpp:259
bool clip_over() const
Clip (clamp) values greater than max() to be inside [ min, max ].
Definition: map.hpp:141
Store a list of colors and offer them as a map for values in range [ 0.0, 1.0 ].
Definition: map.hpp:61
void make_svg_color_list_entry_(size_t i, Color const &color, std::string const &label, SvgGroup &group)
Definition: color_bar.cpp:365
std::unordered_set< std::string > labels(SequenceSet const &set)
Return a set of all labels of the SequenceSet.
Definition: labels.cpp:64
SvgGroup make_svg_color_list(ColorMap const &map, std::vector< std::string > const &labels)
Definition: color_bar.cpp:375
Container namespace for all symbols of genesis in order to keep them separate when used as a library...
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
Header of Color class.
static std::pair< SvgGradientLinear, SvgGroup > make_svg_color_bar_discrete_(SvgColorBarSettings const &settings, std::map< double, Color > const &stops)
Definition: color_bar.cpp:129
static void make_svg_color_bar_tickmarks_(SvgColorBarSettings const &settings, ColorMap const &map, ColorNormalization const &norm, SvgGroup &group)
Definition: color_bar.cpp:211
bool clip_under() const
Clip (clamp) values less than min() to be inside [ min, max ].
Definition: map.hpp:131
Provides some commonly used string utility functions.
size_t size() const
Return the size of the map, that is, the number of colors in the list.
Definition: map.hpp:248
std::map< double, Color > color_stops(ColorMap const &map, ColorNormalization const &norm)
Definition: helpers.cpp:52
Base class for color normalization.
std::map< double, std::string > color_tickmarks(ColorNormalization const &norm, size_t num_ticks)
Definition: helpers.cpp:156
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:324
Color normalization that maps to discrete intervals.
bool is_valid() const
Return whether ranges and other values are correct.
std::shared_ptr< BaseOutputTarget > to_string(std::string &target_string)
Obtain an output target for writing to a string.
Color operators and functions.
ColorMap & palette(std::vector< Color > const &value)
Definition: map.hpp:219