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