Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
RAxis.hxx
Go to the documentation of this file.
1 /// \file ROOT/RAxis.h
2 /// \ingroup Hist ROOT7
3 /// \author Axel Naumann <axel@cern.ch>
4 /// \date 2015-03-23
5 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6 /// is welcome!
7 
8 /*************************************************************************
9  * Copyright (C) 1995-2015, Rene Brun and Fons Rademakers. *
10  * All rights reserved. *
11  * *
12  * For the licensing terms see $ROOTSYS/LICENSE. *
13  * For the list of contributors see $ROOTSYS/README/CREDITS. *
14  *************************************************************************/
15 
16 #ifndef ROOT7_RAxis
17 #define ROOT7_RAxis
18 
19 #include <algorithm>
20 #include <cmath>
21 #include <initializer_list>
22 #include <string>
23 #include <unordered_map>
24 #include <vector>
25 
26 #include "ROOT/RStringView.hxx"
27 #include "ROOT/RLogger.hxx"
28 
29 namespace ROOT {
30 namespace Experimental {
31 
32 /**
33  \class RAxisBase
34  Histogram axis base class. Keeps track of the number of bins and overflow
35  handling. Offers bin iteration.
36 
37  Bin indices are starting from 0 for the underflow bin (representing values that
38  are lower than the axis range). Starting at index 1 are the actual bins of the
39  axis, up to N + 1 for an axis with N bins. Index N + 2 is the overflow bin for
40  values larger than the axis range.
41  */
42 class RAxisBase {
43 public:
44  /// Status of FindBin(x)
45  enum class EFindStatus {
46  kCanGrow, ///< Coordinate could fit after growing the axis
47  kValid ///< The returned bin index is valid
48  };
49 
50 protected:
51  ///\name Inaccessible copy, assignment
52  /// The copy and move constructors and assignment operators are protected to
53  /// prevent slicing.
54  ///\{
55  RAxisBase(const RAxisBase &) = default;
56  RAxisBase(RAxisBase &&) = default;
57  RAxisBase &operator=(const RAxisBase &) = default;
58  RAxisBase &operator=(RAxisBase &&) = default;
59  ///\}
60 
61  /// Default construct a RAxisBase (for use by derived classes for I/O)
62  RAxisBase() = default;
63 
64  /// Given rawbin (<0 for underflow, >= GetNBinsNoOver() for overflow), determine the
65  /// actual bin number taking into account how over/underflow should be
66  /// handled.
67  ///
68  /// \param[out] status result status of the bin determination.
69  /// \return Returns the bin number adjusted for potential over- and underflow
70  /// bins. Returns kIgnoreBin if the axis cannot handle the over- / underflow,
71  /// in which case `status` will tell how to deal with this overflow.
72  int AdjustOverflowBinNumber(double rawbin) const
73  {
74  if (rawbin < 0)
75  return 0;
76  // Take underflow into account.
77  ++rawbin;
78 
79  if (rawbin >= GetNBins())
80  return GetNBins() - 1;
81 
82  return (int)rawbin;
83  }
84 
85 public:
86  /**
87  \class const_iterator
88  Random const_iterator through bins. Represents the bin index, not a bin
89  content: the axis has no notion of any content.
90  */
91  class const_iterator: public std::iterator<std::random_access_iterator_tag, int /*value*/, int /*distance*/,
92  const int * /*pointer*/, const int & /*ref*/> {
93  int fCursor = 0; ///< Current iteration position
94 
95  public:
96  const_iterator() = default;
97 
98  /// Initialize a const_iterator with its position
99  explicit const_iterator(int cursor) noexcept: fCursor(cursor) {}
100 
101  /// ++i
102  const_iterator &operator++() noexcept
103  {
104  // Could check whether fCursor < fEnd - but what for?
105  ++fCursor;
106  return *this;
107  }
108 
109  /// --i
110  const_iterator &operator--() noexcept
111  {
112  // Could check whether fCursor > fBegin - but what for?
113  --fCursor;
114  return *this;
115  }
116 
117  /// i++
118  const_iterator operator++(int)noexcept
119  {
120  const_iterator old(*this);
121  ++(*this);
122  return old;
123  }
124 
125  // i--
126  const_iterator operator--(int)noexcept
127  {
128  const_iterator old(*this);
129  --(*this);
130  return old;
131  }
132 
133  // i += 2
134  const_iterator &operator+=(int d) noexcept
135  {
136  fCursor += d;
137  return *this;
138  }
139 
140  // i -= 2
141  const_iterator &operator-=(int d) noexcept
142  {
143  fCursor -= d;
144  return *this;
145  }
146 
147  // i + 2
148  const_iterator operator+(int d) noexcept
149  {
150  const_iterator ret(*this);
151  ret += d;
152  return ret;
153  }
154 
155  // i - 2
156  const_iterator operator-(int d) noexcept
157  {
158  const_iterator ret(*this);
159  ret -= d;
160  return ret;
161  }
162 
163  // *i
164  const int *operator*() const noexcept { return &fCursor; }
165 
166  // i->
167  int operator->() const noexcept { return fCursor; }
168 
169  friend bool operator<(const_iterator lhs, const_iterator rhs) noexcept;
170  friend bool operator>(const_iterator lhs, const_iterator rhs) noexcept;
171  friend bool operator<=(const_iterator lhs, const_iterator rhs) noexcept;
172  friend bool operator>=(const_iterator lhs, const_iterator rhs) noexcept;
173  friend bool operator==(const_iterator lhs, const_iterator rhs) noexcept;
174  friend bool operator!=(const_iterator lhs, const_iterator rhs) noexcept;
175  };
176 
177  /// FindBin() returns this bin to signal that the bin number is invalid.
178  constexpr static const int kIgnoreBin = -1;
179 
180  /// Extra bins for each EAxisOverflow value.
181  constexpr static const int kNOverflowBins[4] = {0, 1, 1, 2};
182 
183  /// Construct a RAxisBase.
184  ///
185  ///\param[in] title - axis title used for graphics and text representation.
186  ///\param[in] nbins - number of bins in this axis, excluding under- and
187  /// overflow bins.
188  ///\param[in] canGrow - whether this axis can extend its range.
189  RAxisBase(std::string_view title, int nbinsNoOver, bool canGrow) noexcept
190  : fNBins(nbinsNoOver + (canGrow ? 0 : 2)), fTitle(title), fCanGrow(canGrow)
191  {}
192 
193  /// Construct a RAxisBase.
194  ///
195  ///\param[in] nbins - number of bins in this axis, excluding under- and
196  /// overflow bins.
197  ///\param[in] canGrow - whether this axis can extend its range.
198  RAxisBase(int nbinsNoOver, bool canGrow) noexcept: RAxisBase("", nbinsNoOver, canGrow) {}
199 
200  const std::string &GetTitle() const { return fTitle; }
201 
202  /// Get the number of bins, excluding under- and overflow.
203  int GetNBinsNoOver() const noexcept { return fNBins - GetNOverflowBins(); }
204 
205  /// Get the number of bins, including under- and overflow.
206  int GetNBins() const noexcept { return fNBins; }
207 
208  /// Get the number of over- and underflow bins: 0 for growable axes, 2 otherwise.
209  int GetNOverflowBins() const noexcept
210  {
211  if (fCanGrow)
212  return 0;
213  else
214  return 2;
215  };
216 
217  /// Get the bin index for the underflow bin.
218  int GetUnderflowBin() const noexcept { return 0; }
219 
220  /// Get the bin index for the underflow bin (or the next bin outside range
221  /// if CanGrow()).
222  int GetOverflowBin() const noexcept { return GetNBinsNoOver() + 1; }
223 
224  /// Whether the bin index is referencing a bin lower than the axis range.
225  bool IsUnderflowBin(int bin) const noexcept { return bin <= GetUnderflowBin(); }
226 
227  /// Whether the bin index is referencing a bin higher than the axis range.
228  bool IsOverflowBin(int bin) const noexcept { return bin >= GetOverflowBin(); }
229 
230  ///\name Iterator interfaces
231  ///\{
232 
233  /// Get a const_iterator pointing to the first non-underflow bin.
234  const_iterator begin() const noexcept { return const_iterator{1}; }
235 
236  /// Get a const_iterator pointing the underflow bin.
237  const_iterator begin_with_underflow() const noexcept { return const_iterator{0}; }
238 
239  /// Get a const_iterator pointing right beyond the last non-overflow bin
240  /// (i.e. pointing to the overflow bin).
241  const_iterator end() const noexcept { return const_iterator{GetOverflowBin()}; }
242 
243  /// Get a const_iterator pointing right beyond the overflow bin.
244  const_iterator end_with_overflow() const noexcept { return const_iterator{GetOverflowBin() + 1}; }
245  ///\}
246 
247 private:
248  unsigned int fNBins; ///< Number of bins including under- and overflow.
249  std::string fTitle; ///< Title of this axis, used for graphics / text.
250  bool fCanGrow = false; ///< Whether this axis can grow (and thus has no overflow bins).
251 };
252 
253 ///\name RAxisBase::const_iterator comparison operators
254 ///\{
255 
256 /// i < j
257 inline bool operator<(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
258 {
259  return lhs.fCursor < rhs.fCursor;
260 }
261 
262 /// i > j
263 inline bool operator>(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
264 {
265  return lhs.fCursor > rhs.fCursor;
266 }
267 
268 /// i <= j
269 inline bool operator<=(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
270 {
271  return lhs.fCursor <= rhs.fCursor;
272 }
273 
274 /// i >= j
275 inline bool operator>=(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
276 {
277  return lhs.fCursor >= rhs.fCursor;
278 }
279 
280 /// i == j
281 inline bool operator==(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
282 {
283  return lhs.fCursor == rhs.fCursor;
284 }
285 
286 /// i != j
287 inline bool operator!=(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
288 {
289  return lhs.fCursor != rhs.fCursor;
290 }
291 ///\}
292 
293 /**
294 \class RAxisConfig
295 Objects used to configure the different axis types. It can store the
296 properties of all possible axis types, together with the type of the axis.
297 
298 RODO: that's what a variant will be invented for!
299 */
300 class RAxisConfig: public RAxisBase {
301 public:
302  enum EKind {
303  kEquidistant, ///< represents a RAxisEquidistant
304  kGrow, ///< represents a RAxisGrow
305  kIrregular, ///< represents a RAxisIrregular
306  kLabels, ///< represents a RAxisLabels
307  kNumKinds
308  };
309 
310 private:
311  EKind fKind; ///< The kind of axis represented by this configuration
312  std::vector<double> fBinBorders; ///< Bin borders of the RAxisIrregular
313  std::vector<std::string> fLabels; ///< Bin labels for a RAxisLabels
314 
315  /// Represents a `RAxisEquidistant` with `nbins` from `from` to `to`, and
316  /// axis title.
317  explicit RAxisConfig(std::string_view title, int nbins, double from, double to, EKind kind)
318  : RAxisBase(title, nbins, kind == kGrow), fKind(kind), fBinBorders(2)
319  {
320  if (from > to)
321  std::swap(to, from);
322 
323  fBinBorders[0] = from;
324  fBinBorders[1] = to;
325  }
326 
327 public:
328  /// Tag type signalling that an axis should be able to grow; used for calling
329  /// the appropriate constructor.
330  struct Grow_t {
331  };
332  /// Tag signalling that an axis should be able to grow; used for calling the
333  /// appropriate constructor like so:
334  /// RAxisConfig ac(RAxisConfig::Grow, 10, 0., 1.);
335  constexpr static const Grow_t Grow{};
336 
337  /// Represents a `RAxisEquidistant` with `nbins` from `from` to `to`, and
338  /// axis title.
339  RAxisConfig(std::string_view title, int nbins, double from, double to)
340  : RAxisConfig(title, nbins, from, to, kEquidistant)
341  {}
342 
343  /// Represents a `RAxisEquidistant` with `nbins` from `from` to `to`.
344  RAxisConfig(int nbins, double from, double to): RAxisConfig("", nbins, from, to, kEquidistant) {}
345 
346  /// Represents a `RAxisGrow` with `nbins` from `from` to `to`, and axis title.
347  RAxisConfig(std::string_view title, Grow_t, int nbins, double from, double to)
348  : RAxisConfig(title, nbins, from, to, kGrow)
349  {}
350 
351  /// Represents a `RAxisGrow` with `nbins` from `from` to `to`.
352  RAxisConfig(Grow_t, int nbins, double from, double to): RAxisConfig("", nbins, from, to, kGrow) {}
353 
354  /// Represents a `RAxisIrregular` with `binborders` and title.
355  RAxisConfig(std::string_view title, const std::vector<double> &binborders)
356  : RAxisBase(title, binborders.size() - 1, false /*canGrow*/), fKind(kIrregular), fBinBorders(binborders)
357  {}
358 
359  /// Represents a `RAxisIrregular` with `binborders`.
360  RAxisConfig(const std::vector<double> &binborders): RAxisConfig("", binborders) {}
361 
362  /// Represents a `RAxisIrregular` with `binborders` and title.
363  RAxisConfig(std::string_view title, std::vector<double> &&binborders) noexcept
364  : RAxisBase(title, binborders.size() - 1, false /*canGrow*/), fKind(kIrregular),
365  fBinBorders(std::move(binborders))
366  {}
367 
368  /// Represents a `RAxisIrregular` with `binborders`.
369  RAxisConfig(std::vector<double> &&binborders) noexcept: RAxisConfig("", std::move(binborders)) {}
370 
371  /// Represents a `RAxisLabels` with `labels` and title.
372  RAxisConfig(std::string_view title, const std::vector<std::string_view> &labels)
373  : RAxisBase(title, labels.size(), true /*canGrow*/), fKind(kLabels), fLabels(labels.begin(), labels.end())
374  {}
375 
376  /// Represents a `RAxisLabels` with `labels`.
377  RAxisConfig(const std::vector<std::string_view> &labels): RAxisConfig("", labels) {}
378 
379  /// Represents a `RAxisLabels` with `labels` and title.
380  RAxisConfig(std::string_view title, std::vector<std::string> &&labels)
381  : RAxisBase(title, labels.size(), true /*canGrow*/), fKind(kLabels), fLabels(std::move(labels))
382  {}
383 
384  /// Represents a `RAxisLabels` with `labels`.
385  RAxisConfig(std::vector<std::string> &&labels): RAxisConfig("", std::move(labels)) {}
386 
387  /// Get the axis kind represented by this `RAxisConfig`.
388  EKind GetKind() const noexcept { return fKind; }
389 
390  /// Get the bin borders; non-empty if the GetKind() == kIrregular.
391  const std::vector<double> &GetBinBorders() const noexcept { return fBinBorders; }
392 
393  /// Get the bin labels; non-empty if the GetKind() == kLabels.
394  const std::vector<std::string> &GetBinLabels() const noexcept { return fLabels; }
395 };
396 
397 /**
398  Axis with equidistant bin borders. Defined by lower l and upper u limit and
399  the number of bins n. All bins have the same width (u-l)/n.
400 
401  This axis cannot grow; use `RAxisGrow` for that.
402  */
403 class RAxisEquidistant: public RAxisBase {
404 protected:
405  double fLow = 0.; ///< The lower limit of the axis
406  double fInvBinWidth = 0.; ///< The inverse of the bin width
407 
408  /// Determine the inverse bin width.
409  /// \param nbinsNoOver - number of bins without unter-/overflow
410  /// \param lowOrHigh - first axis boundary
411  /// \param lighOrLow - second axis boundary
412  static double GetInvBinWidth(int nbinsNoOver, double lowOrHigh, double highOrLow)
413  {
414  return nbinsNoOver / std::abs(highOrLow - lowOrHigh);
415  }
416 
417  /// Initialize a RAxisEquidistant.
418  /// \param[in] title - axis title used for graphics and text representation.
419  /// \param nbins - number of bins in the axis, excluding under- and overflow
420  /// bins.
421  /// \param low - the low axis range. Any coordinate below that is considered
422  /// as underflow. The first bin's lower edge is at this value.
423  /// \param high - the high axis range. Any coordinate above that is considered
424  /// as overflow. The last bin's higher edge is at this value.
425  explicit RAxisEquidistant(std::string_view title, int nbinsNoOver, double low, double high, bool canGrow) noexcept
426  : RAxisBase(title, nbinsNoOver, canGrow), fLow(low), fInvBinWidth(GetInvBinWidth(nbinsNoOver, low, high))
427  {}
428 
429  /// Initialize a RAxisEquidistant.
430  /// \param nbins - number of bins in the axis, excluding under- and overflow
431  /// bins.
432  /// \param low - the low axis range. Any coordinate below that is considered
433  /// as underflow. The first bin's lower edge is at this value.
434  /// \param high - the high axis range. Any coordinate above that is considered
435  /// as overflow. The last bin's higher edge is at this value.
436  explicit RAxisEquidistant(int nbinsNoOver, double low, double high, bool canGrow) noexcept
437  : RAxisEquidistant("", nbinsNoOver, low, high, canGrow)
438  {}
439 
440 public:
441  RAxisEquidistant() = default;
442 
443  /// Initialize a RAxisEquidistant.
444  /// \param nbins - number of bins in the axis, excluding under- and overflow
445  /// bins.
446  /// \param low - the low axis range. Any coordinate below that is considered
447  /// as underflow. The first bin's lower edge is at this value.
448  /// \param high - the high axis range. Any coordinate above that is considered
449  /// as overflow. The last bin's higher edge is at this value.
450  /// \param canGrow - whether this axis can extend its range.
451  explicit RAxisEquidistant(int nbinsNoOver, double low, double high) noexcept
452  : RAxisEquidistant(nbinsNoOver, low, high, false /*canGrow*/)
453  {}
454 
455  /// Initialize a RAxisEquidistant.
456  /// \param[in] title - axis title used for graphics and text representation.
457  /// \param nbins - number of bins in the axis, excluding under- and overflow
458  /// bins.
459  /// \param low - the low axis range. Any coordinate below that is considered
460  /// as underflow. The first bin's lower edge is at this value.
461  /// \param high - the high axis range. Any coordinate above that is considered
462  /// as overflow. The last bin's higher edge is at this value.
463  explicit RAxisEquidistant(std::string_view title, int nbinsNoOver, double low, double high) noexcept
464  : RAxisEquidistant(title, nbinsNoOver, low, high, false /*canGrow*/)
465  {}
466 
467  /// Convert to RAxisConfig.
468  operator RAxisConfig() const { return RAxisConfig(GetNBinsNoOver(), GetMinimum(), GetMaximum()); }
469 
470  /// Find the bin index for the given coordinate.
471  /// \note Passing a bin border coordinate can either return the bin above or
472  /// below the bin border. I.e. don't do that for reliable results!
473  int FindBin(double x) const noexcept
474  {
475  double rawbin = (x - fLow) * fInvBinWidth;
476  return AdjustOverflowBinNumber(rawbin);
477  }
478 
479  /// This axis cannot grow.
480  static bool CanGrow() noexcept { return false; }
481 
482  /// Get the low end of the axis range.
483  double GetMinimum() const noexcept { return fLow; }
484 
485  /// Get the high end of the axis range.
486  double GetMaximum() const noexcept { return fLow + GetNBinsNoOver() / fInvBinWidth; }
487 
488  /// Get the width of the bins
489  double GetBinWidth() const noexcept { return 1. / fInvBinWidth; }
490 
491  /// Get the inverse of the width of the bins
492  double GetInverseBinWidth() const noexcept { return fInvBinWidth; }
493 
494  /// Get the bin center for the given bin index.
495  /// For the bin == 1 (the first bin) of 2 bins for an axis (0., 1.), this
496  /// returns 0.25.
497  double GetBinCenter(int bin) const noexcept { return fLow + (bin - 0.5) / fInvBinWidth; }
498 
499  /// Get the low bin border for the given bin index.
500  /// For the bin == 1 (the first bin) of 2 bins for an axis (0., 1.), this
501  /// returns 0.
502  double GetBinFrom(int bin) const noexcept { return fLow + (bin - 1) / fInvBinWidth; }
503 
504  /// Get the high bin border for the given bin index.
505  /// For the bin == 1 (the first bin) of 2 bins for an axis (0., 1.), this
506  /// returns 0.5.
507  double GetBinTo(int bin) const noexcept { return GetBinFrom(bin + 1); }
508 
509  int GetBinIndexForLowEdge(double x) const noexcept;
510 };
511 
512 /// Equality-compare two RAxisEquidistant.
513 inline bool operator==(const RAxisEquidistant &lhs, const RAxisEquidistant &rhs) noexcept
514 {
515  return lhs.GetNBins() == rhs.GetNBins() && lhs.GetMinimum() == rhs.GetMinimum() &&
516  lhs.GetInverseBinWidth() == rhs.GetInverseBinWidth();
517 }
518 
519 /** An axis that can extend its range, keeping the number of its bins unchanged.
520  The axis is constructed with an initial range. Apart from its ability to
521  grow, this axis behaves like a RAxisEquidistant.
522  */
523 class RAxisGrow: public RAxisEquidistant {
524 public:
525  /// Initialize a RAxisGrow.
526  /// \param nbins - number of bins in the axis, excluding under- and overflow
527  /// bins. This value is fixed over the lifetime of the object.
528  /// \param low - the initial value for the low axis range. Any coordinate
529  /// below that is considered as underflow. To trigger the growing of the
530  /// axis call Grow().
531  /// \param high - the initial value for the high axis range. Any coordinate
532  /// above that is considered as overflow. To trigger the growing of the
533  /// axis call Grow()
534  explicit RAxisGrow(std::string_view title, int nbins, double low, double high) noexcept
535  : RAxisEquidistant(title, nbins, low, high, CanGrow())
536  {}
537 
538  /// Initialize a RAxisGrow.
539  /// \param[in] title - axis title used for graphics and text representation.
540  /// \param nbins - number of bins in the axis, excluding under- and overflow
541  /// bins. This value is fixed over the lifetime of the object.
542  /// \param low - the initial value for the low axis range. Any coordinate
543  /// below that is considered as underflow. To trigger the growing of the
544  /// axis call Grow().
545  /// \param high - the initial value for the high axis range. Any coordinate
546  /// above that is considered as overflow. To trigger the growing of the
547  /// axis call Grow()
548  explicit RAxisGrow(int nbins, double low, double high) noexcept: RAxisEquidistant(nbins, low, high, CanGrow()) {}
549 
550  /// Convert to RAxisConfig.
551  operator RAxisConfig() const { return RAxisConfig(RAxisConfig::Grow, GetNBinsNoOver(), GetMinimum(), GetMaximum()); }
552 
553  /// Grow this axis to make the "virtual bin" toBin in-range. This keeps the
554  /// non-affected axis limit unchanged, and extends the other axis limit such
555  /// that a number of consecutive bins are merged.
556  ///
557  /// Example, assuming an initial RAxisGrow with 10 bins from 0. to 1.:
558  /// - `Grow(0)`: that (virtual) bin spans from -0.1 to 0. To include it
559  /// in the axis range, the lower limit must be shifted. The minimal number
560  /// of bins that can be merged is 2, thus the new axis will span from
561  /// -1. to 1.
562  /// - `Grow(-1)`: that (virtual) bin spans from -0.2 to 0.1. To include it
563  /// in the axis range, the lower limit must be shifted. The minimal number
564  /// of bins that can be merged is 2, thus the new axis will span from
565  /// -1. to 1.
566  /// - `Grow(50)`: that (virtual) bin spans from 4.9 to 5.0. To include it
567  /// in the axis range, the higher limit must be shifted. Five bins need to
568  /// be merged, making the new axis range 0. to 5.0.
569  ///
570  /// \param toBin - the "virtual" bin number, as if the axis had an infinite
571  /// number of bins with the current bin width. For instance, for an axis
572  /// with ten bins in the range 0. to 1., the coordinate 2.05 has the virtual
573  /// bin index 20.
574  /// \return Returns the number of bins that were merged to reach the value.
575  /// A value of 1 means that no bins were merged (toBin was in the original
576  /// axis range).
577  int Grow(int toBin);
578 
579  /// This axis kind can increase its range.
580  bool CanGrow() const { return true; }
581 };
582 
583 /**
584  An axis with non-equidistant bins (also known as "variable binning"). It is
585  defined by an array of bin borders - one more than the number of
586  (non-overflow-) bins it has! As an example, an axis with two bin needs three
587  bin borders:
588  - lower edge of the first bin;
589  - higher edge of the first bin, identical to the lower edge of the second
590  bin;
591  - higher edge of the second bin
592 
593  This axis cannot grow; the size of new bins would not be well defined.
594  */
595 class RAxisIrregular: public RAxisBase {
596 private:
597  /// Bin borders, one more than the number of non-overflow bins.
598  std::vector<double> fBinBorders;
599 
600 public:
601  RAxisIrregular() = default;
602 
603  /// Construct a RAxisIrregular from a vector of bin borders.
604  /// \note The bin borders must be sorted in increasing order!
605  explicit RAxisIrregular(const std::vector<double> &binborders)
606  : RAxisBase(binborders.size() - 1, CanGrow()), fBinBorders(binborders)
607  {
608 #ifdef R__DO_RANGE_CHECKS
609  if (!std::is_sorted(fBinBorders.begin(), fBinBorders.end()))
610  R__ERROR_HERE("HIST") << "Bin borders must be sorted!";
611 #endif // R__DO_RANGE_CHECKS
612  }
613 
614  /// Construct a RAxisIrregular from a vector of bin borders.
615  /// \note The bin borders must be sorted in increasing order!
616  /// Faster, noexcept version taking an rvalue of binborders. The compiler will
617  /// know when it can take this one.
618  explicit RAxisIrregular(std::vector<double> &&binborders) noexcept
619  : RAxisBase(binborders.size() - 1, CanGrow()), fBinBorders(std::move(binborders))
620  {
621 #ifdef R__DO_RANGE_CHECKS
622  if (!std::is_sorted(fBinBorders.begin(), fBinBorders.end()))
623  R__ERROR_HERE("HIST") << "Bin borders must be sorted!";
624 #endif // R__DO_RANGE_CHECKS
625  }
626 
627  /// Construct a RAxisIrregular from a vector of bin borders.
628  /// \note The bin borders must be sorted in increasing order!
629  explicit RAxisIrregular(std::string_view title, const std::vector<double> &binborders)
630  : RAxisBase(title, binborders.size() - 1, CanGrow()), fBinBorders(binborders)
631  {
632 #ifdef R__DO_RANGE_CHECKS
633  if (!std::is_sorted(fBinBorders.begin(), fBinBorders.end()))
634  R__ERROR_HERE("HIST") << "Bin borders must be sorted!";
635 #endif // R__DO_RANGE_CHECKS
636  }
637 
638  /// Construct a RAxisIrregular from a vector of bin borders.
639  /// \note The bin borders must be sorted in increasing order!
640  /// Faster, noexcept version taking an rvalue of binborders. The compiler will
641  /// know when it can take this one.
642  explicit RAxisIrregular(std::string_view title, std::vector<double> &&binborders) noexcept
643  : RAxisBase(title, binborders.size() - 1, CanGrow()), fBinBorders(std::move(binborders))
644  {
645 #ifdef R__DO_RANGE_CHECKS
646  if (!std::is_sorted(fBinBorders.begin(), fBinBorders.end()))
647  R__ERROR_HERE("HIST") << "Bin borders must be sorted!";
648 #endif // R__DO_RANGE_CHECKS
649  }
650 
651  /// Convert to RAxisConfig.
652  operator RAxisConfig() const { return RAxisConfig(GetBinBorders()); }
653 
654  /// Find the bin index corresponding to coordinate x. If the coordinate is
655  /// below the axis range, return 0. If it is above, return N + 1 for an axis
656  /// with N non-overflow bins.
657  int FindBin(double x) const noexcept
658  {
659  const auto bBegin = fBinBorders.begin();
660  const auto bEnd = fBinBorders.end();
661  // lower_bound finds the first bin border that is >= x.
662  auto iNotLess = std::lower_bound(bBegin, bEnd, x);
663  int rawbin = iNotLess - bBegin;
664  // No need for AdjustOverflowBinNumber(rawbin) here; lower_bound() is the
665  // answer: e.g. for x < *bBegin, rawbin is 0.
666  return rawbin;
667  }
668 
669  /// Get the bin center of the bin with the given index.
670  ///
671  /// For the bin at index 0 (i.e. the underflow bin), a bin center of
672  /// `std::numeric_limits<double>::min()` is returned, i.e. the minimum value
673  /// that can be held in a double.
674  /// Similarly, for the bin at index N + 1 (i.e. the overflow bin), a bin
675  /// center of `std::numeric_limits<double>::max()` is returned, i.e. the
676  /// maximum value that can be held in a double.
677  double GetBinCenter(int bin) const noexcept
678  {
679  if (IsUnderflowBin(bin))
680  return std::numeric_limits<double>::min();
681  if (IsOverflowBin(bin))
682  return std::numeric_limits<double>::max();
683  return 0.5 * (fBinBorders[bin - 1] + fBinBorders[bin]);
684  }
685 
686  /// Get the lower bin border for a given bin index.
687  ///
688  /// For the bin at index 0 (i.e. the underflow bin), a lower bin border of
689  /// `std::numeric_limits<double>::min()` is returned, i.e. the minimum value
690  /// that can be held in a double.
691  double GetBinFrom(int bin) const noexcept
692  {
693  if (IsUnderflowBin(bin))
694  return std::numeric_limits<double>::min();
695  // bin 0 is underflow;
696  // bin 1 starts at fBinBorders[0]
697  return fBinBorders[bin - 1];
698  }
699 
700  /// Get the higher bin border for a given bin index.
701  ///
702  /// For the bin at index N + 1 (i.e. the overflow bin), a bin border of
703  /// `std::numeric_limits<double>::max()` is returned, i.e. the maximum value
704  /// that can be held in a double.
705  double GetBinTo(int bin) const noexcept
706  {
707  if (IsOverflowBin(bin))
708  return std::numeric_limits<double>::max();
709  return GetBinFrom(bin + 1);
710  }
711 
712  /// This axis cannot be extended.
713  static bool CanGrow() noexcept { return false; }
714 
715  /// Access to the bin borders used by this axis.
716  const std::vector<double> &GetBinBorders() const noexcept { return fBinBorders; }
717 };
718 
719 /**
720  \class RAxisLabels
721  A RAxisGrow that has a label assigned to each bin and a bin width of 1.
722 
723  While filling still works through coordinates (i.e. arrays of doubles),
724  RAxisLabels allows to convert a string to a bin number or the bin's coordinate
725  center. The number of labels and the number of bins reported by RAxisGrow might
726  differ: the RAxisGrow will only grow when seeing a Fill(), while the RAxisLabels
727  will add a new label whenever `GetBinCenter()` is called.
728 
729  Implementation details:
730  Filling happens often; GetBinCenter() needs to be fast. Thus the unordered_map.
731  The painter needs the reverse: it wants the label for bin 0, bin 1 etc. The axis
732  should only store the bin labels once; referencing them is (due to re-allocation,
733  hashing etc) non-trivial. So instead, build a vector<string_view> for the few
734  times the axis needs to be painted.
735  */
736 class RAxisLabels: public RAxisGrow {
737 private:
738  /// Map of label (view on `fLabels`'s elements) to bin index
739  std::unordered_map<std::string, int /*bin number*/> fLabelsIndex;
740 
741 public:
742  /// Construct a RAxisLables from a `vector` of `string_view`s, with title.
743  explicit RAxisLabels(std::string_view title, const std::vector<std::string_view> &labels)
744  : RAxisGrow(title, labels.size(), 0., static_cast<double>(labels.size()))
745  {
746  for (size_t i = 0, n = labels.size(); i < n; ++i)
747  fLabelsIndex[std::string(labels[i])] = i;
748  }
749 
750  /// Construct a RAxisLables from a `vector` of `string`s, with title.
751  explicit RAxisLabels(std::string_view title, const std::vector<std::string> &labels)
752  : RAxisGrow(title, labels.size(), 0., static_cast<double>(labels.size()))
753  {
754  for (size_t i = 0, n = labels.size(); i < n; ++i)
755  fLabelsIndex[labels[i]] = i;
756  }
757 
758  /// Construct a RAxisLables from a `vector` of `string_view`s
759  explicit RAxisLabels(const std::vector<std::string_view> &labels): RAxisLabels("", labels) {}
760 
761  /// Construct a RAxisLables from a `vector` of `string`s
762  explicit RAxisLabels(const std::vector<std::string> &labels): RAxisLabels("", labels) {}
763 
764  /// Get the bin index with label.
765  int GetBinIndex(const std::string &label)
766  {
767  auto insertResult = fLabelsIndex.insert({label, -1});
768  if (insertResult.second) {
769  // we have created a new label
770  int idx = fLabelsIndex.size() - 1;
771  insertResult.first->second = idx;
772  return idx;
773  }
774  return insertResult.first->second;
775  }
776 
777  /// Get the center of the bin with label.
778  double GetBinCenter(const std::string &label)
779  {
780  return GetBinIndex(label) - 0.5; // bin *center*
781  }
782 
783  /// Build a vector of labels. The position in the vector defines the label's bin.
784  std::vector<std::string_view> GetBinLabels() const
785  {
786  std::vector<std::string_view> vec(fLabelsIndex.size());
787  for (const auto &kv: fLabelsIndex)
788  vec.at(kv.second) = kv.first;
789  return vec;
790  }
791 };
792 
793 namespace Internal {
794 
795 /// Converts a RAxisConfig of whatever kind to the corresponding RAxisBase-derived
796 /// object.
797 template <RAxisConfig::EKind>
798 struct AxisConfigToType; // Only specializations are defined.
799 
800 template <>
801 struct AxisConfigToType<RAxisConfig::kEquidistant> {
802  using Axis_t = RAxisEquidistant;
803 
804  Axis_t operator()(const RAxisConfig &cfg) noexcept
805  {
806  return RAxisEquidistant(cfg.GetTitle(), cfg.GetNBinsNoOver(), cfg.GetBinBorders()[0], cfg.GetBinBorders()[1]);
807  }
808 };
809 
810 template <>
811 struct AxisConfigToType<RAxisConfig::kGrow> {
812  using Axis_t = RAxisGrow;
813 
814  Axis_t operator()(const RAxisConfig &cfg) noexcept
815  {
816  return RAxisGrow(cfg.GetTitle(), cfg.GetNBinsNoOver(), cfg.GetBinBorders()[0], cfg.GetBinBorders()[1]);
817  }
818 };
819 template <>
820 struct AxisConfigToType<RAxisConfig::kIrregular> {
821  using Axis_t = RAxisIrregular;
822 
823  Axis_t operator()(const RAxisConfig &cfg) { return RAxisIrregular(cfg.GetTitle(), cfg.GetBinBorders()); }
824 };
825 
826 template <>
827 struct AxisConfigToType<RAxisConfig::kLabels> {
828  using Axis_t = RAxisLabels;
829 
830  Axis_t operator()(const RAxisConfig &cfg) { return RAxisLabels(cfg.GetTitle(), cfg.GetBinLabels()); }
831 };
832 
833 } // namespace Internal
834 
835 /// Common view on a RAxis, no matter what its kind.
836 class RAxisView {
837  /// View on a `RAxisEquidistant`, `RAxisGrow` or `RAxisLabel`.
838  const RAxisEquidistant *fEqui = nullptr;
839  /// View on a `RAxisIrregular`.
840  const RAxisIrregular *fIrr = nullptr;
841 
842 public:
843  RAxisView() = default;
844 
845  /// Construct a view on a `RAxisEquidistant`, `RAxisGrow` or `RAxisLabel`.
846  RAxisView(const RAxisEquidistant &equi): fEqui(&equi) {}
847 
848  /// Construct a view on a `RAxisIrregular`.
849  RAxisView(const RAxisIrregular &irr): fIrr(&irr) {}
850 
851  const std::string &GetTitle() const { return fEqui ? fEqui->GetTitle() : fIrr->GetTitle(); }
852 
853  /// Find the bin containing coordinate `x`. Forwards to the underlying axis.
854  int FindBin(double x) const noexcept
855  {
856  if (fEqui)
857  return fEqui->FindBin(x);
858  return fIrr->FindBin(x);
859  }
860 
861  /// Get the number of bins. Forwards to the underlying axis.
862  int GetNBins() const noexcept
863  {
864  if (fEqui)
865  return fEqui->GetNBins();
866  return fIrr->GetNBins();
867  }
868 
869  /// Get the lower axis limit.
870  double GetFrom() const { return GetBinFrom(1); }
871  /// Get the upper axis limit.
872  double GetTo() const { return GetBinTo(GetNBins() - 2); }
873 
874  /// Get the bin center of bin index `i`. Forwards to the underlying axis.
875  double GetBinCenter(int i) const noexcept
876  {
877  if (fEqui)
878  return fEqui->GetBinCenter(i);
879  return fIrr->GetBinCenter(i);
880  }
881 
882  /// Get the minimal coordinate of bin index `i`. Forwards to the underlying axis.
883  double GetBinFrom(int i) const noexcept
884  {
885  if (fEqui)
886  return fEqui->GetBinFrom(i);
887  return fIrr->GetBinFrom(i);
888  }
889 
890  /// Get the maximal coordinate of bin index `i`. Forwards to the underlying axis.
891  double GetBinTo(int i) const noexcept
892  {
893  if (fEqui)
894  return fEqui->GetBinTo(i);
895  return fIrr->GetBinTo(i);
896  }
897 
898  /// Get the axis as a RAxisEquidistant; returns nullptr if it's a RAxisIrregular.
899  const RAxisEquidistant *GetAsEquidistant() const { return fEqui; }
900  /// Get the axis as a RAxisIrregular; returns nullptr if it's a RAxisEquidistant.
901  const RAxisIrregular *GetAsIrregular() const { return fIrr; }
902 };
903 
904 ///\name Axis Compatibility
905 ///\{
906 enum class EAxisCompatibility {
907  kIdentical, ///< Source and target axes are identical
908 
909  kContains, ///< The source is a subset of bins of the target axis
910 
911  /// The bins of the source axis have finer granularity, but the bin borders
912  /// are compatible. Example:
913  /// source: 0., 1., 2., 3., 4., 5., 6.; target: 0., 2., 5., 6.
914  /// Note that this is *not* a symmetrical property: only one of
915  /// CanMerge(source, target), CanMap(target, source) can return kContains.
916  kSampling,
917 
918  /// The source axis and target axis have different binning. Example:
919  /// source: 0., 1., 2., 3., 4., target: 0., 0.1, 0.2, 0.3, 0.4
920  kIncompatible
921 };
922 
923 EAxisCompatibility CanMap(RAxisEquidistant &target, RAxisEquidistant &source) noexcept;
924 ///\}
925 
926 } // namespace Experimental
927 } // namespace ROOT
928 
929 #endif