ostream.hpp 9.0 KB

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