mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 09:09:03 -04:00
[plots] move plot.cpp to C plot.c #1421
This commit is contained in:
parent
d62ced7aa4
commit
acc5be058c
@ -119,6 +119,7 @@ if(NOT "${HAVE_QRCODEGEN_H}")
|
||||
endif()
|
||||
set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND qrcodegen)
|
||||
endif()
|
||||
find_library(LIBM m)
|
||||
find_library(LIBRT rt)
|
||||
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
@ -163,6 +164,7 @@ target_link_libraries(notcurses-core
|
||||
PRIVATE
|
||||
"${TERMINFO_LIBRARIES}"
|
||||
"${READLINE_LIBRARIES}"
|
||||
"${LIBM}"
|
||||
"${LIBRT}"
|
||||
unistring
|
||||
PUBLIC
|
||||
@ -172,6 +174,7 @@ target_link_libraries(notcurses-core-static
|
||||
PRIVATE
|
||||
"${TERMINFO_STATIC_LIBRARIES}"
|
||||
"${READLINE_STATIC_LIBRARIES}"
|
||||
"${LIBM}"
|
||||
"${LIBRT}"
|
||||
unistring
|
||||
PUBLIC
|
||||
|
@ -103,14 +103,14 @@ portability, you should by all means use that fine library.
|
||||
Minimum versions generally indicate the oldest version I've tested with; it
|
||||
may well be possible to use still older versions. Let me know of any successes!
|
||||
|
||||
* (build) A C11 and a C++17 compiler
|
||||
* (build) CMake 3.14.0+
|
||||
* (build) CMake 3.14.0+ and a C11 compiler
|
||||
* (OPTIONAL) (OpenImageIO, testing, C++ bindings): A C++17 compiler
|
||||
* (build+runtime) From [NCURSES](https://invisible-island.net/ncurses/announce.html): terminfo 6.1+
|
||||
* (build+runtime) GNU [libunistring](https://www.gnu.org/software/libunistring/) 0.9.10+
|
||||
* (build+runtime) GNU [Readline](https://www.gnu.org/software/readline/) 8.0+
|
||||
* (OPTIONAL) (build+runtime) From QR-Code-generator: [libqrcodegen](https://github.com/nayuki/QR-Code-generator) 1.5.0+
|
||||
* (OPTIONAL) (build+runtime) From [FFmpeg](https://www.ffmpeg.org/): libswscale 5.0+, libavformat 57.0+, libavutil 56.0+
|
||||
* (OPTIONAL) (build+runtime) [OpenImageIO](https://github.com/OpenImageIO/oiio) 2.15.0+
|
||||
* (OPTIONAL) (build+runtime) [OpenImageIO](https://github.com/OpenImageIO/oiio) 2.15.0+, requires C++
|
||||
* (OPTIONAL) (testing) [Doctest](https://github.com/onqtam/doctest) 2.3.5+
|
||||
* (OPTIONAL) (documentation) [pandoc](https://pandoc.org/index.html) 1.19.2+
|
||||
* (OPTIONAL) (python bindings): Python 3.7+, [CFFI](https://pypi.org/project/cffi/) 1.13.2+, [pypandoc](https://pypi.org/project/pypandoc/) 1.5+
|
||||
|
485
src/lib/plot.c
Normal file
485
src/lib/plot.c
Normal file
@ -0,0 +1,485 @@
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include "internal.h"
|
||||
|
||||
#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 */ \
|
||||
} nc##X##plot; \
|
||||
\
|
||||
int redraw_plot_##T(nc##X##plot* ncp){ \
|
||||
ncplane_erase(ncp->ncp); \
|
||||
const int scale = ncp->bset->width; \
|
||||
int dimy, dimx; \
|
||||
ncplane_dim_yx(ncp->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; \
|
||||
/* 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->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); */ \
|
||||
}else{ \
|
||||
interval = 0; \
|
||||
} \
|
||||
}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 */ \
|
||||
/* 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){ \
|
||||
/* 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); \
|
||||
char buf[PREFIXSTRLEN + 1]; \
|
||||
if(ncp->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{ \
|
||||
ncmetric(pow(interval, (y + 1) * states) * 100, 100, buf, 0, 1000, '\0'); \
|
||||
} \
|
||||
}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); \
|
||||
}else{ \
|
||||
ncplane_printf_yx(ncp->ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s", buf); \
|
||||
} \
|
||||
} \
|
||||
}else if(strlen(ncp->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); \
|
||||
} \
|
||||
ncplane_set_styles(ncp->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 */ \
|
||||
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. */ \
|
||||
T gvals[MAXWIDTH]; \
|
||||
/* load it retaining the same ordering we have in the actual array */ \
|
||||
for(int i = scale - 1 ; i >= 0 ; --i){ \
|
||||
gvals[i] = ncp->slots[idx]; /* clip the value at the limits of the graph */ \
|
||||
if(gvals[i] < ncp->miny){ \
|
||||
gvals[i] = ncp->miny; \
|
||||
} \
|
||||
if(gvals[i] > ncp->maxy){ \
|
||||
gvals[i] = ncp->maxy; \
|
||||
} \
|
||||
/* FIXME if there are an odd number, only go up through the valid ones... */ \
|
||||
if(--idx < 0){ \
|
||||
idx = ncp->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->egcs; \
|
||||
bool done = !ncp->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); \
|
||||
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, \
|
||||
we're going to print *something* */ \
|
||||
for(int i = 0 ; i < scale ; ++i){ \
|
||||
sumidx *= states; \
|
||||
if(intervalbase < gvals[i]){ \
|
||||
if(ncp->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; \
|
||||
egcidx = scaled - sival; \
|
||||
}else{ \
|
||||
egcidx = (gvals[i] - intervalbase) / interval; \
|
||||
} \
|
||||
if(egcidx >= states){ \
|
||||
egcidx = states - 1; \
|
||||
done = false; \
|
||||
} \
|
||||
sumidx += egcidx; \
|
||||
}else{ \
|
||||
egcidx = 0; \
|
||||
} \
|
||||
/* 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); */ \
|
||||
} \
|
||||
/* if we're not UTF8, we can only arrive here via NCBLIT_1x1 (otherwise \
|
||||
we would have errored out during construction). even then, however, \
|
||||
we need handle ASCII differently, since it can't print full block. \
|
||||
in ASCII mode, sumidx != 0 means swap colors and use space. in all \
|
||||
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))){ \
|
||||
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); \
|
||||
cell_set_bchannel(c, channels_bchannel(channels)); \
|
||||
cell_set_fchannel(c, channels_fchannel(channels)); \
|
||||
cell_set_styles(c, NCSTYLE_NONE); \
|
||||
if(pool_blit_direct(&ncp->ncp->pool, c, utf8, bytes, 1) <= 0){ \
|
||||
return -1; \
|
||||
} \
|
||||
}else{ \
|
||||
const uint64_t swapbg = channels_bchannel(channels); \
|
||||
const uint64_t swapfg = channels_fchannel(channels); \
|
||||
channels_set_bchannel(&channels, swapfg); \
|
||||
channels_set_fchannel(&channels, swapbg); \
|
||||
ncplane_set_channels(ncp->ncp, channels); \
|
||||
if(ncplane_putchar_yx(ncp->ncp, dimy - y - 1, x, ' ') <= 0){ \
|
||||
return -1; \
|
||||
} \
|
||||
channels_set_bchannel(&channels, swapbg); \
|
||||
channels_set_fchannel(&channels, swapfg); \
|
||||
ncplane_set_channels(ncp->ncp, channels); \
|
||||
} \
|
||||
} \
|
||||
if(done){ \
|
||||
break; \
|
||||
} \
|
||||
if(ncp->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_printf_aligned(ncp->ncp, 0, NCALIGN_RIGHT, "%ju", (uintmax_t)ncp->slots[lastslot]); \
|
||||
} \
|
||||
ncplane_home(ncp->ncp); \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
static bool \
|
||||
create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, const T miny, const T maxy, \
|
||||
const T trueminy, const T truemaxy){ \
|
||||
ncplot_options zeroed = {}; \
|
||||
if(!opts){ \
|
||||
opts = &zeroed; \
|
||||
} \
|
||||
if(opts->flags >= (NCPLOT_OPTION_PRINTSAMPLE << 1u)){ \
|
||||
logwarn(ncplane_notcurses(n), "Provided unsupported flags %016jx\n", (uintmax_t)opts->flags); \
|
||||
} \
|
||||
const notcurses* nc = ncplane_notcurses_const(n); \
|
||||
/* if miny == maxy (enabling domain detection), they both must be equal to 0 */ \
|
||||
if(miny == maxy && miny){ \
|
||||
ncplane_destroy(n); \
|
||||
return false; \
|
||||
} \
|
||||
if(opts->rangex < 0){ \
|
||||
logerror(nc, "Supplied negative independent range %d\n", opts->rangex); \
|
||||
ncplane_destroy(n); \
|
||||
return false; \
|
||||
} \
|
||||
if(maxy < miny){ \
|
||||
ncplane_destroy(n); \
|
||||
return false; \
|
||||
} \
|
||||
/* DETECTMAXONLY can't be used without domain detection */ \
|
||||
if(opts->flags & NCPLOT_OPTION_DETECTMAXONLY && (miny != maxy)){ \
|
||||
logerror(nc, "Supplied DETECTMAXONLY without domain detection"); \
|
||||
ncplane_destroy(n); \
|
||||
return false; \
|
||||
} \
|
||||
const notcurses* notc = ncplane_notcurses(n); \
|
||||
ncblitter_e blitfxn = opts ? opts->gridtype : NCBLIT_DEFAULT; \
|
||||
if(blitfxn == NCBLIT_DEFAULT){ \
|
||||
blitfxn = ncplot_defblitter(notc); \
|
||||
} \
|
||||
bool degrade_blitter = !(opts && (opts->flags & NCPLOT_OPTION_NODEGRADE)); \
|
||||
const struct blitset* bset = lookup_blitset(¬c->tcache, blitfxn, degrade_blitter); \
|
||||
if(bset == NULL){ \
|
||||
ncplane_destroy(n); \
|
||||
return false; \
|
||||
} \
|
||||
int sdimy, sdimx; \
|
||||
ncplane_dim_yx(n, &sdimy, &sdimx); \
|
||||
if(sdimx <= 0){ \
|
||||
ncplane_destroy(n); \
|
||||
return false; \
|
||||
} \
|
||||
int dimx = sdimx; \
|
||||
ncpp->title = strdup(opts->title ? opts->title : ""); \
|
||||
ncpp->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(dimx < ncpp->rangex){ \
|
||||
ncpp->slotcount = scaleddim; \
|
||||
} \
|
||||
ncpp->legendstyle = opts->legendstyle; \
|
||||
if( (ncpp->labelaxisd = opts->flags & NCPLOT_OPTION_LABELTICKSD) ){ \
|
||||
if(ncpp->slotcount + scaledprefixlen > scaleddim){ \
|
||||
if(scaleddim > scaledprefixlen){ \
|
||||
ncpp->slotcount = scaleddim - scaledprefixlen; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
size_t slotsize = sizeof(*ncpp->slots) * ncpp->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->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->maxy = trueminy; \
|
||||
ncpp->miny = truemaxy; \
|
||||
} \
|
||||
ncpp->slotstart = 0; \
|
||||
ncpp->slotx = 0; \
|
||||
redraw_plot_##T(ncpp); \
|
||||
return true; \
|
||||
} \
|
||||
/* if x is less than the window, return -1, as the sample will be thrown away. \
|
||||
if the x is within the current window, find the proper slot and update it. \
|
||||
otherwise, the x is the newest sample. if it is obsoletes all existing slots, \
|
||||
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 */ \
|
||||
return -1; \
|
||||
}else if(x <= ncp->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; \
|
||||
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; \
|
||||
if(slotsreset > xdiff){ \
|
||||
slotsreset = xdiff; \
|
||||
} \
|
||||
if(slotsreset){ \
|
||||
memset(ncp->slots + ncp->slotstart + 1, 0, slotsreset * sizeof(*ncp->slots)); \
|
||||
} \
|
||||
ncp->slotstart = (ncp->slotstart + xdiff) % ncp->slotcount; \
|
||||
xdiff -= slotsreset; \
|
||||
if(xdiff){ /* throw away some at the beginning */ \
|
||||
memset(ncp->slots, 0, xdiff * sizeof(*ncp->slots)); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
/* x must be within n's window at this point */ \
|
||||
inline 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; \
|
||||
if(reset){ \
|
||||
ncp->slots[idx] = y; \
|
||||
}else{ \
|
||||
ncp->slots[idx] += y; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/* if we're doing domain detection, update the domain to reflect the value we \
|
||||
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){ \
|
||||
if(val > ncp->maxy){ \
|
||||
ncp->maxy = val; \
|
||||
} \
|
||||
if(!ncp->detectonlymax){ \
|
||||
if(val < ncp->miny){ \
|
||||
ncp->miny = val; \
|
||||
} \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
if(val > ncp->maxy || val < ncp->miny){ \
|
||||
return -1; \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
int set_sample_##T(nc##X##plot* ncpp, uint64_t x, T y){ \
|
||||
if(window_slide_##T(ncpp, x)){ \
|
||||
return -1; \
|
||||
} \
|
||||
update_sample_##T(ncpp, x, y, true); \
|
||||
if(update_domain_##T(ncpp, x)){ \
|
||||
return -1; \
|
||||
} \
|
||||
return redraw_plot_##T(ncpp); \
|
||||
} \
|
||||
/* Add to or set the value corresponding to this x. If x is beyond the current \
|
||||
x window, the x window is advanced to include x, and values passing beyond \
|
||||
the window are lost. The first call will place the initial window. The plot \
|
||||
will be redrawn, but notcurses_render() is not called. */ \
|
||||
int add_sample_##T(nc##X##plot* ncpp, uint64_t x, T y){ \
|
||||
if(window_slide_##T(ncpp, x)){ \
|
||||
return -1; \
|
||||
} \
|
||||
update_sample_##T(ncpp, x, y, false); \
|
||||
if(update_domain_##T(ncpp, x)){ \
|
||||
return -1; \
|
||||
} \
|
||||
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 */ \
|
||||
return -1; \
|
||||
}else if(x > ncp->slotx){ /* x is ahead of window */ \
|
||||
return -1; \
|
||||
} \
|
||||
*y = ncp->slots[x % ncp->slotcount]; \
|
||||
return 0; \
|
||||
} \
|
||||
void destroy_##T(nc##X##plot* ncpp){ \
|
||||
free(ncpp->slots); \
|
||||
ncplane_destroy(ncpp->ncp); \
|
||||
}
|
||||
|
||||
CREATE(uint64_t, u)
|
||||
CREATE(double, d)
|
||||
|
||||
ncuplot* ncuplot_create(ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy){
|
||||
ncuplot* ret = malloc(sizeof(*ret));
|
||||
if(ret){
|
||||
memset(ret, 0, sizeof(*ret));
|
||||
if(create_uint64_t(ret, n, opts, miny, maxy, 0, UINT64_MAX) == 0){
|
||||
return ret;
|
||||
}
|
||||
free(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ncplane* ncuplot_plane(ncuplot* n){
|
||||
return n->ncp;
|
||||
}
|
||||
|
||||
int ncuplot_add_sample(ncuplot* n, uint64_t x, uint64_t y){
|
||||
return add_sample_uint64_t(n, x, y);
|
||||
}
|
||||
|
||||
int ncuplot_set_sample(ncuplot* n, uint64_t x, uint64_t y){
|
||||
return set_sample_uint64_t(n, x, y);
|
||||
}
|
||||
|
||||
void ncuplot_destroy(ncuplot* n){
|
||||
if(n){
|
||||
destroy_uint64_t(n);
|
||||
free(n);
|
||||
}
|
||||
}
|
||||
|
||||
ncdplot* ncdplot_create(ncplane* n, const ncplot_options* opts, double miny, double maxy){
|
||||
ncdplot* ret = malloc(sizeof(*ret));
|
||||
if(ret){
|
||||
memset(ret, 0, sizeof(*ret));
|
||||
if(create_double(ret, n, opts, miny, maxy, -DBL_MAX, DBL_MAX) == 0){
|
||||
return ret;
|
||||
}
|
||||
free(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ncplane* ncdplot_plane(ncdplot* n){
|
||||
return n->ncp;
|
||||
}
|
||||
|
||||
int ncdplot_add_sample(ncdplot* n, uint64_t x, double y){
|
||||
return add_sample_double(n, x, y);
|
||||
}
|
||||
|
||||
int ncdplot_set_sample(ncdplot* n, uint64_t x, double y){
|
||||
return set_sample_double(n, x, y);
|
||||
}
|
||||
|
||||
int ncuplot_sample(const ncuplot* n, uint64_t x, uint64_t* y){
|
||||
return sample_uint64_t(n, x, y);
|
||||
}
|
||||
|
||||
int ncdplot_sample(const ncdplot* n, uint64_t x, double* y){
|
||||
return sample_double(n, x, y);
|
||||
}
|
||||
|
||||
void ncdplot_destroy(ncdplot* n) {
|
||||
if(n){
|
||||
destroy_double(n);
|
||||
free(n);
|
||||
}
|
||||
}
|
504
src/lib/plot.cpp
504
src/lib/plot.cpp
@ -1,504 +0,0 @@
|
||||
#include <cmath>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include "internal.h"
|
||||
|
||||
template<typename T>
|
||||
class ncppplot {
|
||||
public:
|
||||
|
||||
// these were all originally plain C, sorry for the non-idiomatic usage FIXME
|
||||
static bool create(ncppplot<T>* ncpp, ncplane* n, const ncplot_options* opts, T miny, T maxy) {
|
||||
ncplot_options zeroed = {};
|
||||
if(!opts){
|
||||
opts = &zeroed;
|
||||
}
|
||||
if(opts->flags >= (NCPLOT_OPTION_PRINTSAMPLE << 1u)){
|
||||
logwarn(ncplane_notcurses(n), "Provided unsupported flags %016jx\n", (uintmax_t)opts->flags);
|
||||
}
|
||||
auto nc = ncplane_notcurses(n);
|
||||
// if miny == maxy (enabling domain detection), they both must be equal to 0
|
||||
if(miny == maxy && miny){
|
||||
ncplane_destroy(n);
|
||||
return false;
|
||||
}
|
||||
if(opts->rangex < 0){
|
||||
logerror(nc, "Supplied negative independent range %d\n", opts->rangex);
|
||||
ncplane_destroy(n);
|
||||
return false;
|
||||
}
|
||||
if(maxy < miny){
|
||||
ncplane_destroy(n);
|
||||
return false;
|
||||
}
|
||||
// DETECTMAXONLY can't be used without domain detection
|
||||
if(opts->flags & NCPLOT_OPTION_DETECTMAXONLY && (miny != maxy)){
|
||||
logerror(nc, "Supplied DETECTMAXONLY without domain detection");
|
||||
ncplane_destroy(n);
|
||||
return false;
|
||||
}
|
||||
const notcurses* notc = ncplane_notcurses(n);
|
||||
ncblitter_e blitfxn = opts ? opts->gridtype : NCBLIT_DEFAULT;
|
||||
if(blitfxn == NCBLIT_DEFAULT){
|
||||
blitfxn = ncplot_defblitter(notc);
|
||||
}
|
||||
bool degrade_blitter = !(opts && (opts->flags & NCPLOT_OPTION_NODEGRADE));
|
||||
auto bset = lookup_blitset(¬c->tcache, blitfxn, degrade_blitter);
|
||||
if(bset == nullptr){
|
||||
ncplane_destroy(n);
|
||||
return false;
|
||||
}
|
||||
int sdimy, sdimx;
|
||||
ncplane_dim_yx(n, &sdimy, &sdimx);
|
||||
if(sdimx <= 0){
|
||||
ncplane_destroy(n);
|
||||
return false;
|
||||
}
|
||||
int dimx = sdimx;
|
||||
if(opts->title){
|
||||
ncpp->title = std::string(opts->title);
|
||||
}
|
||||
ncpp->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(dimx < ncpp->rangex){
|
||||
ncpp->slotcount = scaleddim;
|
||||
}
|
||||
ncpp->legendstyle = opts->legendstyle;
|
||||
if( (ncpp->labelaxisd = opts->flags & NCPLOT_OPTION_LABELTICKSD) ){
|
||||
if(ncpp->slotcount + scaledprefixlen > scaleddim){
|
||||
if(scaleddim > scaledprefixlen){
|
||||
ncpp->slotcount = scaleddim - scaledprefixlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t slotsize = sizeof(*ncpp->slots) * ncpp->slotcount;
|
||||
ncpp->slots = static_cast<T*>(malloc(slotsize));
|
||||
if(ncpp->slots == nullptr){
|
||||
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->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->maxy = 0;
|
||||
ncpp->miny = std::numeric_limits<T>::max();
|
||||
}
|
||||
ncpp->slotstart = 0;
|
||||
ncpp->slotx = 0;
|
||||
ncpp->redraw_plot();
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy(){
|
||||
free(slots);
|
||||
ncplane_destroy(ncp);
|
||||
}
|
||||
|
||||
auto redraw_plot() -> int {
|
||||
ncplane_erase(ncp);
|
||||
const int scale = bset->width;
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(ncp, &dimy, &dimx);
|
||||
const int scaleddim = dimx * scale;
|
||||
// each transition is worth this much change in value
|
||||
const size_t states = 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(exponentiali){
|
||||
if(maxy > miny){
|
||||
interval = pow(maxy - miny, (double)1 / (dimy * states));
|
||||
//fprintf(stderr, "miny: %ju maxy: %ju dimy: %d states: %zu\n", miny, maxy, dimy, states);
|
||||
}else{
|
||||
interval = 0;
|
||||
}
|
||||
}else{
|
||||
interval = maxy < miny ? 0 : (maxy - miny) / ((double)dimy * states);
|
||||
}
|
||||
const int startx = 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 = (slotcount < scaleddim - 1 - (startx * scale) ? startx + (slotcount / scale) - 1 : dimx - 1);
|
||||
ncplane_set_styles(ncp, legendstyle);
|
||||
if(labelaxisd){
|
||||
// show the *top* of each interval range
|
||||
for(int y = 0 ; y < dimy ; ++y){
|
||||
uint64_t channels = 0;
|
||||
calc_gradient_channels(&channels, minchannels, minchannels,
|
||||
maxchannels, maxchannels, y, 0, dimy, dimx);
|
||||
ncplane_set_channels(ncp, channels);
|
||||
char buf[PREFIXSTRLEN + 1];
|
||||
if(exponentiali){
|
||||
if(y == dimy - 1){ // we cheat on the top row to exactly match maxy
|
||||
ncmetric(maxy * 100, 100, buf, 0, 1000, '\0');
|
||||
}else{
|
||||
ncmetric(pow(interval, (y + 1) * states) * 100, 100, buf, 0, 1000, '\0');
|
||||
}
|
||||
}else{
|
||||
ncmetric((maxy - interval * states * (dimy - y - 1)) * 100, 100, buf, 0, 1000, '\0');
|
||||
}
|
||||
if(y == dimy - 1 && !title.empty()){
|
||||
ncplane_printf_yx(ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s %s", buf, title.c_str());
|
||||
}else{
|
||||
ncplane_printf_yx(ncp, dimy - y - 1, PREFIXCOLUMNS - strlen(buf), "%s", buf);
|
||||
}
|
||||
}
|
||||
}else if(!title.empty()){
|
||||
uint64_t channels = 0;
|
||||
calc_gradient_channels(&channels, minchannels, minchannels,
|
||||
maxchannels, maxchannels, dimy - 1, 0, dimy, dimx);
|
||||
ncplane_set_channels(ncp, channels);
|
||||
ncplane_printf_yx(ncp, 0, PREFIXCOLUMNS - title.length(), "%s", title.c_str());
|
||||
}
|
||||
ncplane_set_styles(ncp, NCSTYLE_NONE);
|
||||
if(finalx < startx){ // exit on pathologically narrow planes
|
||||
return 0;
|
||||
}
|
||||
if(!interval){
|
||||
interval = 1;
|
||||
}
|
||||
#define MAXWIDTH 2
|
||||
int idx = 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.
|
||||
T gvals[MAXWIDTH];
|
||||
// load it retaining the same ordering we have in the actual array
|
||||
for(int i = scale - 1 ; i >= 0 ; --i){
|
||||
gvals[i] = slots[idx]; // clip the value at the limits of the graph
|
||||
if(gvals[i] < miny){
|
||||
gvals[i] = miny;
|
||||
}
|
||||
if(gvals[i] > maxy){
|
||||
gvals[i] = maxy;
|
||||
}
|
||||
// FIXME if there are an odd number, only go up through the valid ones...
|
||||
if(--idx < 0){
|
||||
idx = 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 = miny;
|
||||
const wchar_t* egc = bset->egcs;
|
||||
bool done = !bset->fill;
|
||||
for(int y = 0 ; y < dimy ; ++y){
|
||||
uint64_t channels = 0;
|
||||
calc_gradient_channels(&channels, minchannels, minchannels,
|
||||
maxchannels, maxchannels, y, x, dimy, dimx);
|
||||
ncplane_set_channels(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,
|
||||
// we're going to print *something*
|
||||
for(int i = 0 ; i < scale ; ++i){
|
||||
sumidx *= states;
|
||||
if(intervalbase < gvals[i]){
|
||||
if(exponentiali){
|
||||
// we want the log-base-interval of gvals[i]
|
||||
double scaled = log(gvals[i] - miny) / log(interval);
|
||||
double sival = intervalbase ? log(intervalbase) / log(interval) : 0;
|
||||
egcidx = scaled - sival;
|
||||
}else{
|
||||
egcidx = (gvals[i] - intervalbase) / interval;
|
||||
}
|
||||
if(egcidx >= states){
|
||||
egcidx = states - 1;
|
||||
done = false;
|
||||
}
|
||||
sumidx += egcidx;
|
||||
}else{
|
||||
egcidx = 0;
|
||||
}
|
||||
//fprintf(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);
|
||||
}
|
||||
// if we're not UTF8, we can only arrive here via NCBLIT_1x1 (otherwise
|
||||
// we would have errored out during construction). even then, however,
|
||||
// we need handle ASCII differently, since it can't print full block.
|
||||
// in ASCII mode, sumidx != 0 means swap colors and use space. in all
|
||||
// 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))){
|
||||
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, dimy - y - 1, x);
|
||||
cell_set_bchannel(c, channels_bchannel(channels));
|
||||
cell_set_fchannel(c, channels_fchannel(channels));
|
||||
cell_set_styles(c, NCSTYLE_NONE);
|
||||
if(pool_blit_direct(&ncp->pool, c, utf8, bytes, 1) <= 0){
|
||||
return -1;
|
||||
}
|
||||
}else{
|
||||
const uint64_t swapbg = channels_bchannel(channels);
|
||||
const uint64_t swapfg = channels_fchannel(channels);
|
||||
channels_set_bchannel(&channels, swapfg);
|
||||
channels_set_fchannel(&channels, swapbg);
|
||||
ncplane_set_channels(ncp, channels);
|
||||
if(ncplane_putchar_yx(ncp, dimy - y - 1, x, ' ') <= 0){
|
||||
return -1;
|
||||
}
|
||||
channels_set_bchannel(&channels, swapbg);
|
||||
channels_set_fchannel(&channels, swapfg);
|
||||
ncplane_set_channels(ncp, channels);
|
||||
}
|
||||
}
|
||||
if(done){
|
||||
break;
|
||||
}
|
||||
if(exponentiali){
|
||||
intervalbase = miny + pow(interval, (y + 1) * states - 1);
|
||||
}else{
|
||||
intervalbase += (states * interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(printsample){
|
||||
int lastslot = slotstart ? slotstart - 1 : slotcount - 1;
|
||||
ncplane_set_styles(ncp, legendstyle);
|
||||
ncplane_printf_aligned(ncp, 0, NCALIGN_RIGHT, "%ju", (uintmax_t)slots[lastslot]);
|
||||
}
|
||||
ncplane_home(ncp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add to or set the value corresponding to this x. If x is beyond the current
|
||||
// x window, the x window is advanced to include x, and values passing beyond
|
||||
// the window are lost. The first call will place the initial window. The plot
|
||||
// will be redrawn, but notcurses_render() is not called.
|
||||
auto add_sample(uint64_t x, T y) -> int {
|
||||
if(window_slide(x)){
|
||||
return -1;
|
||||
}
|
||||
update_sample(x, y, false);
|
||||
if(update_domain(x)){
|
||||
return -1;
|
||||
}
|
||||
return redraw_plot();
|
||||
}
|
||||
|
||||
auto set_sample(uint64_t x, T y) -> int {
|
||||
if(window_slide(x)){
|
||||
return -1;
|
||||
}
|
||||
update_sample(x, y, true);
|
||||
if(update_domain(x)){
|
||||
return -1;
|
||||
}
|
||||
return redraw_plot();
|
||||
}
|
||||
|
||||
auto sample(int64_t x, T* y) const -> int {
|
||||
if(x < slotx - (slotcount - 1)){ // x is behind window
|
||||
return -1;
|
||||
}else if(x > slotx){ // x is ahead of window
|
||||
return -1;
|
||||
}
|
||||
*y = slots[x % slotcount];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if we're doing domain detection, update the domain to reflect the value we
|
||||
// just set. if we're not, check the result against the known ranges, and
|
||||
// return -1 if the value is outside of that range.
|
||||
auto update_domain(uint64_t x) -> int {
|
||||
const T val = slots[x % slotcount];
|
||||
if(detectdomain){
|
||||
if(val > maxy){
|
||||
maxy = val;
|
||||
}
|
||||
if(!detectonlymax){
|
||||
if(val < miny){
|
||||
miny = val;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if(val > maxy || val < miny){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if x is less than the window, return -1, as the sample will be thrown away.
|
||||
// if the x is within the current window, find the proper slot and update it.
|
||||
// otherwise, the x is the newest sample. if it is obsoletes all existing slots,
|
||||
// reset them, and write the new sample anywhere. otherwise, write it to the
|
||||
// proper slot based on the current newest slot.
|
||||
auto window_slide(int64_t x) -> int {
|
||||
if(x < slotx - (slotcount - 1)){ // x is behind window, won't be counted
|
||||
return -1;
|
||||
}else if(x <= slotx){ // x is within window, do nothing
|
||||
return 0;
|
||||
} // x is newest; we might be keeping some, might not
|
||||
int64_t xdiff = x - slotx; // the raw amount we're advancing
|
||||
slotx = x;
|
||||
if(xdiff >= slotcount){ // we're throwing away all old samples, write to 0
|
||||
memset(slots, 0, sizeof(*slots) * slotcount); // FIXME need a STL operation?
|
||||
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 = slotcount - slotstart - 1;
|
||||
if(slotsreset > xdiff){
|
||||
slotsreset = xdiff;
|
||||
}
|
||||
if(slotsreset){
|
||||
memset(slots + slotstart + 1, 0, slotsreset * sizeof(*slots));
|
||||
}
|
||||
slotstart = (slotstart + xdiff) % slotcount;
|
||||
xdiff -= slotsreset;
|
||||
if(xdiff){ // throw away some at the beginning
|
||||
memset(slots, 0, xdiff * sizeof(*slots));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// x must be within n's window at this point
|
||||
inline void update_sample(int64_t x, T y, bool reset){
|
||||
const int64_t diff = slotx - x; // amount behind
|
||||
const int idx = (slotstart + slotcount - diff) % slotcount;
|
||||
if(reset){
|
||||
slots[idx] = y;
|
||||
}else{
|
||||
slots[idx] += y;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
T* slots;
|
||||
int64_t slotx; // x value corresponding to slots[slotstart] (newest x)
|
||||
|
||||
private:
|
||||
|
||||
uint64_t maxchannels;
|
||||
uint64_t minchannels;
|
||||
uint16_t legendstyle;
|
||||
bool vertical_indep; // not yet implemented FIXME
|
||||
const struct blitset* bset;
|
||||
std::string 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.
|
||||
T miny, maxy;
|
||||
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
|
||||
|
||||
};
|
||||
|
||||
using ncuplot = struct ncuplot {
|
||||
ncppplot<uint64_t> n;
|
||||
};
|
||||
|
||||
using ncdplot = struct ncdplot {
|
||||
ncppplot<double> n;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
auto ncuplot_create(ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy) -> ncuplot* {
|
||||
auto ret = new ncuplot;
|
||||
if(ret){
|
||||
if(ncppplot<uint64_t>::create(&ret->n, n, opts, miny, maxy)){
|
||||
return ret;
|
||||
}
|
||||
delete ret;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ncuplot_plane(ncuplot* n) -> ncplane* {
|
||||
return n->n.ncp;
|
||||
}
|
||||
|
||||
auto ncuplot_add_sample(ncuplot* n, uint64_t x, uint64_t y) -> int {
|
||||
return n->n.add_sample(x, y);
|
||||
}
|
||||
|
||||
auto ncuplot_set_sample(ncuplot* n, uint64_t x, uint64_t y) -> int {
|
||||
return n->n.set_sample(x, y);
|
||||
}
|
||||
|
||||
void ncuplot_destroy(ncuplot* n) {
|
||||
if(n){
|
||||
n->n.destroy();
|
||||
delete n;
|
||||
}
|
||||
}
|
||||
|
||||
auto ncdplot_create(ncplane* n, const ncplot_options* opts, double miny, double maxy) -> ncdplot* {
|
||||
auto ret = new ncdplot;
|
||||
if(ret){
|
||||
if(ncppplot<double>::create(&ret->n, n, opts, miny, maxy)){
|
||||
return ret;
|
||||
}
|
||||
delete ret;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ncdplot_plane(ncdplot* n) -> ncplane* {
|
||||
return n->n.ncp;
|
||||
}
|
||||
|
||||
auto ncdplot_add_sample(ncdplot* n, uint64_t x, double y) -> int {
|
||||
return n->n.add_sample(x, y);
|
||||
}
|
||||
|
||||
auto ncdplot_set_sample(ncdplot* n, uint64_t x, double y) -> int {
|
||||
return n->n.set_sample(x, y);
|
||||
}
|
||||
|
||||
auto ncuplot_sample(const ncuplot* n, uint64_t x, uint64_t* y) -> int {
|
||||
return n->n.sample(x, y);
|
||||
}
|
||||
|
||||
auto ncdplot_sample(const ncdplot* n, uint64_t x, double* y) -> int {
|
||||
return n->n.sample(x, y);
|
||||
}
|
||||
|
||||
void ncdplot_destroy(ncdplot* n) {
|
||||
if(n){
|
||||
n->n.destroy();
|
||||
delete n;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user