plots: extract common struct #1976

This commit is contained in:
nick black 2021-08-01 23:05:04 -04:00 committed by nick black
parent 16c2fff06b
commit ba830418c7

View File

@ -5,52 +5,57 @@
#include <inttypes.h>
#include "internal.h"
// common elements of type-parameterized plots
typedef struct ncplot {
ncplane* ncp;
/* sloutcount-element circular buffer of samples. the newest one (rightmost)
is at slots[slotstart]; they get older as you go back (and around).
elements. slotcount is max(columns, rangex), less label room. */
int64_t slotx; /* x value corresponding to slots[slotstart] (newest x) */
uint64_t maxchannels;
uint64_t minchannels;
uint16_t legendstyle;
bool vertical_indep; /* not yet implemented FIXME */
const struct blitset* bset;
char* title;
/* requested number of slots. 0 for automatically setting the number of slots
to span the horizontal area. if there are more slots than there are
columns, we prefer showing more recent slots to less recent. if there are
fewer slots than there are columns, they prefer the left side. */
int rangex;
/* domain minimum and maximum. if detectdomain is true, these are
progressively enlarged/shrunk to fit the sample set. if not, samples
outside these bounds are counted, but the displayed range covers only this. */
int slotcount;
int slotstart; /* index of most recently-written slot */
bool labelaxisd; /* label dependent axis (consumes PREFIXCOLUMNS columns) */
bool exponentiali; /* exponential independent axis */
bool detectdomain; /* is domain detection in effect (stretch the domain)? */
bool detectonlymax; /* domain detection applies only to max, not min */
bool printsample; /* print the most recent sample */
} ncplot;
#define MAXWIDTH 2
#define CREATE(T, X) \
typedef struct nc##X##plot { \
T* slots; \
T miny, maxy; \
ncplane* ncp; \
/* sloutcount-element circular buffer of samples. the newest one (rightmost) \
is at slots[slotstart]; they get older as you go back (and around). \
elements. slotcount is max(columns, rangex), less label room. */ \
int64_t slotx; /* x value corresponding to slots[slotstart] (newest x) */ \
uint64_t maxchannels; \
uint64_t minchannels; \
uint16_t legendstyle; \
bool vertical_indep; /* not yet implemented FIXME */ \
const struct blitset* bset; \
char* title; \
/* requested number of slots. 0 for automatically setting the number of slots \
to span the horizontal area. if there are more slots than there are \
columns, we prefer showing more recent slots to less recent. if there are \
fewer slots than there are columns, they prefer the left side. */ \
int rangex; \
/* domain minimum and maximum. if detectdomain is true, these are \
progressively enlarged/shrunk to fit the sample set. if not, samples \
outside these bounds are counted, but the displayed range covers only this. */ \
int slotcount; \
int slotstart; /* index of most recently-written slot */ \
bool labelaxisd; /* label dependent axis (consumes PREFIXCOLUMNS columns) */ \
bool exponentiali; /* exponential independent axis */ \
bool detectdomain; /* is domain detection in effect (stretch the domain)? */ \
bool detectonlymax; /* domain detection applies only to max, not min */ \
bool printsample; /* print the most recent sample */ \
ncplot plot; \
} nc##X##plot; \
\
int redraw_plot_##T(nc##X##plot* ncp){ \
ncplane_erase(ncp->ncp); \
const int scale = ncp->bset->width; \
ncplane_erase(ncp->plot.ncp); \
const int scale = ncp->plot.bset->width; \
int dimy, dimx; \
ncplane_dim_yx(ncp->ncp, &dimy, &dimx); \
ncplane_dim_yx(ncp->plot.ncp, &dimy, &dimx); \
const int scaleddim = dimx * scale; \
/* each transition is worth this much change in value */ \
const size_t states = ncp->bset->height + 1; \
const size_t states = ncp->plot.bset->height + 1; \
/* FIXME can we not rid ourselves of this meddlesome double? either way, the \
interval is one row's range (for linear plots), or the base (base^slots== \
maxy-miny) of the range (for exponential plots). */ \
double interval; \
if(ncp->exponentiali){ \
if(ncp->plot.exponentiali){ \
if(ncp->maxy > ncp->miny){ \
interval = pow(ncp->maxy - ncp->miny, (double)1 / (dimy * states)); \
/* fprintf(stderr, "miny: %ju maxy: %ju dimy: %d states: %zu\n", miny, maxy, dimy, states); */ \
@ -60,20 +65,21 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
}else{ \
interval = ncp->maxy < ncp->miny ? 0 : (ncp->maxy - ncp->miny) / ((double)dimy * states); \
} \
const int startx = ncp->labelaxisd ? PREFIXCOLUMNS : 0; /* plot cols begin here */ \
const int startx = ncp->plot.labelaxisd ? PREFIXCOLUMNS : 0; /* plot cols begin here */ \
/* if we want fewer slots than there are available columns, our final column \
will be other than the plane's final column. most recent x goes here. */ \
const int finalx = (ncp->slotcount < scaleddim - 1 - (startx * scale) ? startx + (ncp->slotcount / scale) - 1 : dimx - 1); \
ncplane_set_styles(ncp->ncp, ncp->legendstyle); \
if(ncp->labelaxisd){ \
const int finalx = (ncp->plot.slotcount < scaleddim - 1 - (startx * scale) ? \
startx + (ncp->plot.slotcount / scale) - 1 : dimx - 1); \
ncplane_set_styles(ncp->plot.ncp, ncp->plot.legendstyle); \
if(ncp->plot.labelaxisd){ \
/* show the *top* of each interval range */ \
for(int y = 0 ; y < dimy ; ++y){ \
uint64_t channels = 0; \
calc_gradient_channels(&channels, ncp->minchannels, ncp->minchannels, \
ncp->maxchannels, ncp->maxchannels, y, 0, dimy, dimx); \
ncplane_set_channels(ncp->ncp, channels); \
calc_gradient_channels(&channels, ncp->plot.minchannels, ncp->plot.minchannels, \
ncp->plot.maxchannels, ncp->plot.maxchannels, y, 0, dimy, dimx); \
ncplane_set_channels(ncp->plot.ncp, channels); \
char buf[PREFIXSTRLEN + 1]; \
if(ncp->exponentiali){ \
if(ncp->plot.exponentiali){ \
if(y == dimy - 1){ /* we cheat on the top row to exactly match maxy */ \
ncmetric(ncp->maxy * 100, 100, buf, 0, 1000, '\0'); \
}else{ \
@ -82,27 +88,27 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
}else{ \
ncmetric((ncp->maxy - interval * states * (dimy - y - 1)) * 100, 100, buf, 0, 1000, '\0'); \
} \
if(y == dimy - 1 && strlen(ncp->title)){ \
ncplane_printf_yx(ncp->ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s %s", buf, ncp->title); \
if(y == dimy - 1 && strlen(ncp->plot.title)){ \
ncplane_printf_yx(ncp->plot.ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s %s", buf, ncp->plot.title); \
}else{ \
ncplane_printf_yx(ncp->ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s", buf); \
ncplane_printf_yx(ncp->plot.ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s", buf); \
} \
} \
}else if(strlen(ncp->title)){ \
}else if(strlen(ncp->plot.title)){ \
uint64_t channels = 0; \
calc_gradient_channels(&channels, ncp->minchannels, ncp->minchannels, \
ncp->maxchannels, ncp->maxchannels, dimy - 1, 0, dimy, dimx); \
ncplane_set_channels(ncp->ncp, channels); \
ncplane_printf_yx(ncp->ncp, 0, PREFIXCOLUMNS - strlen(ncp->title), "%s", ncp->title); \
calc_gradient_channels(&channels, ncp->plot.minchannels, ncp->plot.minchannels, \
ncp->plot.maxchannels, ncp->plot.maxchannels, dimy - 1, 0, dimy, dimx); \
ncplane_set_channels(ncp->plot.ncp, channels); \
ncplane_printf_yx(ncp->plot.ncp, 0, PREFIXCOLUMNS - strlen(ncp->plot.title), "%s", ncp->plot.title); \
} \
ncplane_set_styles(ncp->ncp, NCSTYLE_NONE); \
ncplane_set_styles(ncp->plot.ncp, NCSTYLE_NONE); \
if(finalx < startx){ /* exit on pathologically narrow planes */ \
return 0; \
} \
if(!interval){ \
interval = 1; \
} \
int idx = ncp->slotstart; /* idx holds the real slot index; we move backwards */ \
int idx = ncp->plot.slotstart; /* idx holds the real slot index; we move backwards */ \
for(int x = finalx ; x >= startx ; --x){ \
/* a single column might correspond to more than 1 ('scale', up to \
MAXWIDTH) slot's worth of samples. prepare the working gval set. */ \
@ -118,20 +124,20 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
} \
/* FIXME if there are an odd number, only go up through the valid ones... */ \
if(--idx < 0){ \
idx = ncp->slotcount - 1; \
idx = ncp->plot.slotcount - 1; \
} \
} \
/* starting from the least-significant row, progress in the more significant \
direction, drawing egcs from the grid specification, aborting early if \
we can't draw anything in a given cell. */ \
T intervalbase = ncp->miny; \
const wchar_t* egc = ncp->bset->plotegcs; \
bool done = !ncp->bset->fill; \
const wchar_t* egc = ncp->plot.bset->plotegcs; \
bool done = !ncp->plot.bset->fill; \
for(int y = 0 ; y < dimy ; ++y){ \
uint64_t channels = 0; \
calc_gradient_channels(&channels, ncp->minchannels, ncp->minchannels, \
ncp->maxchannels, ncp->maxchannels, y, x, dimy, dimx); \
ncplane_set_channels(ncp->ncp, channels); \
calc_gradient_channels(&channels, ncp->plot.minchannels, ncp->plot.minchannels, \
ncp->plot.maxchannels, ncp->plot.maxchannels, y, x, dimy, dimx); \
ncplane_set_channels(ncp->plot.ncp, channels); \
size_t egcidx = 0, sumidx = 0; \
/* if we've got at least one interval's worth on the number of positions \
times the number of intervals per position plus the starting offset, \
@ -139,7 +145,7 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
for(int i = 0 ; i < scale ; ++i){ \
sumidx *= states; \
if(intervalbase < gvals[i]){ \
if(ncp->exponentiali){ \
if(ncp->plot.exponentiali){ \
/* we want the log-base-interval of gvals[i] */ \
double scaled = log(gvals[i] - ncp->miny) / log(interval); \
double sival = intervalbase ? log(intervalbase) / log(interval) : 0; \
@ -164,18 +170,18 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
modes, sumidx == 0 means don't do shit, since we erased earlier. */ \
/* if(sumidx)fprintf(stderr, "dimy: %d y: %d x: %d sumidx: %zu egc[%zu]: %lc\n", dimy, y, x, sumidx, sumidx, egc[sumidx]); */ \
if(sumidx){ \
if(notcurses_canutf8(ncplane_notcurses(ncp->ncp))){ \
if(notcurses_canutf8(ncplane_notcurses(ncp->plot.ncp))){ \
char utf8[MB_CUR_MAX + 1]; \
int bytes = wctomb(utf8, egc[sumidx]); \
if(bytes < 0){ \
return -1; \
} \
utf8[bytes] = '\0'; \
nccell* c = ncplane_cell_ref_yx(ncp->ncp, dimy - y - 1, x); \
nccell* c = ncplane_cell_ref_yx(ncp->plot.ncp, dimy - y - 1, x); \
cell_set_bchannel(c, ncchannels_bchannel(channels)); \
cell_set_fchannel(c, ncchannels_fchannel(channels)); \
nccell_set_styles(c, NCSTYLE_NONE); \
if(pool_blit_direct(&ncp->ncp->pool, c, utf8, bytes, 1) <= 0){ \
if(pool_blit_direct(&ncp->plot.ncp->pool, c, utf8, bytes, 1) <= 0){ \
return -1; \
} \
}else{ \
@ -183,32 +189,32 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
const uint64_t swapfg = ncchannels_fchannel(channels); \
ncchannels_set_bchannel(&channels, swapfg); \
ncchannels_set_fchannel(&channels, swapbg); \
ncplane_set_channels(ncp->ncp, channels); \
if(ncplane_putchar_yx(ncp->ncp, dimy - y - 1, x, ' ') <= 0){ \
ncplane_set_channels(ncp->plot.ncp, channels); \
if(ncplane_putchar_yx(ncp->plot.ncp, dimy - y - 1, x, ' ') <= 0){ \
return -1; \
} \
ncchannels_set_bchannel(&channels, swapbg); \
ncchannels_set_fchannel(&channels, swapfg); \
ncplane_set_channels(ncp->ncp, channels); \
ncplane_set_channels(ncp->plot.ncp, channels); \
} \
} \
if(done){ \
break; \
} \
if(ncp->exponentiali){ \
if(ncp->plot.exponentiali){ \
intervalbase = ncp->miny + pow(interval, (y + 1) * states - 1); \
}else{ \
intervalbase += (states * interval); \
} \
} \
} \
if(ncp->printsample){ \
int lastslot = ncp->slotstart ? ncp->slotstart - 1 : ncp->slotcount - 1; \
ncplane_set_styles(ncp->ncp, ncp->legendstyle); \
ncplane_set_channels(ncp->ncp, ncp->maxchannels); \
ncplane_printf_aligned(ncp->ncp, 0, NCALIGN_RIGHT, "%" PRIu64, (uint64_t)ncp->slots[lastslot]); \
if(ncp->plot.printsample){ \
int lastslot = ncp->plot.slotstart ? ncp->plot.slotstart - 1 : ncp->plot.slotcount - 1; \
ncplane_set_styles(ncp->plot.ncp, ncp->plot.legendstyle); \
ncplane_set_channels(ncp->plot.ncp, ncp->plot.maxchannels); \
ncplane_printf_aligned(ncp->plot.ncp, 0, NCALIGN_RIGHT, "%" PRIu64, (uint64_t)ncp->slots[lastslot]); \
} \
ncplane_home(ncp->ncp); \
ncplane_home(ncp->plot.ncp); \
return 0; \
} \
\
@ -260,49 +266,49 @@ create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, const T mi
return false; \
} \
int dimx = sdimx; \
ncpp->title = strdup(opts->title ? opts->title : ""); \
ncpp->rangex = opts->rangex; \
ncpp->plot.title = strdup(opts->title ? opts->title : ""); \
ncpp->plot.rangex = opts->rangex; \
/* if we're sizing the plot based off the plane dimensions, scale it by the \
plot geometry's width for all calculations */ \
const int scaleddim = dimx * bset->width; \
const int scaledprefixlen = PREFIXCOLUMNS * bset->width; \
if((ncpp->slotcount = ncpp->rangex) == 0){ \
ncpp->slotcount = scaleddim; \
if((ncpp->plot.slotcount = ncpp->plot.rangex) == 0){ \
ncpp->plot.slotcount = scaleddim; \
} \
if(dimx < ncpp->rangex){ \
ncpp->slotcount = scaleddim; \
if(dimx < ncpp->plot.rangex){ \
ncpp->plot.slotcount = scaleddim; \
} \
ncpp->legendstyle = opts->legendstyle; \
if( (ncpp->labelaxisd = opts->flags & NCPLOT_OPTION_LABELTICKSD) ){ \
if(ncpp->slotcount + scaledprefixlen > scaleddim){ \
ncpp->plot.legendstyle = opts->legendstyle; \
if( (ncpp->plot.labelaxisd = opts->flags & NCPLOT_OPTION_LABELTICKSD) ){ \
if(ncpp->plot.slotcount + scaledprefixlen > scaleddim){ \
if(scaleddim > scaledprefixlen){ \
ncpp->slotcount = scaleddim - scaledprefixlen; \
ncpp->plot.slotcount = scaleddim - scaledprefixlen; \
} \
} \
} \
size_t slotsize = sizeof(*ncpp->slots) * ncpp->slotcount; \
size_t slotsize = sizeof(*ncpp->slots) * ncpp->plot.slotcount; \
ncpp->slots = malloc(slotsize); \
if(ncpp->slots == NULL){ \
ncplane_destroy(n); \
return false; \
} \
memset(ncpp->slots, 0, slotsize); \
ncpp->ncp = n; \
ncpp->maxchannels = opts->maxchannels; \
ncpp->minchannels = opts->minchannels; \
ncpp->bset = bset; \
ncpp->plot.ncp = n; \
ncpp->plot.maxchannels = opts->maxchannels; \
ncpp->plot.minchannels = opts->minchannels; \
ncpp->plot.bset = bset; \
ncpp->miny = miny; \
ncpp->maxy = maxy; \
ncpp->vertical_indep = opts->flags & NCPLOT_OPTION_VERTICALI; \
ncpp->exponentiali = opts->flags & NCPLOT_OPTION_EXPONENTIALD; \
ncpp->detectonlymax = opts->flags & NCPLOT_OPTION_DETECTMAXONLY; \
ncpp->printsample = opts->flags & NCPLOT_OPTION_PRINTSAMPLE; \
if( (ncpp->detectdomain = (miny == maxy)) ){ \
ncpp->plot.vertical_indep = opts->flags & NCPLOT_OPTION_VERTICALI; \
ncpp->plot.exponentiali = opts->flags & NCPLOT_OPTION_EXPONENTIALD; \
ncpp->plot.detectonlymax = opts->flags & NCPLOT_OPTION_DETECTMAXONLY; \
ncpp->plot.printsample = opts->flags & NCPLOT_OPTION_PRINTSAMPLE; \
if( (ncpp->plot.detectdomain = (miny == maxy)) ){ \
ncpp->maxy = trueminy; \
ncpp->miny = truemaxy; \
} \
ncpp->slotstart = 0; \
ncpp->slotx = 0; \
ncpp->plot.slotstart = 0; \
ncpp->plot.slotx = 0; \
redraw_plot_##T(ncpp); \
return true; \
} \
@ -312,29 +318,29 @@ create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, const T mi
reset them, and write the new sample anywhere. otherwise, write it to the \
proper slot based on the current newest slot. */ \
int window_slide_##T(nc##X##plot* ncp, int64_t x){ \
if(x < ncp->slotx - (ncp->slotcount - 1)){ /* x is behind window, won't be counted */ \
if(x < ncp->plot.slotx - (ncp->plot.slotcount - 1)){ /* x is behind window, won't be counted */ \
return -1; \
}else if(x <= ncp->slotx){ /* x is within window, do nothing */ \
}else if(x <= ncp->plot.slotx){ /* x is within window, do nothing */ \
return 0; \
} /* x is newest; we might be keeping some, might not */ \
int64_t xdiff = x - ncp->slotx; /* the raw amount we're advancing */ \
ncp->slotx = x; \
if(xdiff >= ncp->slotcount){ /* we're throwing away all old samples, write to 0 */ \
memset(ncp->slots, 0, sizeof(*ncp->slots) * ncp->slotcount); \
ncp->slotstart = 0; \
int64_t xdiff = x - ncp->plot.slotx; /* the raw amount we're advancing */ \
ncp->plot.slotx = x; \
if(xdiff >= ncp->plot.slotcount){ /* we're throwing away all old samples, write to 0 */ \
memset(ncp->slots, 0, sizeof(*ncp->slots) * ncp->plot.slotcount); \
ncp->plot.slotstart = 0; \
return 0; \
} \
/* we're throwing away only xdiff slots, which is less than slotcount. \
first, we'll try to clear to the right...number to reset on the right of \
the circular buffer. min of (available at current or to right, xdiff) */ \
int slotsreset = ncp->slotcount - ncp->slotstart - 1; \
int slotsreset = ncp->plot.slotcount - ncp->plot.slotstart - 1; \
if(slotsreset > xdiff){ \
slotsreset = xdiff; \
} \
if(slotsreset){ \
memset(ncp->slots + ncp->slotstart + 1, 0, slotsreset * sizeof(*ncp->slots)); \
memset(ncp->slots + ncp->plot.slotstart + 1, 0, slotsreset * sizeof(*ncp->slots)); \
} \
ncp->slotstart = (ncp->slotstart + xdiff) % ncp->slotcount; \
ncp->plot.slotstart = (ncp->plot.slotstart + xdiff) % ncp->plot.slotcount; \
xdiff -= slotsreset; \
if(xdiff){ /* throw away some at the beginning */ \
memset(ncp->slots, 0, xdiff * sizeof(*ncp->slots)); \
@ -344,8 +350,8 @@ int window_slide_##T(nc##X##plot* ncp, int64_t x){ \
\
/* x must be within n's window at this point */ \
void update_sample_##T(nc##X##plot* ncp, int64_t x, T y, bool reset){ \
const int64_t diff = ncp->slotx - x; /* amount behind */ \
const int idx = (ncp->slotstart + ncp->slotcount - diff) % ncp->slotcount; \
const int64_t diff = ncp->plot.slotx - x; /* amount behind */ \
const int idx = (ncp->plot.slotstart + ncp->plot.slotcount - diff) % ncp->plot.slotcount; \
if(reset){ \
ncp->slots[idx] = y; \
}else{ \
@ -357,12 +363,12 @@ void update_sample_##T(nc##X##plot* ncp, int64_t x, T y, bool reset){ \
just set. if we're not, check the result against the known ranges, and \
return -1 if the value is outside of that range. */ \
int update_domain_##T(nc##X##plot* ncp, uint64_t x){ \
const T val = ncp->slots[x % ncp->slotcount]; \
if(ncp->detectdomain){ \
const T val = ncp->slots[x % ncp->plot.slotcount]; \
if(ncp->plot.detectdomain){ \
if(val > ncp->maxy){ \
ncp->maxy = val; \
} \
if(!ncp->detectonlymax){ \
if(!ncp->plot.detectonlymax){ \
if(val < ncp->miny){ \
ncp->miny = val; \
} \
@ -399,18 +405,18 @@ int add_sample_##T(nc##X##plot* ncpp, uint64_t x, T y){ \
return redraw_plot_##T(ncpp); \
} \
int sample_##T(const nc##X##plot* ncp, int64_t x, T* y){ \
if(x < ncp->slotx - (ncp->slotcount - 1)){ /* x is behind window */ \
if(x < ncp->plot.slotx - (ncp->plot.slotcount - 1)){ /* x is behind window */ \
return -1; \
}else if(x > ncp->slotx){ /* x is ahead of window */ \
}else if(x > ncp->plot.slotx){ /* x is ahead of window */ \
return -1; \
} \
*y = ncp->slots[x % ncp->slotcount]; \
*y = ncp->slots[x % ncp->plot.slotcount]; \
return 0; \
} \
void destroy_##T(nc##X##plot* ncpp){ \
free(ncpp->title); \
free(ncpp->plot.title); \
free(ncpp->slots); \
ncplane_destroy(ncpp->ncp); \
ncplane_destroy(ncpp->plot.ncp); \
}
CREATE(uint64_t, u)
@ -432,7 +438,7 @@ ncuplot* ncuplot_create(ncplane* n, const ncplot_options* opts, uint64_t miny, u
}
ncplane* ncuplot_plane(ncuplot* n){
return n->ncp;
return n->plot.ncp;
}
int ncuplot_add_sample(ncuplot* n, uint64_t x, uint64_t y){
@ -466,7 +472,7 @@ ncdplot* ncdplot_create(ncplane* n, const ncplot_options* opts, double miny, dou
}
ncplane* ncdplot_plane(ncdplot* n){
return n->ncp;
return n->plot.ncp;
}
int ncdplot_add_sample(ncdplot* n, uint64_t x, double y){