35#include "attributes.hpp"
41 struct xlabel {
using value_type = tag::xlabel_t; std::string value; };
42 struct ylabel {
using value_type = tag::ylabel_t; std::string value; };
44 struct xlog {
using value_type = tag::xlog_t;
double base = 10.0; };
45 struct ylog {
using value_type = tag::ylog_t;
double base = 10.0; };
50 struct series_t { std::string label; std::vector<double> data; };
53 template <
class... arg_t>
54 const theme_t& resolve_theme(arg_t... args){
55 using theme_pos =
typename arg::tpos<tag::theme_t, arg_t...>;
56 if constexpr( theme_pos::present ){
57 auto tuple = std::forward_as_tuple(args...);
62 thread_local theme_t picked;
63 picked = std::get<theme_pos::position>(tuple).value;
71 inline std::uint32_t gradient3(
const std::array<std::uint32_t,3>& g,
double t){
72 if( t < 0.0 ) t = 0.0;
else if( t > 1.0 ) t = 1.0;
73 auto chan = [](std::uint32_t c,
int sh){
return double((c >> sh) & 0xFF); };
74 auto lerp = [](
double a,
double b,
double u){
return a + u*(b-a); };
78 r = lerp(chan(g[0],16), chan(g[1],16), u);
79 gn = lerp(chan(g[0], 8), chan(g[1], 8), u);
80 b = lerp(chan(g[0], 0), chan(g[1], 0), u);
82 double u = (t - 0.5) / 0.5;
83 r = lerp(chan(g[1],16), chan(g[2],16), u);
84 gn = lerp(chan(g[1], 8), chan(g[2], 8), u);
85 b = lerp(chan(g[1], 0), chan(g[2], 0), u);
87 auto c8 = [](
double v)->std::uint32_t {
88 if( v < 0 ) v = 0;
else if( v > 255 ) v = 255;
89 return std::uint32_t(v + 0.5); };
90 return (c8(r) << 16) | (c8(gn) << 8) | c8(b);
94 inline double nice_step(
double range,
int count){
95 if( range <= 0 || count <= 0 )
return 1.0;
96 double raw = range / count;
97 double mag = std::pow(10.0, std::floor(std::log10(raw)));
98 double norm = raw / mag;
100 if( norm < 1.5 ) step = 1;
101 else if( norm < 3 ) step = 2;
102 else if( norm < 7 ) step = 5;
109 double lo = 0, hi = 1;
112 std::vector<double> ticks;
113 std::vector<std::string> labels;
116 double norm(
double v)
const {
117 double span = (hi > lo) ? (hi - lo) : 1.0;
118 return (v - lo) / span;
121 double xform(
double v)
const {
123 double b = (base > 0 && base != 1.0) ? base : 10.0;
124 double safe = v > 0 ? v : std::numeric_limits<double>::min();
125 return std::log(safe) / std::log(b);
129 inline std::string tick_label(
double v){
131 if( std::abs(v - std::llround(v)) < 1e-9 * (1.0 + std::abs(v)) )
132 return std::format(
"{}", std::llround(v));
133 return std::format(
"{:.4g}", v);
137 inline scale_t make_scale(
double dmin,
double dmax,
bool log,
double base){
138 scale_t s; s.log = log; s.base = base;
140 double b = (base > 0 && base != 1.0) ? base : 10.0;
141 double lmin = dmin > 0 ? dmin : std::numeric_limits<double>::min();
142 double lmax = dmax > 0 ? dmax : std::numeric_limits<double>::min();
143 s.lo = std::log(lmin)/std::log(b);
144 s.hi = std::log(lmax)/std::log(b);
145 if( s.hi <= s.lo ) s.hi = s.lo + 1.0;
149 double t0 = std::floor(s.lo + 1e-9), t1 = std::ceil(s.hi - 1e-9);
150 s.lo = t0; s.hi = t1;
151 for(
double t = t0; t <= t1 + 0.5; t += 1.0){
152 s.ticks.push_back(t);
153 s.labels.push_back(tick_label(std::pow(b, t)));
156 if( dmax <= dmin ){ dmax = dmin + 1.0; }
157 double step = nice_step(dmax - dmin, 5);
158 double t0 = std::floor(dmin/step)*step;
159 double t1 = std::ceil(dmax/step)*step;
160 s.lo = t0; s.hi = t1;
161 for(
double t = t0; t <= t1 + step*0.5; t += step){
162 s.ticks.push_back(t);
163 s.labels.push_back(tick_label(t));
176 template <
class draw_fn>
177 void render_panel_into(canvas_t& canvas,
float ox,
float oy,
178 std::size_t width, std::size_t height,
const theme_t& th,
179 const scale_t& sx,
const scale_t& sy,
180 const std::string& title,
const std::string& xlab,
const std::string& ylab,
181 const std::vector<std::string>& legend_labels,
183 using attribute_t = plot::attribute::element_t;
184 using color_t = plot::attribute::color_t;
185 using stroke_t = plot::attribute::stroke_t;
188 canvas.group(
static_cast<std::size_t
>(ox),
static_cast<std::size_t
>(oy), grp,
192 const float tick_char = 0.60f * 9.0f;
193 const float left = 56.0f;
196 const float right = std::max(14.0f, sx.labels.empty() ? 14.0f
197 : tick_char * float(sx.labels.back().size()) + 8.0f);
198 const float top = title.empty() ? 18.0f : 34.0f;
199 const float bottom = 44.0f;
200 const float x0 = left;
201 const float y0 = top;
202 const float pw = std::max(1.0f,
float(width) - left - right);
203 const float ph = std::max(1.0f,
float(height) - top - bottom);
206 { attribute_t bg; bg.color = color_t{ th.bg };
207 canvas.rect(0,0,
float(width),
float(height), 0,0, bg); }
209 { attribute_t pn; pn.color = color_t{ th.panel };
210 canvas.rect(x0, y0, pw, ph, 2,2, pn); }
212 auto px = [&](
double v){
return x0 + float(sx.norm(v)) * pw; };
213 auto py = [&](
double v){
return y0 + ph - float(sy.norm(v)) * ph; };
216 attribute_t grid_attr; grid_attr.color = color_t{ th.grid };
217 grid_attr.stroke = stroke_t{1.0f, 0.5f, {}, {}, {}};
218 attribute_t tick_attr; tick_attr.color = color_t{ th.fg };
219 tick_attr.font = plot::attribute::font_t{
"Ubuntu Mono, monospace",
"normal", 9u};
225 const float label_gap = 14.0f;
226 for(std::size_t i=0;i<sx.ticks.size();++i){
227 float X = px(sx.ticks[i]);
228 canvas.line(X, y0, X, y0+ph, grid_attr);
229 attribute_t a = tick_attr; a.align = plot::attribute::align_t::center;
230 canvas.text(sx.labels[i], std::size_t(X), std::size_t(y0+ph+label_gap), a);
232 for(std::size_t i=0;i<sy.ticks.size();++i){
233 float Y = py(sy.ticks[i]);
234 canvas.line(x0, Y, x0+pw, Y, grid_attr);
235 attribute_t a = tick_attr; a.align = plot::attribute::align_t::right;
236 float tx = x0 - label_gap - tick_char * float(sy.labels[i].size());
237 canvas.text(sy.labels[i], std::size_t(tx < 0 ? 0 : tx), std::size_t(Y+3), a);
241 attribute_t axis_attr; axis_attr.color = color_t{ th.axis };
242 axis_attr.stroke = stroke_t{1.0f, 1.2f, {}, {}, {}};
243 canvas.line(x0, y0, x0, y0+ph, axis_attr);
244 canvas.line(x0, y0+ph, x0+pw, y0+ph, axis_attr);
247 if( !title.empty() ){
248 attribute_t a; a.color = color_t{ th.fg };
249 a.font = plot::attribute::font_t{
"Arial, sans-serif",
"bold", 13u};
250 a.align = plot::attribute::align_t::center;
251 canvas.text(title, std::size_t(x0 + pw/2), std::size_t(top-16), a);
254 attribute_t a; a.color = color_t{ th.fg };
255 a.font = plot::attribute::font_t{
"Arial, sans-serif",
"normal", 10u};
256 a.align = plot::attribute::align_t::center;
257 canvas.text(xlab, std::size_t(x0 + pw/2), std::size_t(height-8), a);
260 attribute_t a; a.color = color_t{ th.fg };
261 a.font = plot::attribute::font_t{
"Arial, sans-serif",
"normal", 10u};
262 a.align = plot::attribute::align_t::center;
263 a.rotate = plot::attribute::degree_t{270.0f};
264 canvas.text(ylab, std::size_t(14), std::size_t(y0 + ph/2), a);
268 draw(canvas, px, py);
271 if( !legend_labels.empty() ){
272 float lx = x0 + pw - 110.0f;
273 float ly = y0 + 12.0f;
274 for(std::size_t i=0;i<legend_labels.size();++i){
275 std::uint32_t c = th.series.empty() ? th.fg
276 : th.series[i % th.series.size()];
277 attribute_t sw; sw.color = color_t{ c };
278 canvas.rect(lx, ly +
float(i)*14.0f - 8.0f, 10, 10, 1,1, sw);
279 attribute_t tx; tx.color = color_t{ th.fg };
280 tx.font = plot::attribute::font_t{
"Arial, sans-serif",
"normal", 9u};
281 tx.align = plot::attribute::align_t::left;
282 canvas.text(legend_labels[i], std::size_t(lx+14),
283 std::size_t(ly +
float(i)*14.0f), tx);
291 template <
class draw_fn>
292 void render_panel(std::ostream& os,
const theme_t& th,
293 std::size_t width, std::size_t height,
const std::array<float,4>& margin,
294 const scale_t& sx,
const scale_t& sy,
295 const std::string& title,
const std::string& xlab,
const std::string& ylab,
296 const std::vector<std::string>& legend_labels,
298 canvas_t canvas(os, width, height, margin);
299 render_panel_into(canvas, 0, 0, width, height, th, sx, sy,
300 title, xlab, ylab, legend_labels, std::forward<draw_fn>(draw));
304 template <
class... arg_t>
305 std::tuple<std::size_t,std::size_t,std::array<float,4>>
306 geometry(arg_t... args){
307 using width_t =
typename arg::tpos<tag::width_t, arg_t...>;
308 using height_t =
typename arg::tpos<tag::height_t, arg_t...>;
309 using margin_t =
typename arg::tpos<tag::margin_t, arg_t...>;
310 auto tuple = std::forward_as_tuple(args...);
311 std::size_t W = 640, H = 400;
312 std::array<float,4> M{5,5,5,5};
313 if constexpr( width_t::present ) W = std::get<width_t::position>(tuple).value;
314 if constexpr( height_t::present ) H = std::get<height_t::position>(tuple).value;
315 if constexpr( margin_t::present ) M = std::get<margin_t::position>(tuple).value;
319 template <
class... arg_t>
320 std::tuple<std::string,std::string,std::string>
321 texts(arg_t... args){
322 using title_t =
typename arg::tpos<tag::title_t, arg_t...>;
323 using xlabel_t =
typename arg::tpos<tag::xlabel_t, arg_t...>;
324 using ylabel_t =
typename arg::tpos<tag::ylabel_t, arg_t...>;
325 auto tuple = std::forward_as_tuple(args...);
326 std::string t, xl, yl;
327 if constexpr( title_t::present ) t = std::get<title_t::position>(tuple).txt;
328 if constexpr( xlabel_t::present ) xl = std::get<xlabel_t::position>(tuple).value;
329 if constexpr( ylabel_t::present ) yl = std::get<ylabel_t::position>(tuple).value;
334 template <
class... arg_t>
335 std::pair<bool,double> xlog_of(arg_t... args){
336 using xlog_t =
typename arg::tpos<tag::xlog_t, arg_t...>;
337 auto tuple = std::forward_as_tuple(args...);
338 if constexpr( xlog_t::present )
return {
true, std::get<xlog_t::position>(tuple).base};
339 else return {
false, 10.0};
341 template <
class... arg_t>
342 std::pair<bool,double> ylog_of(arg_t... args){
343 using ylog_t =
typename arg::tpos<tag::ylog_t, arg_t...>;
344 auto tuple = std::forward_as_tuple(args...);
345 if constexpr( ylog_t::present )
return {
true, std::get<ylog_t::position>(tuple).base};
346 else return {
false, 10.0};
350 std::pair<double,double> minmax_of(
const V& v){
351 double lo = std::numeric_limits<double>::infinity();
352 double hi = -std::numeric_limits<double>::infinity();
353 for(
auto e : v){
double d = double(e);
if(d<lo) lo=d;
if(d>hi) hi=d; }
354 if( !(lo <= hi) ){ lo = 0; hi = 1; }
361 template <
class X,
class Y,
class... arg_t>
362 void line(std::ostream& os,
const std::vector<X>& xs,
const std::vector<Y>& ys, arg_t... args){
363 const theme_t& th = impl::resolve_theme(args...);
364 auto [W,H,M] = impl::geometry(args...);
365 auto [title, xl, yl] = impl::texts(args...);
366 auto [xlg, xb] = impl::xlog_of(args...);
367 auto [ylg, yb] = impl::ylog_of(args...);
369 auto [xmn,xmx] = impl::minmax_of(xs);
370 auto [ymn,ymx] = impl::minmax_of(ys);
371 impl::scale_t sx = impl::make_scale(xmn,xmx,xlg,xb);
372 impl::scale_t sy = impl::make_scale(ymn,ymx,ylg,yb);
374 impl::render_panel(os, th, W, H, M, sx, sy, title, xl, yl, {},
375 [&](impl::canvas_t& cv,
auto px,
auto py){
376 using attribute_t = plot::attribute::element_t;
377 std::vector<float> xpx, ypx;
378 const std::size_t n = std::min(xs.size(), ys.size());
379 for(std::size_t i=0;i<n;++i){
380 xpx.push_back(px(sx.xform(
double(xs[i]))));
381 ypx.push_back(py(sy.xform(
double(ys[i]))));
384 a.color = plot::attribute::color_t{ th.series.empty()? th.fg : th.series[0] };
385 a.stroke = plot::attribute::stroke_t{1.0f, 1.8f, {}, {}, {}};
386 cv.poly_line(xpx, ypx, a);
391 template <
class X,
class... arg_t>
392 void line(std::ostream& os,
const std::vector<X>& xs,
393 const std::vector<impl::series_t>& series, arg_t... args){
394 const theme_t& th = impl::resolve_theme(args...);
395 auto [W,H,M] = impl::geometry(args...);
396 auto [title, xl, yl] = impl::texts(args...);
397 auto [xlg, xb] = impl::xlog_of(args...);
398 auto [ylg, yb] = impl::ylog_of(args...);
400 auto [xmn,xmx] = impl::minmax_of(xs);
401 double ymn = std::numeric_limits<double>::infinity();
402 double ymx = -std::numeric_limits<double>::infinity();
403 for(
const auto& s : series){
404 auto [a,b] = impl::minmax_of(s.data);
405 if(a<ymn) ymn=a;
if(b>ymx) ymx=b;
407 if( !(ymn<=ymx) ){ ymn=0; ymx=1; }
408 impl::scale_t sx = impl::make_scale(xmn,xmx,xlg,xb);
409 impl::scale_t sy = impl::make_scale(ymn,ymx,ylg,yb);
411 std::vector<std::string> labels;
412 for(
const auto& s : series) labels.push_back(s.label);
414 impl::render_panel(os, th, W, H, M, sx, sy, title, xl, yl, labels,
415 [&](impl::canvas_t& cv,
auto px,
auto py){
416 using attribute_t = plot::attribute::element_t;
417 for(std::size_t k=0;k<series.size();++k){
418 const auto& s = series[k];
419 std::vector<float> xpx, ypx;
420 const std::size_t n = std::min(xs.size(), s.data.size());
421 for(std::size_t i=0;i<n;++i){
422 xpx.push_back(px(sx.xform(
double(xs[i]))));
423 ypx.push_back(py(sy.xform(
double(s.data[i]))));
425 std::uint32_t c = th.series.empty()? th.fg
426 : th.series[k % th.series.size()];
427 attribute_t a; a.color = plot::attribute::color_t{ c };
428 a.stroke = plot::attribute::stroke_t{1.0f, 1.8f, {}, {}, {}};
429 cv.poly_line(xpx, ypx, a);
435 template <
class X,
class... arg_t>
436 void line(std::ostream& os,
const std::vector<X>& xs,
437 std::initializer_list<impl::series_t> series, arg_t... args){
438 line(os, xs, std::vector<impl::series_t>(series), args...);
442 template <
class X,
class Y,
class... arg_t>
443 void scatter(std::ostream& os,
const std::vector<X>& xs,
const std::vector<Y>& ys, arg_t... args){
444 const theme_t& th = impl::resolve_theme(args...);
445 auto [W,H,M] = impl::geometry(args...);
446 auto [title, xl, yl] = impl::texts(args...);
447 auto [xlg, xb] = impl::xlog_of(args...);
448 auto [ylg, yb] = impl::ylog_of(args...);
450 auto [xmn,xmx] = impl::minmax_of(xs);
451 auto [ymn,ymx] = impl::minmax_of(ys);
452 impl::scale_t sx = impl::make_scale(xmn,xmx,xlg,xb);
453 impl::scale_t sy = impl::make_scale(ymn,ymx,ylg,yb);
455 impl::render_panel(os, th, W, H, M, sx, sy, title, xl, yl, {},
456 [&](impl::canvas_t& cv,
auto px,
auto py){
457 using attribute_t = plot::attribute::element_t;
458 const std::size_t n = std::min(xs.size(), ys.size());
459 std::uint32_t c = th.series.empty()? th.fg : th.series[0];
460 for(std::size_t i=0;i<n;++i){
461 attribute_t a; a.color = plot::attribute::color_t{ c };
462 cv.circle(px(sx.xform(
double(xs[i]))),
463 py(sy.xform(
double(ys[i]))), 3.0f, a);
469 template <
class X,
class Y,
class... arg_t>
470 void line(
const std::string& filename,
const std::vector<X>& xs,
const std::vector<Y>& ys, arg_t... args){
471 std::ofstream ofs(filename, std::ios::out | std::ios::trunc);
472 line(ofs, xs, ys, args...);
474 template <
class X,
class... arg_t>
475 void line(
const std::string& filename,
const std::vector<X>& xs,
476 std::initializer_list<impl::series_t> series, arg_t... args){
477 std::ofstream ofs(filename, std::ios::out | std::ios::trunc);
478 line(ofs, xs, std::vector<impl::series_t>(series), args...);
480 template <
class X,
class Y,
class... arg_t>
481 void scatter(
const std::string& filename,
const std::vector<X>& xs,
const std::vector<Y>& ys, arg_t... args){
482 std::ofstream ofs(filename, std::ios::out | std::ios::trunc);
483 scatter(ofs, xs, ys, args...);