plot
Header-only C++ SVG charts — heatmap, line, scatter — GR-style API + Solarized themes
Loading...
Searching...
No Matches
bar.hpp
1/* Copyright (c) 2026 Steven Varga, Toronto, ON, Canada
2 * MIT License — see LICENSE
3 *
4 * plot::bar(labels, values, opts...) — a deferred view (plot::view) drawing one
5 * categorical bar per (label,value) pair over a cartesian panel. The x ticks are
6 * the category labels (a hand-built impl::scale_t whose tick labels are the
7 * category names rather than numbers), bar colours cycle theme.series.
8 *
9 * Built on the #5 view pattern + impl::render_panel_into. Dependency-free.
10 */
11#ifndef PLOT_BAR_HPP
12#define PLOT_BAR_HPP
13
14#include <string>
15#include <vector>
16#include <tuple>
17#include <utility>
18#include <algorithm>
19#include <cmath>
20#include <cstddef>
21#include <cstdint>
22
23#include "tags.hpp"
24#include "meta.hpp"
25#include "attributes.hpp"
26#include "canvas.hpp"
27#include "theme.hpp"
28#include "gr.hpp"
29#include "view.hpp"
30
31#ifndef PLOT_WIREFRAME_DEFINED
32#define PLOT_WIREFRAME_DEFINED
33namespace plot {
34 // outline-only style marker for bar/histogram: stroke in the series colour,
35 // fill="none" — the wire frame of the columns (issue #13).
36 struct wireframe { using value_type = tag::wireframe_t; };
37}
38#endif
39
40namespace plot::impl {
41 template <class... opt_t>
42 struct bar_view {
43 using value_type = tag::view_t;
44 std::vector<std::string> labels;
45 std::vector<double> values;
46 std::tuple<opt_t...> opts;
47
48 std::pair<std::size_t,std::size_t> natural() const {
49 return std::apply([](const auto&... o){
50 return impl::natural_size(std::size_t{640}, std::size_t{400}, o...); }, opts);
51 }
52 void draw_into(canvas_t& cv, float x, float y, float w, float h) const {
53 std::apply([&](const auto&... o){
54 const theme_t& th = impl::resolve_theme(o...);
55 auto [title, xl, yl] = impl::texts(o...);
56 const std::size_t n = std::min(labels.size(), values.size());
57 constexpr bool wire = arg::tpos<tag::wireframe_t, opt_t...>::present;
58
59 double vmax = 0.0, vmin = 0.0;
60 for(std::size_t i=0;i<n;++i){
61 if( values[i] > vmax ) vmax = values[i];
62 if( values[i] < vmin ) vmin = values[i];
63 }
64 if( vmax <= vmin ) vmax = vmin + 1.0;
65
66 // categorical x scale: one tick per category, label = name.
67 impl::scale_t sx;
68 sx.lo = -0.5; sx.hi = double(n) - 0.5;
69 for(std::size_t i=0;i<n;++i){ sx.ticks.push_back(double(i)); sx.labels.push_back(labels[i]); }
70 impl::scale_t sy = impl::make_scale(vmin, vmax, false, 10.0);
71
72 impl::render_panel_into(cv, x, y,
73 static_cast<std::size_t>(w), static_cast<std::size_t>(h),
74 th, sx, sy, title, xl, yl, {},
75 [&](impl::canvas_t& c, auto px, auto py){
76 using attribute_t = plot::attribute::element_t;
77 const float y0 = py(0.0);
78 const float cellw = (px(1.0) - px(0.0));
79 const float bw = std::max(1.0f, cellw * 0.7f);
80 for(std::size_t i=0;i<n;++i){
81 float cx = px(double(i));
82 float yt = py(values[i]);
83 std::uint32_t col = th.series.empty()? th.fg
84 : th.series[i % th.series.size()];
85 float top = std::min(yt, y0);
86 float hgt = std::abs(y0 - yt);
87 float xl0 = cx - bw*0.5f, xr0 = cx + bw*0.5f;
88 float yb0 = top + hgt;
89 if constexpr( wire ){
90 // outline-only column: fill="none", stroke in series colour.
91 attribute_t a; a.color = plot::attribute::color_t{ col };
92 a.stroke = plot::attribute::stroke_t{1.0f, 1.5f, {}, {}, {}};
93 std::vector<float> X{xl0, xr0, xr0, xl0, xl0};
94 std::vector<float> Y{top, top, yb0, yb0, top};
95 c.poly_line(X, Y, a);
96 } else {
97 attribute_t a; a.color = plot::attribute::color_t{ col };
98 c.rect(xl0, top, bw, hgt, 0, 0, a);
99 }
100 }
101 });
102 }, opts);
103 }
104 };
105}
106
107namespace plot {
108 template <class... opt_t>
109 impl::bar_view<opt_t...> bar(std::vector<std::string> labels,
110 std::vector<double> values, opt_t... opts){
111 return impl::bar_view<opt_t...>{ std::move(labels), std::move(values),
112 std::make_tuple(opts...) };
113 }
114}
115#endif