plot
Header-only C++ SVG charts — heatmap, line, scatter — GR-style API + Solarized themes
Loading...
Searching...
No Matches
ohlc.hpp
1/* Copyright (c) 2026 Steven Varga, Toronto, ON, Canada
2 * MIT License — see LICENSE
3 *
4 * plot::ohlc(times, open, high, low, close, opts...) — a deferred view
5 * (plot::view) drawing a candlestick chart from five parallel std::vector<double>
6 * series. Each bar is a vertical <line> from low to high plus a body <rect>
7 * spanning open↔close. Up bars (close >= open) and down bars use distinct theme
8 * series colours (green = series[1], red = series[4] in the Solarized palette,
9 * with foreground fallbacks).
10 *
11 * Built on the #5 view pattern + impl::render_panel_into. Dependency-free.
12 */
13#ifndef PLOT_OHLC_HPP
14#define PLOT_OHLC_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#include <limits>
25
26#include "tags.hpp"
27#include "meta.hpp"
28#include "attributes.hpp"
29#include "canvas.hpp"
30#include "theme.hpp"
31#include "gr.hpp"
32#include "view.hpp"
33
34namespace plot::impl {
35 template <class... opt_t>
36 struct ohlc_view {
37 using value_type = tag::view_t;
38 std::vector<double> t, o, hi, lo, c;
39 std::tuple<opt_t...> opts;
40
41 std::pair<std::size_t,std::size_t> natural() const {
42 return std::apply([](const auto&... oo){
43 return impl::natural_size(std::size_t{640}, std::size_t{400}, oo...); }, opts);
44 }
45 void draw_into(canvas_t& cv, float x, float y, float w, float h) const {
46 std::apply([&](const auto&... oo){
47 const theme_t& th = impl::resolve_theme(oo...);
48 auto [title, xl, yl] = impl::texts(oo...);
49 const std::size_t n = std::min({t.size(), o.size(), hi.size(), lo.size(), c.size()});
50
51 auto [xmn, xmx] = impl::minmax_of(t);
52 double ymn = std::numeric_limits<double>::infinity();
53 double ymx = -std::numeric_limits<double>::infinity();
54 for(std::size_t i=0;i<n;++i){
55 ymn = std::min({ymn, lo[i]}); ymx = std::max({ymx, hi[i]});
56 }
57 if( !(ymn<=ymx) ){ ymn=0; ymx=1; }
58
59 impl::scale_t sx = impl::make_scale(xmn, xmx, false, 10.0);
60 impl::scale_t sy = impl::make_scale(ymn, ymx, false, 10.0);
61
62 // up = green-ish series[1], down = red-ish series[4], with fallbacks.
63 std::uint32_t up = th.series.size() > 1 ? th.series[1] : th.fg;
64 std::uint32_t down = th.series.size() > 4 ? th.series[4] : th.fg;
65
66 impl::render_panel_into(cv, x, y,
67 static_cast<std::size_t>(w), static_cast<std::size_t>(h),
68 th, sx, sy, title, xl, yl, {},
69 [&](impl::canvas_t& cc, auto px, auto py){
70 using attribute_t = plot::attribute::element_t;
71 // candle half-width in pixels: ~40% of the inter-bar spacing.
72 float spacing = (n > 1) ? std::abs(px(sx.xform(t[1])) - px(sx.xform(t[0]))) : 10.0f;
73 float bw = std::max(2.0f, spacing * 0.4f);
74 for(std::size_t i=0;i<n;++i){
75 bool upbar = c[i] >= o[i];
76 std::uint32_t col = upbar ? up : down;
77 float X = px(sx.xform(t[i]));
78 // high-low wick.
79 attribute_t la; la.color = plot::attribute::color_t{ col };
80 la.stroke = plot::attribute::stroke_t{1.0f, 1.2f, {}, {}, {}};
81 cc.line(X, py(hi[i]), X, py(lo[i]), la);
82 // open-close body.
83 float yo = py(o[i]), yc = py(c[i]);
84 float top = std::min(yo, yc);
85 float bh = std::max(1.0f, std::abs(yc - yo));
86 attribute_t ra; ra.color = plot::attribute::color_t{ col };
87 cc.rect(X - bw, top, bw*2.0f, bh, 0, 0, ra);
88 }
89 });
90 }, opts);
91 }
92 };
93}
94
95namespace plot {
96 template <class... opt_t>
97 impl::ohlc_view<opt_t...> ohlc(std::vector<double> times,
98 std::vector<double> open, std::vector<double> high,
99 std::vector<double> low, std::vector<double> close, opt_t... opts){
100 return impl::ohlc_view<opt_t...>{ std::move(times), std::move(open),
101 std::move(high), std::move(low), std::move(close),
102 std::make_tuple(opts...) };
103 }
104}
105#endif