ostream.hpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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/detail/counting_streambuf.hpp>
  12. #include <boost/histogram/detail/detect.hpp>
  13. #include <boost/histogram/detail/priority.hpp>
  14. #include <boost/histogram/detail/term_info.hpp>
  15. #include <boost/histogram/indexed.hpp>
  16. #include <cmath>
  17. #include <iomanip>
  18. #include <ios>
  19. #include <limits>
  20. #include <numeric>
  21. #include <ostream>
  22. #include <streambuf>
  23. #include <type_traits>
  24. /**
  25. \file boost/histogram/ostream.hpp
  26. A simple streaming operator for the histogram type. The text representation is
  27. rudimentary and not guaranteed to be stable between versions of Boost.Histogram. This
  28. header is not included by any other header and must be explicitly included to use the
  29. streaming operator.
  30. To use your own, simply include your own implementation instead of this header.
  31. */
  32. namespace boost {
  33. namespace histogram {
  34. namespace detail {
  35. template <class OStream, unsigned N>
  36. class tabular_ostream_wrapper : public std::array<int, N> {
  37. using base_t = std::array<int, N>;
  38. using char_type = typename OStream::char_type;
  39. using traits_type = typename OStream::traits_type;
  40. public:
  41. template <class T>
  42. tabular_ostream_wrapper& operator<<(const T& t) {
  43. if (collect_) {
  44. if (static_cast<unsigned>(iter_ - base_t::begin()) == size_) {
  45. ++size_;
  46. assert(size_ <= N);
  47. assert(iter_ != end());
  48. *iter_ = 0;
  49. }
  50. count_ = 0;
  51. os_ << t;
  52. *iter_ = (std::max)(*iter_, static_cast<int>(count_));
  53. } else {
  54. assert(iter_ != end());
  55. os_ << std::setw(*iter_) << t;
  56. }
  57. ++iter_;
  58. return *this;
  59. }
  60. tabular_ostream_wrapper& operator<<(decltype(std::setprecision(0)) t) {
  61. os_ << t;
  62. return *this;
  63. }
  64. tabular_ostream_wrapper& operator<<(decltype(std::fixed) t) {
  65. os_ << t;
  66. return *this;
  67. }
  68. tabular_ostream_wrapper& row() {
  69. iter_ = base_t::begin();
  70. return *this;
  71. }
  72. explicit tabular_ostream_wrapper(OStream& os)
  73. : os_(os), cbuf_(count_), 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. 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. unsigned size_ = 0;
  85. std::streamsize count_ = 0;
  86. bool collect_ = true;
  87. OStream& os_;
  88. counting_streambuf<char_type, traits_type> cbuf_;
  89. std::basic_streambuf<char_type, traits_type>* orig_;
  90. };
  91. template <class OStream, class T>
  92. void ostream_value_impl(OStream& os, const T& t,
  93. decltype(static_cast<double>(t), priority<1>{})) {
  94. // a value from histogram cell
  95. const auto d = static_cast<double>(t);
  96. if ((std::numeric_limits<int>::min)() <= d && d <= (std::numeric_limits<int>::max)()) {
  97. const auto i = static_cast<int>(d);
  98. if (i == d) {
  99. os << i;
  100. return;
  101. }
  102. }
  103. os << std::defaultfloat << std::setprecision(4) << d;
  104. }
  105. template <class OStream, class T>
  106. void ostream_value_impl(OStream& os, const T& t, priority<0>) {
  107. os << t;
  108. }
  109. template <class OStream, class T>
  110. void ostream_value(OStream& os, const T& t) {
  111. ostream_value_impl(os << std::left, t, priority<1>{});
  112. }
  113. template <class OStream, class Axis>
  114. auto ostream_bin(OStream& os, const Axis& ax, axis::index_type i, std::true_type,
  115. priority<1>) -> decltype((void)ax.value(i)) {
  116. auto a = ax.value(i), b = ax.value(i + 1);
  117. os << std::right << std::defaultfloat << std::setprecision(4);
  118. // round edges to zero if deviation from zero is small
  119. const auto eps = 1e-8 * std::abs(b - a);
  120. if (std::abs(a) < 1e-14 && std::abs(a) < eps) a = 0;
  121. if (std::abs(b) < 1e-14 && std::abs(b) < eps) b = 0;
  122. os << "[" << a << ", " << b << ")";
  123. }
  124. template <class OStream, class Axis>
  125. auto ostream_bin(OStream& os, const Axis& ax, axis::index_type i, std::false_type,
  126. priority<1>) -> decltype((void)ax.value(i)) {
  127. os << std::right;
  128. os << ax.value(i);
  129. }
  130. template <class OStream, class... Ts>
  131. void ostream_bin(OStream& os, const axis::category<Ts...>& ax, axis::index_type i,
  132. std::false_type, priority<1>) {
  133. os << std::right;
  134. if (i < ax.size())
  135. os << ax.value(i);
  136. else
  137. os << "other";
  138. }
  139. template <class OStream, class Axis, class B>
  140. void ostream_bin(OStream& os, const Axis&, axis::index_type i, B, priority<0>) {
  141. os << std::right;
  142. os << i;
  143. }
  144. struct line {
  145. const char* ch;
  146. const int size;
  147. line(const char* a, int b) : ch{a}, size{(std::max)(b, 0)} {}
  148. };
  149. template <class T>
  150. std::basic_ostream<char, T>& operator<<(std::basic_ostream<char, T>& os, line&& l) {
  151. for (int i = 0; i < l.size; ++i) os << l.ch;
  152. return os;
  153. }
  154. template <class OStream, class Axis>
  155. void ostream_head(OStream& os, const Axis& ax, int index, double val) {
  156. axis::visit(
  157. [&](const auto& ax) {
  158. using A = std::decay_t<decltype(ax)>;
  159. ostream_bin(os, ax, index, axis::traits::is_continuous<A>{}, priority<1>{});
  160. os << ' ';
  161. ostream_value(os, val);
  162. },
  163. ax);
  164. }
  165. template <class OStream>
  166. void ostream_bar(OStream& os, int zero_offset, double z, int width, bool utf8) {
  167. int k = static_cast<int>(std::lround(z * width));
  168. if (utf8) {
  169. os << " │";
  170. if (z > 0) {
  171. const char* scale[8] = {" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"};
  172. int j = static_cast<int>(std::lround(8 * (z * width - k)));
  173. if (j < 0) {
  174. --k;
  175. j += 8;
  176. }
  177. os << line(" ", zero_offset) << line("█", k);
  178. os << scale[j];
  179. os << line(" ", width - zero_offset - k);
  180. } else if (z < 0) {
  181. os << line(" ", zero_offset + k) << line("█", -k)
  182. << line(" ", width - zero_offset + 1);
  183. } else {
  184. os << line(" ", width + 1);
  185. }
  186. os << "│\n";
  187. } else {
  188. os << " |";
  189. if (z >= 0) {
  190. os << line(" ", zero_offset) << line("=", k) << line(" ", width - zero_offset - k);
  191. } else {
  192. os << line(" ", zero_offset + k) << line("=", -k) << line(" ", width - zero_offset);
  193. }
  194. os << " |\n";
  195. }
  196. }
  197. // cannot display generalized histograms yet; line not reachable by coverage tests
  198. template <class OStream, class Histogram>
  199. void plot(OStream&, const Histogram&, int, std::false_type) {} // LCOV_EXCL_LINE
  200. template <class OStream, class Histogram>
  201. void plot(OStream& os, const Histogram& h, int w_total, std::true_type) {
  202. if (w_total == 0) {
  203. w_total = term_info::width();
  204. if (w_total == 0 || w_total > 78) w_total = 78;
  205. }
  206. bool utf8 = term_info::utf8();
  207. const auto& ax = h.axis();
  208. // value range; can be integer or float, positive or negative
  209. double vmin = 0;
  210. double vmax = 0;
  211. tabular_ostream_wrapper<OStream, 7> tos(os);
  212. // first pass to get widths
  213. for (auto&& v : indexed(h, coverage::all)) {
  214. auto w = static_cast<double>(*v);
  215. ostream_head(tos.row(), ax, v.index(), w);
  216. vmin = (std::min)(vmin, w);
  217. vmax = (std::max)(vmax, w);
  218. }
  219. tos.complete();
  220. if (vmax == 0) vmax = 1;
  221. // calculate width useable by bar (notice extra space at top)
  222. // <-- head --> |<--- bar ---> |
  223. // w_head + 2 + 2
  224. const int w_head = std::accumulate(tos.begin(), tos.end(), 0);
  225. const int w_bar = w_total - 4 - w_head;
  226. if (w_bar < 0) return;
  227. // draw upper line
  228. os << '\n' << line(" ", w_head + 1);
  229. if (utf8)
  230. os << "┌" << line("─", w_bar + 1) << "┐\n";
  231. else
  232. os << '+' << line("-", w_bar + 1) << "+\n";
  233. const int zero_offset = static_cast<int>(std::lround((-vmin) / (vmax - vmin) * w_bar));
  234. for (auto&& v : indexed(h, coverage::all)) {
  235. auto w = static_cast<double>(*v);
  236. ostream_head(tos.row(), ax, v.index(), w);
  237. // rest uses os, not tos
  238. ostream_bar(os, zero_offset, w / (vmax - vmin), w_bar, utf8);
  239. }
  240. // draw lower line
  241. os << line(" ", w_head + 1);
  242. if (utf8)
  243. os << "└" << line("─", w_bar + 1) << "┘\n";
  244. else
  245. os << '+' << line("-", w_bar + 1) << "+\n";
  246. }
  247. template <class OStream, class Histogram>
  248. void ostream(OStream& os, const Histogram& h, const bool show_values = true) {
  249. os << "histogram(";
  250. unsigned iaxis = 0;
  251. const auto rank = h.rank();
  252. h.for_each_axis([&](const auto& ax) {
  253. if ((show_values && rank > 0) || rank > 1) os << "\n ";
  254. ostream_any(os, ax);
  255. });
  256. if (show_values && rank > 0) {
  257. tabular_ostream_wrapper<OStream, (BOOST_HISTOGRAM_DETAIL_AXES_LIMIT + 1)> tos(os);
  258. for (auto&& v : indexed(h, coverage::all)) {
  259. tos.row();
  260. for (auto i : v.indices()) tos << std::right << i;
  261. ostream_value(tos, *v);
  262. }
  263. tos.complete();
  264. const int w_item = std::accumulate(tos.begin(), tos.end(), 0) + 4 + h.rank();
  265. const int nrow = (std::max)(1, 65 / w_item);
  266. int irow = 0;
  267. for (auto&& v : indexed(h, coverage::all)) {
  268. os << (irow == 0 ? "\n (" : " (");
  269. tos.row();
  270. iaxis = 0;
  271. for (auto i : v.indices()) {
  272. tos << std::right << i;
  273. os << (++iaxis == h.rank() ? "):" : " ");
  274. }
  275. os << ' ';
  276. ostream_value(tos, *v);
  277. ++irow;
  278. if (nrow > 0 && irow == nrow) irow = 0;
  279. }
  280. os << '\n';
  281. }
  282. os << ')';
  283. }
  284. } // namespace detail
  285. #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
  286. template <class CharT, class Traits, class A, class S>
  287. std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
  288. const histogram<A, S>& h) {
  289. // save fmt
  290. const auto flags = os.flags();
  291. os.flags(std::ios::dec | std::ios::left);
  292. const auto w = static_cast<int>(os.width());
  293. os.width(0);
  294. using value_type = typename histogram<A, S>::value_type;
  295. using convertible = detail::is_explicitly_convertible<value_type, double>;
  296. // must be non-const to avoid a msvc warning about possible use of if constexpr
  297. bool show_plot = convertible::value && h.rank() == 1;
  298. if (show_plot) {
  299. detail::ostream(os, h, false);
  300. detail::plot(os, h, w, convertible{});
  301. } else {
  302. detail::ostream(os, h);
  303. }
  304. // restore fmt
  305. os.flags(flags);
  306. return os;
  307. }
  308. #endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
  309. } // namespace histogram
  310. } // namespace boost
  311. #endif