1111#include < boost/histogram/accumulators/ostream.hpp>
1212#include < boost/histogram/axis/ostream.hpp>
1313#include < boost/histogram/detail/counting_streambuf.hpp>
14+ #include < boost/histogram/detail/detect.hpp>
1415#include < boost/histogram/detail/priority.hpp>
16+ #include < boost/histogram/detail/term_info.hpp>
1517#include < boost/histogram/indexed.hpp>
1618#include < cmath>
1719#include < iomanip>
@@ -162,19 +164,14 @@ void ostream_bin(OStream& os, const Axis&, axis::index_type i, B, priority<0>) {
162164 os << i;
163165}
164166
165- template < class CharT >
166- struct line_t {
167- CharT ch ;
168- int size;
167+ struct line {
168+ const char * ch;
169+ const int size ;
170+ line ( const char * a, int b) : ch{a}, size{ std::max (b, 0 )} {}
169171};
170172
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) {
173+ template <class T >
174+ std::basic_ostream<char , T>& operator <<(std::basic_ostream<char , T>& os, line&& l) {
178175 for (int i = 0 ; i < l.size ; ++i) os << l.ch ;
179176 return os;
180177}
@@ -191,13 +188,50 @@ void ostream_head(OStream& os, const Axis& ax, int index, double val) {
191188 ax);
192189}
193190
191+ template <class OStream >
192+ void ostream_bar (OStream& os, int zero_offset, double z, int width, bool utf8) {
193+ int k = static_cast <int >(std::lround (z * width));
194+ if (utf8) {
195+ os << " │" ;
196+ if (z > 0 ) {
197+ const char * scale[8 ] = {" " , " ▏" , " ▎" , " ▍" , " ▌" , " ▋" , " ▊" , " ▉" };
198+ int j = static_cast <int >(std::lround (8 * (z * width - k)));
199+ if (j < 0 ) {
200+ --k;
201+ j += 8 ;
202+ }
203+ os << line (" " , zero_offset) << line (" █" , k);
204+ os << scale[j];
205+ os << line (" " , width - zero_offset - k);
206+ } else if (z < 0 ) {
207+ os << line (" " , zero_offset + k) << line (" █" , -k)
208+ << line (" " , width - zero_offset + 1 );
209+ } else {
210+ os << line (" " , width + 1 );
211+ }
212+ os << " │\n " ;
213+ } else {
214+ os << " |" ;
215+ if (z >= 0 ) {
216+ os << line (" " , zero_offset) << line (" =" , k) << line (" " , width - zero_offset - k);
217+ } else {
218+ os << line (" " , zero_offset + k) << line (" =" , -k) << line (" " , width - zero_offset);
219+ }
220+ os << " |\n " ;
221+ }
222+ }
223+
194224// cannot display generalized histograms yet; line not reachable by coverage tests
195225template <class OStream , class Histogram >
196- void ascii_plot (OStream&, const Histogram&, int , std::false_type) {} // LCOV_EXCL_LINE
226+ void plot (OStream&, const Histogram&, int , std::false_type) {} // LCOV_EXCL_LINE
197227
198228template <class OStream , class Histogram >
199- void ascii_plot (OStream& os, const Histogram& h, int w_total, std::true_type) {
200- if (w_total == 0 ) w_total = 78 ; // TODO detect actual width of terminal
229+ void plot (OStream& os, const Histogram& h, int w_total, std::true_type) {
230+ if (w_total == 0 ) {
231+ w_total = term_info::width ();
232+ if (w_total == 0 || w_total > 78 ) w_total = 78 ;
233+ }
234+ bool utf8 = term_info::utf8 ();
201235
202236 const auto & ax = h.axis ();
203237
@@ -207,39 +241,42 @@ void ascii_plot(OStream& os, const Histogram& h, int w_total, std::true_type) {
207241 tabular_ostream_wrapper<OStream, 7 > tos (os);
208242 // first pass to get widths
209243 for (auto && v : indexed (h, coverage::all)) {
210- ostream_head (tos.row (), ax, v.index (), *v);
211- vmin = std::min (vmin, static_cast <double >(*v));
212- vmax = std::max (vmax, static_cast <double >(*v));
244+ auto w = static_cast <double >(*v);
245+ ostream_head (tos.row (), ax, v.index (), w);
246+ vmin = std::min (vmin, w);
247+ vmax = std::max (vmax, w);
213248 }
214249 tos.complete ();
215250 if (vmax == 0 ) vmax = 1 ;
216251
217252 // calculate width useable by bar (notice extra space at top)
218253 // <-- head --> |<--- bar ---> |
219254 // w_head + 2 + 2
220- const auto w_head = std::accumulate (tos.begin (), tos.end (), 0 );
221- const auto w_bar = w_total - 4 - w_head;
255+ const int w_head = std::accumulate (tos.begin (), tos.end (), 0 );
256+ const int w_bar = w_total - 4 - w_head;
222257 if (w_bar < 0 ) return ;
223258
224259 // draw upper line
225- os << ' \n ' << line (' ' , w_head + 1 ) << ' +' << line (' -' , w_bar + 1 ) << " +\n " ;
260+ os << ' \n ' << line (" " , w_head + 1 );
261+ if (utf8)
262+ os << " ┌" << line (" ─" , w_bar + 1 ) << " ┐\n " ;
263+ else
264+ os << ' +' << line (" -" , w_bar + 1 ) << " +\n " ;
226265
227266 const int zero_offset = static_cast <int >(std::lround ((-vmin) / (vmax - vmin) * w_bar));
228267 for (auto && v : indexed (h, coverage::all)) {
229- ostream_head (tos.row (), ax, v.index (), *v);
268+ auto w = static_cast <double >(*v);
269+ ostream_head (tos.row (), ax, v.index (), w);
230270 // 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 " ;
271+ ostream_bar (os, zero_offset, w / (vmax - vmin), w_bar, utf8);
239272 }
240273
241274 // draw lower line
242- os << line (' ' , w_head + 1 ) << ' +' << line (' -' , w_bar + 1 ) << " +\n " ;
275+ os << line (" " , w_head + 1 );
276+ if (utf8)
277+ os << " └" << line (" ─" , w_bar + 1 ) << " ┘\n " ;
278+ else
279+ os << ' +' << line (" -" , w_bar + 1 ) << " +\n " ;
243280}
244281
245282template <class OStream , class Histogram >
@@ -300,11 +337,12 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
300337
301338 using value_type = typename histogram<A, S>::value_type;
302339
340+ using convertible = detail::is_explicitly_convertible<value_type, double >;
303341 // 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 ) {
342+ bool show_plot = convertible ::value && h.rank () == 1 ;
343+ if (show_plot ) {
306344 detail::ostream (os, h, false );
307- detail::ascii_plot (os, h, w, std::is_convertible<value_type, double > {});
345+ detail::plot (os, h, w, convertible {});
308346 } else {
309347 detail::ostream (os, h);
310348 }
@@ -314,9 +352,9 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
314352 return os;
315353}
316354
355+ #endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
356+
317357} // namespace histogram
318358} // namespace boost
319359
320- #endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
321-
322360#endif
0 commit comments