numerics 0.1.0
Loading...
Searching...
No Matches
plot.hpp
Go to the documentation of this file.
1/// @file plot/plot.hpp
2/// @brief Matplotlib-style plotting via a gnuplot pipe.
3#pragma once
4
5#include <cstdio>
6#include <stdexcept>
7#include <string>
8#include <utility>
9#include <vector>
10
11namespace num {
12
13using Point = std::pair<double, double>;
14
15struct Series : std::vector<Point> {
16 using std::vector<Point>::vector;
17 void store(double x, double y) { emplace_back(x, y); }
18};
19
20class Gnuplot {
21 public:
22 explicit Gnuplot(const std::string& args = "") {
23 std::string cmd = "gnuplot " + args;
24 pipe_ = popen(cmd.c_str(), "w");
25 if (!pipe_)
26 throw std::runtime_error("could not open gnuplot -- is it installed?");
27 }
29 if (pipe_)
30 pclose(pipe_);
31 }
32 Gnuplot(const Gnuplot&) = delete;
33 Gnuplot& operator=(const Gnuplot&) = delete;
34
35 Gnuplot& operator<<(const std::string& cmd) {
36 fputs(cmd.c_str(), pipe_);
37 return *this;
38 }
39 void send1d(const Series& data) {
40 for (const auto& [x, y] : data)
41 fprintf(pipe_, "%.15g %.15g\n", x, y);
42 fputs("e\n", pipe_);
43 fflush(pipe_);
44 }
45 void flush() { fflush(pipe_); }
46
47 private:
48 FILE* pipe_ = nullptr;
49};
50
51/// Apply SIAM-style theme to a raw Gnuplot pipe.
52inline void apply_siam_style(Gnuplot& gp) {
53 gp << "set style line 1 lt 1 lw 2 pt 7 ps 0.8 lc rgb 'black'\n"
54 << "set style line 2 lt 2 lw 2 pt 5 ps 0.8 lc rgb 'black'\n"
55 << "set style line 3 lt 3 lw 2 pt 9 ps 0.8 lc rgb 'black'\n"
56 << "set style line 4 lt 4 lw 2 pt 13 ps 0.8 lc rgb 'black'\n"
57 << "set style line 5 lt 5 lw 2 pt 11 ps 0.8 lc rgb 'black'\n"
58 << "set style line 6 lt 6 lw 2 pt 15 ps 0.8 lc rgb 'black'\n"
59 << "set style line 100 lt 1 lw 0.5 lc rgb '#cccccc'\n"
60 << "set grid back ls 100\n"
61 << "set border 3 lw 1.5\n"
62 << "set tics nomirror\n"
63 << "set key top left Left reverse samplen 3 spacing 1.2\n"
64 << "set key box lt 1 lw 0.5\n";
65}
66
67inline void set_loglog(Gnuplot& gp) {
68 gp << "set logscale xy\nset format x '10^{%L}'\nset format y '10^{%L}'\n";
69}
70inline void set_logx(Gnuplot& gp) {
71 gp << "set logscale x\nset format x '10^{%L}'\n";
72}
73inline void save_png(Gnuplot& gp, const std::string& filename, int w = 900, int h = 600) {
74 gp << "set terminal pngcairo size " + std::to_string(w) + "," + std::to_string(h)
75 + " enhanced font 'Arial,11'\n"
76 << "set output '" + filename + "'\n";
77}
78
79namespace plt {
80namespace detail {
81
82struct SeriesEntry {
83 Series data;
84 std::string label;
85 std::string style; // gnuplot "with" clause, e.g. "lines"
86};
87
88/// 2-D field snapshot for heatmap rendering via gnuplot pm3d map.
89struct HeatmapEntry {
90 std::vector<double> data; // NxN row-major values
91 int N = 0;
92 double h = 1.0;
93 double vmin = 0.0;
94 double vmax = 1.0;
95};
96
97struct Panel {
98 std::vector<SeriesEntry> series;
99 std::vector<HeatmapEntry> heatmaps;
100 std::string title_, xlabel_, ylabel_;
101 std::string xrange_, yrange_;
102 std::string palette_; // gnuplot palette string; empty = hot/fire
103 bool legend_ = false;
104 bool logx_ = false;
105 bool logy_ = false;
106};
107
108struct State {
109 Panel current;
110 std::vector<Panel> panels; // accumulated panels in multiplot mode
111 int mp_rows_ = 0, mp_cols_ = 0; // 0 = single-plot mode
112
113 void reset() { *this = State{}; }
114};
115
116inline State& state() {
117 static State s;
118 return s;
119}
120
121// Write all datablocks for a panel, then emit the plot command for that panel.
122// block_offset: index of the first $d_N block allocated to this panel.
123inline void write_panel(FILE* pipe, const Panel& p, int block_offset) {
124 if (p.series.empty() && p.heatmaps.empty())
125 return;
126
127 // Common decorators
128 if (!p.title_.empty())
129 fprintf(pipe, "set title '%s'\n", p.title_.c_str());
130 else
131 fputs("unset title\n", pipe);
132 if (!p.xlabel_.empty())
133 fprintf(pipe, "set xlabel '%s'\n", p.xlabel_.c_str());
134 else
135 fputs("unset xlabel\n", pipe);
136 if (!p.ylabel_.empty())
137 fprintf(pipe, "set ylabel '%s'\n", p.ylabel_.c_str());
138 else
139 fputs("unset ylabel\n", pipe);
140
141 if (!p.heatmaps.empty()) {
142 // Heatmap panel rendered with pm3d map.
143 const auto& hm = p.heatmaps[0];
144 if (!p.palette_.empty())
145 fprintf(pipe, "set palette %s\n", p.palette_.c_str());
146 else
147 fputs("set palette defined "
148 "(0 'white', 0.35 '#ffffb2', 0.65 '#fd8d3c', 1 '#bd0026')\n",
149 pipe);
150 fprintf(pipe, "set cbrange [%g:%g]\n", hm.vmin, hm.vmax);
151 fputs("set pm3d map\n", pipe);
152 fputs("set size ratio 1\n", pipe);
153 if (!p.xrange_.empty())
154 fprintf(pipe, "set xrange %s\n", p.xrange_.c_str());
155 else
156 fputs("set xrange [*:*]\n", pipe);
157 if (!p.yrange_.empty())
158 fprintf(pipe, "set yrange %s\n", p.yrange_.c_str());
159 else
160 fputs("set yrange [*:*]\n", pipe);
161 fputs("unset key\n", pipe);
162 fprintf(pipe, "splot $d_%d with pm3d notitle\n", block_offset);
163 } else {
164 // Line-plot panel
165 fputs("unset pm3d\n", pipe);
166 if (!p.xrange_.empty())
167 fprintf(pipe, "set xrange %s\n", p.xrange_.c_str());
168 else
169 fputs("set xrange [*:*]\n", pipe);
170 if (!p.yrange_.empty())
171 fprintf(pipe, "set yrange %s\n", p.yrange_.c_str());
172 else
173 fputs("set yrange [*:*]\n", pipe);
174
175 if (p.logx_ && p.logy_) {
176 fputs("set logscale xy\nset format x '10^{%L}'\nset format y "
177 "'10^{%L}'\n",
178 pipe);
179 } else if (p.logx_) {
180 fputs("set logscale x\nset format x '10^{%L}'\n", pipe);
181 } else if (p.logy_) {
182 fputs("set logscale y\nset format y '10^{%L}'\n", pipe);
183 } else {
184 fputs("unset logscale\n", pipe);
185 }
186
187 if (p.legend_) {
188 fputs("set key top right Left reverse samplen 3 spacing 1.2\n"
189 "set key box lt 1 lw 0.5\n",
190 pipe);
191 } else {
192 fputs("unset key\n", pipe);
193 }
194
195 fputs("plot ", pipe);
196 for (std::size_t i = 0; i < p.series.size(); ++i) {
197 if (i)
198 fputs(", ", pipe);
199 const auto& e = p.series[i];
200 fprintf(pipe,
201 "$d_%d with %s ls %zu",
202 block_offset + (int)i,
203 e.style.c_str(),
204 i + 1);
205 if (!e.label.empty())
206 fprintf(pipe, " title '%s'", e.label.c_str());
207 else
208 fputs(" notitle", pipe);
209 }
210 fputc('\n', pipe);
211 }
212}
213
214inline void flush_to(FILE* pipe, const std::string& outfile) {
215 auto& s = state();
216
217 // Collect all panels (push current last)
218 std::vector<Panel> all = s.panels;
219 all.push_back(s.current);
220
221 bool multiplot = (s.mp_rows_ > 0);
222
223 // Terminal
224 if (outfile.empty()) {
225 int h = multiplot ? 300 * s.mp_rows_ : 600;
226 fprintf(pipe, "set terminal qt size 900,%d\n", h);
227 } else {
228 std::string ext = outfile.size() > 4 ? outfile.substr(outfile.size() - 4) : "";
229 if (ext == ".pdf") {
230 double h = multiplot ? 3.0 * s.mp_rows_ : 4.0;
231 fprintf(pipe, "set terminal pdfcairo size 6,%.0f font 'Arial,11'\n", h);
232 } else {
233 int h = multiplot ? 350 * s.mp_rows_ : 600;
234 fprintf(pipe,
235 "set terminal pngcairo size 900,%d enhanced font 'Arial,11'\n",
236 h);
237 }
238 fprintf(pipe, "set output '%s'\n", outfile.c_str());
239 }
240
241 // Global theme
242 fputs("set style line 1 lt 1 lw 2 pt 7 ps 0.7 lc rgb '#2c3e50'\n", pipe);
243 fputs("set style line 2 lt 2 lw 2 pt 5 ps 0.7 lc rgb '#c0392b'\n", pipe);
244 fputs("set style line 3 lt 3 lw 2 pt 9 ps 0.7 lc rgb '#2980b9'\n", pipe);
245 fputs("set style line 4 lt 4 lw 2 pt 13 ps 0.7 lc rgb '#27ae60'\n", pipe);
246 fputs("set style line 5 lt 5 lw 2 pt 11 ps 0.7 lc rgb '#8e44ad'\n", pipe);
247 fputs("set style line 100 lt 1 lw 0.5 lc rgb '#cccccc'\n", pipe);
248 fputs("set grid back ls 100\n", pipe);
249 fputs("set border 3 lw 1.5\n", pipe);
250 fputs("set tics nomirror\n", pipe);
251
252 // Write all datablocks up front (required for multiplot; harmless for
253 // single)
254 int block = 0;
255 for (const auto& p : all) {
256 for (const auto& e : p.series) {
257 fprintf(pipe, "$d_%d << EOD\n", block++);
258 for (const auto& [x, y] : e.data)
259 fprintf(pipe, "%.15g %.15g\n", x, y);
260 fputs("EOD\n", pipe);
261 }
262 for (const auto& hm : p.heatmaps) {
263 fprintf(pipe, "$d_%d << EOD\n", block++);
264 for (int i = 0; i < hm.N; ++i) {
265 double xi = (i + 1) * hm.h;
266 for (int j = 0; j < hm.N; ++j)
267 fprintf(pipe,
268 "%.8g %.8g %.8g\n",
269 xi,
270 (j + 1) * hm.h,
271 hm.data[static_cast<std::size_t>(i) * hm.N + j]);
272 fputs("\n", pipe);
273 }
274 fputs("EOD\n", pipe);
275 }
276 }
277
278 if (multiplot) {
279 fprintf(pipe,
280 "set multiplot layout %d,%d spacing 0.08,0.12\n",
281 s.mp_rows_,
282 s.mp_cols_);
283 int off = 0;
284 for (const auto& p : all) {
285 write_panel(pipe, p, off);
286 off += (int)p.series.size() + (int)p.heatmaps.size();
287 }
288 fputs("unset multiplot\n", pipe);
289 } else {
290 write_panel(pipe, all.back(), 0);
291 }
292
293 fflush(pipe);
294}
295
296} // namespace detail
297
298// -- Series builders ----------------------------------------------------------
299
300/// Append a Series (vector of (x,y) pairs) to the current panel.
301inline void plot(const Series& data,
302 const std::string& label = "",
303 const std::string& style = "lines") {
304 detail::state().current.series.push_back({data, label, style});
305}
306
307/// Append parallel x and y vectors to the current panel.
308inline void plot(const std::vector<double>& x,
309 const std::vector<double>& y,
310 const std::string& label = "",
311 const std::string& style = "lines") {
312 Series s;
313 s.reserve(x.size());
314 for (std::size_t i = 0; i < x.size() && i < y.size(); ++i)
315 s.emplace_back(x[i], y[i]);
316 detail::state().current.series.push_back({std::move(s), label, style});
317}
318
319// -- Decorators ---------------------------------------------------------------
320
321inline void title(const std::string& t) {
322 detail::state().current.title_ = t;
323}
324inline void xlabel(const std::string& l) {
325 detail::state().current.xlabel_ = l;
326}
327inline void ylabel(const std::string& l) {
328 detail::state().current.ylabel_ = l;
329}
330
331/// Set x-axis range, e.g. xlim(0, 10).
332inline void xlim(double lo, double hi) {
333 detail::state().current.xrange_ =
334 "[" + std::to_string(lo) + ":" + std::to_string(hi) + "]";
335}
336/// Set y-axis range.
337inline void ylim(double lo, double hi) {
338 detail::state().current.yrange_ =
339 "[" + std::to_string(lo) + ":" + std::to_string(hi) + "]";
340}
341
342/// Show a legend using the labels passed to plot().
343inline void legend() {
344 detail::state().current.legend_ = true;
345}
346
347/// Log-log axes.
348inline void loglog() {
349 detail::state().current.logx_ = detail::state().current.logy_ = true;
350}
351/// Log y-axis only.
352inline void semilogy() {
353 detail::state().current.logy_ = true;
354}
355/// Log x-axis only.
356inline void semilogx() {
357 detail::state().current.logx_ = true;
358}
359
360// -- 2-D heatmap --------------------------------------------------------------
361
362/// Add a 2-D heatmap to the current panel.
363/// @tparam Container Any type with .data() and .size() (num::Vector,
364/// std::vector<double>, etc.)
365/// @param u NxN row-major field values
366/// @param N Grid side length
367/// @param h Grid spacing (node (i,j) lives at ((i+1)*h, (j+1)*h))
368/// @param vmin Lower bound of the colour scale (default 0)
369/// @param vmax Upper bound of the colour scale (default 1)
370template<typename Container>
371inline void heatmap(const Container& u,
372 int N,
373 double h,
374 double vmin = 0.0,
375 double vmax = 1.0) {
376 detail::HeatmapEntry e;
377 e.data.assign(u.data(), u.data() + u.size());
378 e.N = N;
379 e.h = h;
380 e.vmin = vmin;
381 e.vmax = vmax;
382 detail::state().current.heatmaps.push_back(std::move(e));
383}
384
385/// Override the gnuplot palette for the current panel's heatmap.
386/// @param palette A gnuplot palette definition string, e.g.
387/// "defined (0 'blue', 1 'red')" or "rgbformulae 33,13,10"
388inline void colormap(const std::string& palette) {
389 detail::state().current.palette_ = palette;
390}
391
392// -- Multiplot ----------------------------------------------------------------
393
394/// @brief Start a multiplot with the given grid dimensions.
395inline void subplot(int rows, int cols = 1) {
396 detail::state().reset();
397 detail::state().mp_rows_ = rows;
398 detail::state().mp_cols_ = cols;
399}
400
401/// @brief Advance to the next panel.
402inline void next() {
403 detail::state().panels.push_back(detail::state().current);
404 detail::state().current = detail::Panel{};
405}
406
407// -- Output -------------------------------------------------------------------
408
409/// Open an interactive gnuplot window; blocks until the window is closed.
410/// Resets figure state afterwards.
411inline void show() {
412 FILE* pipe = popen("gnuplot", "w");
413 if (!pipe)
414 throw std::runtime_error("could not open gnuplot -- is it installed?");
415 detail::flush_to(pipe, "");
416 fputs("pause mouse close\n", pipe);
417 fflush(pipe);
418 pclose(pipe);
419 detail::state().reset();
420}
421
422/// Save the figure to a file (PNG or PDF inferred from extension).
423/// Resets figure state afterwards.
424inline void savefig(const std::string& filename) {
425 FILE* pipe = popen("gnuplot", "w");
426 if (!pipe)
427 throw std::runtime_error("could not open gnuplot -- is it installed?");
428 detail::flush_to(pipe, filename);
429 fflush(pipe);
430 pclose(pipe);
431 detail::state().reset();
432}
433
434/// Clear the current figure (discard all series and settings).
435inline void clf() {
436 detail::state().reset();
437}
438
439} // namespace plt
440} // namespace num
Gnuplot(const std::string &args="")
Definition plot.hpp:22
void flush()
Definition plot.hpp:45
Gnuplot & operator=(const Gnuplot &)=delete
void send1d(const Series &data)
Definition plot.hpp:39
Gnuplot(const Gnuplot &)=delete
Gnuplot & operator<<(const std::string &cmd)
Definition plot.hpp:35
void heatmap(const ScalarField2D &g, double vmin=0.0, double vmax=1.0)
Definition stencil.hpp:226
void set_loglog(Gnuplot &gp)
Definition plot.hpp:67
void apply_siam_style(Gnuplot &gp)
Apply SIAM-style theme to a raw Gnuplot pipe.
Definition plot.hpp:52
void save_png(Gnuplot &gp, const std::string &filename, int w=900, int h=600)
Definition plot.hpp:73
constexpr real e
Definition math.hpp:44
std::pair< double, double > Point
Definition plot.hpp:13
void set_logx(Gnuplot &gp)
Definition plot.hpp:70
void store(double x, double y)
Definition plot.hpp:17