1 // Copyright 2015-2019 Hans Dembinski
2 // Copyright 2019 Przemyslaw Bartosik
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)
8 #ifndef BOOST_HISTOGRAM_OSTREAM_HPP
9 #define BOOST_HISTOGRAM_OSTREAM_HPP
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>
26 #include <type_traits>
29 \file boost/histogram/ostream.hpp
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
36 To you use your own, simply include your own implementation instead of this header.
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;
51 tabular_ostream_wrapper& operator<<(const T& t) {
53 if (static_cast<std::size_t>(iter_ - base_t::begin()) == size_) {
55 BOOST_ASSERT(size_ <= N);
56 BOOST_ASSERT(iter_ != end());
61 *iter_ = std::max(*iter_, static_cast<int>(cbuf_.count));
63 BOOST_ASSERT(iter_ != end());
64 os_ << std::setw(*iter_) << t;
70 tabular_ostream_wrapper& operator<<(decltype(std::setprecision(0)) t) {
75 tabular_ostream_wrapper& operator<<(decltype(std::fixed) t) {
80 tabular_ostream_wrapper& row() {
81 iter_ = base_t::begin();
85 explicit tabular_ostream_wrapper(OStream& os) : os_(os), orig_(os_.rdbuf(&cbuf_)) {}
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_; }
92 BOOST_ASSERT(collect_); // only call this once
98 typename base_t::iterator iter_ = base_t::begin();
99 std::size_t size_ = 0;
100 bool collect_ = true;
102 counting_streambuf<char_type, traits_type> cbuf_;
103 std::basic_streambuf<char_type, traits_type>* orig_;
106 template <class OStream, class T>
107 void ostream_value(OStream& os, const T& val) {
108 // a value from bin or histogram cell
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);
120 os << std::defaultfloat << std::setprecision(4) << d;
122 [](auto& os, const auto& val) { os << val; }, os, val);
125 template <class OStream, class Axis>
126 void ostream_bin(OStream& os, const Axis& ax, const int i) {
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
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 << ")";
142 [&](const auto& ax) { os << ax.value(i); }, ax);
144 [&](const auto&) { os << i; }, ax);
147 template <class OStream, class... Ts>
148 void ostream_bin(OStream& os, const axis::category<Ts...>& ax, const int i) {
156 template <class CharT>
162 template <class CharT>
163 auto line(CharT c, int n) {
164 return line_t<CharT>{c, n};
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;
173 template <class OStream, class Axis, class T>
174 void stream_head(OStream& os, const Axis& ax, int index, const T& val) {
176 [&](const auto& ax) {
177 ostream_bin(os, ax, index);
179 ostream_value(os, val);
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
188 const auto& ax = h.axis();
190 // value range; can be integer or float, positive or negative
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));
201 if (vmax == 0) vmax = 1;
203 // calculate width useable by bar (notice extra space at top)
204 // <-- head --> |<--- bar ---> |
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;
211 os << '\n' << line(' ', w_head + 1) << '+' << line('-', w_bar + 1) << "+\n";
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
218 const int k = static_cast<int>(std::lround(*v / (vmax - vmin) * w_bar));
220 os << line(' ', zero_offset + k) << line('=', -k) << line(' ', w_bar - zero_offset);
222 os << line(' ', zero_offset) << line('=', k) << line(' ', w_bar - zero_offset - k);
228 os << line(' ', w_head + 1) << '+' << line('-', w_bar + 1) << "+\n";
231 template <class OStream, class Histogram>
232 void ostream(OStream& os, const Histogram& h, const bool show_values = true) {
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);
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)) {
248 for (auto i : v.indices()) tos << std::right << i;
249 ostream_value(tos, *v);
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);
256 for (auto&& v : indexed(h, coverage::all)) {
257 os << (irow == 0 ? "\n (" : " (");
260 for (auto i : v.indices()) {
261 tos << std::right << i;
262 os << (++iaxis == h.rank() ? "):" : " ");
265 ostream_value(tos, *v);
267 if (nrow > 0 && irow == nrow) irow = 0;
274 } // namespace detail
276 #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
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) {
282 const auto flags = os.flags();
284 os.flags(std::ios::dec | std::ios::left);
286 const auto w = static_cast<int>(os.width());
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) {
293 detail::ostream(os, h, false);
294 detail::ascii_plot(os, h, w);
296 detail::ostream(os, h);
298 [&os](const auto& h) { detail::ostream(os, h); }, h);
305 } // namespace histogram
308 #endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED