Imported Upstream version 1.72.0
[platform/upstream/boost.git] / boost / histogram / ostream.hpp
1 // Copyright 2015-2019 Hans Dembinski
2 // Copyright 2019 Przemyslaw Bartosik
3 //
4 // Distributed under the Boost Software License, Version 1.0.
5 // (See accompanying file LICENSE_1_0.txt
6 // or copy at http://www.boost.org/LICENSE_1_0.txt)
7
8 #ifndef BOOST_HISTOGRAM_OSTREAM_HPP
9 #define BOOST_HISTOGRAM_OSTREAM_HPP
10
11 #include <boost/histogram/accumulators/ostream.hpp>
12 #include <boost/histogram/axis/ostream.hpp>
13 #include <boost/histogram/axis/variant.hpp>
14 #include <boost/histogram/detail/axes.hpp>
15 #include <boost/histogram/detail/counting_streambuf.hpp>
16 #include <boost/histogram/detail/detect.hpp>
17 #include <boost/histogram/detail/static_if.hpp>
18 #include <boost/histogram/indexed.hpp>
19 #include <cmath>
20 #include <iomanip>
21 #include <ios>
22 #include <limits>
23 #include <numeric>
24 #include <ostream>
25 #include <streambuf>
26 #include <type_traits>
27
28 /**
29   \file boost/histogram/ostream.hpp
30
31   A simple streaming operator for the histogram type. The text representation is
32   rudimentary and not guaranteed to be stable between versions of Boost.Histogram. This
33   header is not included by any other header and must be explicitly included to use the
34   streaming operator.
35
36   To you use your own, simply include your own implementation instead of this header.
37  */
38
39 namespace boost {
40 namespace histogram {
41 namespace detail {
42
43 template <class OStream, unsigned N>
44 class tabular_ostream_wrapper : public std::array<int, N> {
45   using base_t = std::array<int, N>;
46   using char_type = typename OStream::char_type;
47   using traits_type = typename OStream::traits_type;
48
49 public:
50   template <class T>
51   tabular_ostream_wrapper& operator<<(const T& t) {
52     if (collect_) {
53       if (static_cast<std::size_t>(iter_ - base_t::begin()) == size_) {
54         ++size_;
55         BOOST_ASSERT(size_ <= N);
56         BOOST_ASSERT(iter_ != end());
57         *iter_ = 0;
58       }
59       cbuf_.count = 0;
60       os_ << t;
61       *iter_ = std::max(*iter_, static_cast<int>(cbuf_.count));
62     } else {
63       BOOST_ASSERT(iter_ != end());
64       os_ << std::setw(*iter_) << t;
65     }
66     ++iter_;
67     return *this;
68   }
69
70   tabular_ostream_wrapper& operator<<(decltype(std::setprecision(0)) t) {
71     os_ << t;
72     return *this;
73   }
74
75   tabular_ostream_wrapper& operator<<(decltype(std::fixed) t) {
76     os_ << t;
77     return *this;
78   }
79
80   tabular_ostream_wrapper& row() {
81     iter_ = base_t::begin();
82     return *this;
83   }
84
85   explicit tabular_ostream_wrapper(OStream& os) : os_(os), orig_(os_.rdbuf(&cbuf_)) {}
86
87   auto end() { return base_t::begin() + size_; }
88   auto end() const { return base_t::begin() + size_; }
89   auto cend() const { return base_t::cbegin() + size_; }
90
91   void complete() {
92     BOOST_ASSERT(collect_); // only call this once
93     collect_ = false;
94     os_.rdbuf(orig_);
95   }
96
97 private:
98   typename base_t::iterator iter_ = base_t::begin();
99   std::size_t size_ = 0;
100   bool collect_ = true;
101   OStream& os_;
102   counting_streambuf<char_type, traits_type> cbuf_;
103   std::basic_streambuf<char_type, traits_type>* orig_;
104 };
105
106 template <class OStream, class T>
107 void ostream_value(OStream& os, const T& val) {
108   // a value from bin or histogram cell
109   os << std::left;
110   static_if_c<(std::is_convertible<T, double>::value && !std::is_integral<T>::value)>(
111       [](auto& os, const auto& val) {
112         const auto d = static_cast<double>(val);
113         if (std::isfinite(d)) {
114           const auto i = static_cast<std::int64_t>(d);
115           if (i == d) {
116             os << i;
117             return;
118           }
119         }
120         os << std::defaultfloat << std::setprecision(4) << d;
121       },
122       [](auto& os, const auto& val) { os << val; }, os, val);
123 }
124
125 template <class OStream, class Axis>
126 void ostream_bin(OStream& os, const Axis& ax, const int i) {
127   os << std::right;
128   static_if<has_method_value<Axis>>(
129       [&](const auto& ax) {
130         static_if<axis::traits::is_continuous<Axis>>(
131             [&](const auto& ax) {
132               os << std::defaultfloat << std::setprecision(4);
133               auto a = ax.value(i);
134               auto b = ax.value(i + 1);
135               // round bin edge to zero if deviation from zero is absolut and relatively
136               // small
137               const auto eps = 1e-8 * std::abs(b - a);
138               if (std::abs(a) < 1e-14 && std::abs(a) < eps) a = 0;
139               if (std::abs(b) < 1e-14 && std::abs(b) < eps) b = 0;
140               os << "[" << a << ", " << b << ")";
141             },
142             [&](const auto& ax) { os << ax.value(i); }, ax);
143       },
144       [&](const auto&) { os << i; }, ax);
145 }
146
147 template <class OStream, class... Ts>
148 void ostream_bin(OStream& os, const axis::category<Ts...>& ax, const int i) {
149   os << std::right;
150   if (i < ax.size())
151     os << ax.value(i);
152   else
153     os << "other";
154 }
155
156 template <class CharT>
157 struct line_t {
158   CharT ch;
159   int size;
160 };
161
162 template <class CharT>
163 auto line(CharT c, int n) {
164   return line_t<CharT>{c, n};
165 }
166
167 template <class C, class T>
168 std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& os, line_t<C>&& l) {
169   for (int i = 0; i < l.size; ++i) os << l.ch;
170   return os;
171 }
172
173 template <class OStream, class Axis, class T>
174 void stream_head(OStream& os, const Axis& ax, int index, const T& val) {
175   axis::visit(
176       [&](const auto& ax) {
177         ostream_bin(os, ax, index);
178         os << ' ';
179         ostream_value(os, val);
180       },
181       ax);
182 }
183
184 template <class OStream, class Histogram>
185 void ascii_plot(OStream& os, const Histogram& h, int w_total) {
186   if (w_total == 0) w_total = 78; // TODO detect actual width of terminal
187
188   const auto& ax = h.axis();
189
190   // value range; can be integer or float, positive or negative
191   double vmin = 0;
192   double vmax = 0;
193   tabular_ostream_wrapper<OStream, 7> tos(os);
194   // first pass to get widths
195   for (auto&& v : indexed(h, coverage::all)) {
196     stream_head(tos.row(), ax, v.index(), *v);
197     vmin = std::min(vmin, static_cast<double>(*v));
198     vmax = std::max(vmax, static_cast<double>(*v));
199   }
200   tos.complete();
201   if (vmax == 0) vmax = 1;
202
203   // calculate width useable by bar (notice extra space at top)
204   // <-- head --> |<--- bar ---> |
205   // w_head + 2 + 2
206   const int w_head = std::accumulate(tos.begin(), tos.end(), 0);
207   const int w_bar = w_total - 4 - w_head;
208   if (w_bar < 0) return;
209
210   // draw upper line
211   os << '\n' << line(' ', w_head + 1) << '+' << line('-', w_bar + 1) << "+\n";
212
213   const int zero_offset = static_cast<int>(std::lround((-vmin) / (vmax - vmin) * w_bar));
214   for (auto&& v : indexed(h, coverage::all)) {
215     stream_head(tos.row(), ax, v.index(), *v);
216     // rest uses os, not tos
217     os << " |";
218     const int k = static_cast<int>(std::lround(*v / (vmax - vmin) * w_bar));
219     if (k < 0) {
220       os << line(' ', zero_offset + k) << line('=', -k) << line(' ', w_bar - zero_offset);
221     } else {
222       os << line(' ', zero_offset) << line('=', k) << line(' ', w_bar - zero_offset - k);
223     }
224     os << " |\n";
225   }
226
227   // draw lower line
228   os << line(' ', w_head + 1) << '+' << line('-', w_bar + 1) << "+\n";
229 }
230
231 template <class OStream, class Histogram>
232 void ostream(OStream& os, const Histogram& h, const bool show_values = true) {
233   os << "histogram(";
234
235   unsigned iaxis = 0;
236   const auto rank = h.rank();
237   h.for_each_axis([&](const auto& ax) {
238     using A = std::decay_t<decltype(ax)>;
239     if ((show_values && rank > 0) || rank > 1) os << "\n  ";
240     static_if<is_streamable<A>>([&](const auto& ax) { os << ax; },
241                                 [&](const auto&) { os << "<unstreamable>"; }, ax);
242   });
243
244   if (show_values && rank > 0) {
245     tabular_ostream_wrapper<OStream, (BOOST_HISTOGRAM_DETAIL_AXES_LIMIT + 1)> tos(os);
246     for (auto&& v : indexed(h, coverage::all)) {
247       tos.row();
248       for (auto i : v.indices()) tos << std::right << i;
249       ostream_value(tos, *v);
250     }
251     tos.complete();
252
253     const int w_item = std::accumulate(tos.begin(), tos.end(), 0) + 4 + h.rank();
254     const int nrow = std::max(1, 65 / w_item);
255     int irow = 0;
256     for (auto&& v : indexed(h, coverage::all)) {
257       os << (irow == 0 ? "\n  (" : " (");
258       tos.row();
259       iaxis = 0;
260       for (auto i : v.indices()) {
261         tos << std::right << i;
262         os << (++iaxis == h.rank() ? "):" : " ");
263       }
264       os << ' ';
265       ostream_value(tos, *v);
266       ++irow;
267       if (nrow > 0 && irow == nrow) irow = 0;
268     }
269     os << '\n';
270   }
271   os << ')';
272 }
273
274 } // namespace detail
275
276 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
277
278 template <typename CharT, typename Traits, typename A, typename S>
279 std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
280                                               const histogram<A, S>& h) {
281   // save fmt
282   const auto flags = os.flags();
283
284   os.flags(std::ios::dec | std::ios::left);
285
286   const auto w = static_cast<int>(os.width());
287   os.width(0);
288
289   using value_type = typename histogram<A, S>::value_type;
290   detail::static_if<std::is_convertible<value_type, double>>(
291       [&os, w](const auto& h) {
292         if (h.rank() == 1) {
293           detail::ostream(os, h, false);
294           detail::ascii_plot(os, h, w);
295         } else
296           detail::ostream(os, h);
297       },
298       [&os](const auto& h) { detail::ostream(os, h); }, h);
299
300   // restore fmt
301   os.flags(flags);
302   return os;
303 }
304
305 } // namespace histogram
306 } // namespace boost
307
308 #endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
309
310 #endif