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);
       +}