add an experimental ploot-braille tool - 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 1f4e757723ea483ab2c60c8fec2937569441af9e
(DIR) parent ffb9fc9caeaf3a79f5ab4c7fcbbf4994c1037582
(HTM) Author: Josuah Demangeon <me@josuah.net>
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(-)
---
(DIR) diff --git a/.gitignore b/.gitignore
@@ -1,4 +1,5 @@
*.o
*.core
+ploot-braille
ploot-farbfeld
ploot-feed
(DIR) 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 util.c scale.c
all: $(BIN)
+${SRC:.c=.o} ${BIN:=.o}: arg.h def.h Makefile
${BIN}: ${SRC:.c=.o} ${BIN:=.o}
${CC} $(LFLAGS) -o $@ $@.o ${SRC:.c=.o} $(LIB)
(DIR) 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]) \
(DIR) 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;
+}
(DIR) 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 **, size_t *);
+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 *, time_t *, time_t *, double *, double *, double *);
/* util.c */
+size_t strlcpy (char *, const char *, size_t);
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 *, va_list);
+void warn (char const *, ...);
+void err (int, char const *, ...);
(DIR) 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 algorythm */
};
/*
@@ -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, int y1)
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, int y1)
void
drawille_line_hist(struct drawille *drw, int x0, int y0, int x1, int y1, int zero)
{
- 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, int x1, int y1, int zer
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 zero)
static int
drawille_text_glyph(struct drawille *drw, int x, int y, struct font *font, char c)
{
- 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 font *font, char *s)
{
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;
(DIR) 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
(DIR) 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 t2)
+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, int col)
{
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;
}
(DIR) 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[] = {
(DIR) 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("|");
(DIR) 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 time
+ * 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);
+}
(DIR) 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);
+}