add an experimental ploot-braille tool - ploot - simple plotting tools | |
git clone git://bitreich.org/ploot git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
LICENSE | |
--- | |
commit 1f4e757723ea483ab2c60c8fec2937569441af9e | |
parent ffb9fc9caeaf3a79f5ab4c7fcbbf4994c1037582 | |
Author: Josuah Demangeon <[email protected]> | |
Date: Sat, 15 Feb 2020 15:23:03 +0100 | |
add an experimental ploot-braille tool | |
Diffstat: | |
M .gitignore | 1 + | |
M Makefile | 4 ++-- | |
M arg.h | 2 -- | |
A csv.c | 95 ++++++++++++++++++++++++++++++ | |
M def.h | 24 ++++++++++++++++++++++-- | |
M drawille.c | 39 +++++++++++++++--------------… | |
D log.h | 45 -----------------------------… | |
M ploot-braille.c | 201 +++++++++++++----------------… | |
M ploot-farbfeld.c | 7 +++---- | |
M ploot-feed.c | 14 ++++++++------ | |
A scale.c | 139 ++++++++++++++++++++++++++++++ | |
M util.c | 53 ++++++++++++++++++++++++++++-… | |
12 files changed, 425 insertions(+), 199 deletions(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -1,4 +1,5 @@ | |
*.o | |
*.core | |
+ploot-braille | |
ploot-farbfeld | |
ploot-feed | |
diff --git a/Makefile b/Makefile | |
@@ -1,5 +1,4 @@ | |
-CFLAGS = -Wall -Wextra -std=c99 -pedantic -fPIC \ | |
- -D_POSIX_C_SOURCE=200809L | |
+CFLAGS = -Wall -Wextra -std=c99 -pedantic -fPIC | |
LFLAGS = -static | |
BIN = ploot-farbfeld ploot-feed ploot-braille | |
LIB = -lm | |
@@ -9,6 +8,7 @@ SRC = csv.c drawille.c font.c font7.c font8.c font13.c u… | |
all: $(BIN) | |
+${SRC:.c=.o} ${BIN:=.o}: arg.h def.h Makefile | |
${BIN}: ${SRC:.c=.o} ${BIN:=.o} | |
${CC} $(LFLAGS) -o $@ [email protected] ${SRC:.c=.o} $(LIB) | |
diff --git a/arg.h b/arg.h | |
@@ -1,8 +1,6 @@ | |
#ifndef ARG_H | |
#define ARG_H | |
-extern char const *arg0; | |
- | |
#define ARG_SWITCH(argc, argv) \ | |
arg0 = *argv; \ | |
while (++argv && --argc && **argv == '-' && (*argv)[1]) … | |
diff --git a/csv.c b/csv.c | |
@@ -0,0 +1,95 @@ | |
+/* | |
+ * Read CSV data onto a set of (struct vlist). | |
+ */ | |
+ | |
+#include <string.h> | |
+#include <time.h> | |
+#include <stdlib.h> | |
+ | |
+#include "def.h" | |
+ | |
+static void | |
+csv_addtime(struct vlist *vl, time_t epoch) | |
+{ | |
+ if ((vl->t = realloc(vl->t, (vl->n + 1) * sizeof(*vl->t))) == NULL) | |
+ err(1, "reallocating values buffer"); | |
+ vl->t[vl->n] = epoch; | |
+} | |
+ | |
+static void | |
+csv_addval(struct vlist *vl, double field) | |
+{ | |
+ if ((vl->v = realloc(vl->v, (vl->n + 1) * sizeof(*vl->v))) == NULL) | |
+ err(1, "reallocating values buffer"); | |
+ vl->v[vl->n] = field; | |
+} | |
+ | |
+/* | |
+ * Add to each column the value on the current row. | |
+ */ | |
+void | |
+csv_addrow(struct vlist *vl, size_t ncol, char *line) | |
+{ | |
+ char *field; | |
+ | |
+ if ((field = strsep(&line, ",")) == NULL) | |
+ err(1, "missing epoch at row %zu", vl->n); | |
+ | |
+ csv_addtime(vl, eatol(field)); | |
+ for (; (field = strsep(&line, ",")) != NULL; ncol--, vl->n++, vl++) { | |
+ if (ncol == 0) | |
+ err(1, "too many fields at line %zu", vl->n); | |
+ csv_addval(vl, eatof(field)); | |
+ } | |
+ if (ncol > 0) | |
+ err(1, "too few fields at line %zu", vl->n); | |
+} | |
+ | |
+/* | |
+ * < *ncol > | |
+ * epoch,label1,label2,label3 | |
+ */ | |
+void | |
+csv_labels(FILE *fp, char *buf, struct vlist **vl, size_t *ncol) | |
+{ | |
+ char *field; | |
+ size_t sz; | |
+ | |
+ if (esfgets(buf, LINE_MAX, fp) == NULL) | |
+ err(1, "missing label line"); | |
+ | |
+ if (strcmp(strsep(&buf, ","), "epoch") != 0) | |
+ err(1, "first label must be \"epoch\""); | |
+ | |
+ *vl = NULL; | |
+ for (*ncol = 0; (field = strsep(&buf, ",")) != NULL; ++*ncol) { | |
+ sz = (*ncol + 1) * sizeof **vl; | |
+ if ((*vl = realloc(*vl, sz)) == NULL) | |
+ err(1, "realloc"); | |
+ (*vl)[*ncol].label = field; | |
+ } | |
+} | |
+ | |
+/* | |
+ * < ncol > | |
+ * epoch,a1,b1,c1 ^ | |
+ * epoch,a2,b2,c2 vl->n | |
+ * epoch,a3,b3,c3 v | |
+ */ | |
+void | |
+csv_values(FILE *fp, struct vlist *vl, size_t ncol) | |
+{ | |
+ char line[LINE_MAX]; | |
+ time_t *tbuf; | |
+ | |
+ while (esfgets(line, sizeof(line), fp) != NULL) | |
+ csv_addrow(vl, ncol, line); | |
+ if (vl->n == 0) | |
+ err(1, "no value could be read"); | |
+ if (vl->n == 1) | |
+ err(1, "only one value could be read"); | |
+ | |
+ /* The same time buffer can be used for all. */ | |
+ for (tbuf = vl->t; ncol > 0; ncol--, vl++) | |
+ vl->t = tbuf; | |
+} | |
diff --git a/def.h b/def.h | |
@@ -1,4 +1,5 @@ | |
#include <limits.h> | |
+#include <stdarg.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
@@ -37,8 +38,8 @@ struct vlist { | |
/* csv.c */ | |
void csv_addrow (struct vlist *, size_t, char *); | |
-void csv_values (struct vlist *, size_t); | |
-void csv_labels (struct vlist *, char **, char *… | |
+void csv_labels (FILE *, char *, struct vlist **… | |
+void csv_values (FILE *, struct vlist *, size_t); | |
/* drawille.c */ | |
@@ -61,12 +62,28 @@ struct font font13; | |
struct font font7; | |
struct font font8; | |
+/* ploot-braille.c */ | |
+ | |
+char const *arg0; | |
+ | |
+/* ploot-farbfeld.c */ | |
+ | |
+char const *arg0; | |
+ | |
+/* ploot-feed.c */ | |
+ | |
+char const *arg0; | |
+ | |
/* scale.c */ | |
+int scale_ypos (double, double, double, int); | |
+int scale_xpos (time_t, time_t, time_t, int); | |
+void scale_vminmax (double *, double *, int); | |
void scale (struct vlist *, int, time_t … | |
/* util.c */ | |
+size_t strlcpy (char *, const char *, si… | |
void put3utf (long); | |
char * strsep (char **, const char *); | |
void estriplf (char *); | |
@@ -74,3 +91,6 @@ double eatof (char *); | |
long eatol (char *); | |
char * esfgets (char *, size_t, FILE *); | |
int humanize (char *, double); | |
+void vlog (char const *, char const *, v… | |
+void warn (char const *, ...); | |
+void err (int, char const *, ...); | |
diff --git a/drawille.c b/drawille.c | |
@@ -1,3 +1,7 @@ | |
+/* | |
+ * Terminal-based plotting using drawille character, aka drawille. | |
+ */ | |
+ | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
@@ -5,14 +9,10 @@ | |
#include "def.h" | |
-/* | |
- * Terminal-based plotting using drawille character, aka drawille. | |
- */ | |
- | |
/* parameters used to draw a line */ | |
struct line { | |
- int x0, y0, x1, y1; /* point of the line */ | |
- int dx, dy, sx, sy, err; /* parameters for the algorythm */ | |
+ int x0, y0, x1, y1; /* point of the line… | |
+ int dx, dy, sx, sy, err; /* parameters for the a… | |
}; | |
/* | |
@@ -36,7 +36,7 @@ drawille_cell_dot(uint8_t *cell, int row, int col) | |
static size_t | |
drawille_cell_utf(uint8_t cell, char *utf) | |
{ | |
- long rune; | |
+ long rune; | |
rune = 10240 + cell; | |
utf[0] = (char)(0xe0 | (0x0f & (rune >> 12))); /* 1110xxxx */ | |
@@ -54,8 +54,8 @@ drawille_get(struct drawille *drw, int row, int col) | |
size_t | |
drawille_fmt_row(struct drawille *drw, char *buf, size_t sz, int row) | |
{ | |
- char txt[] = "xxx"; | |
- size_t n; | |
+ char txt[] = "xxx"; | |
+ size_t n; | |
n = 0; | |
for (int col = 0; col < drw->col; col++) { | |
@@ -82,7 +82,7 @@ drawille_dot(struct drawille *drw, int x, int y) | |
struct drawille * | |
drawille_new(int row, int col) | |
{ | |
- struct drawille *drw; | |
+ struct drawille *drw; | |
if ((drw = calloc(sizeof(struct drawille) + row * col, 1)) == NULL) | |
return NULL; | |
@@ -108,10 +108,10 @@ drawille_line_init(struct line *l, int x0, int y0, int x1… | |
static int | |
drawille_line_next(struct line *l) | |
{ | |
- int e; | |
+ int e; | |
if (l->x0 == l->x1 && l->y0 == l->y1) | |
- return 0; | |
+ return -1; | |
e = l->err; | |
if (e > -l->dx) { | |
@@ -122,13 +122,13 @@ drawille_line_next(struct line *l) | |
l->y0 += l->sy; | |
l->err += l->dx; | |
} | |
- return 1; | |
+ return 0; | |
} | |
void | |
drawille_line(struct drawille *drw, int x0, int y0, int x1, int y1) | |
{ | |
- struct line l; | |
+ struct line l; | |
drawille_line_init(&l, x0, y0, x1, y1); | |
do { | |
@@ -139,8 +139,8 @@ drawille_line(struct drawille *drw, int x0, int y0, int x1,… | |
void | |
drawille_line_hist(struct drawille *drw, int x0, int y0, int x1, int y1, int z… | |
{ | |
- struct line l; | |
- int sign; | |
+ struct line l; | |
+ int sign; | |
drawille_line_init(&l, x0, y0, x1, y1); | |
do { | |
@@ -153,7 +153,7 @@ drawille_line_hist(struct drawille *drw, int x0, int y0, in… | |
void | |
drawille_dot_hist(struct drawille *drw, int x, int y, int zero) | |
{ | |
- int sign; | |
+ int sign; | |
sign = (y > zero) ? (-1) : (+1); | |
for (; y != zero + sign; y += sign) | |
@@ -163,8 +163,8 @@ drawille_dot_hist(struct drawille *drw, int x, int y, int z… | |
static int | |
drawille_text_glyph(struct drawille *drw, int x, int y, struct font *font, cha… | |
{ | |
- int width; | |
- char *glyph; | |
+ int width; | |
+ char *glyph; | |
if ((unsigned)c > 127) | |
glyph = font->glyph[0]; | |
@@ -187,7 +187,6 @@ drawille_text(struct drawille *drw, int x, int y, struct fo… | |
{ | |
if (drw->row*4 < font->height) | |
return NULL; | |
- | |
for (; *s != '\0' && x < drw->col/2; s++, x++) | |
x += drawille_text_glyph(drw, x, y, font, *s); | |
return s; | |
diff --git a/log.h b/log.h | |
@@ -1,45 +0,0 @@ | |
-#ifndef LOG_H | |
-#define LOG_H | |
- | |
-#include <errno.h> | |
-#include <stdarg.h> | |
-#include <stdio.h> | |
-#include <stdlib.h> | |
-#include <string.h> | |
- | |
-char const *arg0; /* Should be set by the library caller. */ | |
- | |
-static inline void | |
-vlog(char const *base, char const *fmt, va_list va) | |
-{ | |
- fprintf(stderr, "%s: ", base); | |
- vfprintf(stderr, fmt, va); | |
- if (errno) | |
- fprintf(stderr, ": %s", strerror(errno)); | |
- fputc('\n', stderr); | |
- fflush(stderr); | |
- errno = 0; /* avoid repeating the error in loop */ | |
-} | |
- | |
-static inline void | |
-warn(char const *fmt, ...) | |
-{ | |
- va_list va; | |
- | |
- va_start(va, fmt); | |
- vlog(arg0, fmt, va); | |
- va_end(va); | |
-} | |
- | |
-static inline void | |
-err(int e, char const *fmt, ...) | |
-{ | |
- va_list va; | |
- | |
- va_start(va, fmt); | |
- vlog(arg0, fmt, va); | |
- va_end(va); | |
- exit(e); | |
-} | |
- | |
-#endif | |
diff --git a/ploot-braille.c b/ploot-braille.c | |
@@ -8,48 +8,15 @@ | |
#include <math.h> | |
#include "def.h" | |
+#include "arg.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; | |
-} | |
+char const *arg0 = NULL; | |
/* | |
* Return the step between two values. | |
*/ | |
static int | |
-plot_time_interval(time_t step) | |
+braille_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, | |
@@ -65,136 +32,142 @@ plot_time_interval(time_t step) | |
} | |
static size_t | |
-plot_axis_x(char *buf, size_t sz, time_t step, time_t t2, int col) | |
+braille_axis_x(FILE *fp, time_t step, time_t tmax, 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); | |
+ interval = braille_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 = tmax - col * 2 * step; | |
t += interval - t % interval; | |
- for (; t < t2; t += interval) { | |
+ for (; t < tmax; t += interval) { | |
strftime(tmp, sizeof tmp, fmt, localtime(&t)); | |
- x = ((t - t2) / 2 + col * step) / step; | |
+ x = ((t - tmax) / 2 + col * step) / step; | |
prec = x - n + strlen(tmp); | |
- assert((n += snprintf(buf+n, sz-n, "%*s", prec, tmp)) <= sz); | |
+ fprintf(fp, "%*s", prec, tmp); | |
} | |
- assert((n += strlcpy(buf+n, "\n", sz-n)) < sz); | |
- return n; | |
+ fputc('\n', fp); | |
+ return 1; | |
} | |
/* | |
* 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) | |
+static void | |
+braille_axis_y(FILE *fp, 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); | |
+ humanize(tmp, val); | |
s = (r == 0) ? "┌" : | |
(r == rows - 1) ? "└" : | |
"├"; | |
- i = snprintf(buf, sz, "%s%-6s ", s, tmp); | |
- return (i > sz) ? (sz) : (i); | |
+ fprintf(fp, "%s%-6s ", s, tmp); | |
} | |
-static char * | |
-plot_render(struct drawille *drw, double min, double max, time_t step, time_t … | |
+static int | |
+braille_render(struct drawille *drw, FILE *fp, time_t tmin, time_t tmax) | |
{ | |
- char *buf; | |
- size_t sz; | |
- size_t n; | |
+ char buf[LINE_MAX]; | |
/* 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); | |
+ drawille_fmt_row(drw, buf, sizeof buf, row); | |
+ braille_axis_y(fp, tmin, tmax, row, drw->row); | |
+ fputc('\n', fp); | |
} | |
- plot_axis_x(buf+n, sz-n, step, t2, drw->col); | |
- return buf; | |
-err: | |
- errno = ENOBUFS; | |
- free(buf); | |
- return NULL; | |
+ return 0; | |
} | |
/* | |
* 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) | |
+static int | |
+braille_hist(struct vlist *vl, FILE *fp, time_t tmin, time_t tmax, int row, in… | |
{ | |
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; | |
- } | |
+ double *v, vmin, vmax; | |
+ time_t *t; | |
+ size_t n; | |
+ struct drawille *drw; | |
- return plot_render(drw, min, max, vl->step, t2); | |
+ if ((drw = drawille_new(row, col)) == NULL) | |
+ err(1, "allocating drawille canvas"); | |
+ | |
+ shift = (drw->row > 1) ? (2) : (0); /* center values on "|-" marks */ | |
+ vmin = vmax = 0; | |
+ zero = scale_ypos(0, vmin, vmax, drw->row*4) - shift; | |
+ v = vl->v; | |
+ t = vl->t; | |
+ n = vl->n; | |
+ for (; n > 0; n--, t++, v++) { | |
+ if (isnan(*v)) /* XXX: better handling? */ | |
+ continue; | |
+ y = scale_ypos(*v, vmin, vmax, drw->row * 4) - shift; | |
+ x = scale_xpos(*t, tmin, tmax, drw->col * 2); | |
+ drawille_dot_hist(drw, x, y, zero); | |
+ } | |
+ if (braille_render(drw, fp, tmin, tmax) == -1) | |
+ err(1, "rendering braille canvas"); | |
+ free(drw); | |
+ return 0; | |
} | |
-static char * | |
-plot(struct vlist *vl, time_t t2, int row, int col) | |
+static int | |
+plot(struct vlist *vl, FILE *fp, size_t ncol, int row, int col) | |
{ | |
- struct drawille *drw; | |
size_t len; | |
- char *buf; | |
+ double vmin, vmax, vstep; | |
+ time_t tmin, tmax, tstep; | |
len = 500; | |
- buf = NULL; | |
- drw = NULL; | |
col -= 8; | |
- if (timeserie_read(vl) == -1) | |
- goto err; | |
+ scale(vl, ncol, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep); | |
- if ((drw = drawille_new(row, col)) == NULL) | |
- goto err; | |
+ if (braille_hist(vl, fp, tmin, tmax, row, col) == -1) | |
+ err(1, "allocating drawille canvas"); | |
+ braille_axis_x(fp, tstep, tmax, col); | |
+ return 0; | |
+} | |
- buf = plot_hist(vl, t2, drw); | |
-err: | |
- if (buf == NULL) | |
- timedb_close(&vl->db); | |
- free(drw); | |
- return buf; | |
+static void | |
+usage(void) | |
+{ | |
+ fprintf(stderr, "usage: %s\n", arg0); | |
+ exit(1); | |
+} | |
+ | |
+int | |
+main(int argc, char **argv) | |
+{ | |
+ struct vlist *vl; | |
+ char labels[LINE_MAX]; | |
+ size_t ncol; | |
+ | |
+ ARG_SWITCH(argc, argv) { | |
+ default: | |
+ usage(); | |
+ } | |
+ | |
+ if (argc > 0) | |
+ usage(); | |
+ | |
+ csv_labels(stdin, labels, &vl, &ncol); | |
+ csv_values(stdin, vl, ncol); | |
+ | |
+ plot(vl, stdout, ncol, 20, 80); | |
+ | |
+ free(vl); | |
+ return 1; | |
} | |
diff --git a/ploot-farbfeld.c b/ploot-farbfeld.c | |
@@ -13,7 +13,6 @@ | |
#include <math.h> | |
#include "arg.h" | |
-#include "log.h" | |
#include "def.h" | |
#define MARGIN 4 | |
@@ -66,9 +65,9 @@ struct canvas { | |
struct color *buf; | |
}; | |
-char const *arg0; | |
-static char *tflag = ""; | |
-static char *uflag = ""; | |
+char const *arg0 = NULL; | |
+static char *tflag = ""; | |
+static char *uflag = ""; | |
static struct font *font = &font13; | |
static struct cname cname[] = { | |
diff --git a/ploot-feed.c b/ploot-feed.c | |
@@ -13,9 +13,9 @@ | |
#define WIDTH_MAX 1024 | |
#define BRAILLE_START 10240 | |
-int wflag = 80; | |
-int width = 0; | |
-char const *arg0 = NULL; | |
+char const *arg0 = NULL; | |
+static int wflag = 80; | |
+static int width = 0; | |
/* | |
* Turn the bit at position (row, col) on in the . | |
@@ -139,8 +139,11 @@ plot(char labels[LINE_MAX], double *max, int ncol) | |
last_epoch = epoch = 0; | |
for (n = 0;; n = (n == 25 ? 0 : n + 1)) { | |
- if (n == 0) | |
- put_time(0, 0, 2), fputs(labels, stdout), puts("│"); | |
+ if (n == 0) { | |
+ put_time(0, 0, 2); | |
+ fputs(labels, stdout); | |
+ puts("│"); | |
+ } | |
epoch = plot_line(out, max, ncol); | |
put_time(epoch, last_epoch, n); | |
@@ -224,7 +227,6 @@ main(int argc, char **argv) | |
int ncol, nmax; | |
char *labv[LINE_MAX / 2], labels[LINE_MAX]; | |
- setvbuf(stdin, NULL, _IOLBF, 0); | |
nmax = parse_args(argc, argv, max); | |
ncol = read_labels(labv); | |
width = (wflag - sizeof("XXxXXxXX _")) / ncol - sizeof("|"); | |
diff --git a/scale.c b/scale.c | |
@@ -0,0 +1,139 @@ | |
+#include "def.h" | |
+#include "err.h" | |
+ | |
+#define XDENSITY 7 /* nb of values on x axis */ | |
+#define YDENSITY 7 /* nb of values on y axis */ | |
+ | |
+/* | |
+ * - <max ^ | |
+ * - | Translate the coordinates between double values | |
+ * - <val szy and height in the plot of <row> rows. | |
+ * - | | |
+ * - <min v | |
+ */ | |
+int | |
+scale_ypos(double val, double min, double max, int szy) | |
+{ | |
+ return szy * (val - min) / (max - min); | |
+} | |
+ | |
+/* | |
+ * <---- szx ----> Translate the coordinates between the… | |
+ * range and position in the plot of <col> cols. | |
+ * t1 t t2 | |
+ * | . . | . . | | |
+ */ | |
+int | |
+scale_xpos(time_t t, time_t t1, time_t t2, int szx) | |
+{ | |
+ return szx * (t - t1) / (t2 - t1); | |
+} | |
+ | |
+static void | |
+scale_minmax(struct vlist *vl, int ncol, | |
+ time_t *tmin, time_t *tmax, | |
+ double *vmin, double *vmax) | |
+{ | |
+ double *v; | |
+ time_t *t; | |
+ size_t n; | |
+ | |
+ *vmin = *vmax = 0; | |
+ *tmin = *tmax = *vl->t; | |
+ | |
+ for (; ncol > 0; ncol--, vl++) { | |
+ for (t = vl->t, v = vl->v, n = vl->n; n > 0; t++, v++, n--) { | |
+ if (*v < *vmin) *vmin = *v; | |
+ if (*v > *vmax) *vmax = *v; | |
+ if (*t < *tmin) *tmin = *t; | |
+ if (*t > *tmax) *tmax = *t; | |
+ } | |
+ } | |
+ | |
+ if (*tmin == *tmax) | |
+ err(1, "invalid time scale: min=%lld max=%lld", *tmin, *tmax); | |
+} | |
+ | |
+static time_t | |
+scale_tstep(time_t min, time_t max, int density) | |
+{ | |
+ 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) | |
+ return *s; | |
+ return 0; | |
+} | |
+ | |
+static double | |
+scale_vstep(double min, double max, int density) | |
+{ | |
+ double dv, d, *s, scale[] = { 1, 2, 3, 5 }; | |
+ | |
+ dv = max - min; | |
+ | |
+ if (dv > 1) | |
+ for (d = 1; d != 0; d *= 10) | |
+ for (s = scale; s < scale + LEN(scale); s++) | |
+ if (dv < *s * d * density) | |
+ return *s * d; | |
+ if (dv < 1) | |
+ for (d = 1; d != 0; d *= 10) | |
+ for (s = scale + LEN(scale) - 1; s >= scale; s--) | |
+ if (dv > *s / d * density / 2) | |
+ return *s / d; | |
+ return 0; | |
+} | |
+ | |
+/* | |
+ * Adjust the vertical scale so that everything fits, with nice | |
+ * scale values. | |
+ */ | |
+void | |
+scale_vminmax(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; | |
+} | |
+ | |
+void | |
+scale(struct vlist *vl, int ncol, | |
+ time_t *tmin, time_t *tmax, time_t *tstep, | |
+ double *vmin, double *vmax, double *vstep) | |
+{ | |
+ scale_minmax(vl, ncol, tmin, tmax, vmin, vmax); | |
+ *tstep = scale_tstep(*tmin, *tmax, XDENSITY); | |
+ *vstep = scale_vstep(*vmin, *vmax, YDENSITY); | |
+} | |
diff --git a/util.c b/util.c | |
@@ -1,12 +1,24 @@ | |
-#include <string.h> | |
+#include <ctype.h> | |
#include <errno.h> | |
-#include <stdio.h> | |
#include <limits.h> | |
+#include <stdarg.h> | |
+#include <stdio.h> | |
#include <stdlib.h> | |
-#include <ctype.h> | |
+#include <string.h> | |
#include "def.h" | |
+size_t | |
+strlcpy(char *buf, const char *str, size_t sz) | |
+{ | |
+ size_t len, cpy; | |
+ | |
+ cpy = ((len = strlen(str)) > sz) ? (sz) : (len); | |
+ memcpy(buf, str, cpy); | |
+ buf[sz - 1] = '\0'; | |
+ return len; | |
+} | |
+ | |
void | |
put3utf(long rune) | |
{ | |
@@ -79,7 +91,7 @@ esfgets(char *buf, size_t n, FILE *file) | |
} | |
/* | |
- * Set 'str' to a human-readable form of 'num' with always a width of 8 (+ 1 | |
+ * Set 'str' to a human-readable form of 'num' with always a width of 8 (+1 for | |
* the '\0' terminator). Buffer overflow is ensured not to happen due to the | |
* max size of a double. Return the exponent. | |
*/ | |
@@ -102,3 +114,36 @@ humanize(char *str, double val) | |
return exp * 3; | |
} | |
+ | |
+void | |
+vlog(char const *base, char const *fmt, va_list va) | |
+{ | |
+ fprintf(stderr, "%s: ", base); | |
+ vfprintf(stderr, fmt, va); | |
+ if (errno) | |
+ fprintf(stderr, ": %s", strerror(errno)); | |
+ fputc('\n', stderr); | |
+ fflush(stderr); | |
+ errno = 0; /* avoid repeating the error in loop */ | |
+} | |
+ | |
+void | |
+warn(char const *fmt, ...) | |
+{ | |
+ va_list va; | |
+ | |
+ va_start(va, fmt); | |
+ vlog(arg0, fmt, va); | |
+ va_end(va); | |
+} | |
+ | |
+void | |
+err(int e, char const *fmt, ...) | |
+{ | |
+ va_list va; | |
+ | |
+ va_start(va, fmt); | |
+ vlog(arg0, fmt, va); | |
+ va_end(va); | |
+ exit(e); | |
+} |