Notcurses 3.0.16
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
plot.c
Go to the documentation of this file.
1#include <math.h>
2#include <float.h>
3#include <limits.h>
4#include <string.h>
5#include <inttypes.h>
6#include "internal.h"
7
8// common elements of type-parameterized plots
9typedef struct ncplot {
11 ncplane* pixelp; // only used for NCBLIT_PIXEL
12 /* sloutcount-element circular buffer of samples. the newest one (rightmost)
13 is at slots[slotstart]; they get older as you go back (and around).
14 elements. slotcount is max(columns, rangex), less label room. */
15 int64_t slotx; /* x value corresponding to slots[slotstart] (newest x) */
16 uint64_t maxchannels;
17 uint64_t minchannels;
18 uint16_t legendstyle;
19 bool vertical_indep; /* not yet implemented FIXME */
20 unsigned chancount; // channel count (can change on cell-pixel geom change)
21 uint64_t* channels; // computed in calculate_gradient_vector() (constructor)
22 const struct blitset* bset;
23 char* title;
24 /* requested number of slots. 0 for automatically setting the number of slots
25 to span the horizontal area. if there are more slots than there are
26 columns, we prefer showing more recent slots to less recent. if there are
27 fewer slots than there are columns, they prefer the left side. */
28 unsigned rangex;
29 /* domain minimum and maximum. if detectdomain is true, these are
30 progressively enlarged/shrunk to fit the sample set. if not, samples
31 outside these bounds are counted, but the displayed range covers only this. */
32 unsigned slotcount;
33 int slotstart; /* index of most recently-written slot */
34 bool labelaxisd; /* label dependent axis (consumes NCPREFIXCOLUMNS columns) */
35 bool exponentiali; /* exponential independent axis */
36 bool detectdomain; /* is domain detection in effect (stretch the domain)? */
37 bool detectonlymax; /* domain detection applies only to max, not min */
38 bool printsample; /* print the most recent sample */
40
41static inline int
42create_pixelp(ncplot *p, ncplane* n){
43 if(((p->pixelp = ncplane_dup(n, NULL)) == NULL)){
44 return -1;
45 }
46 if(ncplane_set_name(p->pixelp, "pmap")){
48 return -1;
49 }
52 uint64_t basechan = 0;
53 ncchannels_set_bg_alpha(&basechan, NCALPHA_TRANSPARENT);
54 ncchannels_set_fg_alpha(&basechan, NCALPHA_TRANSPARENT);
55 ncplane_set_base(n, "", 0, basechan);
56 return 0;
57}
58
59// we have some color gradient across the life of the plot (almost; it gets
60// recalculated if the cell-pixel geometry changes and we're using
61// NCBLIT_PIXEL). if we're using cell blitting, we only get one channel pair
62// per row, no matter what height we have. with pixels, we get cellpxy * rows.
63static int
64calculate_gradient_vector(ncplot* p, unsigned pixelp){
65 const int dimy = ncplane_dim_y(p->ncp);
66 const unsigned states = dimy * (pixelp ? ncplane_pile(p->ncp)->cellpxy : 1);
67 if(states == p->chancount){ // no need to recalculate
68 return 0;
69 }
70 uint64_t* tmp = realloc(p->channels, states * sizeof(*p->channels));
71 if(tmp == NULL){
72 return -1;
73 }
74 p->channels = tmp;
75 p->chancount = states;
76 for(unsigned y = 0 ; y < p->chancount ; ++y){ \
77 calc_gradient_channels(&p->channels[y], p->minchannels, p->minchannels,
79 y, 0, p->chancount, 0);
80 }
81 return 0;
82}
83
84#define MAXWIDTH 2
85#define CREATE(T, X) \
86typedef struct nc##X##plot { \
87 T* slots; \
88 T miny, maxy; \
89 ncplot plot; \
90} nc##X##plot; \
91\
92static int redraw_pixelplot_##T(nc##X##plot* ncp){ \
93 if(calculate_gradient_vector(&ncp->plot, 1)){ \
94 return -1; \
95 } \
96 const int scale = ncplane_pile_const(ncp->plot.ncp)->cellpxx; \
97 ncplane_erase(ncp->plot.ncp); \
98 unsigned dimy, dimx; \
99 ncplane_dim_yx(ncp->plot.ncp, &dimy, &dimx); \
100 const unsigned scaleddim = dimx * scale; \
101 /* each transition is worth this much change in value */ \
102 const size_t states = ncplane_pile_const(ncp->plot.ncp)->cellpxy; \
103 /* FIXME can we not rid ourselves of this meddlesome double? either way, the \
104 interval is one row's range (for linear plots), or the base \
105 (base^slots == maxy-miny) of the range (for exponential plots). */ \
106 double interval; \
107 if(ncp->plot.exponentiali){ \
108 if(ncp->maxy > ncp->miny){ \
109 interval = pow(ncp->maxy - ncp->miny, (double)1 / (dimy * states)); \
110/* fprintf(stderr, "miny: %ju maxy: %ju dimy: %d states: %zu\n", miny, maxy, dimy, states); */ \
111 }else{ \
112 interval = 0; \
113 } \
114 }else{ \
115 interval = ncp->maxy < ncp->miny ? 0 : (ncp->maxy - ncp->miny) / ((double)dimy * states); \
116 } \
117 const int startx = ncp->plot.labelaxisd ? NCPREFIXCOLUMNS : 0; /* plot cols begin here */ \
118 /* if we want fewer slots than there are available columns, our final column \
119 will be other than the plane's final column. most recent x goes here. */ \
120 const unsigned finalx = (ncp->plot.slotcount < scaleddim - 1 - (startx * scale) ? \
121 startx + (ncp->plot.slotcount / scale) - 1 : dimx - 1); \
122 ncplane_set_styles(ncp->plot.ncp, ncp->plot.legendstyle); \
123 if(ncp->plot.labelaxisd){ \
124 /* show the *top* of each interval range */ \
125 for(unsigned y = 0 ; y < dimy ; ++y){ \
126 ncplane_set_channels(ncp->plot.ncp, ncp->plot.channels[y * states]); \
127 char buf[NCPREFIXSTRLEN + 1]; \
128 if(ncp->plot.exponentiali){ \
129 if(y == dimy - 1){ /* we cheat on the top row to exactly match maxy */ \
130 ncqprefix(ncp->maxy * 100, 100, buf, 0); \
131 }else{ \
132 ncqprefix(pow(interval, (y + 1) * states) * 100, 100, buf, 0); \
133 } \
134 }else{ \
135 ncqprefix((ncp->maxy - interval * states * (dimy - y - 1)) * 100, 100, buf, 0); \
136 } \
137 if(y == dimy - 1 && strlen(ncp->plot.title)){ \
138 ncplane_printf_yx(ncp->plot.ncp, dimy - y - 1, 0, "%*.*s %s", \
139 NCPREFIXSTRLEN, NCPREFIXSTRLEN, buf, ncp->plot.title); \
140 }else{ \
141 ncplane_printf_yx(ncp->plot.ncp, dimy - y - 1, 0, "%*.*s", \
142 NCPREFIXSTRLEN, NCPREFIXSTRLEN, buf); \
143 } \
144 } \
145 }else if(strlen(ncp->plot.title)){ \
146 ncplane_set_channels(ncp->plot.ncp, ncp->plot.channels[(dimy - 1) * states]); \
147 ncplane_printf_yx(ncp->plot.ncp, 0, NCPREFIXCOLUMNS - strlen(ncp->plot.title), "%s", ncp->plot.title); \
148 } \
149 ncplane_set_styles(ncp->plot.ncp, NCSTYLE_NONE); \
150 if((int)finalx < startx){ /* exit on pathologically narrow planes */ \
151 return 0; \
152 } \
153 if(!interval){ \
154 interval = 1; \
155 } \
156 uint32_t* pixels = malloc(dimy * dimx * states * scale * sizeof(*pixels)); \
157 if(pixels == NULL){ \
158 return -1; \
159 } \
160 /* FIXME just zero out as we copy to each */ \
161 memset(pixels, 0, dimy * dimx * states * scale * sizeof(*pixels)); \
162 /* a column corresponds to |scale| slots' worth of samples. prepare the working gval set. */ \
163 T* gvals = malloc(sizeof(*gvals) * scale); \
164 if(gvals == NULL){ \
165 free(pixels); \
166 return -1; \
167 } \
168 int idx = ncp->plot.slotstart; /* idx holds the real slot index; we move backwards */ \
169 /* iterate backwards across the plot from the final (rightmost) x being \
170 plotted (finalx) to the first (leftmost) x being plotted (startx). */ \
171 for(int x = finalx ; x >= startx ; --x){ \
172 /* load gvals retaining the same ordering we have in the actual array */ \
173 for(int i = scale - 1 ; i >= 0 ; --i){ \
174 gvals[i] = ncp->slots[idx]; /* clip the value at the limits of the graph */ \
175 if(gvals[i] < ncp->miny){ \
176 gvals[i] = ncp->miny; \
177 } \
178 if(gvals[i] > ncp->maxy){ \
179 gvals[i] = ncp->maxy; \
180 } \
181 /* FIXME if there are an odd number, only go up through the valid ones... */ \
182 if(--idx < 0){ \
183 idx = ncp->plot.slotcount - 1; \
184 } \
185 } \
186 /* starting from the least-significant row, progress in the more significant \
187 direction, prepping pixels, aborting early if we can't draw anything in a \
188 given cell. */ \
189 T intervalbase = ncp->miny; \
190 bool done = !ncp->plot.bset->fill; \
191 for(unsigned y = 0 ; y < dimy ; ++y){ \
192 /* if we've got at least one interval's worth on the number of positions \
193 times the number of intervals per position plus the starting offset, \
194 we're going to print *something* */ \
195 for(int i = 0 ; i < scale ; ++i){ \
196 size_t egcidx; \
197 if(intervalbase < gvals[i]){ \
198 if(ncp->plot.exponentiali){ \
199 /* we want the log-base-interval of gvals[i] */ \
200 double scaled = log(gvals[i] - ncp->miny) / log(interval); \
201 double sival = intervalbase ? log(intervalbase) / log(interval) : 0; \
202 egcidx = scaled - sival; \
203 }else{ \
204 egcidx = (gvals[i] - intervalbase) / interval; \
205 } \
206 if(egcidx >= states){ \
207 egcidx = states; \
208 done = false; \
209 } \
210 }else{ \
211 egcidx = 0; \
212 } \
213/*fprintf(stderr, "WRITING TO y/x %d/%d (%zu)\n", y, x, dimx * dimy * scale * states); */\
214 for(size_t yy = 0 ; yy < egcidx ; ++yy){ \
215 int poff = x * scale + i + (((dimy - 1 - y) * states + (states - 1 - yy)) * dimx * scale); \
216 uint32_t color = ncchannels_fg_rgb(ncp->plot.channels[y * states + yy]); \
217 ncpixel_set_a(&color, 0xff); \
218 pixels[poff] = color; \
219 } \
220 } \
221 if(done){ \
222 break; \
223 } \
224 if(ncp->plot.exponentiali){ \
225 intervalbase = ncp->miny + pow(interval, (y + 1) * states - 1); \
226 }else{ \
227 intervalbase += (states * interval); \
228 } \
229 } \
230 } \
231 if(ncp->plot.printsample){ \
232 ncplane_set_styles(ncp->plot.ncp, ncp->plot.legendstyle); \
233 ncplane_set_channels(ncp->plot.ncp, ncp->plot.maxchannels); \
234 /* FIXME is this correct for double? */ \
235 /* we use idx, and thus get an immediate count, changing as we load it.
236 * if you want a stable summary, print the previous slot */ \
237 ncplane_printf_aligned(ncp->plot.ncp, 0, NCALIGN_RIGHT, "%" PRIu64, (uint64_t)ncp->slots[idx]); \
238 } \
239 ncplane_home(ncp->plot.ncp); \
240 struct ncvisual* ncv = ncvisual_from_rgba(pixels, dimy * states, dimx * scale * 4, dimx * scale); \
241 free(pixels); \
242 free(gvals); \
243 if(ncv == NULL){ \
244 return -1; \
245 } \
246 struct ncvisual_options vopts = { \
247 .n = ncp->plot.pixelp, \
248 .blitter = NCBLIT_PIXEL, \
249 .flags = NCVISUAL_OPTION_NODEGRADE, \
250 }; \
251 if(ncvisual_blit(ncplane_notcurses(ncp->plot.ncp), ncv, &vopts) == NULL){ \
252 ncvisual_destroy(ncv); \
253 return -1; \
254 } \
255 ncvisual_destroy(ncv); \
256 return 0; \
257} \
258\
259static int redraw_plot_##T(nc##X##plot* ncp){ \
260 if(ncp->plot.bset->geom == NCBLIT_PIXEL){ \
261 return redraw_pixelplot_##T(ncp); \
262 } \
263 if(calculate_gradient_vector(&ncp->plot, 0)){ \
264 return -1; \
265 } \
266 ncplane_erase(ncp->plot.ncp); \
267 const unsigned scale = ncp->plot.bset->width; \
268 unsigned dimy, dimx; \
269 ncplane_dim_yx(ncp->plot.ncp, &dimy, &dimx); \
270 const unsigned scaleddim = dimx * scale; \
271 /* each transition is worth this much change in value */ \
272 const size_t states = ncp->plot.bset->height + 1; \
273 /* FIXME can we not rid ourselves of this meddlesome double? either way, the \
274 interval is one row's range (for linear plots), or the base \
275 (base^slots == maxy-miny) of the range (for exponential plots). */ \
276 double interval; \
277 if(ncp->plot.exponentiali){ \
278 if(ncp->maxy > ncp->miny){ \
279 interval = pow(ncp->maxy - ncp->miny, (double)1 / (dimy * states)); \
280/* fprintf(stderr, "miny: %ju maxy: %ju dimy: %d states: %zu\n", miny, maxy, dimy, states); */ \
281 }else{ \
282 interval = 0; \
283 } \
284 }else{ \
285 interval = ncp->maxy < ncp->miny ? 0 : (ncp->maxy - ncp->miny) / ((double)dimy * states); \
286 } \
287 const int startx = ncp->plot.labelaxisd ? NCPREFIXCOLUMNS : 0; /* plot cols begin here */ \
288 /* if we want fewer slots than there are available columns, our final column \
289 will be other than the plane's final column. most recent x goes here. */ \
290 const unsigned finalx = (ncp->plot.slotcount < scaleddim - 1 - (startx * scale) ? \
291 startx + (ncp->plot.slotcount / scale) - 1 : dimx - 1); \
292 ncplane_set_styles(ncp->plot.ncp, ncp->plot.legendstyle); \
293 if(ncp->plot.labelaxisd){ \
294 /* show the *top* of each interval range */ \
295 for(unsigned y = 0 ; y < dimy ; ++y){ \
296 ncplane_set_channels(ncp->plot.ncp, ncp->plot.channels[y]); \
297 char buf[NCPREFIXSTRLEN + 1]; \
298 if(ncp->plot.exponentiali){ \
299 if(y == dimy - 1){ /* we cheat on the top row to exactly match maxy */ \
300 ncqprefix(ncp->maxy * 100, 100, buf, 0); \
301 }else{ \
302 ncqprefix(pow(interval, (y + 1) * states) * 100, 100, buf, 0); \
303 } \
304 }else{ \
305 ncqprefix((ncp->maxy - interval * states * (dimy - y - 1)) * 100, 100, buf, 0); \
306 } \
307 if(y == dimy - 1 && strlen(ncp->plot.title)){ \
308 ncplane_printf_yx(ncp->plot.ncp, dimy - y - 1, NCPREFIXCOLUMNS - strlen(buf), "%s %s", buf, ncp->plot.title); \
309 }else{ \
310 ncplane_printf_yx(ncp->plot.ncp, dimy - y - 1, NCPREFIXCOLUMNS - strlen(buf), "%s", buf); \
311 } \
312 } \
313 }else if(strlen(ncp->plot.title)){ \
314 ncplane_set_channels(ncp->plot.ncp, ncp->plot.channels[dimy - 1]); \
315 ncplane_printf_yx(ncp->plot.ncp, 0, NCPREFIXCOLUMNS - strlen(ncp->plot.title), "%s", ncp->plot.title); \
316 } \
317 ncplane_set_styles(ncp->plot.ncp, NCSTYLE_NONE); \
318 if((int)finalx < startx){ /* exit on pathologically narrow planes */ \
319 return 0; \
320 } \
321 if(!interval){ \
322 interval = 1; \
323 } \
324 int idx = ncp->plot.slotstart; /* idx holds the real slot index; we move backwards */ \
325 for(int x = finalx ; x >= startx ; --x){ \
326 /* a single column might correspond to more than 1 ('scale', up to \
327 MAXWIDTH) slots' worth of samples. prepare the working gval set. */ \
328 T gvals[MAXWIDTH]; \
329 /* load it retaining the same ordering we have in the actual array */ \
330 for(int i = scale - 1 ; i >= 0 ; --i){ \
331 gvals[i] = ncp->slots[idx]; /* clip the value at the limits of the graph */ \
332 if(gvals[i] < ncp->miny){ \
333 gvals[i] = ncp->miny; \
334 } \
335 if(gvals[i] > ncp->maxy){ \
336 gvals[i] = ncp->maxy; \
337 } \
338 /* FIXME if there are an odd number, only go up through the valid ones... */ \
339 if(--idx < 0){ \
340 idx = ncp->plot.slotcount - 1; \
341 } \
342 } \
343 /* starting from the least-significant row, progress in the more significant \
344 direction, drawing egcs from the grid specification, aborting early if \
345 we can't draw anything in a given cell. */ \
346 T intervalbase = ncp->miny; \
347 const wchar_t* egc = ncp->plot.bset->plotegcs; \
348 bool done = !ncp->plot.bset->fill; \
349 for(unsigned y = 0 ; y < dimy ; ++y){ \
350 ncplane_set_channels(ncp->plot.ncp, ncp->plot.channels[y]); \
351 size_t egcidx = 0, sumidx = 0; \
352 /* if we've got at least one interval's worth on the number of positions \
353 times the number of intervals per position plus the starting offset, \
354 we're going to print *something* */ \
355 for(unsigned i = 0 ; i < scale ; ++i){ \
356 sumidx *= states; \
357 if(intervalbase < gvals[i]){ \
358 if(ncp->plot.exponentiali){ \
359 /* we want the log-base-interval of gvals[i] */ \
360 double scaled = log(gvals[i] - ncp->miny) / log(interval); \
361 double sival = intervalbase ? log(intervalbase) / log(interval) : 0; \
362 egcidx = scaled - sival; \
363 }else{ \
364 egcidx = (gvals[i] - intervalbase) / interval; \
365 } \
366 if(egcidx >= states){ \
367 egcidx = states - 1; \
368 done = false; \
369 } \
370 sumidx += egcidx; \
371 }else{ \
372 egcidx = 0; \
373 } \
374/* printf(stderr, "y: %d i(scale): %d gvals[%d]: %ju egcidx: %zu sumidx: %zu interval: %f intervalbase: %ju\n", y, i, i, gvals[i], egcidx, sumidx, interval, intervalbase); */ \
375 } \
376 /* if we're not UTF8, we can only arrive here via NCBLIT_1x1 (otherwise \
377 we would have errored out during construction). even then, however, \
378 we need handle ASCII differently, since it can't print full block. \
379 in ASCII mode, sumidx != 0 means swap colors and use space. in all \
380 modes, sumidx == 0 means don't do shit, since we erased earlier. */ \
381/* if(sumidx)fprintf(stderr, "dimy: %d y: %d x: %d sumidx: %zu egc[%zu]: %lc\n", dimy, y, x, sumidx, sumidx, egc[sumidx]); */ \
382 if(sumidx){ \
383 uint64_t chan = ncp->plot.channels[y]; \
384 if(notcurses_canutf8(ncplane_notcurses(ncp->plot.ncp))){ \
385 char utf8[MB_LEN_MAX + 1]; \
386 int bytes = wctomb(utf8, egc[sumidx]); \
387 if(bytes < 0){ \
388 return -1; \
389 } \
390 utf8[bytes] = '\0'; \
391 nccell* c = ncplane_cell_ref_yx(ncp->plot.ncp, dimy - y - 1, x); \
392 cell_set_bchannel(c, ncchannels_bchannel(chan)); \
393 cell_set_fchannel(c, ncchannels_fchannel(chan)); \
394 nccell_set_styles(c, NCSTYLE_NONE); \
395 if(pool_blit_direct(&ncp->plot.ncp->pool, c, utf8, bytes, 1) <= 0){ \
396 return -1; \
397 } \
398 }else{ \
399 const uint64_t swapbg = ncchannels_bchannel(chan); \
400 const uint64_t swapfg = ncchannels_fchannel(chan); \
401 ncchannels_set_bchannel(&chan, swapfg); \
402 ncchannels_set_fchannel(&chan, swapbg); \
403 ncplane_set_channels(ncp->plot.ncp, chan); \
404 if(ncplane_putchar_yx(ncp->plot.ncp, dimy - y - 1, x, ' ') <= 0){ \
405 return -1; \
406 } \
407 ncchannels_set_bchannel(&chan, swapbg); \
408 ncchannels_set_fchannel(&chan, swapfg); \
409 ncplane_set_channels(ncp->plot.ncp, chan); \
410 } \
411 } \
412 if(done){ \
413 break; \
414 } \
415 if(ncp->plot.exponentiali){ \
416 intervalbase = ncp->miny + pow(interval, (y + 1) * states - 1); \
417 }else{ \
418 intervalbase += (states * interval); \
419 } \
420 } \
421 } \
422 if(ncp->plot.printsample){ \
423 ncplane_set_styles(ncp->plot.ncp, ncp->plot.legendstyle); \
424 ncplane_set_channels(ncp->plot.ncp, ncp->plot.maxchannels); \
425 ncplane_printf_aligned(ncp->plot.ncp, 0, NCALIGN_RIGHT, "%" PRIu64, (uint64_t)ncp->slots[idx]); \
426 } \
427 ncplane_home(ncp->plot.ncp); \
428 return 0; \
429} \
430\
431static const struct blitset* \
432create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, \
433 const T miny, const T maxy, const T trueminy, const T truemaxy){ \
434 /* set up ->plot.ncp first so it gets destroyed on error */ \
435 ncpp->plot.ncp = n; \
436 if(ncplane_set_widget(ncpp->plot.ncp, ncpp, (void(*)(void*))nc##X##plot_destroy)){ \
437 return NULL; \
438 } \
439 ncplot_options zeroed = {0}; \
440 if(!opts){ \
441 opts = &zeroed; \
442 } \
443 if(opts->flags >= (NCPLOT_OPTION_PRINTSAMPLE << 1u)){ \
444 logwarn("provided unsupported flags %016" PRIx64, opts->flags); \
445 } \
446 /* if miny == maxy (enabling domain detection), they both must be equal to 0 */ \
447 if(miny == maxy && miny){ \
448 return NULL; \
449 } \
450 if(opts->rangex < 0){ \
451 logerror("error: supplied negative independent range %d", opts->rangex); \
452 return NULL; \
453 } \
454 if(maxy < miny){ \
455 logerror("error: supplied maxy < miny"); \
456 return NULL; \
457 } \
458 /* DETECTMAXONLY can't be used without domain detection */ \
459 if(opts->flags & NCPLOT_OPTION_DETECTMAXONLY && (miny != maxy)){ \
460 logerror("supplied DETECTMAXONLY without domain detection"); \
461 return NULL; \
462 } \
463 const notcurses* notc = ncplane_notcurses(n); \
464 ncblitter_e blitfxn = opts ? opts->gridtype : NCBLIT_DEFAULT; \
465 if(blitfxn == NCBLIT_DEFAULT){ \
466 blitfxn = ncplot_defblitter(notc); \
467 } \
468 bool degrade_blitter = !(opts && (opts->flags & NCPLOT_OPTION_NODEGRADE)); \
469 const struct blitset* bset = lookup_blitset(&notc->tcache, blitfxn, degrade_blitter); \
470 if(bset == NULL){ \
471 return NULL; \
472 } \
473 unsigned sdimy, sdimx; \
474 ncplane_dim_yx(n, &sdimy, &sdimx); \
475 if(sdimx <= 0){ \
476 return NULL; \
477 } \
478 unsigned dimx = sdimx; \
479 ncpp->plot.title = strdup(opts->title ? opts->title : ""); \
480 ncpp->plot.rangex = opts->rangex; \
481 /* if we're sizing the plot based off the plane dimensions, scale it by the \
482 plot geometry's width for all calculations */ \
483 const unsigned scaleddim = dimx * (bset->geom == NCBLIT_PIXEL ? ncplane_pile_const(n)->cellpxx : bset->width); \
484 const unsigned scaledprefixlen = NCPREFIXCOLUMNS * (bset->geom == NCBLIT_PIXEL ? ncplane_pile_const(n)->cellpxx : bset->width); \
485 if((ncpp->plot.slotcount = ncpp->plot.rangex) == 0){ \
486 ncpp->plot.slotcount = scaleddim; \
487 } \
488 if(dimx < ncpp->plot.rangex){ \
489 ncpp->plot.slotcount = scaleddim; \
490 } \
491 ncpp->plot.legendstyle = opts->legendstyle; \
492 if( (ncpp->plot.labelaxisd = opts->flags & NCPLOT_OPTION_LABELTICKSD) ){ \
493 if(ncpp->plot.slotcount + scaledprefixlen > scaleddim){ \
494 if(scaleddim > scaledprefixlen){ \
495 ncpp->plot.slotcount = scaleddim - scaledprefixlen; \
496 } \
497 } \
498 } \
499 size_t slotsize = sizeof(*ncpp->slots) * ncpp->plot.slotcount; \
500 ncpp->slots = malloc(slotsize); \
501 if(ncpp->slots == NULL){ \
502 return NULL; \
503 } \
504 memset(ncpp->slots, 0, slotsize); \
505 ncpp->plot.maxchannels = opts->maxchannels; \
506 ncpp->plot.minchannels = opts->minchannels; \
507 ncpp->plot.bset = bset; \
508 ncpp->miny = miny; \
509 ncpp->maxy = maxy; \
510 ncpp->plot.vertical_indep = opts->flags & NCPLOT_OPTION_VERTICALI; \
511 ncpp->plot.exponentiali = opts->flags & NCPLOT_OPTION_EXPONENTIALD; \
512 ncpp->plot.detectonlymax = opts->flags & NCPLOT_OPTION_DETECTMAXONLY; \
513 ncpp->plot.printsample = opts->flags & NCPLOT_OPTION_PRINTSAMPLE; \
514 if( (ncpp->plot.detectdomain = (miny == maxy)) ){ \
515 ncpp->maxy = trueminy; \
516 if(!ncpp->plot.detectonlymax){ \
517 ncpp->miny = truemaxy; \
518 } \
519 } \
520 ncpp->plot.slotstart = 0; \
521 ncpp->plot.slotx = 0; \
522 ncpp->plot.chancount = 0; \
523 ncpp->plot.channels = NULL; \
524 if(bset->geom == NCBLIT_PIXEL){ \
525 if(create_pixelp(&ncpp->plot, n)){ \
526 return NULL; \
527 } \
528 } \
529 redraw_plot_##T(ncpp); \
530 return bset; \
531} \
532/* if x is less than the window, return -1, as the sample will be thrown away. \
533 if the x is within the current window, find the proper slot and update it. \
534 otherwise, the x is the newest sample. if it is obsoletes all existing slots, \
535 reset them, and write the new sample anywhere. otherwise, write it to the \
536 proper slot based on the current newest slot. */ \
537int window_slide_##T(nc##X##plot* ncp, int64_t x){ \
538 if(x <= ncp->plot.slotx){ /* x is within window, do nothing */ \
539 return 0; \
540 } /* x is newest; we might be keeping some, might not */ \
541 int64_t xdiff = x - ncp->plot.slotx; /* the raw amount we're advancing */ \
542 ncp->plot.slotx = x; \
543 if(xdiff >= ncp->plot.slotcount){ /* we're throwing away all old samples, write to 0 */ \
544 memset(ncp->slots, 0, sizeof(*ncp->slots) * ncp->plot.slotcount); \
545 ncp->plot.slotstart = 0; \
546 return 0; \
547 } \
548 /* we're throwing away only xdiff slots, which is less than slotcount. \
549 first, we'll try to clear to the right...number to reset on the right of \
550 the circular buffer. min of (available at current or to right, xdiff) */ \
551 int slotsreset = ncp->plot.slotcount - ncp->plot.slotstart - 1; \
552 if(slotsreset > xdiff){ \
553 slotsreset = xdiff; \
554 } \
555 if(slotsreset){ \
556 memset(ncp->slots + ncp->plot.slotstart + 1, 0, slotsreset * sizeof(*ncp->slots)); \
557 } \
558 ncp->plot.slotstart = (ncp->plot.slotstart + xdiff) % ncp->plot.slotcount; \
559 xdiff -= slotsreset; \
560 if(xdiff){ /* throw away some at the beginning */ \
561 memset(ncp->slots, 0, xdiff * sizeof(*ncp->slots)); \
562 } \
563 return 0; \
564} \
565\
566static int update_domain_##T(nc##X##plot* ncp, uint64_t x); \
567static void update_sample_##T(nc##X##plot* ncp, int64_t x, T y, bool reset); \
568\
569/* Add to or set the value corresponding to this x. If x is beyond the current \
570 x window, the x window is advanced to include x, and values passing beyond \
571 the window are lost. The first call will place the initial window. The plot \
572 will be redrawn, but notcurses_render() is not called. */ \
573int add_sample_##T(nc##X##plot* ncpp, int64_t x, T y){ \
574 if(x < ncpp->plot.slotx - (ncpp->plot.slotcount - 1)){ /* x is behind window, won't be counted */ \
575 return -1; \
576 } \
577 if(y == 0 && x <= ncpp->plot.slotx){ \
578 return 0; /* no need to redraw plot; nothing changed */ \
579 } \
580 if(window_slide_##T(ncpp, x)){ \
581 return -1; \
582 } \
583 update_sample_##T(ncpp, x, y, false); \
584 if(update_domain_##T(ncpp, x)){ \
585 return -1; \
586 } \
587 return redraw_plot_##T(ncpp); \
588} \
589int sample_##T(const nc##X##plot* ncp, int64_t x, T* y){ \
590 if(x < ncp->plot.slotx - (ncp->plot.slotcount - 1)){ /* x is behind window */ \
591 return -1; \
592 }else if(x > ncp->plot.slotx){ /* x is ahead of window */ \
593 return -1; \
594 } \
595 *y = ncp->slots[x % ncp->plot.slotcount]; \
596 return 0; \
597}
598
599CREATE(uint64_t, u)
600CREATE(double, d)
601
602static void
603ncplot_destroy(ncplot* n){
604 free(n->title);
605 if(ncplane_set_widget(n->ncp, NULL, NULL) == 0){
606 ncplane_destroy(n->ncp);
607 }
608 ncplane_destroy(n->pixelp);
609 free(n->channels);
610}
611
612/* if we're doing domain detection, update the domain to reflect the value we
613 just set. if we're not, check the result against the known ranges, and
614 return -1 if the value is outside of that range. */
615int update_domain_uint64_t(ncuplot* ncp, uint64_t x){
616 const uint64_t val = ncp->slots[x % ncp->plot.slotcount];
617 if(ncp->plot.detectdomain){
618 if(val > ncp->maxy){
619 ncp->maxy = val;
620 }
621 if(!ncp->plot.detectonlymax){
622 if(val < ncp->miny){
623 ncp->miny = val;
624 }
625 }
626 return 0;
627 }
628 if(val > ncp->maxy || val < ncp->miny){
629 return -1;
630 }
631 return 0;
632}
633
634int update_domain_double(ncdplot* ncp, uint64_t x){
635 const double val = ncp->slots[x % ncp->plot.slotcount];
636 if(ncp->plot.detectdomain){
637 if(val > ncp->maxy){
638 ncp->maxy = val;
639 }
640 if(!ncp->plot.detectonlymax){
641 if(val < ncp->miny){
642 ncp->miny = val;
643 }
644 }
645 return 0;
646 }
647 if(val > ncp->maxy || val < ncp->miny){
648 return -1;
649 }
650 return 0;
651}
652
653/* x must be within n's window at this point */
654static void
655update_sample_uint64_t(ncuplot* ncp, int64_t x, uint64_t y, bool reset){
656 const int64_t diff = ncp->plot.slotx - x; /* amount behind */
657 const int idx = (ncp->plot.slotstart + ncp->plot.slotcount - diff) % ncp->plot.slotcount;
658 if(reset){
659 ncp->slots[idx] = y;
660 }else{
661 ncp->slots[idx] += y;
663}
664
665/* x must be within n's window at this point */
666static void
667update_sample_double(ncdplot* ncp, int64_t x, double y, bool reset){
668 const int64_t diff = ncp->plot.slotx - x; /* amount behind */
669 const int idx = (ncp->plot.slotstart + ncp->plot.slotcount - diff) % ncp->plot.slotcount;
670 if(reset){
671 ncp->slots[idx] = y;
672 }else{
673 ncp->slots[idx] += y;
674 }
675}
676
677// takes ownership of n on all paths
678ncuplot* ncuplot_create(ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy){
679 ncuplot* ret = malloc(sizeof(*ret));
680 if(ret == NULL){
682 return NULL;
683 }
684 memset(ret, 0, sizeof(*ret));
685 const struct blitset* bset = create_uint64_t(ret, n, opts, miny, maxy, 0, UINT64_MAX);
686 if(bset == NULL){ // create_uint64_t() destroys n on error
687 ncuplot_destroy(ret);
688 return NULL;
689 }
690 return ret;
691}
692
693ncplane* ncuplot_plane(ncuplot* n){
694 return n->plot.ncp;
695}
696
697int ncuplot_add_sample(ncuplot* n, uint64_t x, uint64_t y){
698 return add_sample_uint64_t(n, x, y);
699}
700
701int ncuplot_set_sample(ncuplot* n, uint64_t x, uint64_t y){
702 if(window_slide_uint64_t(n, x)){
703 return -1;
704 }
705 update_sample_uint64_t(n, x, y, true);
707 return -1;
708 }
709 return redraw_plot_uint64_t(n);
710}
711
712void ncuplot_destroy(ncuplot* n){
713 if(n){
714 ncplot_destroy(&n->plot);
715 free(n->slots);
716 free(n);
717 }
718}
719
720// takes ownership of n on all paths
721ncdplot* ncdplot_create(ncplane* n, const ncplot_options* opts, double miny, double maxy){
722 ncdplot* ret = malloc(sizeof(*ret));
723 if(ret == NULL){
725 return NULL;
726 }
727 memset(ret, 0, sizeof(*ret));
728 const struct blitset* bset = create_double(ret, n, opts, miny, maxy, -DBL_MAX, DBL_MAX);
729 if(bset == NULL){ // create_double() destroys n on error
730 ncdplot_destroy(ret);
731 return NULL;
733 return ret;
734}
735
736ncplane* ncdplot_plane(ncdplot* n){
737 return n->plot.ncp;
738}
739
740int ncdplot_add_sample(ncdplot* n, uint64_t x, double y){
741 return add_sample_double(n, x, y);
742}
743
744int ncdplot_set_sample(ncdplot* n, uint64_t x, double y){
745 if(window_slide_double(n, x)){
746 return -1;
747 }
748 update_sample_double(n, x, y, true);
750 return -1;
751 }
752 return redraw_plot_double(n);
753}
754
755int ncuplot_sample(const ncuplot* n, uint64_t x, uint64_t* y){
756 return sample_uint64_t(n, x, y);
757}
758
759int ncdplot_sample(const ncdplot* n, uint64_t x, double* y){
760 return sample_double(n, x, y);
761}
762
763void ncdplot_destroy(ncdplot* n) {
764 if(n){
765 ncplot_destroy(&n->plot);
766 free(n->slots);
767 free(n);
768 }
769}
uint32_t idx
Definition egcpool.h:209
ncplane * ncplane_dup(const ncplane *n, void *opaque)
Definition notcurses.c:763
int ncplane_destroy(ncplane *ncp)
Definition notcurses.c:1021
int ncplane_set_base(ncplane *ncp, const char *egc, uint16_t stylemask, uint64_t channels)
Definition notcurses.c:1568
ncplane * ncplane_reparent(ncplane *n, ncplane *newparent)
Definition notcurses.c:2796
int ncplane_set_name(ncplane *n, const char *name)
Definition notcurses.c:2664
int ncplane_move_below(ncplane *restrict n, ncplane *restrict below)
Definition notcurses.c:1631
int y
Definition notcurses.h:1905
const struct ncplane_options * opts
Definition notcurses.h:3487
#define NCALPHA_TRANSPARENT
Definition notcurses.h:106
vopts n
Definition notcurses.h:3506
int int x
Definition notcurses.h:1905
int ncdplot_add_sample(ncdplot *n, uint64_t x, double y)
Definition plot.c:709
int update_domain_uint64_t(ncuplot *ncp, uint64_t x)
Definition plot.c:584
ncplane * ncuplot_plane(ncuplot *n)
Definition plot.c:662
void ncuplot_destroy(ncuplot *n)
Definition plot.c:681
ncplane * ncdplot_plane(ncdplot *n)
Definition plot.c:705
ncuplot * ncuplot_create(ncplane *n, const ncplot_options *opts, uint64_t miny, uint64_t maxy)
Definition plot.c:647
int ncdplot_set_sample(ncdplot *n, uint64_t x, double y)
Definition plot.c:713
#define CREATE(T, X)
Definition plot.c:85
void ncdplot_destroy(ncdplot *n)
Definition plot.c:732
int ncuplot_set_sample(ncuplot *n, uint64_t x, uint64_t y)
Definition plot.c:670
int ncuplot_add_sample(ncuplot *n, uint64_t x, uint64_t y)
Definition plot.c:666
ncdplot * ncdplot_create(ncplane *n, const ncplot_options *opts, double miny, double maxy)
Definition plot.c:690
int ncdplot_sample(const ncdplot *n, uint64_t x, double *y)
Definition plot.c:728
int ncuplot_sample(const ncuplot *n, uint64_t x, uint64_t *y)
Definition plot.c:724
int update_domain_double(ncdplot *ncp, uint64_t x)
Definition plot.c:603
Definition plot.c:9
bool vertical_indep
Definition plot.c:19
uint64_t * channels
Definition plot.c:21
bool labelaxisd
Definition plot.c:34
ncplane * ncp
Definition plot.c:10
int slotstart
Definition plot.c:33
unsigned rangex
Definition plot.c:28
bool detectdomain
Definition plot.c:36
int64_t slotx
Definition plot.c:15
uint16_t legendstyle
Definition plot.c:18
bool detectonlymax
Definition plot.c:37
uint64_t maxchannels
Definition plot.c:16
ncplane * pixelp
Definition plot.c:11
const struct blitset * bset
Definition plot.c:22
unsigned chancount
Definition plot.c:20
uint64_t minchannels
Definition plot.c:17
bool printsample
Definition plot.c:38
bool exponentiali
Definition plot.c:35
char * title
Definition plot.c:23
unsigned slotcount
Definition plot.c:32
return NULL
Definition termdesc.h:229