auto-scale - ploot - simple plotting tools
(HTM) git clone git://bitreich.org/ploot git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ploot
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) README
(DIR) LICENSE
---
(DIR) commit c3911021718dc5e5b2dd6469495e7f3bc4befdd2
(DIR) parent d97badd46c19a6903a589c41b8b87d044f48f8dc
(HTM) Author: Josuah Demangeon <mail@josuah.net>
Date: Wed, 2 May 2018 06:37:05 +0200
auto-scale
Diffstat:
M Makefile | 2 +-
M ffplot.c | 66 +++++++++++++++++++++++++++++--
D main.c | 191 -------------------------------
M ploot.c | 364 +++++++++++--------------------
M ploot.h | 3 +--
5 files changed, 186 insertions(+), 440 deletions(-)
---
(DIR) 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
(DIR) 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;
(DIR) 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;
-}
(DIR) 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;
}
(DIR) 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 *);