plot
Header-only C++ SVG charts — heatmap, line, scatter — GR-style API + Solarized themes
Loading...
Searching...
No Matches
graph.hpp
1/* Copyright (c) 2026 Steven Varga, Toronto, ON, Canada
2 * MIT License — see LICENSE
3 *
4 * plot::graph(nodes, edges, opts...) — a deferred view (plot::view) drawing a
5 * node-link diagram. `nodes` is a std::vector<std::string> of labels; `edges` is
6 * a std::vector<std::pair<std::size_t,std::size_t>> of node-index pairs.
7 *
8 * Layout: a simple CIRCULAR layout — nodes are placed evenly on a circle
9 * inscribed in the cell. (A force-directed / spring layout would give nicer
10 * spacing for dense graphs and can be added later behind a layout option; the
11 * circular layout is deterministic, dependency-free and needs no iteration.)
12 * Edges draw as <line>, nodes as <circle> + a <text> label; node colours cycle
13 * theme.series.
14 *
15 * Built on the #5 view pattern (draws straight into the cell, no cartesian
16 * panel). Dependency-free.
17 */
18#ifndef PLOT_GRAPH_HPP
19#define PLOT_GRAPH_HPP
20
21#include <string>
22#include <vector>
23#include <tuple>
24#include <utility>
25#include <algorithm>
26#include <cmath>
27#include <cstddef>
28#include <cstdint>
29
30#include "tags.hpp"
31#include "meta.hpp"
32#include "attributes.hpp"
33#include "canvas.hpp"
34#include "theme.hpp"
35#include "gr.hpp"
36#include "view.hpp"
37
38namespace plot::impl {
39 template <class... opt_t>
40 struct graph_view {
41 using value_type = tag::view_t;
42 std::vector<std::string> nodes;
43 std::vector<std::pair<std::size_t,std::size_t>> edges;
44 std::tuple<opt_t...> opts;
45
46 std::pair<std::size_t,std::size_t> natural() const {
47 return std::apply([](const auto&... o){
48 return impl::natural_size(std::size_t{500}, std::size_t{500}, o...); }, opts);
49 }
50 void draw_into(canvas_t& cv, float x, float y, float w, float h) const {
51 std::apply([&](const auto&... o){
52 const theme_t& th = impl::resolve_theme(o...);
53 auto [title, xl, yl] = impl::texts(o...);
54 using attribute_t = plot::attribute::element_t;
55
56 cv.group(static_cast<std::size_t>(x), static_cast<std::size_t>(y), attribute_t{},
57 [&](){
58 // background panel for the cell.
59 { attribute_t bg; bg.color = plot::attribute::color_t{ th.bg };
60 cv.rect(0,0, w, h, 0,0, bg); }
61 float top = title.empty() ? 8.0f : 26.0f;
62 if( !title.empty() ){
63 attribute_t a; a.color = plot::attribute::color_t{ th.fg };
64 a.font = plot::attribute::font_t{"Arial, sans-serif", "bold", 13u};
65 a.align = plot::attribute::align_t::center;
66 cv.text(title, std::size_t(w/2), std::size_t(16), a);
67 }
68 const std::size_t n = nodes.size();
69 if( n == 0 ) return;
70 float cx = w*0.5f, cy = top + (h - top)*0.5f;
71 float R = std::max(10.0f, std::min(w, h - top)*0.5f - 28.0f);
72
73 auto pos = [&](std::size_t i)->std::pair<float,float>{
74 double ang = -1.5707963267948966 + 2.0*3.141592653589793*double(i)/double(n);
75 return { cx + R*float(std::cos(ang)), cy + R*float(std::sin(ang)) };
76 };
77
78 // edges first so nodes sit on top.
79 attribute_t ea; ea.color = plot::attribute::color_t{ th.grid };
80 ea.stroke = plot::attribute::stroke_t{1.0f, 1.2f, {}, {}, {}};
81 for(auto [a,b] : edges){
82 if( a >= n || b >= n ) continue;
83 auto [ax,ay] = pos(a); auto [bx,by] = pos(b);
84 cv.line(ax, ay, bx, by, ea);
85 }
86 // nodes + labels.
87 for(std::size_t i=0;i<n;++i){
88 auto [nx,ny] = pos(i);
89 std::uint32_t col = th.series.empty()? th.fg
90 : th.series[i % th.series.size()];
91 attribute_t na; na.color = plot::attribute::color_t{ col };
92 cv.circle(nx, ny, 7.0f, na);
93 attribute_t ta; ta.color = plot::attribute::color_t{ th.fg };
94 ta.font = plot::attribute::font_t{"Arial, sans-serif", "normal", 10u};
95 ta.align = plot::attribute::align_t::center;
96 // push labels radially outward so they clear the node disc.
97 float lx = nx + (nx - cx)*0.16f, ly = ny + (ny - cy)*0.16f;
98 cv.text(nodes[i], std::size_t(lx < 0 ? 0 : lx), std::size_t(ly + 3), ta);
99 }
100 });
101 }, opts);
102 }
103 };
104}
105
106namespace plot {
107 template <class... opt_t>
108 impl::graph_view<opt_t...> graph(std::vector<std::string> nodes,
109 std::vector<std::pair<std::size_t,std::size_t>> edges, opt_t... opts){
110 return impl::graph_view<opt_t...>{ std::move(nodes), std::move(edges),
111 std::make_tuple(opts...) };
112 }
113}
114#endif