put code in commont between ploot-ff.c and ploot-braille.c to separate files - ploot - simple plotting tools HTML git clone git://bitreich.org/ploot git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ploot DIR Log DIR Files DIR Refs DIR Tags DIR README DIR LICENSE --- DIR commit 1c1a69494de95f4f5a3a439a16fac98026e2aa09 DIR parent 62211b846caa7b980b6a43dea1fdf0a0e2f6de34 HTML Author: Josuah Demangeon <me@josuah.net> Date: Sat, 8 Feb 2020 18:38:14 +0100 put code in commont between ploot-ff.c and ploot-braille.c to separate files Diffstat: M Makefile | 2 +- M arg.h | 6 +++--- M def.h | 27 +++++++++++++++++++++------ A ploot-braille.c | 200 +++++++++++++++++++++++++++++++ M ploot-ff.c | 295 +++++++------------------------ D ploot-plot.c | 201 ------------------------------ 6 files changed, 292 insertions(+), 439 deletions(-) --- DIR diff --git a/Makefile b/Makefile @@ -5,7 +5,7 @@ BIN = ploot-ff ploot-feed LIB = -lm MANDIR = $(PREFIX)/share/man -SRC = util.c drawille.c font.c font7.c font8.c font13.c +SRC = csv.c drawille.c font.c font7.c font8.c font13.c util.c scale.c all: $(BIN) DIR diff --git a/arg.h b/arg.h @@ -5,11 +5,11 @@ extern char const *arg0; #define ARG_SWITCH(argc, argv) \ arg0 = *argv; \ - while (++argv && --argc && **argv == '-' && (*argv)[1]) \ + while (++argv && --argc && **argv == '-' && (*argv)[1]) \ if ((*argv)[1] == '-' && (*argv)[2] == '\0') { \ ++argv; break; \ - } else for (int stop = 0; !stop && *++*argv != '\0' ;) \ - switch (**argv) + } else for (int stop = 0; !stop && *++*argv != '\0' ;) \ + switch (**argv) #define ARG ((*++*argv != '\0' || *++argv != NULL) \ ? ((stop = 1), argc--, *argv) \ DIR diff --git a/def.h b/def.h @@ -23,6 +23,23 @@ struct font { char *glyph[128]; /* 0: end, 1: off, 2: on. */ }; +/* + * List of values and timestamps. Both have their dedicated buffer + * so that the timestamp buffer can be shared across vlist objects. + */ +struct vlist { + time_t *t; /* array of timestamps */ + double *v; /* array of values */ + size_t n; /* number of values */ + char *label; /* for the legend */ +}; + +/* csv.c */ + +void csv_addrow (struct vlist *, size_t, char *); +void csv_values (struct vlist *, size_t); +void csv_labels (struct vlist *, char **, char *); + /* drawille.c */ size_t drawille_fmt_row (struct drawille *, char *, size_t, int); @@ -38,17 +55,15 @@ char * drawille_text (struct drawille *, int, int, struct font *, char *); size_t font_width (struct font *, int); size_t font_strlen (struct font *, char *); -/* font13.c */ +/* font*.c */ struct font font13; - -/* font7.c */ - +struct font font7; struct font font8; -/* font8.c */ +/* scale.c */ -struct font font8; +void scale (struct vlist *, int, time_t *, time_t *, time_t *, double *, double *, double *); /* util.c */ DIR diff --git a/ploot-braille.c b/ploot-braille.c @@ -0,0 +1,200 @@ +#include <assert.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <math.h> + +#include "def.h" + +/* + * Adjust the vertical scale so that it gets possible to + */ +static void +plot_scale(double *min, double *max, int row) +{ + double unit, range, mi; + + range = *max - *min; + unit = 1; + + /* Zoom until it fills the canvas. */ + for (; (row - 1) * unit > range; unit /= 10) + continue; + + /* Dezoom until it fits the canvas. */ + for (; (row - 1) * unit < range; unit *= 10) + continue; + + /* Fine tune. */ + if ((row - 1) * unit / 5 > range) + unit /= 5; + if ((row - 1) * unit / 4 > range) + unit /= 4; + if ((row - 1) * unit / 2 > range) + unit /= 2; + + /* Align the minimum (and the zero). */ + for (mi = 0; mi > *min - unit; mi -= unit) + continue; + + /* Update the displayed minimal and maximal. */ + *min = mi; + *max = mi + unit * row; +} + +/* + * Return the step between two values. + */ +static int +plot_time_interval(time_t step) +{ + time_t scale[] = { + 1, 5, 2, 10, 20, 30, 60, 60*2, 60*5, 60*10, 60*20, 60*30, + 3600, 3600*2, 3600*5, 3600*10, 3600*18, 3600*24, 3600*24*2, + 3600*24*5, 3600*24*10, 3600*24*20, 3600*24*30, 3600*24*50, + 3600*24*100, 3600*24*365, 0 + }; + + for (time_t *s = scale; *s != 0; s++) + if (*s >= 20 * step) + return *s; + return 1; +} + +static size_t +plot_axis_x(char *buf, size_t sz, time_t step, time_t t2, int col) +{ + int x, prec; + char tmp[sizeof("MM/DD HH:MM")], *fmt; + size_t n; + time_t t, interval; + + interval = plot_time_interval(step); + fmt = (step < 3600 * 12) ? "^%H:%M:%S" : + (step < 3600 * 24) ? "^%m/%d %H:%M" : + "^%Y/%m/%d"; + n = x = 0; + + t = t2 - col * 2 * step; + t += interval - t % interval; + for (; t < t2; t += interval) { + strftime(tmp, sizeof tmp, fmt, localtime(&t)); + x = ((t - t2) / 2 + col * step) / step; + prec = x - n + strlen(tmp); + assert((n += snprintf(buf+n, sz-n, "%*s", prec, tmp)) <= sz); + } + assert((n += strlcpy(buf+n, "\n", sz-n)) < sz); + return n; +} + +/* + * Plot a single line out of the y axis, at row <r> out of <rows>. + */ +static size_t +plot_axis_y(char *buf, size_t sz, double min, double max, int r, int rows) +{ + size_t i; + char tmp[10] = "", *s; + double val; + + val = (max - min) * (rows - r) / rows + min; + humanize(tmp, sizeof tmp, val); + s = (r == 0) ? "┌" : + (r == rows - 1) ? "└" : + "├"; + i = snprintf(buf, sz, "%s%-6s ", s, tmp); + return (i > sz) ? (sz) : (i); +} + +static char * +plot_render(struct drawille *drw, double min, double max, time_t step, time_t t2) +{ + char *buf; + size_t sz; + size_t n; + + /* Render the plot line by line. */ + sz = drw->row * (20 + drw->col * 3 + 1) + 1; + sz += drw->col + 1 + 100000; + if ((buf = calloc(sz, 1)) == NULL) + goto err; + n = 0; + for (int row = 0; row < drw->row; row++) { + n += drawille_fmt_row(drw, buf+n, sz-n, row); + n += plot_axis_y(buf+n, sz-n, min, max, row, drw->row); + n += strlcpy(buf+n, "\n", sz-n); + } + plot_axis_x(buf+n, sz-n, step, t2, drw->col); + return buf; +err: + errno = ENOBUFS; + free(buf); + return NULL; +} + +/* + * Plot the body as an histogram interpolating the gaps and include + * a vertical and horizontal axis. + */ +static char * +plot_hist(struct vlist *vl, time_t t2, struct drawille *drw) +{ + int x, y, zero, shift; + double min, max, val; + time_t t1, t; + + /* Adjust the y scale. */ + shift = min = max = 0; + timeserie_stats(vl, &min, &max); + if (drw->row > 1) { + shift = 2; /* Align values to the middle of the scale: |- */ + plot_scale(&min, &max, drw->row); + } + zero = timeserie_ypos(0, min, max, drw->row*4) - shift; + + /* Adjust the x scale. */ + t2 = t2 + vl->step - t2 % vl->step; + t1 = t2 - vl->step * vl->len; + + /* Plot the data in memory in <drw> starting from the end (t2). */ + t = t2; + for (x = drw->col * 2; x > 0; x--) { + val = timeserie_get(vl, t); + if (!isnan(val)) { + y = timeserie_ypos(val, min, max, drw->row*4) - shift; + drawille_dot_hist(drw, x, y, zero); + } + t -= vl->step; + } + + return plot_render(drw, min, max, vl->step, t2); +} + +static char * +plot(struct vlist *vl, time_t t2, int row, int col) +{ + struct drawille *drw; + size_t len; + char *buf; + + len = 500; + buf = NULL; + drw = NULL; + col -= 8; + + if (timeserie_read(vl) == -1) + goto err; + + if ((drw = drawille_new(row, col)) == NULL) + goto err; + + buf = plot_hist(vl, t2, drw); +err: + if (buf == NULL) + timedb_close(&vl->db); + free(drw); + return buf; +} DIR diff --git a/ploot-ff.c b/ploot-ff.c @@ -18,9 +18,6 @@ #define MARGIN 4 -#define XDENSITX 7 /* nb of values on x axis */ -#define YDENSITX 7 /* nb of values on y axis */ - #define IMAGE_H (TITLE_H + PLOT_H + XLABEL_H) #define IMAGE_W (YLABEL_W + PLOT_W + LEGEND_W) @@ -45,8 +42,8 @@ #define PLOT_H (160) #define LEGEND_X (IMAGE_W - LEGEND_W) -#define LEGEND_Y (XLABEL_H) -#define LEGEND_W (150) +#define LEGEND_Y (TITLE_H + PLOT_H - (font)->height) +#define LEGEND_W (100) #define LEGEND_H (PLOT_H) struct color { @@ -56,12 +53,9 @@ struct color { uint16_t alpha; }; -struct vlist { - struct color color; /* color to use to draw the line */ - time_t *t; /* array of timestamps */ - double *v; /* array of values */ - int n; /* number of values */ - char *label; /* for the legend */ +struct cname { + char *name; + struct color color; }; struct canvas { @@ -72,17 +66,12 @@ struct canvas { struct color *buf; }; -struct clist { - char *name; - struct color color; -}; - char const *arg0; static char *tflag = ""; static char *uflag = ""; static struct font *font = &font13; -struct clist clist[] = { +static struct cname cname[] = { /* name red green blue alpha */ { "red", { 0xffff, 0x4444, 0x4444, 0xffff } }, { "orange", { 0xffff, 0x9999, 0x4444, 0xffff } }, @@ -93,98 +82,6 @@ struct clist clist[] = { { NULL, { 0, 0, 0, 0 } } }; -static struct color * -name_to_color(char *name) -{ - for (struct clist *c = clist; c->name != NULL; c++) - if (strcmp(name, c->name) == 0) - return &c->color; - return NULL; -} - -static void -scale_minmax(struct vlist *v, int n, - time_t *tmin, time_t *tmax, - double *vmin, double *vmax) -{ - int i; - - *vmin = *vmax = 0; - *tmin = *tmax = *v->t; - - for (; n-- > 0; v++) { - for (i = 0; i < v->n; i++) { - if (v->v[i] < *vmin) - *vmin = v->v[i]; - if (v->v[i] > *vmax) - *vmax = v->v[i]; - if (v->t[i] < *tmin) - *tmin = v->t[i]; - if (v->t[i] > *tmax) - *tmax = v->t[i]; - } - } -} - -static void -scale_tstep(time_t *step, int density, time_t min, time_t max) -{ - time_t dt, *s, scale[] = { - 1, 5, 2, 10, 20, 30, 60, 60*2, 60*5, 60*10, 60*20, 60*30, 3600, - 3600*2, 3600*5, 3600*10, 3600*18, 3600*24, 3600*24*2, - 3600*24*5, 3600*24*10, 3600*24*20, 3600*24*30, 3600*24*50, - 3600*24*100, 3600*24*365, 0 - }; - - dt = max - min; - - for (s = scale; s < scale + LEN(scale); s++) { - if (dt < *s * density) { - *step = *s; - break; - } - } -} - -static void -scale_vstep(double *step, int density, double min, double max) -{ - double dv, *s, scale[] = { 1, 2, 3, 5 }; - int i; - - dv = max - min; - - if (dv > 1) { - for (i = 1; i != 0; i *= 10) { - for (s = scale; s < scale + LEN(scale); s++) { - if (dv < *s * i * density) { - *step = *s * i; - return; - } - } - } - } else { - for (i = 1; i != 0; i *= 10) { - for (s = scale + LEN(scale) - 1; s >= scale; s--) { - if (dv > *s / i * density / 2) { - *step = *s / i; - return; - } - } - } - } -} - -static void -scale(struct vlist *v, int n, - time_t *tmin, time_t *tmax, time_t *tstep, - double *vmin, double *vmax, double *vstep) -{ - scale_minmax(v, n, tmin, tmax, vmin, vmax); - scale_tstep(tstep, XDENSITX, *tmin, *tmax); - scale_vstep(vstep, YDENSITX, *vmin, *vmax); -} - /* * Convert (x,y) coordinates to (row,col) for printing into the buffer. * The buffer only contain one number, so the coordinate is a single integer: @@ -196,18 +93,18 @@ scale(struct vlist *v, int n, * - (0,1) is above it. +--x */ static void -ff_pixel(struct canvas *can, struct color *col, +ff_pixel(struct canvas *can, struct color *color, int x, int y) { x += can->x; y += can->y; if (x < 0 || x >= can->w || y < 0 || y >= can->h) return; - memcpy(can->buf + can->w * (can->h - 1 - y) + x, col, sizeof(*can->buf)); + memcpy(can->buf + can->w * (can->h - 1 - y) + x, color, sizeof(*can->buf)); } static void -ff_rectangle(struct canvas *can, struct color *col, +ff_rectangle(struct canvas *can, struct color *color, int y1, int x1, int y2, int x2) { @@ -218,14 +115,14 @@ ff_rectangle(struct canvas *can, struct color *col, for (y = ymin; y <= ymax; y++) for (x = xmin; x <= xmax; x++) - ff_pixel(can, col, x, y); + ff_pixel(can, color, x, y); } /* * From Bresenham's line algorithm and dcat's tplot. */ static void -ff_line(struct canvas *can, struct color *col, +ff_line(struct canvas *can, struct color *color, int x0, int y0, int x1, int y1) { @@ -238,7 +135,7 @@ ff_line(struct canvas *can, struct color *col, err = (dy > dx ? dy : -dx) / 2; for (;;) { - ff_pixel(can, col, x0, y0); + ff_pixel(can, color, x0, y0); if (y0 == y1 && x0 == x1) break; @@ -259,7 +156,7 @@ ff_line(struct canvas *can, struct color *col, * Draw a coloured glyph from font f centered on y. */ static int -ff_char(struct canvas *can, struct color *col, char c, +ff_char(struct canvas *can, struct color *color, char c, int x, int y) { int yf, xf, wf; @@ -271,7 +168,7 @@ ff_char(struct canvas *can, struct color *col, char c, for (xf = 0; xf < wf; xf++) for (yf = 0; yf < font->height; yf++) if (font->glyph[(int)c][wf * (font->height - yf) + xf] == 3) - ff_pixel(can, col, x + xf, y + yf); + ff_pixel(can, color, x + xf, y + yf); return wf + 1; } @@ -279,11 +176,11 @@ ff_char(struct canvas *can, struct color *col, char c, * Draw a left aligned string without wrapping it. */ static size_t -ff_text_left(struct canvas *can, struct color *col, char *s, +ff_text_left(struct canvas *can, struct color *color, char *s, int x, int y) { for (; *s != '\0'; s++) - x += ff_char(can, col, *s, x, y); + x += ff_char(can, color, *s, x, y); return x; } @@ -291,22 +188,22 @@ ff_text_left(struct canvas *can, struct color *col, char *s, * Draw a center aligned string without wrapping it. */ static size_t -ff_text_center(struct canvas *can, struct color *col, char *s, +ff_text_center(struct canvas *can, struct color *color, char *s, int x, int y) { x -= font_strlen(font, s) / 2; - return ff_text_left(can, col, s, x, y); + return ff_text_left(can, color, s, x, y); } /* * Draw a right aligned string without wrapping it. */ static size_t -ff_text_right(struct canvas *can, struct color *col, char *s, +ff_text_right(struct canvas *can, struct color *color, char *s, int x, int y) { x -= font_strlen(font, s); - return ff_text_left(can, col, s, x, y); + return ff_text_left(can, color, s, x, y); } static void @@ -326,12 +223,16 @@ ff_print(struct canvas *can) static int ff_t2x(time_t t, time_t tmin, time_t tmax) { + if (tmin == tmax) + return PLOT_W; return (t - tmin) * PLOT_W / (tmax - tmin); } static int ff_v2y(double v, double vmin, double vmax) { + if (vmin == vmax) + return PLOT_H; return (v - vmin) * PLOT_H / (vmax - vmin); } @@ -394,7 +295,7 @@ ff_title(struct canvas *can, } static void -ff_plot(struct canvas *can, struct vlist *v, +ff_plot(struct canvas *can, struct vlist *vl, struct color *color, double vmin, double vmax, time_t tmin, time_t tmax) { @@ -403,12 +304,12 @@ ff_plot(struct canvas *can, struct vlist *v, int x, y, n, ylast, xlast, first; first = 1; - for (tp = v->t, vp = v->v, n = v->n; n > 0; n--, vp++, tp++) { + for (tp = vl->t, vp = vl->v, n = vl->n; n > 0; n--, vp++, tp++) { y = ff_v2y(*vp, vmin, vmax); x = ff_t2x(*tp, tmin, tmax); if (!first) - ff_line(can, &v->color, xlast, ylast, x, y); + ff_line(can, color, xlast, ylast, x, y); ylast = y; xlast = x; @@ -417,24 +318,24 @@ ff_plot(struct canvas *can, struct vlist *v, } static void -ff_values(struct canvas *can, struct vlist *v, int n, +ff_values(struct canvas *can, struct vlist *vl, struct color **cl, size_t ncol, time_t tmin, time_t tmax, double vmin, double vmax) { - for (; n > 0; n--, v++) - ff_plot(can, v, vmin, vmax, tmin, tmax); + for (; ncol > 0; ncol--, vl++, cl++) + ff_plot(can, vl, *cl, vmin, vmax, tmin, tmax); } static void -ff_legend(struct canvas *can, struct color *label_fg, struct vlist *v, int n) +ff_legend(struct canvas *can, struct color *fg, struct vlist *vl, struct color **cl, size_t ncol) { - int i, x, y; + size_t i, x, y; - for (i = 0; i < n; i++, v++) { - x = MARGIN; - x = ff_text_left(can, &v->color, "\1", x, y); - x = ff_text_left(can, label_fg, v->label, x, y); - y = LEGEND_H - i * (font->height + MARGIN) - font->height / 2; + for (i = 0; i < ncol; i++, vl++, cl++) { + x = MARGIN * 2; + x = ff_text_left(can, *cl, "\1", x, y) + MARGIN; + x = ff_text_left(can, fg, vl->label, x, y); + y = LEGEND_H - i * (font->height + MARGIN); } } @@ -450,7 +351,7 @@ ff_legend(struct canvas *can, struct color *label_fg, struct vlist *v, int n) * x label here */ static void -ff(struct vlist *v, int n, char *name, char *units) +ff(struct vlist *vl, struct color **cl, size_t ncol, char *name, char *units) { struct canvas can = { IMAGE_W, IMAGE_H, 0, 0, NULL }; struct color plot_bg = { 0x2222, 0x2222, 0x2222, 0xffff }; @@ -461,7 +362,7 @@ ff(struct vlist *v, int n, char *name, char *units) double vmin, vmax, vstep; time_t tmin, tmax, tstep; - scale(v, n, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep); + scale(vl, ncol, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep); assert(can.buf = calloc(IMAGE_H * IMAGE_W, sizeof *can.buf)); @@ -487,108 +388,41 @@ ff(struct vlist *v, int n, char *name, char *units) can.x = PLOT_X; can.y = PLOT_Y; - ff_values(&can, v, n, tmin, tmax, vmin, vmax); + ff_values(&can, vl, cl, ncol, tmin, tmax, vmin, vmax); can.x = LEGEND_X; can.y = LEGEND_Y; - ff_legend(&can, &label_fg, v, n); + ff_legend(&can, &label_fg, vl, cl, ncol); ff_print(&can); } - -static void -csv_labels(struct vlist *v, char **argv, char *buf) -{ - struct color *color; - - if (esfgets(buf, LINE_MAX, stdin) == NULL) - err(1, "missing label line"); - - if (strcmp(strsep(&buf, ","), "epoch") != 0) - err(1, "first label must be \"epoch\""); - - for (; *argv != NULL; v++, argv++) { - if ((v->label = strsep(&buf, ",")) == NULL) - err(1, "more arguments than columns"); - else if ((color = name_to_color(*argv)) == NULL) - err(1, "unknown color: %s", *argv); - v->color = *color; - } - - if (strsep(&buf, ",") != NULL) - err(1, "more columns than arguments"); -} - -static int -csv_addval(struct vlist *v, size_t sz, size_t nval, double field, time_t epoch) -{ - if (nval >= sz) { - sz = sz * 2 + 1; - if ((v->v = realloc(v->v, sz * sizeof(*v->v))) == NULL) - err(1, "reallocating values buffer"); - if ((v->t = realloc(v->t, sz * sizeof(*v->t))) == NULL) - err(1, "reallocating values buffer"); - } - v->v[nval] = field; - v->t[nval] = epoch; - v->n = nval + 1; - - return sz; -} -/* - * Add to each column the value on the current row. - */ -static int -csv_addrow(struct vlist *v, size_t sz, size_t ncol, size_t nval, char *line) +static struct color * +name_to_color(char *name) { - time_t epoch; - int bs; - char *field, *dot; - - if ((field = strsep(&line, ",")) == NULL) - err(1, "%d: missing epoch", nval); - - if ((dot = strchr(field, '.')) != NULL) - *dot = '\0'; - epoch = eatol(field); - for (; (field = strsep(&line, ",")) != NULL; ncol--, v++) { - if (ncol <= 0) - err(1, "%d: too many fields", nval); - bs = csv_addval(v, sz, nval, eatof(field), epoch); - } - if (ncol > 0) - err(1, "%d: too few fields", ncol); + struct cname *cn; - return bs; + for (cn = cname; cn->name != NULL; cn++) + if (strcmp(name, cn->name) == 0) + return &cn->color; + return NULL; } -/* - * < ncol > - * epoch,a1,b1,c1 ^ - * epoch,a2,b2,c2 nval - * epoch,a3,b3,c3 v - */ static void -csv_values(struct vlist *v, size_t ncol) +argv_to_color(struct color **cl, char **argv) { - int nval, sz; - char line[LINE_MAX]; - - sz = 0; - for (nval = 0; esfgets(line, sizeof(line), stdin) != NULL; nval++) - sz = csv_addrow(v, sz, ncol, nval, line); - if (nval == 0) - err(1, "no value could be read\n"); + for (; *argv != NULL; cl++, argv++) + if ((*cl = name_to_color(*argv)) == NULL) + err(1, "unknown color name: %s", *argv); } static void usage(void) { fprintf(stderr, "usage: %s [-t title] [-u unit] {", arg0); - fputs(clist->name, stderr); - for (struct clist *c = clist + 1; c->name != NULL; c++) - fprintf(stderr, ",%s", c->name); + fputs(cname->name, stderr); + for (struct cname *cn = cname + 1; cn->name != NULL; cn++) + fprintf(stderr, ",%s", cn->name); fputs("}...\n", stderr); exit(1); } @@ -596,7 +430,8 @@ usage(void) int main(int argc, char **argv) { - struct vlist *v; + struct vlist *vl; + struct color **cl; char labels[LINE_MAX]; ARG_SWITCH(argc, argv) { @@ -610,15 +445,19 @@ main(int argc, char **argv) usage(); } - fflush(stdout); + if (argc == 0) + usage(); - if ((v = calloc(argc, sizeof(*v))) == NULL) - err(1, "calloc value list"); + assert(vl = calloc(argc, sizeof(*vl))); + assert(cl = calloc(argc, sizeof(*cl))); - csv_labels(v, argv, labels); - csv_values(v, argc); + csv_labels(vl, argv, labels); + csv_values(vl, argc); + argv_to_color(cl, argv); - ff(v, argc, tflag, uflag); + ff(vl, cl, argc, tflag, uflag); + free(vl); + free(cl); return 0; } DIR diff --git a/ploot-plot.c b/ploot-plot.c @@ -1,201 +0,0 @@ -#include <assert.h> -#include <errno.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <math.h> - -#include "def.h" - -/* - * Adjust the vertical scale so that it gets possible to - */ -static void -plot_scale(double *min, double *max, int row) -{ - double unit, range, mi; - - range = *max - *min; - unit = 1; - - /* Zoom until it fills the canvas. */ - for (; (row - 1) * unit > range; unit /= 10) - continue; - - /* Dezoom until it fits the canvas. */ - for (; (row - 1) * unit < range; unit *= 10) - continue; - - /* Fine tune. */ - if ((row - 1) * unit / 5 > range) - unit /= 5; - if ((row - 1) * unit / 4 > range) - unit /= 4; - if ((row - 1) * unit / 2 > range) - unit /= 2; - - /* Align the minimum (and the zero). */ - for (mi = 0; mi > *min - unit; mi -= unit) - continue; - - /* Update the displayed minimal and maximal. */ - *min = mi; - *max = mi + unit * row; -} - -/* - * Return the step between two values. - */ -static int -plot_time_interval(time_t step) -{ - time_t scale[] = { - 1, 5, 2, 10, 20, 30, 60, 60*2, 60*5, 60*10, 60*20, 60*30, - 3600, 3600*2, 3600*5, 3600*10, 3600*18, 3600*24, 3600*24*2, - 3600*24*5, 3600*24*10, 3600*24*20, 3600*24*30, 3600*24*50, - 3600*24*100, 3600*24*365, 0 - }; - - for (time_t *s = scale; *s != 0; s++) - if (*s >= 20 * step) - return *s; - return 1; -} - -static size_t -plot_axis_x(char *buf, size_t sz, time_t step, time_t t2, int col) -{ - int x, prec; - char tmp[sizeof("MM/DD HH:MM")], *fmt; - size_t n; - time_t t, interval; - - interval = plot_time_interval(step); - fmt = (step < 3600 * 12) ? "^%H:%M:%S" : - (step < 3600 * 24) ? "^%m/%d %H:%M" : - "^%Y/%m/%d"; - n = x = 0; - - t = t2 - col * 2 * step; - t += interval - t % interval; - for (; t < t2; t += interval) { - strftime(tmp, sizeof tmp, fmt, localtime(&t)); - x = ((t - t2) / 2 + col * step) / step; - prec = x - n + strlen(tmp); - assert((n += snprintf(buf+n, sz-n, "%*s", prec, tmp)) <= sz); - } - assert((n += strlcpy(buf+n, "\n", sz-n)) < sz); - return n; -} - -/* - * Plot a single line out of the y axis, at row <r> out of <rows>. - */ -static size_t -plot_axis_y(char *buf, size_t sz, double min, double max, int r, int rows) -{ - size_t i; - char tmp[10] = "", *s; - double val; - - val = (max - min) * (rows - r) / rows + min; - humanize(tmp, sizeof tmp, val); - s = (r == 0) ? "┌" : - (r == rows - 1) ? "└" : - "├"; - i = snprintf(buf, sz, "%s%-6s ", s, tmp); - return (i > sz) ? (sz) : (i); -} - -static char * -plot_render(struct drawille *drw, double min, double max, time_t step, time_t t2) -{ - char *buf; - size_t sz; - size_t n; - - /* Render the plot line by line. */ - sz = drw->row * (20 + drw->col * 3 + 1) + 1; - sz += drw->col + 1 + 100000; - if ((buf = calloc(sz, 1)) == NULL) - goto err; - n = 0; - for (int row = 0; row < drw->row; row++) { - n += drawille_fmt_row(drw, buf+n, sz-n, row); - n += plot_axis_y(buf+n, sz-n, min, max, row, drw->row); - n += strlcpy(buf+n, "\n", sz-n); - } - plot_axis_x(buf+n, sz-n, step, t2, drw->col); - - return buf; -err: - errno = ENOBUFS; - free(buf); - return NULL; -} - -/* - * Plot the body as an histogram interpolating the gaps and include - * a vertical and horizontal axis. - */ -static char * -plot_hist(struct timeserie *ts, time_t t2, struct drawille *drw) -{ - int x, y, zero, shift; - double min, max, val; - time_t t1, t; - - /* Adjust the y scale. */ - shift = min = max = 0; - timeserie_stats(ts, &min, &max); - if (drw->row > 1) { - shift = 2; /* Align values to the middle of the scale: |- */ - plot_scale(&min, &max, drw->row); - } - zero = timeserie_ypos(0, min, max, drw->row*4) - shift; - - /* Adjust the x scale. */ - t2 = t2 + ts->step - t2 % ts->step; - t1 = t2 - ts->step * ts->len; - - /* Plot the data in memory in <drw> starting from the end (t2). */ - t = t2; - for (x = drw->col * 2; x > 0; x--) { - val = timeserie_get(ts, t); - if (!isnan(val)) { - y = timeserie_ypos(val, min, max, drw->row*4) - shift; - drawille_dot_hist(drw, x, y, zero); - } - t -= ts->step; - } - - return plot_render(drw, min, max, ts->step, t2); -} - -static char * -plot(struct timeserie *ts, time_t t2, int row, int col) -{ - struct drawille *drw; - size_t len; - char *buf; - - len = 500; - buf = NULL; - drw = NULL; - col -= 8; - - if (timeserie_read(ts) == -1) - goto err; - - if ((drw = drawille_new(row, col)) == NULL) - goto err; - - buf = plot_hist(ts, t2, drw); -err: - if (buf == NULL) - timedb_close(&ts->db); - free(drw); - return buf; -}