mirror of
https://github.com/dankamongmen/notcurses.git
synced 2024-10-31 15:20:13 +00:00
Floating-point ncplot, genericize ncplot (#531)
* compile ncplot as c++ generic #446 * add floating-point plots #446
This commit is contained in:
parent
5a017574f6
commit
0e73b9d3d5
@ -1,6 +1,11 @@
|
||||
This document attempts to list user-visible changes and any major internal
|
||||
rearrangements of Notcurses.
|
||||
|
||||
* 1.3.3 (not yet released)
|
||||
* The `ncdplot` type has been added for plots based on `double`s rather than
|
||||
`uint64_t`s. The `ncplot` type and all `ncplot_*` functions were renamed
|
||||
`ncuplot` for symmetry.
|
||||
|
||||
* 1.3.2 (2020-04-19)
|
||||
* `ncdirect_cursor_push()`, `notcurses_cursor_pop()`, and
|
||||
`ncdirect_cursor_yx()` have been added. These are not supported on all
|
||||
|
@ -37,8 +37,8 @@ if(${USE_TESTS})
|
||||
find_package(doctest 2.3.5 REQUIRED)
|
||||
endif()
|
||||
|
||||
# libnotcurses
|
||||
file(GLOB NCSRCS CONFIGURE_DEPENDS src/lib/*.c)
|
||||
# libnotcurses (core shared library and static library)
|
||||
file(GLOB NCSRCS CONFIGURE_DEPENDS src/lib/*.c src/lib/*.cpp)
|
||||
add_library(notcurses SHARED ${NCSRCS})
|
||||
add_library(notcurses-static STATIC ${NCSRCS})
|
||||
set_target_properties(
|
||||
@ -599,7 +599,7 @@ if(${USE_PYTHON})
|
||||
OUTPUT
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/build/pytimestamp"
|
||||
COMMAND
|
||||
env LDFLAGS="-L${CMAKE_CURRENT_BINARY_DIR}" "${Python3_EXECUTABLE}" ${SETUP_PY} build &&
|
||||
env LDFLAGS="-Wl,-soname,_notcurses.so.1 -L${CMAKE_CURRENT_BINARY_DIR}" "${Python3_EXECUTABLE}" ${SETUP_PY} build &&
|
||||
"${Python3_EXECUTABLE}" ${SETUP_PY} egg_info
|
||||
DEPENDS
|
||||
${PYSRC} ${SETUP_PY} ${SETUP_CFG} notcurses
|
||||
|
@ -12,11 +12,13 @@ notcurses_plot - high level widget for plotting
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
NCPLOT_1x1, // full block █
|
||||
NCPLOT_2x1, // full/lower blocks █▄
|
||||
NCPLOT_1x1x4, // shaded full blocks █▓▒░
|
||||
NCPLOT_4x1, // four vert levels █▆▄▂
|
||||
NCPLOT_8x1, // eight vert levels █▇▆▅▄▃▂▁
|
||||
NCPLOT_1x1, // full block █
|
||||
NCPLOT_2x1, // full/(upper|left) blocks ▄█
|
||||
NCPLOT_1x1x4, // shaded full blocks ▓▒░█
|
||||
NCPLOT_2x2, // quadrants ▗▐ ▖▄▟▌▙█
|
||||
NCPLOT_4x1, // four vert/horz levels █▆▄▂ / ▎▌▊█
|
||||
NCPLOT_4x2, // 4x2-way braille ⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿
|
||||
NCPLOT_8x1, // eight vert/horz levels █▇▆▅▄▃▂▁ / ▏▎▍▌▋▊▉█
|
||||
} ncgridgeom_e;
|
||||
|
||||
typedef struct ncplot_options {
|
||||
@ -28,38 +30,74 @@ typedef struct ncplot_options {
|
||||
ncgridgeom_e gridtype;
|
||||
// independent variable is a contiguous range
|
||||
int rangex;
|
||||
// dependent min and max. set both equal to 0 to
|
||||
// use domain autodiscovery.
|
||||
uint64_t miny, maxy;
|
||||
bool labelaxisd; // label dependent axis
|
||||
bool exponentialy; // is dependent exponential?
|
||||
bool exponentially; // is dependent exponential?
|
||||
bool vertical_indep; // vertical independent variable
|
||||
} ncplot_options;
|
||||
```
|
||||
|
||||
**struct ncplot* ncplot_create(struct ncplane* n, const ncplot_options* opts);**
|
||||
**struct ncuplot* ncuplot_create(struct ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy);**
|
||||
**struct ncdplot* ncdplot_create(struct ncplane* n, const ncplot_options* opts, double miny, double maxy);**
|
||||
|
||||
**struct ncplane* ncplot_plane(struct ncplot* n);**
|
||||
**struct ncplane* ncuplot_plane(struct ncuplot* n);**
|
||||
**struct ncplane* ncdplot_plane(struct ncdplot* n);**
|
||||
|
||||
**int ncplot_add_sample(struct ncplot* n, uint64_t x, uint64_t y);**
|
||||
**int ncplot_set_sample(struct ncplot* n, uint64_t x, uint64_t y);**
|
||||
**int ncuplot_add_sample(struct ncuplot* n, uint64_t x, uint64_t y);**
|
||||
**int ncdplot_add_sample(struct ncdplot* n, uint64_t x, double y);**
|
||||
|
||||
**void ncplot_destroy(struct ncplot* n);**
|
||||
**int ncuplot_set_sample(struct ncuplot* n, uint64_t x, uint64_t y);**
|
||||
**int ncdplot_set_sample(struct ncdplot* n, uint64_t x, double y);**
|
||||
|
||||
**void ncuplot_destroy(struct ncuplot* n);**
|
||||
**void ncdplot_destroy(struct ncdplot* n);**
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
These functions support histograms. The independent variable is always an
|
||||
**uint64_t**. The samples are either **uint64_t**s (**ncuplot**) or **double**s
|
||||
(**ncdplot**). Only a window over the samples is retained at any given time,
|
||||
and this window can only move towards larger values of the independent
|
||||
variable. The window is moved forward whenever an **x** larger than the current
|
||||
window's maximum is supplied to **add_sample** or **set_sample**.
|
||||
|
||||
**add_sample** increments the current value corresponding to this **x** by
|
||||
**y**. **set_sample** replaces the current value corresponding to this **x**.
|
||||
|
||||
If **rangex** is 0, or larger than the bound plane will support, it is capped
|
||||
to the available space. The domain can either be specified as **miny** and
|
||||
**maxy**, or domain autodetection can be invoked via setting both to 0. If the
|
||||
domain is specified, samples outside the domain are an error, and do not
|
||||
contribute to the plot. Supplying an **x** below the current window is an
|
||||
error, and has no effect.
|
||||
|
||||
The different **ncgridgeom_e** values select from among available glyph sets:
|
||||
|
||||
* **NCPLOT_1x1**: Full block (█) or empty glyph
|
||||
* **NCPLOT_2x1**: Adds the lower half block (▄) to **NCPLOT_1x1**.
|
||||
* **NCPLOT_1x1x4**: Adds three shaded full blocks (▓▒░) to **NCPLOT_1x1**.
|
||||
* **NCPLOT_2x2**: Adds left and right half blocks (▌▐) and quadrants (▖▗▟▙) to **NCPLOT_2x1**.
|
||||
* **NCPLOT_4x1**: Adds ¼ and ¾ blocks (▂▆) to **NCPLOT_2x1**.
|
||||
* **NCPLOT_4x2**: 4 rows and 2 columns of braille (⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿).
|
||||
* **NCPLOT_8x1**: Adds ⅛, ⅜, ⅝, and ⅞ blocks (▇▅▃▁) to **NCPLOT_4x1**.
|
||||
|
||||
More granular block glyphs means more resolution in your plots, but they can
|
||||
be difficult to differentiate at small text sizes. Quadrants and braille allow
|
||||
for more resolution on the independent variable. It can be difficult to predict
|
||||
how the braille glyphs will look in a given font.
|
||||
|
||||
The same **ncplot_options** struct can be used with all ncplot types.
|
||||
|
||||
# NOTES
|
||||
|
||||
Neither **exponentialy** not **vertical_indep** is yet implemented.
|
||||
Neither **exponentially** not **vertical_indep** is yet implemented.
|
||||
|
||||
# RETURN VALUES
|
||||
|
||||
**ncplot_create** will return an error if **miny** equals **maxy**, but they
|
||||
are non-zero. It will also return an error if **maxy** < **miny**. An invalid
|
||||
**create** will return an error if **miny** equals **maxy**, but they are
|
||||
non-zero. It will also return an error if **maxy** < **miny**. An invalid
|
||||
**gridtype** will result in an error.
|
||||
|
||||
**ncplot_plane** returns the **ncplane** on which the plot is drawn. It cannot
|
||||
fail.
|
||||
**plane** returns the **ncplane** on which the plot is drawn. It cannot fail.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
|
@ -32,12 +32,13 @@ namespace ncpp
|
||||
: Plot (const_cast<Plane*>(&plane), opts)
|
||||
{}
|
||||
|
||||
explicit Plot (ncplane *plane, const ncplot_options *opts = nullptr)
|
||||
explicit Plot (ncplane *plane, const ncplot_options *opts = nullptr,
|
||||
uint64_t miny = 0, uint64_t maxy = 0)
|
||||
{
|
||||
if (plane == nullptr)
|
||||
throw invalid_argument ("'plane' must be a valid pointer");
|
||||
|
||||
plot = ncplot_create (plane, opts == nullptr ? &default_options : opts);
|
||||
plot = ncuplot_create (plane, opts == nullptr ? &default_options : opts, miny, maxy);
|
||||
if (plot == nullptr)
|
||||
throw init_error ("notcurses failed to create a new plot");
|
||||
}
|
||||
@ -45,23 +46,23 @@ namespace ncpp
|
||||
~Plot ()
|
||||
{
|
||||
if (!is_notcurses_stopped ())
|
||||
ncplot_destroy (plot);
|
||||
ncuplot_destroy (plot);
|
||||
}
|
||||
|
||||
bool add_sample(uint64_t x, uint64_t y) const NOEXCEPT_MAYBE
|
||||
{
|
||||
return error_guard (ncplot_add_sample (plot, x, y), -1);
|
||||
return error_guard (ncuplot_add_sample (plot, x, y), -1);
|
||||
}
|
||||
|
||||
bool set_sample(uint64_t x, uint64_t y) const NOEXCEPT_MAYBE
|
||||
{
|
||||
return error_guard (ncplot_set_sample (plot, x, y), -1);
|
||||
return error_guard (ncuplot_set_sample (plot, x, y), -1);
|
||||
}
|
||||
|
||||
Plane* get_plane () const noexcept;
|
||||
|
||||
private:
|
||||
ncplot *plot;
|
||||
ncuplot *plot;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
@ -31,7 +31,8 @@ struct notcurses; // notcurses state for a given terminal, composed of ncplanes
|
||||
struct ncplane; // a drawable notcurses surface, composed of cells
|
||||
struct cell; // a coordinate on an ncplane: an EGC plus styling
|
||||
struct ncvisual; // a visual bit of multimedia opened with LibAV
|
||||
struct ncplot; // a histogram, bound to a plane
|
||||
struct ncuplot; // a histogram, bound to a plane (uint64_ts)
|
||||
struct ncdplot; // a histogram, bound to a plane (non-negative doubles)
|
||||
struct ncfdplane; // i/o wrapper to dump file descriptor to plane
|
||||
struct ncsubproc; // ncfdplane wrapper with subprocess management
|
||||
struct ncselector;// widget supporting selecting 1 from a list of options
|
||||
@ -2618,6 +2619,9 @@ typedef enum {
|
||||
//
|
||||
// The 20 levels at first is a special case. When the domain is only 1 unit,
|
||||
// and autoscaling is in play, assign 50%.
|
||||
//
|
||||
// This options structure works for both the ncuplot (uint64_t) and ncdplot
|
||||
// (double) types.
|
||||
typedef struct ncplot_options {
|
||||
// channels for the maximum and minimum levels. linear interpolation will be
|
||||
// applied across the domain between these two.
|
||||
@ -2629,28 +2633,35 @@ typedef struct ncplot_options {
|
||||
// resolution, the independent variable would be the range [0..3600): 3600.
|
||||
// if rangex is 0, it is dynamically set to the number of columns.
|
||||
int rangex;
|
||||
uint64_t miny, maxy; // y axis min and max. for autodiscovery, set them equal.
|
||||
bool labelaxisd; // generate labels for the dependent axis
|
||||
bool exponentialy; // is y-axis exponential? (not yet implemented)
|
||||
bool exponentially; // is y-axis exponential? (not yet implemented)
|
||||
// independent variable is vertical rather than horizontal
|
||||
bool vertical_indep;
|
||||
} ncplot_options;
|
||||
|
||||
// Use the provided plane 'n' for plotting according to the options 'opts'.
|
||||
// The plot will make free use of the entirety of the plane.
|
||||
API struct ncplot* ncplot_create(struct ncplane* n, const ncplot_options* opts);
|
||||
// for domain autodiscovery, set miny == maxy == 0.
|
||||
API struct ncuplot* ncuplot_create(struct ncplane* n, const ncplot_options* opts,
|
||||
uint64_t miny, uint64_t maxy);
|
||||
API struct ncdplot* ncdplot_create(struct ncplane* n, const ncplot_options* opts,
|
||||
double miny, double maxy);
|
||||
|
||||
// Return a reference to the ncplot's underlying ncplane.
|
||||
API struct ncplane* ncplot_plane(struct ncplot* n);
|
||||
API struct ncplane* ncuplot_plane(struct ncuplot* n);
|
||||
API struct ncplane* ncdplot_plane(struct ncdplot* n);
|
||||
|
||||
// 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.
|
||||
API int ncplot_add_sample(struct ncplot* n, uint64_t x, uint64_t y);
|
||||
API int ncplot_set_sample(struct ncplot* n, uint64_t x, uint64_t y);
|
||||
API int ncuplot_add_sample(struct ncuplot* n, uint64_t x, uint64_t y);
|
||||
API int ncdplot_add_sample(struct ncdplot* n, uint64_t x, double y);
|
||||
API int ncuplot_set_sample(struct ncuplot* n, uint64_t x, uint64_t y);
|
||||
API int ncdplot_set_sample(struct ncdplot* n, uint64_t x, double y);
|
||||
|
||||
API void ncplot_destroy(struct ncplot* n);
|
||||
API void ncuplot_destroy(struct ncuplot* n);
|
||||
API void ncdplot_destroy(struct ncdplot* n);
|
||||
|
||||
typedef int(*ncfdplane_callback)(struct ncfdplane* n, const void* buf, size_t s, void* curry);
|
||||
typedef int(*ncfdplane_done_cb)(struct ncfdplane* n, int fderrno, void* curry);
|
||||
|
@ -415,26 +415,32 @@ void ncplane_translate(const struct ncplane* src, const struct ncplane* dst, int
|
||||
bool ncplane_translate_abs(const struct ncplane* n, int* y, int* x);
|
||||
typedef enum {
|
||||
NCPLOT_1x1, // full block █
|
||||
NCPLOT_2x1, // full/(upper|left) blocks █▀
|
||||
NCPLOT_1x1x4, // shaded full blocks █▓▒░
|
||||
NCPLOT_2x1, // full/(upper|left) blocks ▄█
|
||||
NCPLOT_1x1x4, // shaded full blocks ▓▒░█
|
||||
NCPLOT_2x2, // quadrants ▗▐ ▖▄▟▌▙█
|
||||
NCPLOT_4x1, // four vert/horz levels █▆▄▂ / ▎▌▊█
|
||||
NCPLOT_8x1, // eight vert/horz levels █▇▆▅▄▃▂▁ / ▏▎▍▌▋▊▉█
|
||||
NCPLOT_4x2, // 4 rows, 2 cols (braille) ⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿
|
||||
NCPLOT_8x1, // eight vert/horz levels █▇▆▅▄▃▂▁ / ▏▎▍▌▋▊▉█
|
||||
} ncgridgeom_e;
|
||||
typedef struct ncplot_options {
|
||||
uint64_t maxchannel;
|
||||
uint64_t minchannel;
|
||||
ncgridgeom_e gridtype;
|
||||
uint64_t rangex;
|
||||
uint64_t miny, maxy;
|
||||
bool labelaxisd;
|
||||
bool exponentialy;
|
||||
bool exponentially;
|
||||
bool vertical_indep;
|
||||
} ncplot_options;
|
||||
struct ncplot* ncplot_create(struct ncplane* n, const ncplot_options* opts);
|
||||
struct ncplane* ncplot_plane(struct ncplot* n);
|
||||
int ncplot_add_sample(struct ncplot* n, uint64_t x, uint64_t y);
|
||||
int ncplot_set_sample(struct ncplot* n, uint64_t x, uint64_t y);
|
||||
void ncplot_destroy(struct ncplot* n);
|
||||
struct ncuplot* ncuplot_create(struct ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy);
|
||||
struct ncdplot* ncdplot_create(struct ncplane* n, const ncplot_options* opts, double miny, double maxy);
|
||||
struct ncplane* ncuplot_plane(struct ncuplot* n);
|
||||
struct ncplane* ncdplot_plane(struct ncdplot* n);
|
||||
int ncuplot_add_sample(struct ncuplot* n, uint64_t x, uint64_t y);
|
||||
int ncdplot_add_sample(struct ncdplot* n, uint64_t x, double y);
|
||||
int ncuplot_set_sample(struct ncuplot* n, uint64_t x, uint64_t y);
|
||||
int ncdplot_set_sample(struct ncdplot* n, uint64_t x, double y);
|
||||
void ncuplot_destroy(struct ncuplot* n);
|
||||
void ncdplot_destroy(struct ncdplot* n);
|
||||
bool ncplane_set_scrolling(struct ncplane* n, bool scrollp);
|
||||
""")
|
||||
|
||||
|
@ -29,7 +29,7 @@ std::mutex mtx;
|
||||
uint64_t start;
|
||||
static int dimy, dimx;
|
||||
std::atomic<bool> done;
|
||||
static struct ncplot* plot;
|
||||
static struct ncuplot* plot;
|
||||
|
||||
// return the string version of a special composed key
|
||||
const char* nckeystr(char32_t spkey){
|
||||
@ -187,7 +187,7 @@ dim_rows(const Plane* n){
|
||||
|
||||
void Tick(ncpp::NotCurses* nc, uint64_t sec) {
|
||||
const std::lock_guard<std::mutex> lock(mtx);
|
||||
if(ncplot_add_sample(plot, sec, 0)){
|
||||
if(ncuplot_add_sample(plot, sec, 0)){
|
||||
throw std::runtime_error("couldn't register timetick");
|
||||
}
|
||||
if(!nc->render()){
|
||||
@ -220,7 +220,7 @@ int main(void){
|
||||
channels_set_fg_rgb(&popts.minchannel, 0x40, 0x50, 0xb0);
|
||||
channels_set_fg_rgb(&popts.maxchannel, 0x40, 0xff, 0xd0);
|
||||
popts.gridtype = static_cast<ncgridgeom_e>(NCPLOT_2x2);
|
||||
plot = ncplot_create(pplane, &popts);
|
||||
plot = ncuplot_create(pplane, &popts, 0, 0);
|
||||
if(!plot){
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@ -295,7 +295,7 @@ int main(void){
|
||||
}
|
||||
const uint64_t sec = (timenow_to_ns() - start) / NANOSECS_IN_SEC;
|
||||
mtx.lock();
|
||||
if(ncplot_add_sample(plot, sec, 1)){
|
||||
if(ncuplot_add_sample(plot, sec, 1)){
|
||||
mtx.unlock();
|
||||
break;
|
||||
}
|
||||
|
303
src/lib/cpp.h
Normal file
303
src/lib/cpp.h
Normal file
@ -0,0 +1,303 @@
|
||||
#include "notcurses/notcurses.h"
|
||||
#include <limits>
|
||||
|
||||
static const struct {
|
||||
ncgridgeom_e geom;
|
||||
int width;
|
||||
int height;
|
||||
// the EGCs which form the various levels of a given geometry. if the geometry
|
||||
// is wide, things are arranged with the rightmost side increasing most
|
||||
// quickly, i.e. it can be indexed as height arrays of 1 + height glyphs. i.e.
|
||||
// the first five braille EGCs are all 0 on the left, [0..4] on the right.
|
||||
const wchar_t* egcs;
|
||||
bool fill;
|
||||
} geomdata[] = {
|
||||
{ .geom = NCPLOT_1x1, .width = 1, .height = 2, .egcs = L" █", .fill = false, },
|
||||
{ .geom = NCPLOT_2x1, .width = 1, .height = 3, .egcs = L" ▄█", .fill = false, },
|
||||
{ .geom = NCPLOT_1x1x4, .width = 1, .height = 5, .egcs = L" ▒░▓█", .fill = false, },
|
||||
{ .geom = NCPLOT_2x2, .width = 2, .height = 3, .egcs = L" ▗▐▖▄▟▌▙█", .fill = false, },
|
||||
{ .geom = NCPLOT_4x1, .width = 1, .height = 5, .egcs = L" ▂▄▆█", .fill = false, },
|
||||
{ .geom = NCPLOT_4x2, .width = 2, .height = 5, .egcs = L"⠀⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿", .fill = true, },
|
||||
{ .geom = NCPLOT_8x1, .width = 1, .height = 9, .egcs = L" ▁▂▃▄▅▆▇█", .fill = false, },
|
||||
};
|
||||
|
||||
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){
|
||||
// if miny == maxy, they both must be equal to 0
|
||||
if(miny == maxy && miny){
|
||||
return NULL;
|
||||
}
|
||||
if(opts->rangex < 0){
|
||||
return NULL;
|
||||
}
|
||||
if(maxy < miny){
|
||||
return NULL;
|
||||
}
|
||||
if(opts->gridtype < 0 || opts->gridtype >= sizeof(geomdata) / sizeof(*geomdata)){
|
||||
return NULL;
|
||||
}
|
||||
int sdimy, sdimx;
|
||||
ncplane_dim_yx(n, &sdimy, &sdimx);
|
||||
if(sdimx <= 0){
|
||||
return NULL;
|
||||
}
|
||||
int dimx = sdimx;
|
||||
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 * geomdata[opts->gridtype].width;
|
||||
const int scaledprefixlen = PREFIXSTRLEN * geomdata[opts->gridtype].width;
|
||||
if((ncpp->slotcount = ncpp->rangex) == 0){
|
||||
ncpp->slotcount = scaleddim;
|
||||
}
|
||||
if(dimx < ncpp->rangex){
|
||||
ncpp->slotcount = scaleddim;
|
||||
}
|
||||
if( (ncpp->labelaxisd = opts->labelaxisd) ){
|
||||
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){
|
||||
memset(ncpp->slots, 0, slotsize);
|
||||
ncpp->ncp = n;
|
||||
ncpp->maxchannel = opts->maxchannel;
|
||||
ncpp->minchannel = opts->minchannel;
|
||||
ncpp->miny = miny;
|
||||
ncpp->maxy = maxy;
|
||||
ncpp->vertical_indep = opts->vertical_indep;
|
||||
ncpp->gridtype = opts->gridtype;
|
||||
ncpp->exponentially = opts->exponentially;
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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(uint64_t x, T y){
|
||||
if(window_slide(x)){
|
||||
return -1;
|
||||
}
|
||||
update_sample(x, y, false);
|
||||
if(update_domain(x)){
|
||||
return -1;
|
||||
}
|
||||
return redraw_plot();
|
||||
}
|
||||
|
||||
int set_sample(uint64_t x, T y){
|
||||
if(window_slide(x)){
|
||||
return -1;
|
||||
}
|
||||
update_sample(x, y, true);
|
||||
if(update_domain(x)){
|
||||
return -1;
|
||||
}
|
||||
return redraw_plot();
|
||||
}
|
||||
|
||||
void destroy(){
|
||||
free(slots);
|
||||
}
|
||||
|
||||
// FIXME everything below here ought be private, but it busts unit tests
|
||||
int redraw_plot(){
|
||||
ncplane_erase(ncp);
|
||||
const int scale = geomdata[gridtype].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 = geomdata[gridtype].height;
|
||||
// FIXME can we not rid ourselves of this meddlesome double?
|
||||
double interval = maxy < miny ? 0 : (maxy - miny) / ((double)dimy * states);
|
||||
const int startx = labelaxisd ? PREFIXSTRLEN : 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);
|
||||
if(labelaxisd){
|
||||
// show the *top* of each interval range
|
||||
for(int y = 0 ; y < dimy ; ++y){
|
||||
char buf[PREFIXSTRLEN + 1];
|
||||
ncmetric(interval * states * (y + 1) * 100, 100, buf, 0, 1000, '\0');
|
||||
ncplane_putstr_yx(ncp, dimy - y - 1, PREFIXSTRLEN - strlen(buf), buf);
|
||||
}
|
||||
}
|
||||
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){
|
||||
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.
|
||||
double intervalbase = miny;
|
||||
const wchar_t* egc = geomdata[gridtype].egcs;
|
||||
for(int y = 0 ; y < dimy ; ++y){
|
||||
size_t egcidx, 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*
|
||||
bool done = !geomdata[gridtype].fill;
|
||||
for(int i = 0 ; i < scale ; ++i){
|
||||
sumidx *= states;
|
||||
if(intervalbase < gvals[i]){
|
||||
egcidx = (gvals[i] - intervalbase) / interval;
|
||||
if(egcidx >= states){
|
||||
egcidx = states - 1;
|
||||
}
|
||||
done = false;
|
||||
sumidx += egcidx;
|
||||
}else{
|
||||
egcidx = 0;
|
||||
}
|
||||
}
|
||||
if(done){
|
||||
break;
|
||||
}
|
||||
if(ncplane_putwc_yx(ncp, dimy - y - 1, x, egc[sumidx]) <= 0){
|
||||
return -1;
|
||||
}
|
||||
intervalbase += (states * interval);
|
||||
}
|
||||
}
|
||||
if(ncplane_cursor_move_yx(ncp, 0, 0)){
|
||||
return -1;
|
||||
}
|
||||
if(ncplane_stain(ncp, dimy - 1, dimx - 1, maxchannel, maxchannel,
|
||||
minchannel, minchannel) <= 0){
|
||||
return -1;
|
||||
}
|
||||
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.
|
||||
int update_domain(uint64_t x){
|
||||
const uint64_t val = slots[x % slotcount];
|
||||
if(detectdomain){
|
||||
if(val > maxy){
|
||||
maxy = val;
|
||||
}
|
||||
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.
|
||||
int window_slide(int64_t x){
|
||||
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;
|
||||
uint64_t maxchannel;
|
||||
uint64_t minchannel;
|
||||
bool vertical_indep; // not yet implemented FIXME
|
||||
ncgridgeom_e gridtype;
|
||||
// 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;
|
||||
// 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;
|
||||
int slotcount;
|
||||
int slotstart; // index of most recently-written slot
|
||||
int64_t slotx; // x value corresponding to slots[slotstart] (newest x)
|
||||
bool labelaxisd; // label dependent axis (consumes PREFIXSTRLEN columns)
|
||||
bool exponentially; // not yet implemented FIXME
|
||||
bool detectdomain; // is domain detection in effect (stretch the domain)?
|
||||
|
||||
};
|
@ -160,33 +160,6 @@ typedef struct ncmenu_int_section {
|
||||
int shortcut_offset; // column offset within name of shortcut EGC
|
||||
} ncmenu_int_section;
|
||||
|
||||
typedef struct ncplot {
|
||||
ncplane* ncp;
|
||||
uint64_t maxchannel;
|
||||
uint64_t minchannel;
|
||||
bool vertical_indep; // not yet implemented FIXME
|
||||
ncgridgeom_e gridtype;
|
||||
// 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.
|
||||
uint64_t miny, maxy;
|
||||
// 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.
|
||||
uint64_t* slots;
|
||||
int slotcount;
|
||||
int slotstart; // index of most recently-written slot
|
||||
int64_t slotx; // x value corresponding to slots[slotstart] (newest x)
|
||||
bool labelaxisd; // label dependent axis (consumes PREFIXSTRLEN columns)
|
||||
bool exponentialy; // not yet implemented FIXME
|
||||
bool detectdomain; // is domain detection in effect (stretch the domain)?
|
||||
} ncplot;
|
||||
|
||||
typedef struct ncfdplane {
|
||||
ncfdplane_callback cb; // invoked with fresh hot data
|
||||
ncfdplane_done_cb donecb; // invoked on EOF (if !follow) or error
|
||||
|
284
src/lib/plot.c
284
src/lib/plot.c
@ -1,284 +0,0 @@
|
||||
#include "internal.h"
|
||||
|
||||
static const struct {
|
||||
ncgridgeom_e geom;
|
||||
int width;
|
||||
int height;
|
||||
// the EGCs which form the various levels of a given geometry. if the geometry
|
||||
// is wide, things are arranged with the rightmost side increasing most
|
||||
// quickly, i.e. it can be indexed as height arrays of 1 + height glyphs. i.e.
|
||||
// the first five braille EGCs are all 0 on the left, [0..4] on the right.
|
||||
const wchar_t* egcs;
|
||||
bool fill;
|
||||
} geomdata[] = {
|
||||
{ .geom = NCPLOT_1x1, .width = 1, .height = 2, .egcs = L" █", .fill = false, },
|
||||
{ .geom = NCPLOT_2x1, .width = 1, .height = 3, .egcs = L" ▄█", .fill = false, },
|
||||
{ .geom = NCPLOT_1x1x4, .width = 1, .height = 5, .egcs = L" ▒░▓█", .fill = false, },
|
||||
{ .geom = NCPLOT_2x2, .width = 2, .height = 3, .egcs = L" ▗▐▖▄▟▌▙█", .fill = false, },
|
||||
{ .geom = NCPLOT_4x1, .width = 1, .height = 5, .egcs = L" ▂▄▆█", .fill = false, },
|
||||
{ .geom = NCPLOT_4x2, .width = 2, .height = 5, .egcs = L"⠀⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿", .fill = true, },
|
||||
{ .geom = NCPLOT_8x1, .width = 1, .height = 9, .egcs = L" ▁▂▃▄▅▆▇█", .fill = false, },
|
||||
};
|
||||
|
||||
static int
|
||||
redraw_plot(ncplot* n){
|
||||
ncplane_erase(ncplot_plane(n));
|
||||
const int scale = geomdata[n->gridtype].width;
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(ncplot_plane(n), &dimy, &dimx);
|
||||
const int scaleddim = dimx * scale;
|
||||
// each transition is worth this much change in value
|
||||
const size_t states = geomdata[n->gridtype].height;
|
||||
// FIXME can we not rid ourselves of this meddlesome double?
|
||||
double interval = n->maxy < n->miny ? 0 : (n->maxy - n->miny) / ((double)dimy * states);
|
||||
const int startx = n->labelaxisd ? PREFIXSTRLEN : 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 = (n->slotcount < scaleddim - 1 - (startx * scale) ? startx + (n->slotcount / scale) - 1 : dimx - 1);
|
||||
if(n->labelaxisd){
|
||||
// show the *top* of each interval range
|
||||
for(int y = 0 ; y < dimy ; ++y){
|
||||
char buf[PREFIXSTRLEN + 1];
|
||||
ncmetric(interval * states * (y + 1) * 100, 100, buf, 0, 1000, '\0');
|
||||
ncplane_putstr_yx(ncplot_plane(n), dimy - y - 1, PREFIXSTRLEN - strlen(buf), buf);
|
||||
}
|
||||
}
|
||||
if(finalx < startx){ // exit on pathologically narrow planes
|
||||
return 0;
|
||||
}
|
||||
if(!interval){
|
||||
interval = 1;
|
||||
}
|
||||
#define MAXWIDTH 2
|
||||
int idx = n->slotstart; // idx holds the real slot index; we move backwards
|
||||
for(int x = finalx ; x >= startx ; --x){
|
||||
uint64_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] = n->slots[idx]; // clip the value at the limits of the graph
|
||||
if(gvals[i] < n->miny){
|
||||
gvals[i] = n->miny;
|
||||
}
|
||||
if(gvals[i] > n->maxy){
|
||||
gvals[i] = n->maxy;
|
||||
}
|
||||
// FIXME if there are an odd number, only go up through the valid ones...
|
||||
if(--idx < 0){
|
||||
idx = n->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.
|
||||
double intervalbase = n->miny;
|
||||
const wchar_t* egc = geomdata[n->gridtype].egcs;
|
||||
for(int y = 0 ; y < dimy ; ++y){
|
||||
size_t egcidx, 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*
|
||||
bool done = !geomdata[n->gridtype].fill;
|
||||
for(int i = 0 ; i < scale ; ++i){
|
||||
sumidx *= states;
|
||||
if(intervalbase < gvals[i]){
|
||||
egcidx = (gvals[i] - intervalbase) / interval;
|
||||
if(egcidx >= states){
|
||||
egcidx = states - 1;
|
||||
}
|
||||
done = false;
|
||||
sumidx += egcidx;
|
||||
}else{
|
||||
egcidx = 0;
|
||||
}
|
||||
}
|
||||
if(done){
|
||||
break;
|
||||
}
|
||||
if(ncplane_putwc_yx(ncplot_plane(n), dimy - y - 1, x, egc[sumidx]) <= 0){
|
||||
return -1;
|
||||
}
|
||||
intervalbase += (states * interval);
|
||||
}
|
||||
}
|
||||
if(ncplane_cursor_move_yx(ncplot_plane(n), 0, 0)){
|
||||
return -1;
|
||||
}
|
||||
if(ncplane_stain(ncplot_plane(n), dimy - 1, dimx - 1, n->maxchannel,
|
||||
n->maxchannel, n->minchannel, n->minchannel) <= 0){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ncplot* ncplot_create(ncplane* n, const ncplot_options* opts){
|
||||
// if miny == maxy, they both must be equal to 0
|
||||
if(opts->miny == opts->maxy && opts->miny){
|
||||
return NULL;
|
||||
}
|
||||
if(opts->rangex < 0){
|
||||
return NULL;
|
||||
}
|
||||
if(opts->maxy < opts->miny){
|
||||
return NULL;
|
||||
}
|
||||
if(opts->gridtype < 0 || opts->gridtype >= sizeof(geomdata) / sizeof(*geomdata)){
|
||||
return NULL;
|
||||
}
|
||||
int sdimy, sdimx;
|
||||
ncplane_dim_yx(n, &sdimy, &sdimx);
|
||||
if(sdimx <= 0){
|
||||
return NULL;
|
||||
}
|
||||
int dimx = sdimx;
|
||||
ncplot* ret = malloc(sizeof(*ret));
|
||||
if(ret){
|
||||
ret->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 * geomdata[opts->gridtype].width;
|
||||
const int scaledprefixlen = PREFIXSTRLEN * geomdata[opts->gridtype].width;
|
||||
if((ret->slotcount = ret->rangex) == 0){
|
||||
ret->slotcount = scaleddim;
|
||||
}
|
||||
if(dimx < ret->rangex){
|
||||
ret->slotcount = scaleddim;
|
||||
}
|
||||
if( (ret->labelaxisd = opts->labelaxisd) ){
|
||||
if(ret->slotcount + scaledprefixlen > scaleddim){
|
||||
if(scaleddim > scaledprefixlen){
|
||||
ret->slotcount = scaleddim - scaledprefixlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t slotsize = sizeof(*ret->slots) * ret->slotcount;
|
||||
ret->slots = malloc(slotsize);
|
||||
if(ret->slots){
|
||||
memset(ret->slots, 0, slotsize);
|
||||
ret->ncp = n;
|
||||
ret->maxchannel = opts->maxchannel;
|
||||
ret->minchannel = opts->minchannel;
|
||||
ret->miny = opts->miny;
|
||||
ret->maxy = opts->maxy;
|
||||
ret->vertical_indep = opts->vertical_indep;
|
||||
ret->gridtype = opts->gridtype;
|
||||
ret->exponentialy = opts->exponentialy;
|
||||
if( (ret->detectdomain = (opts->miny == opts->maxy)) ){
|
||||
ret->maxy = 0;
|
||||
ret->miny = ~(uint64_t)0ull;
|
||||
}
|
||||
ret->slotstart = 0;
|
||||
ret->slotx = 0;
|
||||
redraw_plot(ret);
|
||||
return ret;
|
||||
}
|
||||
free(ret);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ncplane* ncplot_plane(ncplot* n){
|
||||
return n->ncp;
|
||||
}
|
||||
|
||||
// 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.
|
||||
static inline int
|
||||
update_domain(ncplot* n, uint64_t x){
|
||||
const uint64_t val = n->slots[x % n->slotcount];
|
||||
if(n->detectdomain){
|
||||
if(val > n->maxy){
|
||||
n->maxy = val;
|
||||
}
|
||||
if(val < n->miny){
|
||||
n->miny = val;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if(val > n->maxy || val < n->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.
|
||||
static inline int
|
||||
window_slide(ncplot* n, int64_t x){
|
||||
if(x < n->slotx - (n->slotcount - 1)){ // x is behind window, won't be counted
|
||||
return -1;
|
||||
}else if(x <= n->slotx){ // x is within window, do nothing
|
||||
return 0;
|
||||
} // x is newest; we might be keeping some, might not
|
||||
int64_t xdiff = x - n->slotx; // the raw amount we're advancing
|
||||
n->slotx = x;
|
||||
if(xdiff >= n->slotcount){ // we're throwing away all old samples, write to 0
|
||||
memset(n->slots, 0, sizeof(*n->slots) * n->slotcount);
|
||||
n->slotstart = 0;
|
||||
return 0;
|
||||
}
|
||||
// we're throwing away only xdiff slots, which is less than n->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 = n->slotcount - n->slotstart - 1;
|
||||
if(slotsreset > xdiff){
|
||||
slotsreset = xdiff;
|
||||
}
|
||||
if(slotsreset){
|
||||
memset(n->slots + n->slotstart + 1, 0, slotsreset * sizeof(*n->slots));
|
||||
}
|
||||
n->slotstart = (n->slotstart + xdiff) % n->slotcount;
|
||||
xdiff -= slotsreset;
|
||||
if(xdiff){ // throw away some at the beginning
|
||||
memset(n->slots, 0, xdiff * sizeof(*n->slots));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// x must be within n's window at this point
|
||||
static inline void
|
||||
update_sample(ncplot* n, int64_t x, uint64_t y, bool reset){
|
||||
const int64_t diff = n->slotx - x; // amount behind
|
||||
const int idx = (n->slotstart + n->slotcount - diff) % n->slotcount;
|
||||
if(reset){
|
||||
n->slots[idx] = y;
|
||||
}else{
|
||||
n->slots[idx] += y;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ncplot_add_sample(ncplot* n, uint64_t x, uint64_t y){
|
||||
if(window_slide(n, x)){
|
||||
return -1;
|
||||
}
|
||||
update_sample(n, x, y, false);
|
||||
if(update_domain(n, x)){
|
||||
return -1;
|
||||
}
|
||||
return redraw_plot(n);
|
||||
}
|
||||
|
||||
int ncplot_set_sample(ncplot* n, uint64_t x, uint64_t y){
|
||||
if(window_slide(n, x)){
|
||||
return -1;
|
||||
}
|
||||
update_sample(n, x, y, true);
|
||||
if(update_domain(n, x)){
|
||||
return -1;
|
||||
}
|
||||
return redraw_plot(n);
|
||||
}
|
||||
|
||||
void ncplot_destroy(ncplot* n){
|
||||
if(n){
|
||||
free(n->slots);
|
||||
free(n);
|
||||
}
|
||||
}
|
73
src/lib/plot.cpp
Normal file
73
src/lib/plot.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include "cpp.h"
|
||||
|
||||
typedef struct ncuplot {
|
||||
ncppplot<uint64_t> n;
|
||||
} ncuplot;
|
||||
|
||||
typedef struct ncdplot {
|
||||
ncppplot<double> n;
|
||||
} ncdplot;
|
||||
|
||||
extern "C" {
|
||||
|
||||
ncuplot* ncuplot_create(ncplane* n, const ncplot_options* opts, uint64_t miny, uint64_t maxy){
|
||||
auto ret = new ncuplot;
|
||||
if(ret){
|
||||
if(ncppplot<uint64_t>::create(&ret->n, n, opts, miny, maxy)){
|
||||
return ret;
|
||||
}
|
||||
free(ret);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ncplane* ncuplot_plane(ncuplot* n){
|
||||
return n->n.ncp;
|
||||
}
|
||||
|
||||
int ncuplot_add_sample(ncuplot* n, uint64_t x, uint64_t y){
|
||||
return n->n.add_sample(x, y);
|
||||
}
|
||||
|
||||
int ncuplot_set_sample(ncuplot* n, uint64_t x, uint64_t y){
|
||||
return n->n.set_sample(x, y);
|
||||
}
|
||||
|
||||
void ncuplot_destroy(ncuplot* n){
|
||||
if(n){
|
||||
n->n.destroy();
|
||||
delete n;
|
||||
}
|
||||
}
|
||||
|
||||
ncdplot* ncdplot_create(ncplane* n, const ncplot_options* opts, double miny, double maxy){
|
||||
auto ret = new ncdplot;
|
||||
if(ret){
|
||||
if(ncppplot<double>::create(&ret->n, n, opts, miny, maxy)){
|
||||
return ret;
|
||||
}
|
||||
free(ret);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ncplane* ncdplot_plane(ncdplot* n){
|
||||
return n->n.ncp;
|
||||
}
|
||||
|
||||
int ncdplot_add_sample(ncdplot* n, uint64_t x, double y){
|
||||
return n->n.add_sample(x, y);
|
||||
}
|
||||
|
||||
int ncdplot_set_sample(ncdplot* n, uint64_t x, double y){
|
||||
return n->n.set_sample(x, y);
|
||||
}
|
||||
|
||||
void ncdplot_destroy(ncdplot* n){
|
||||
if(n){
|
||||
n->n.destroy();
|
||||
delete n;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
159
tests/plot.cpp
159
tests/plot.cpp
@ -1,5 +1,5 @@
|
||||
#include "main.h"
|
||||
#include "internal.h"
|
||||
#include "cpp.h"
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
@ -10,139 +10,134 @@ TEST_CASE("Plot") {
|
||||
notcurses_options nopts{};
|
||||
nopts.inhibit_alternate_screen = true;
|
||||
nopts.suppress_banner = true;
|
||||
FILE* outfp_ = fopen("/dev/tty", "wb");
|
||||
auto outfp_ = fopen("/dev/tty", "wb");
|
||||
REQUIRE(outfp_);
|
||||
struct notcurses* nc_ = notcurses_init(&nopts, outfp_);
|
||||
auto nc_ = notcurses_init(&nopts, outfp_);
|
||||
REQUIRE(nc_);
|
||||
struct ncplane* n_ = notcurses_stdplane(nc_);
|
||||
auto n_ = notcurses_stdplane(nc_);
|
||||
REQUIRE(n_);
|
||||
REQUIRE(0 == ncplane_cursor_move_yx(n_, 0, 0));
|
||||
|
||||
// setting miny == maxy with non-zero domain limits is invalid
|
||||
SUBCASE("DetectRangeBadY"){
|
||||
ncplot_options popts{};
|
||||
popts.maxy = popts.miny = -1;
|
||||
ncplot* p = ncplot_create(n_, &popts);
|
||||
auto p = ncuplot_create(n_, &popts, -1, -1);
|
||||
CHECK(nullptr == p);
|
||||
popts.miny = 1;
|
||||
popts.maxy = 1;
|
||||
p = ncplot_create(n_, &popts);
|
||||
p = ncuplot_create(n_, &popts, 1, 1);
|
||||
CHECK(nullptr == p);
|
||||
popts.miny = 0;
|
||||
popts.maxy = 0;
|
||||
p = ncplot_create(n_, &popts);
|
||||
p = ncuplot_create(n_, &popts, 0, 0);
|
||||
REQUIRE(nullptr != p);
|
||||
ncplot_destroy(p);
|
||||
ncuplot_destroy(p);
|
||||
}
|
||||
|
||||
// maxy < miny is invalid
|
||||
SUBCASE("RejectMaxyLessMiny"){
|
||||
ncplot_options popts{};
|
||||
popts.miny = 2;
|
||||
popts.maxy = 1;
|
||||
ncplot* p = ncplot_create(n_, &popts);
|
||||
auto p = ncuplot_create(n_, &popts, 2, 1);
|
||||
CHECK(nullptr == p);
|
||||
}
|
||||
|
||||
SUBCASE("SimplePlot"){
|
||||
ncplot_options popts{};
|
||||
ncplot* p = ncplot_create(n_, &popts);
|
||||
auto p = ncuplot_create(n_, &popts, 0, 0);
|
||||
REQUIRE(p);
|
||||
CHECK(n_ == ncplot_plane(p));
|
||||
ncplot_destroy(p);
|
||||
CHECK(n_ == ncuplot_plane(p));
|
||||
ncuplot_destroy(p);
|
||||
}
|
||||
|
||||
// 5-ary slot space without any window movement
|
||||
SUBCASE("AugmentSamples5"){
|
||||
ncplot_options popts{};
|
||||
popts.rangex = 5;
|
||||
popts.maxy = 10;
|
||||
popts.miny = 0;
|
||||
ncplot* p = ncplot_create(n_, &popts);
|
||||
REQUIRE(p);
|
||||
CHECK(0 == p->slots[0]);
|
||||
CHECK(0 == ncplot_add_sample(p, 0, 1));
|
||||
CHECK(1 == p->slots[0]);
|
||||
CHECK(0 == ncplot_add_sample(p, 0, 1));
|
||||
CHECK(2 == p->slots[0]);
|
||||
CHECK(0 == p->slots[1]);
|
||||
CHECK(0 == p->slots[2]);
|
||||
CHECK(0 == ncplot_add_sample(p, 2, 3));
|
||||
CHECK(3 == p->slots[2]);
|
||||
CHECK(0 == ncplot_set_sample(p, 2, 3));
|
||||
CHECK(3 == p->slots[2]);
|
||||
CHECK(0 == p->slots[3]);
|
||||
CHECK(0 == p->slots[4]);
|
||||
CHECK(0 == ncplot_add_sample(p, 4, 6));
|
||||
CHECK(6 == p->slots[4]);
|
||||
CHECK(2 == p->slots[0]);
|
||||
CHECK(4 == p->slotx);
|
||||
ncplot_destroy(p);
|
||||
ncppplot<uint64_t> p;
|
||||
ncppplot<uint64_t>::create(&p, n_, &popts, 0, 10);
|
||||
CHECK(0 == p.slots[0]);
|
||||
CHECK(0 == p.add_sample((uint64_t)0, (uint64_t)1));
|
||||
CHECK(1 == p.slots[0]);
|
||||
CHECK(0 == p.add_sample((uint64_t)0, (uint64_t)1));
|
||||
CHECK(2 == p.slots[0]);
|
||||
CHECK(0 == p.slots[1]);
|
||||
CHECK(0 == p.slots[2]);
|
||||
CHECK(0 == p.add_sample((uint64_t)2, (uint64_t)3));
|
||||
CHECK(3 == p.slots[2]);
|
||||
CHECK(0 == p.set_sample((uint64_t)2, (uint64_t)3));
|
||||
CHECK(3 == p.slots[2]);
|
||||
CHECK(0 == p.slots[3]);
|
||||
CHECK(0 == p.slots[4]);
|
||||
CHECK(0 == p.add_sample((uint64_t)4, (uint64_t)6));
|
||||
CHECK(6 == p.slots[4]);
|
||||
CHECK(2 == p.slots[0]);
|
||||
CHECK(4 == p.slotx);
|
||||
p.destroy();
|
||||
}
|
||||
|
||||
// 2-ary slot space with window movement
|
||||
SUBCASE("AugmentCycle2"){
|
||||
ncplot_options popts{};
|
||||
popts.rangex = 2;
|
||||
popts.maxy = 10;
|
||||
popts.miny = 0;
|
||||
ncplot* p = ncplot_create(n_, &popts);
|
||||
REQUIRE(p);
|
||||
CHECK(0 == p->slots[0]);
|
||||
CHECK(0 == ncplot_add_sample(p, 0, 1));
|
||||
CHECK(1 == p->slots[0]);
|
||||
CHECK(0 == ncplot_add_sample(p, 0, 1));
|
||||
CHECK(2 == p->slots[0]);
|
||||
CHECK(0 == ncplot_set_sample(p, 1, 5));
|
||||
CHECK(5 == p->slots[1]);
|
||||
CHECK(0 == ncplot_set_sample(p, 2, 9));
|
||||
CHECK(5 == p->slots[1]);
|
||||
CHECK(9 == p->slots[0]);
|
||||
CHECK(0 == ncplot_add_sample(p, 3, 4));
|
||||
CHECK(9 == p->slots[0]);
|
||||
CHECK(4 == p->slots[1]);
|
||||
CHECK(3 == p->slotx);
|
||||
CHECK(0 == ncplot_add_sample(p, 5, 1));
|
||||
CHECK(1 == p->slots[0]);
|
||||
CHECK(0 == p->slots[1]);
|
||||
CHECK(5 == p->slotx);
|
||||
ncplot_destroy(p);
|
||||
ncppplot<uint64_t> p;
|
||||
ncppplot<uint64_t>::create(&p, n_, &popts, 0, 10);
|
||||
CHECK(0 == p.slots[0]);
|
||||
CHECK(0 == p.add_sample((uint64_t)0, (uint64_t)1));
|
||||
CHECK(1 == p.slots[0]);
|
||||
CHECK(0 == p.add_sample((uint64_t)0, (uint64_t)1));
|
||||
CHECK(2 == p.slots[0]);
|
||||
CHECK(0 == p.set_sample((uint64_t)1, (uint64_t)5));
|
||||
CHECK(5 == p.slots[1]);
|
||||
CHECK(0 == p.set_sample((uint64_t)2, (uint64_t)9));
|
||||
CHECK(5 == p.slots[1]);
|
||||
CHECK(9 == p.slots[0]);
|
||||
CHECK(0 == p.add_sample((uint64_t)3, (uint64_t)4));
|
||||
CHECK(9 == p.slots[0]);
|
||||
CHECK(4 == p.slots[1]);
|
||||
CHECK(3 == p.slotx);
|
||||
CHECK(0 == p.add_sample((uint64_t)5, (uint64_t)1));
|
||||
CHECK(1 == p.slots[0]);
|
||||
CHECK(0 == p.slots[1]);
|
||||
CHECK(5 == p.slotx);
|
||||
p.destroy();
|
||||
}
|
||||
|
||||
// augment past the window, ensuring everything gets zeroed
|
||||
SUBCASE("AugmentLong"){
|
||||
ncplot_options popts{};
|
||||
popts.rangex = 5;
|
||||
popts.maxy = 10;
|
||||
popts.miny = 0;
|
||||
ncplot* p = ncplot_create(n_, &popts);
|
||||
REQUIRE(p);
|
||||
ncppplot<uint64_t> p;
|
||||
ncppplot<uint64_t>::create(&p, n_, &popts, 0, 10);
|
||||
for(int x = 0 ; x < 5 ; ++x){
|
||||
CHECK(0 == p->slots[x]);
|
||||
CHECK(0 == p.slots[x]);
|
||||
}
|
||||
CHECK(0 == ncplot_add_sample(p, 4, 4));
|
||||
CHECK(0 == p.add_sample((uint64_t)4, (uint64_t)4));
|
||||
for(int x = 0 ; x < 4 ; ++x){
|
||||
CHECK(0 == p->slots[x]);
|
||||
CHECK(0 == p.slots[x]);
|
||||
}
|
||||
CHECK(4 == p->slots[4]);
|
||||
CHECK(0 == ncplot_add_sample(p, 10, 5));
|
||||
CHECK(5 == p->slots[0]);
|
||||
CHECK(4 == p.slots[4]);
|
||||
CHECK(0 == p.add_sample((uint64_t)10, (uint64_t)5));
|
||||
CHECK(5 == p.slots[0]);
|
||||
for(int x = 1 ; x < 4 ; ++x){
|
||||
CHECK(0 == p->slots[x]);
|
||||
CHECK(0 == p.slots[x]);
|
||||
}
|
||||
CHECK(0 == ncplot_add_sample(p, 24, 7));
|
||||
CHECK(7 == p->slots[0]);
|
||||
CHECK(0 == p.add_sample((uint64_t)24, (uint64_t)7));
|
||||
CHECK(7 == p.slots[0]);
|
||||
for(int x = 1 ; x < 5 ; ++x){
|
||||
CHECK(0 == p->slots[x]);
|
||||
CHECK(0 == p.slots[x]);
|
||||
}
|
||||
CHECK(0 == ncplot_add_sample(p, 100, 0));
|
||||
CHECK(0 == p.add_sample((uint64_t)100, (uint64_t)0));
|
||||
for(int x = 0 ; x < 5 ; ++x){
|
||||
CHECK(0 == p->slots[x]);
|
||||
CHECK(0 == p.slots[x]);
|
||||
}
|
||||
ncplot_destroy(p);
|
||||
p.destroy();
|
||||
}
|
||||
|
||||
// FIXME need some rendering tests, one for each geometry
|
||||
// FIXME need some high-level rendering tests, one for each geometry
|
||||
|
||||
SUBCASE("SimpleFloatPlot"){
|
||||
ncplot_options popts{};
|
||||
auto p = ncdplot_create(n_, &popts, 0, 0);
|
||||
REQUIRE(p);
|
||||
CHECK(n_ == ncdplot_plane(p));
|
||||
ncdplot_destroy(p);
|
||||
}
|
||||
|
||||
CHECK(0 == notcurses_stop(nc_));
|
||||
CHECK(0 == fclose(outfp_));
|
||||
|
Loading…
Reference in New Issue
Block a user