plot
Header-only C++ SVG charts — heatmap, line, scatter — GR-style API + Solarized themes
Loading...
Searching...
No Matches
histogram.hpp
1/* Copyright (c) 2026 Steven Varga, Toronto, ON, Canada
2 * MIT License — see LICENSE
3 *
4 * plot::histogram(values, opts...) — a deferred view (plot::view) that bins a
5 * vector of doubles into N bins and draws count bars over a cartesian panel.
6 *
7 * Bin count: plot::bins{n} named argument; default ~Sturges' rule
8 * (ceil(log2(N)) + 1). Built on the #5 view pattern + impl::render_panel_into
9 * cartesian core: factory returns a movable struct tagged tag::view_t, captures
10 * data + options by value, exposes draw_into / natural / render / save.
11 * Dependency-free (standard library only).
12 */
13#ifndef PLOT_HISTOGRAM_HPP
14#define PLOT_HISTOGRAM_HPP
15
16#include <string>
17#include <vector>
18#include <tuple>
19#include <utility>
20#include <algorithm>
21#include <cmath>
22#include <cstddef>
23#include <cstdint>
24
25#include "tags.hpp"
26#include "meta.hpp"
27#include "attributes.hpp"
28#include "canvas.hpp"
29#include "theme.hpp"
30#include "gr.hpp"
31#include "view.hpp"
32
33#ifndef PLOT_WIREFRAME_DEFINED
34#define PLOT_WIREFRAME_DEFINED
35namespace plot {
36 // outline-only style marker for bar/histogram: stroke in the series colour,
37 // fill="none" — the wire frame of the columns (issue #13).
38 struct wireframe { using value_type = tag::wireframe_t; };
39}
40#endif
41
42namespace plot {
43 // histogram bin count (order-independent named argument).
44 struct bins { using value_type = tag::bins_t; std::size_t value; };
45}
46
47namespace plot::impl {
48 // resolve the bin count: plot::bins{n} override, else Sturges' rule.
49 template <class... opt_t>
50 std::size_t bins_of(std::size_t n, const opt_t&... opts){
51 using bins_t = typename arg::tpos<tag::bins_t, opt_t...>;
52 if constexpr( bins_t::present ){
53 auto tuple = std::forward_as_tuple(opts...);
54 std::size_t b = std::get<bins_t::position>(tuple).value;
55 return b ? b : 1;
56 }
57 if( n < 2 ) return 1;
58 return static_cast<std::size_t>(std::ceil(std::log2(double(n)))) + 1;
59 }
60
61 template <class... opt_t>
62 struct histogram_view {
63 using value_type = tag::view_t;
64 std::vector<double> values;
65 std::tuple<opt_t...> opts;
66
67 std::pair<std::size_t,std::size_t> natural() const {
68 return std::apply([](const auto&... o){
69 return impl::natural_size(std::size_t{640}, std::size_t{400}, o...); }, opts);
70 }
71 void draw_into(canvas_t& cv, float x, float y, float w, float h) const {
72 std::apply([&](const auto&... o){
73 const theme_t& th = impl::resolve_theme(o...);
74 auto [title, xl, yl] = impl::texts(o...);
75 const std::size_t nb = impl::bins_of(values.size(), o...);
76 constexpr bool wire = arg::tpos<tag::wireframe_t, opt_t...>::present;
77
78 auto [vmn, vmx] = impl::minmax_of(values);
79 if( vmx <= vmn ) vmx = vmn + 1.0;
80 const double width = (vmx - vmn) / double(nb);
81
82 std::vector<double> counts(nb, 0.0);
83 for(double v : values){
84 std::size_t b = static_cast<std::size_t>((v - vmn) / width);
85 if( b >= nb ) b = nb - 1;
86 counts[b] += 1.0;
87 }
88 double cmax = 0.0;
89 for(double c : counts) if( c > cmax ) cmax = c;
90 if( cmax <= 0.0 ) cmax = 1.0;
91
92 impl::scale_t sx = impl::make_scale(vmn, vmx, false, 10.0);
93 impl::scale_t sy = impl::make_scale(0.0, cmax, false, 10.0);
94 impl::render_panel_into(cv, x, y,
95 static_cast<std::size_t>(w), static_cast<std::size_t>(h),
96 th, sx, sy, title, xl, yl, {},
97 [&](impl::canvas_t& c, auto px, auto py){
98 using attribute_t = plot::attribute::element_t;
99 std::uint32_t col = th.series.empty()? th.fg : th.series[0];
100 const float y0 = py(0.0); // pixel baseline for count 0
101 for(std::size_t b=0;b<nb;++b){
102 double lo = vmn + double(b) * width;
103 double hi = vmn + double(b+1) * width;
104 float xl0 = px(lo), xr0 = px(hi);
105 float yt = py(counts[b]);
106 float bw = xr0 - xl0;
107 if( bw < 1.0f ) bw = 1.0f;
108 if constexpr( wire ){
109 // outline-only bin: fill="none", stroke in series colour.
110 attribute_t a; a.color = plot::attribute::color_t{ col };
111 a.stroke = plot::attribute::stroke_t{1.0f, 1.5f, {}, {}, {}};
112 float lx = xl0 + 0.5f, rx = lx + (bw - 1.0f);
113 std::vector<float> X{lx, rx, rx, lx, lx};
114 std::vector<float> Y{yt, yt, y0, y0, yt};
115 c.poly_line(X, Y, a);
116 } else {
117 attribute_t a; a.color = plot::attribute::color_t{ col };
118 // inset by 1px so adjacent bars read as separate columns.
119 c.rect(xl0 + 0.5f, yt, bw - 1.0f, y0 - yt, 0, 0, a);
120 }
121 }
122 });
123 }, opts);
124 }
125 };
126}
127
128namespace plot {
129 template <class... opt_t>
130 impl::histogram_view<opt_t...> histogram(std::vector<double> values, opt_t... opts){
131 return impl::histogram_view<opt_t...>{ std::move(values), std::make_tuple(opts...) };
132 }
133}
134#endif