ploot

plot images or real-time plain text from a .csv file
Log | Files | Refs | README

commit c3911021718dc5e5b2dd6469495e7f3bc4befdd2
parent d97badd46c19a6903a589c41b8b87d044f48f8dc
Author: Josuah Demangeon <mail@josuah.net>
Date:   Wed,  2 May 2018 06:37:05 +0200

auto-scale

Diffstat:
MMakefile | 2+-
Mffplot.c | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Dmain.c | 191-------------------------------------------------------------------------------
Mploot.c | 364+++++++++++++++++++++++++++----------------------------------------------------
Mploot.h | 3+--
5 files changed, 186 insertions(+), 440 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ CFLAGS = -Wall -Wextra -Werror -std=c89 -pedantic -D_POSIX_C_SOURCE=200809L LDFLAGS = -static -SRC = main.c ffplot.c ffdraw.c font_14x7.c +SRC = ploot.c ffplot.c ffdraw.c font_14x7.c OBJ = $(SRC:.c=.o) LIB = -lm diff --git a/ffplot.c b/ffplot.c @@ -18,6 +18,7 @@ #include "font_14x7.h" #define ABS(x) ((x) < 0 ? -(x) : (x)) +#define LEN(x) (sizeof(x) / sizeof(*x)) #define MARGIN 4 @@ -42,7 +43,7 @@ #define PLOT_X (YLABEL_H) #define PLOT_Y (XLABEL_W) #define PLOT_W 700 -#define PLOT_H 200 +#define PLOT_H 160 #define LEGEND_X (YLABEL_H) #define LEGEND_Y (IMAGE_W - LEGEND_W) @@ -198,9 +199,60 @@ legend(Canvas *can, Color *label_fg, Vlist *v, int n) } void -ffdraw(char *name, char *units, Vlist *v, int n, - double vmin, double vmax, double vstep, - time_t tmin, time_t tmax, time_t tstep) +find_scales(Vlist *v, int n, + double *vmin, double *vmax, double *vstep, + time_t *tmin, time_t *tmax, time_t *tstep) +{ + double dv, *vs, vscale[] = { 5, 2, 1 }; + time_t dt, *ts, tscale[] = { + 3600*24*30, 3600*24*5, 3600*24*2, 3600*24, 3600*18, 3600*10, + 3600*5, 3600*2, 3600, 60*30, 60*20, 60*10, 60*5, 60*2, 60, 30, + 20, 10, 5, 2, 1 + }; + int i; + + *vmin = *vmax = *tmin = *tmax = 0; + + 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]; + } + } + + dv = *vmax - *vmin; + dt = *tmax - *tmin; + + for (ts = tscale; ts < tscale + LEN(tscale); ts++) { + if (dt > *ts * 5) { + *tstep = *ts; + break; + } + } + + for (i = 1; i != 0; i *= 10) { + for (vs = vscale; vs < vscale + LEN(vscale); vs++) { + if (dv > *vs * i * 1) { + *vstep = *vs * i * 10; + i = 0; + break; + } + } + } +} + +/* + * Plot the 'n' values list of the 'v' array with title 'name' and + * 'units' label. + */ +void +ffplot(Vlist *v, int n, char *name, char *units) { Canvas can = { IMAGE_W, IMAGE_H, buffer, 0, 0 }; Color plot_bg = { 0x2222, 0x2222, 0x2222, 0xffff }; @@ -208,6 +260,12 @@ ffdraw(char *name, char *units, Vlist *v, int n, Color grid_fg = { 0x3737, 0x3737, 0x3737, 0xffff }; Color label_fg = { 0x8888, 0x8888, 0x8888, 0xffff }; Color title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff }; + double vmin, vmax, vstep = 30; + time_t tmin, tmax, tstep = 30; + + find_scales(v, n, &vmin, &vmax, &vstep, &tmin, &tmax, &tstep); + + fprintf(stderr, "%f %f %lld %lld\n", vmin, vmax, tmin, tmax); can.x = 0; can.y = 0; diff --git a/main.c b/main.c @@ -1,191 +0,0 @@ -#include <time.h> -#include <stdlib.h> -#include <stdio.h> -#include <fcntl.h> -#include <limits.h> -#include <string.h> -#include <ctype.h> - -#include "arg.h" -#include "ploot.h" -#include "config.h" /* after ploot.h for type definitions */ - -#define LEN(x) (sizeof(x) / sizeof(*x)) - -char *argv0; -char *tflag = ""; -char *uflag = ""; - -static int -color(Color *col, char *name) -{ - ColorList *c; - - for (c = colorlist; c->name != NULL; c++) { - if (strcmp(name, c->name) == 0) { - memcpy(col, &c->col, sizeof(*col)); - return 0; - } - } - - return -1; -} - -void -estriplf(char *line) -{ - char *lf; - - if ((lf = strchr(line, '\n')) == NULL || lf[1] != '\0') - fputs("invalid input\n", stderr), exit(1); - *lf = '\0'; -} - -static void -read_labels(Vlist *v, char **argv, char *buf) -{ - if (fgets(buf, LINE_MAX, stdin) == NULL) { - if (ferror(stdin)) - perror("fread from stdin"); - else - fputs("missing label line\n", stderr); - exit(1); - } - estriplf(buf); - - if (strcmp(strsep(&buf, ","), "epoch") != 0) - fputs("first label must be \"epoch\"\n", stderr), exit(1); - - for (; *argv != NULL; v++, argv++) - if ((v->label = strsep(&buf, ",")) == NULL) - fputs("more arguments than columns\n", stderr), exit(1); - else if (color(&v->col, *argv) == -1) - fprintf(stderr, "unknown color: %s\n", *argv), exit(1); - - if (strsep(&buf, ",") != NULL) - fputs("more columns than arguments\n", stderr), exit(1); -} - -double -eatof(char *str) -{ - char *s; - - for (s = str; *s != '\0'; s++) - if (!isdigit(*s) && *s != '.') - fputs("invalid floatrformat", stderr), exit(0); - return atof(str); -} - -long -eatol(char *str) -{ - char *s; - - for (s = str; *s != '\0'; s++) - if (!isdigit(*s)) - fputs("invalid number format", stderr), exit(0); - return atol(str); -} - -void -add_val(Vlist *v, int *bufsiz, int nval, double field, time_t epoch) -{ - if (nval >= *bufsiz) { - *bufsiz = *bufsiz * 2 + 1; - if ((v->v = realloc(v->v, *bufsiz * sizeof(*v->v))) == NULL) - perror("reallocating values buffer"), exit(1); - if ((v->t = realloc(v->t, *bufsiz * sizeof(*v->t))) == NULL) - perror("reallocating values buffer"), exit(1); - } - v->v[nval] = field; - v->t[nval] = epoch; - v->n = nval + 1; -} - -/* - * Add to each column the value on the current row. - */ -void -add_row(Vlist *v, int *bufsiz, int ncol, int nval, char *line) -{ - time_t epoch; - int n; - char *field; - - if ((field = strsep(&line, ",")) == NULL) - fprintf(stderr, "%d: missing epoch\n", nval), exit(0); - - epoch = eatol(field); - for (n = 0; (field = strsep(&line, ",")) != NULL; n++, v++) { - if (n > ncol) - fprintf(stderr, "%d: too many fields\n", nval), exit(0); - add_val(v, bufsiz, nval, eatof(field), epoch); - } - if (n < ncol) - fprintf(stderr, "%d: too few fields\n", nval), exit(0); -} - -/* - * < ncol > - * epoch,a1,b1,c1 ^ - * epoch,a2,b2,c2 nval - * epoch,a3,b3,c3 v - */ -void -read_values(Vlist *v, int ncol) -{ - int nval, bufsiz; - char line[LINE_MAX]; - - bufsiz = 0; - for (nval = 0; fgets(line, sizeof(line), stdin); nval++) { - estriplf(line); - add_row(v, &bufsiz, ncol, nval, line); - } -} - -static void -usage(void) -{ - ColorList *c; - - fprintf(stderr, "usage: %s [-t title] [-u unit] color...\n" - "available colors as defined by \"config.h\":\n", argv0); - for (c = colorlist; c->name != NULL; c++) - fprintf(stderr, "- %s\n", c->name); - exit(1); -} - -int -main(int argc, char **argv) -{ - Vlist *v; - double vmin, vmax, vstep; - time_t tmin, tmax, tstep; - char labels[LINE_MAX]; - - ARGBEGIN { - case 't': - tflag = EARGF(usage()); - break; - case 'u': - uflag = EARGF(usage()); - break; - } ARGEND; - - if ((v = calloc(argc, sizeof(*v))) == NULL) - perror("calloc value list"), exit(1); - - vmin = -30; vmax = 700; vstep = 120; - tmin = 0; tmax = 2000; tstep = 300; - - read_labels(v, argv, labels); - read_values(v, argc); - - ffdraw(tflag, uflag, v, argc, - vmin, vmax, vstep, - tmin, tmax, tstep); - - return 0; -} diff --git a/ploot.c b/ploot.c @@ -1,304 +1,184 @@ -#include <sys/time.h> - -#include <stdio.h> +#include <time.h> #include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <limits.h> #include <string.h> -#include <unistd.h> -#include <time.h> +#include <ctype.h> #include "arg.h" -#include "config.h" +#include "ploot.h" +#include "config.h" /* after ploot.h for type definitions */ -#define ABS(x) ((x) < 0 ? -(x) : (x)) -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#define MAX(x, y) ((x) > (y) ? (x) : (y)) -#define LEN(buf) (sizeof(buf) / sizeof(*(buf))) +#define LEN(x) (sizeof(x) / sizeof(*x)) char *argv0; +char *tflag = ""; +char *uflag = ""; -/* -** Add 'val' at the current position 'pos' of the 'ring' buffer and set pos to -** the next postion. -*/ -#define RING_ADD(rbuf, len, pos, val) \ -do { \ - rbuf[pos] = val; \ - pos = (pos + 1 < len) ? (pos + 1) : (0); \ -} while (0) - -/* -** Copy the ring buffer 'rbuf' content with current position 'pos' into the -** buffer 'buf'. Both buffer of length 'len'. -*/ -#define RING_COPY(buf, rbuf, len, pos) \ -do { \ - memcpy(buf, rbuf + pos, (len - pos) * sizeof(*rbuf)); \ - memcpy(buf + (len - pos), rbuf, pos * sizeof(*rbuf)); \ -} while (0) - -#define MAX_VAL 80 -#define MARGIN 7 - -int hflag = 20; -char *tflag = NULL; -time_t oflag = 0; - -/* -** Set 'str' to a human-readable form of 'num' with always a width of 7 (+ 1 -** the '\0' terminator). Buffer overflow is ensured not to happen due to the -** max size of a double. -*/ -void -humanize(char *str, double val) +static int +color(Color *col, char *name) { - int exp, precision; - char *label = "\0kMGTE"; + ColorList *c; - for (exp = 0; ABS(val) > 1000; exp++) - val /= 1000; + for (c = colorlist; c->name != NULL; c++) { + if (strcmp(name, c->name) == 0) { + memcpy(col, &c->col, sizeof(*col)); + return 0; + } + } - precision = (ABS(val) < 10) ? (3) : (ABS(val) < 100) ? (2) : (1); - if (exp == 0) - precision++; - snprintf(str, 8, "%+.*f%c", precision, val, label[exp]); - str[7] = '\0'; - if (val >= 0) - str[0] = ' '; + return -1; } -/* -** Returns the maximal double of values between 'beg' and 'end'. -*/ -double -maxdv(double *beg, double *end) +static void +estriplf(char *line) { - double *val, max; + char *lf; - max = *beg; - for (val = beg; val < end; val++) { - if (*val > max) - max = *val; - } - return max; + if ((lf = strchr(line, '\n')) == NULL || lf[1] != '\0') + fputs("invalid input\n", stderr), exit(1); + *lf = '\0'; } -/* -** If not null, print the title 'str' centered on width. -*/ -void -title(char *str, int width) +static void +read_labels(Vlist *v, char **argv, char *buf) { - if (str == NULL) - return; - printf("%*s\n", (int)(width + strlen(str)) / 2 + MARGIN + 3, str); -} - -/* -** Print vertical axis with humanized number from time to time, with occurences -** determined after the position on the vertical axis from the bottom 'pos'. -*/ -void -vaxis(double val, int pos) -{ - char label[10]; - - if (pos % 4 == 0) { - humanize(label, val); - printf("%*s -", MARGIN, label); - } else { - printf("%*c ", MARGIN, ' '); + if (fgets(buf, LINE_MAX, stdin) == NULL) { + if (ferror(stdin)) + perror("fread from stdin"); + else + fputs("missing label line\n", stderr); + exit(1); } -} + estriplf(buf); -/* -** Print horizontal axis for up to 'col' values along with dates if reading time -** series. -*/ -void -haxis(double *beg, double *end, time_t time) -{ - double *tp; - char buf[9], dbeg[11], dend[11]; + if (strcmp(strsep(&buf, ","), "epoch") != 0) + fputs("first label must be \"epoch\"\n", stderr), exit(1); - printf("%*d -+", MARGIN, 0); - for (tp = beg; tp < end; tp++) - putchar((*tp < 0) ? ('x') : ('-')); - putchar('\n'); - if (oflag > 0) { - printf("%*c", MARGIN - 1, ' '); - strftime(dbeg, sizeof(dbeg), "%Y/%m/%d", localtime(&time)); - for (tp = beg; tp < end; tp += 7) { - strftime(buf, sizeof(buf), " %H:%M", localtime(&time)); - fputs(buf, stdout); - time += oflag * 7; - } - strftime(dend, sizeof(dend), "%Y/%m/%d", localtime(&time)); - printf("\n %-*s %s\n", (int)(beg - end) + 4, dbeg, dend); - } + for (; *argv != NULL; v++, argv++) + if ((v->label = strsep(&buf, ",")) == NULL) + fputs("more arguments than columns\n", stderr), exit(1); + else if (color(&v->col, *argv) == -1) + fprintf(stderr, "unknown color: %s\n", *argv), exit(1); + + if (strsep(&buf, ",") != NULL) + fputs("more columns than arguments\n", stderr), exit(1); } -/* -** Print two rows of a plot into a single line using ' ', '.' and ':'. -*/ -void -line(double *beg, double *end, double top, double bot) +static double +eatof(char *str) { - double *val; + char *s; - putchar('|'); - for (val = beg; val != end; val++) - putchar((*val < bot) ? ' ' : (*val < top) ? '.' : ':'); - putchar('\n'); + for (s = str; *s != '\0'; s++) + if (!isdigit(*s) && *s != '-' && *s != '.') + fputs("invalid floatrformat", stderr), exit(0); + return atof(str); } -/* -** Plot values between 'beg' and 'end' in a plot of height 'height'. -** If 'str' is not NULL, it is set as a title above the graph. -*/ -void -plot(double *beg, double *end, int height, char *str, time_t start) +static long +eatol(char *str) { - double top, bot, max; - int h; + char *s; - putchar('\n'); - - max = maxdv(beg, end); - for (h = height + height % 2; h > 0; h -= 2) { - top = h * max / height; - bot = (h - 1) * max / height; - - vaxis(top, h); - line(beg, end, top, bot); - } - - haxis(beg, end, start); - - if (str != NULL) - title(str, end - beg); - - putchar('\n'); + for (s = str; *s != '\0'; s++) + if (!isdigit(*s) && *s != '-') + fputs("invalid number format", stderr), exit(0); + return atol(str); } -/* -** Read a simple format with one double per line and save the last 'MAX_WIDTH' -** values into 'buf' which must be at least MAX_VAL wide and return a pointer -** to the last element or NULL if the input contains error. -*/ -double * -read_simple(double buf[MAX_VAL]) +static void +add_val(Vlist *v, int *bufsiz, int nval, double field, time_t epoch) { - double rbuf[MAX_VAL], val; - size_t p, pos, len; - - len = LEN(rbuf); - for (p = pos = 0; scanf("%lf\n", &val) > 0; p++) - RING_ADD(rbuf, len, pos, val); - len = MIN(len, p); - - RING_COPY(buf, rbuf, len, pos); - - return buf + len; + if (nval >= *bufsiz) { + *bufsiz = *bufsiz * 2 + 1; + if ((v->v = realloc(v->v, *bufsiz * sizeof(*v->v))) == NULL) + perror("reallocating values buffer"), exit(1); + if ((v->t = realloc(v->t, *bufsiz * sizeof(*v->t))) == NULL) + perror("reallocating values buffer"), exit(1); + } + v->v[nval] = field; + v->t[nval] = epoch; + v->n = nval + 1; } /* -** Read a format with blank separated time_t-double pairs, one per line and save -** the last 'MAX_WIDTH' values into 'tbuf' and 'vbuf' which must both be at -** least MAX_VAL wide and return a pointer to the last element of 'vbuf' or -** NULL if the input contains error. -*/ -time_t * -read_time_series(double *vbuf, time_t *tbuf) + * Add to each column the value on the current row. + */ +static void +add_row(Vlist *v, int *bufsiz, int ncol, int nval, char *line) { - size_t p, pos, nul, len; - double vrbuf[MAX_VAL], vval, dval; - time_t trbuf[MAX_VAL], tval; - - len = LEN(vrbuf); - for (p = pos = 0; scanf("%lf %lf\n", &dval, &vval) > 0; p++) { - tval = (time_t)dval; - RING_ADD(trbuf, len, pos, tval); - RING_ADD(vrbuf, len, nul, vval); + time_t epoch; + int n; + char *field; + + if ((field = strsep(&line, ",")) == NULL) + fprintf(stderr, "%d: missing epoch\n", nval), exit(0); + + epoch = eatol(field); + for (n = 0; (field = strsep(&line, ",")) != NULL; n++, v++) { + if (n > ncol) + fprintf(stderr, "%d: too many fields\n", nval), exit(0); + add_val(v, bufsiz, nval, eatof(field), epoch); } - len = MIN(len, p); - - RING_COPY(tbuf, trbuf, len, pos); - RING_COPY(vbuf, vrbuf, len, pos); - - return tbuf + len; + if (n < ncol) + fprintf(stderr, "%d: too few fields\n", nval), exit(0); } /* -** Walk from 'tbeg' and 'tend' and add offset in 'tbuf' every time there is no -** value in 'step' amount of time, by setting a value to -1. -*/ -double * -skip_gaps(time_t *tbeg, time_t *tend, double *vbuf, time_t step) + * < ncol > + * epoch,a1,b1,c1 ^ + * epoch,a2,b2,c2 nval + * epoch,a3,b3,c3 v + */ +static void +read_values(Vlist *v, int ncol) { - size_t p, pos, len; - time_t *tp, toff; - double *vp, vrbuf[MAX_VAL]; - - /* Compute the average alignment of the timestamps values according to - ** the step size. */ - toff = 0; - for (tp = tbeg; tp < tend; tp++) - toff += *tp % step; - toff = *tbeg + toff / (tend - tbeg) + step / 2; + int nval, bufsiz; + char line[LINE_MAX]; - /* Fill 'vbuf' with gap added at each time gap using vrbuf as - ** intermediate ring buffer. */ - len = LEN(vrbuf); - for (p = pos = 0, tp = tbeg, vp = vbuf; tp < tend; p++, vp++, tp++) { - for (; toff < *tp; toff += step) - RING_ADD(vrbuf, len, pos, -1); - RING_ADD(vrbuf, len, pos, *vp); - toff += step; + bufsiz = 0; + for (nval = 0; fgets(line, sizeof(line), stdin); nval++) { + estriplf(line); + add_row(v, &bufsiz, ncol, nval, line); } - len = MAX(MIN(p, len), pos); - - RING_COPY(vbuf, vrbuf, len, pos); - - return vbuf + len; } -void +static void usage(void) { - printf("usage: ploot [-h <height>] [-o <offset>] [-t <title>]\n"); + ColorList *c; + + fprintf(stderr, "usage: %s [-t title] [-u unit] color...\n" + "available colors as defined by \"config.h\":\n", argv0); + for (c = colorlist; c->name != NULL; c++) + fprintf(stderr, "- %s\n", c->name); exit(1); } int main(int argc, char **argv) { - time_t tbuf[MAX_VAL], *tend, start; - double vbuf[MAX_VAL], *vend; + Vlist *v; + char labels[LINE_MAX]; - ARGBEGIN(argc, argv) { - case 'h': - if ((hflag = atoi(EARGF(usage()))) <= 0) - usage(); - break; + ARGBEGIN { case 't': tflag = EARGF(usage()); break; - case 'o': - oflag = atol(EARGF(usage())); + case 'u': + uflag = EARGF(usage()); break; - default: - usage(); - } ARGEND + } ARGEND; - if (oflag == 0) { - vend = read_simple(vbuf); - start = 0; - } else { - tend = read_time_series(vbuf, tbuf); - vend = skip_gaps(tbuf, tend, vbuf, oflag); - start = *tbuf; - } + if ((v = calloc(argc, sizeof(*v))) == NULL) + perror("calloc value list"), exit(1); + + read_labels(v, argv, labels); + read_values(v, argc); + + ffplot(v, argc, tflag, uflag); - plot(vbuf, vend, hflag, tflag, start); return 0; } diff --git a/ploot.h b/ploot.h @@ -45,8 +45,7 @@ void ffdraw_fill (Canvas *, Color *); void ffdraw_print (Canvas *); /* ffplot.c */ -void ffdraw (char *, char *, Vlist *, int, double, double, - double, time_t, time_t, time_t); +void ffplot (Vlist *, int, char *, char *); /* util.c */ char *strsep (char **, const char *);