URI: 
       put code in commont between ploot-ff.c and ploot-braille.c to separate files - ploot - simple plotting tools
  HTML git clone git://bitreich.org/ploot git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ploot
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR README
   DIR LICENSE
       ---
   DIR commit 1c1a69494de95f4f5a3a439a16fac98026e2aa09
   DIR parent 62211b846caa7b980b6a43dea1fdf0a0e2f6de34
  HTML Author: Josuah Demangeon <me@josuah.net>
       Date:   Sat,  8 Feb 2020 18:38:14 +0100
       
       put code in commont between ploot-ff.c and ploot-braille.c to separate files
       
       Diffstat:
         M Makefile                            |       2 +-
         M arg.h                               |       6 +++---
         M def.h                               |      27 +++++++++++++++++++++------
         A ploot-braille.c                     |     200 +++++++++++++++++++++++++++++++
         M ploot-ff.c                          |     295 +++++++------------------------
         D ploot-plot.c                        |     201 ------------------------------
       
       6 files changed, 292 insertions(+), 439 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       @@ -5,7 +5,7 @@ BIN        = ploot-ff ploot-feed
        LIB        = -lm
        MANDIR        = $(PREFIX)/share/man
        
       -SRC        = util.c drawille.c font.c font7.c font8.c font13.c
       +SRC        = csv.c drawille.c font.c font7.c font8.c font13.c util.c scale.c
        
        all: $(BIN)
        
   DIR diff --git a/arg.h b/arg.h
       @@ -5,11 +5,11 @@ extern char const        *arg0;
        
        #define ARG_SWITCH(argc, argv)                                                \
                arg0 = *argv;                                                        \
       -        while (++argv && --argc && **argv == '-' && (*argv)[1])        \
       +        while (++argv && --argc && **argv == '-' && (*argv)[1])                \
                        if ((*argv)[1] == '-' && (*argv)[2] == '\0') {                \
                                ++argv; break;                                        \
       -        } else for (int stop = 0; !stop && *++*argv != '\0' ;)                \
       -                switch (**argv)
       +                } else for (int stop = 0; !stop && *++*argv != '\0' ;)        \
       +                        switch (**argv)
        
        #define ARG ((*++*argv != '\0' || *++argv != NULL)                        \
                        ? ((stop = 1), argc--, *argv)                                \
   DIR diff --git a/def.h b/def.h
       @@ -23,6 +23,23 @@ struct font {
                char                *glyph[128];        /* 0: end, 1: off, 2: on.  */
        };
        
       +/*
       + * List of values and timestamps.  Both have their dedicated buffer
       + * so that the timestamp buffer can be shared across vlist objects.
       + */
       +struct vlist {
       +        time_t                *t;                /* array of timestamps */
       +        double                *v;                /* array of values */
       +        size_t                n;                /* number of values */
       +        char                *label;                /* for the legend */
       +};
       +
       +/* csv.c */
       +
       +void                csv_addrow                (struct vlist *, size_t, char *);
       +void                csv_values                (struct vlist *, size_t);
       +void                csv_labels                (struct vlist *, char **, char *);
       +
        /* drawille.c */
        
        size_t                drawille_fmt_row        (struct drawille *, char *, size_t, int);
       @@ -38,17 +55,15 @@ char *                drawille_text                (struct drawille *, int, int, struct font *, char *);
        size_t                font_width                (struct font *, int);
        size_t                font_strlen                (struct font *, char *);
        
       -/* font13.c */
       +/* font*.c */
        
        struct font font13;
       -
       -/* font7.c */
       -
       +struct font font7;
        struct font font8;
        
       -/* font8.c */
       +/* scale.c */
        
       -struct font font8;
       +void                scale                        (struct vlist *, int, time_t *, time_t *, time_t *, double *, double *, double *);
        
        /* util.c */
        
   DIR diff --git a/ploot-braille.c b/ploot-braille.c
       @@ -0,0 +1,200 @@
       +#include <assert.h>
       +#include <errno.h>
       +#include <stdint.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <time.h>
       +#include <math.h>
       +
       +#include "def.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;
       +}
       +
       +/*
       + * Return the step between two values.
       + */
       +static int
       +plot_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,
       +                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
       +        };
       +
       +        for (time_t *s = scale; *s != 0; s++)
       +                if (*s >= 20 * step)
       +                        return *s;
       +        return 1;
       +}
       +
       +static size_t
       +plot_axis_x(char *buf, size_t sz, time_t step, time_t t2, 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);
       +        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 += interval - t % interval;
       +        for (; t < t2; t += interval) {
       +                strftime(tmp, sizeof tmp, fmt, localtime(&t));
       +                x = ((t - t2) / 2 + col * step) / step;
       +                prec = x - n + strlen(tmp);
       +                assert((n += snprintf(buf+n, sz-n, "%*s", prec, tmp)) <= sz);
       +        }
       +        assert((n += strlcpy(buf+n, "\n", sz-n)) < sz);
       +        return n;
       +}
       +
       +/*
       + * 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)
       +{
       +        size_t                i;
       +        char                tmp[10] = "", *s;
       +        double                val;
       +
       +        val = (max - min) * (rows - r) / rows + min;
       +        humanize(tmp, sizeof tmp, val);
       +        s = (r == 0) ? "┌" :
       +            (r == rows - 1) ? "└" :
       +            "├";
       +        i = snprintf(buf, sz, "%s%-6s ", s, tmp);
       +        return (i > sz) ? (sz) : (i);
       +}
       +
       +static char *
       +plot_render(struct drawille *drw, double min, double max, time_t step, time_t t2)
       +{
       +        char                *buf;
       +        size_t                sz;
       +        size_t                n;
       +
       +        /* 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);
       +        }
       +        plot_axis_x(buf+n, sz-n, step, t2, drw->col);
       +        return buf;
       +err:
       +        errno = ENOBUFS;
       +        free(buf);
       +        return NULL;
       +}
       +
       +/*
       + * 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)
       +{
       +        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;
       +        }
       +
       +        return plot_render(drw, min, max, vl->step, t2);
       +}
       +
       +static char *
       +plot(struct vlist *vl, time_t t2, int row, int col)
       +{
       +        struct drawille        *drw;
       +        size_t                len;
       +        char                *buf;
       +
       +        len = 500;
       +        buf = NULL;
       +        drw = NULL;
       +        col -= 8;
       +
       +        if (timeserie_read(vl) == -1)
       +                goto err;
       +
       +        if ((drw = drawille_new(row, col)) == NULL)
       +                goto err;
       +
       +        buf = plot_hist(vl, t2, drw);
       +err:
       +        if (buf == NULL)
       +                timedb_close(&vl->db);
       +        free(drw);
       +        return buf;
       +}
   DIR diff --git a/ploot-ff.c b/ploot-ff.c
       @@ -18,9 +18,6 @@
        
        #define MARGIN                4
        
       -#define XDENSITX        7                /* nb of values on x axis */
       -#define YDENSITX        7                /* nb of values on y axis */
       -
        #define IMAGE_H                (TITLE_H + PLOT_H + XLABEL_H)
        #define IMAGE_W                (YLABEL_W + PLOT_W + LEGEND_W)
        
       @@ -45,8 +42,8 @@
        #define PLOT_H                (160)
        
        #define LEGEND_X        (IMAGE_W - LEGEND_W)
       -#define LEGEND_Y        (XLABEL_H)
       -#define LEGEND_W        (150)
       +#define LEGEND_Y        (TITLE_H + PLOT_H - (font)->height)
       +#define LEGEND_W        (100)
        #define LEGEND_H        (PLOT_H)
        
        struct color {
       @@ -56,12 +53,9 @@ struct color {
                uint16_t        alpha;
        };
        
       -struct vlist {
       -        struct color        color;                /* color to use to draw the line */
       -        time_t                *t;                /* array of timestamps */
       -        double                *v;                /* array of values */
       -        int                n;                /* number of values */
       -        char                *label;                /* for the legend */
       +struct cname {
       +        char                *name;
       +        struct color        color;
        };
        
        struct canvas {
       @@ -72,17 +66,12 @@ struct canvas {
                struct color        *buf;
        };
        
       -struct clist {
       -        char                *name;
       -        struct color        color;
       -};
       -
        char const                *arg0;
        static char                *tflag        = "";
        static char                *uflag        = "";
        static struct font        *font = &font13;
        
       -struct clist clist[] = {
       +static struct cname cname[] = {
                /* name       red     green   blue    alpha */
                { "red",    { 0xffff, 0x4444, 0x4444, 0xffff } },
                { "orange", { 0xffff, 0x9999, 0x4444, 0xffff } },
       @@ -93,98 +82,6 @@ struct clist clist[] = {
                { NULL, { 0, 0, 0, 0 } }
        };
        
       -static struct color *
       -name_to_color(char *name)
       -{
       -        for (struct clist *c = clist; c->name != NULL; c++)
       -                if (strcmp(name, c->name) == 0)
       -                        return &c->color;
       -        return NULL;
       -}
       -
       -static void
       -scale_minmax(struct vlist *v, int n,
       -        time_t *tmin, time_t *tmax,
       -        double *vmin, double *vmax)
       -{
       -        int                i;
       -
       -        *vmin = *vmax = 0;
       -        *tmin = *tmax = *v->t;
       -
       -        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];
       -                }
       -        }
       -}
       -
       -static void
       -scale_tstep(time_t *step, int density, time_t min, time_t max)
       -{
       -        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) {
       -                        *step = *s;
       -                        break;
       -                }
       -        }
       -}
       -
       -static void
       -scale_vstep(double *step, int density, double min, double max)
       -{
       -        double                 dv, *s, scale[] = { 1, 2, 3, 5 };
       -        int                i;
       -
       -        dv = max - min;
       -
       -        if (dv > 1) {
       -                for (i = 1; i != 0; i *= 10) {
       -                        for (s = scale; s < scale + LEN(scale); s++) {
       -                                if (dv < *s * i * density) {
       -                                        *step = *s * i;
       -                                        return;
       -                                }
       -                        }
       -                }
       -        } else {
       -                for (i = 1; i != 0; i *= 10) {
       -                        for (s = scale + LEN(scale) - 1; s >= scale; s--) {
       -                                if (dv > *s / i * density / 2) {
       -                                        *step = *s / i;
       -                                        return;
       -                                }
       -                        }
       -                }
       -        }
       -}
       -
       -static void
       -scale(struct vlist *v, int n,
       -        time_t *tmin, time_t *tmax, time_t *tstep,
       -        double *vmin, double *vmax, double *vstep)
       -{
       -        scale_minmax(v, n, tmin, tmax, vmin, vmax);
       -        scale_tstep(tstep, XDENSITX, *tmin, *tmax);
       -        scale_vstep(vstep, YDENSITX, *vmin, *vmax);
       -}
       -
        /*
         * Convert (x,y) coordinates to (row,col) for printing into the buffer.
         * The buffer only contain one number, so the coordinate is a single integer:
       @@ -196,18 +93,18 @@ scale(struct vlist *v, int n,
         * - (0,1) is above it.                                      +--x
         */
        static void
       -ff_pixel(struct canvas *can, struct color *col,
       +ff_pixel(struct canvas *can, struct color *color,
                int x, int y)
        {
                x += can->x;
                y += can->y;
                if (x < 0 || x >= can->w || y < 0 || y >= can->h)
                        return;
       -        memcpy(can->buf + can->w * (can->h - 1 - y) + x, col, sizeof(*can->buf));
       +        memcpy(can->buf + can->w * (can->h - 1 - y) + x, color, sizeof(*can->buf));
        }
        
        static void
       -ff_rectangle(struct canvas *can, struct color *col,
       +ff_rectangle(struct canvas *can, struct color *color,
                int y1, int x1,
                int y2, int x2)
        {
       @@ -218,14 +115,14 @@ ff_rectangle(struct canvas *can, struct color *col,
        
                for (y = ymin; y <= ymax; y++)
                        for (x = xmin; x <= xmax; x++)
       -                        ff_pixel(can, col, x, y);
       +                        ff_pixel(can, color, x, y);
        }
        
        /*
         * From Bresenham's line algorithm and dcat's tplot.
         */
        static void
       -ff_line(struct canvas *can, struct color *col,
       +ff_line(struct canvas *can, struct color *color,
                int x0, int y0,
                int x1, int y1)
        {
       @@ -238,7 +135,7 @@ ff_line(struct canvas *can, struct color *col,
                err = (dy > dx ? dy : -dx) / 2;
        
                for (;;) {
       -                ff_pixel(can, col, x0, y0);
       +                ff_pixel(can, color, x0, y0);
        
                        if (y0 == y1 && x0 == x1)
                                break;
       @@ -259,7 +156,7 @@ ff_line(struct canvas *can, struct color *col,
         * Draw a coloured glyph from font f centered on y.
         */
        static int
       -ff_char(struct canvas *can, struct color *col, char c,
       +ff_char(struct canvas *can, struct color *color, char c,
                int x, int y)
        {
                int                yf, xf, wf;
       @@ -271,7 +168,7 @@ ff_char(struct canvas *can, struct color *col, char c,
                for (xf = 0; xf < wf; xf++)
                        for (yf = 0; yf < font->height; yf++)
                                if (font->glyph[(int)c][wf * (font->height - yf) + xf] == 3)
       -                                ff_pixel(can, col, x + xf, y + yf);
       +                                ff_pixel(can, color, x + xf, y + yf);
                return wf + 1;
        }
        
       @@ -279,11 +176,11 @@ ff_char(struct canvas *can, struct color *col, char c,
         * Draw a left aligned string without wrapping it.
         */
        static size_t
       -ff_text_left(struct canvas *can, struct color *col, char *s,
       +ff_text_left(struct canvas *can, struct color *color, char *s,
                int x, int y)
        {
                for (; *s != '\0'; s++)
       -                x += ff_char(can, col, *s, x, y);
       +                x += ff_char(can, color, *s, x, y);
                return x;
        }
        
       @@ -291,22 +188,22 @@ ff_text_left(struct canvas *can, struct color *col, char *s,
         * Draw a center aligned string without wrapping it.
         */
        static size_t
       -ff_text_center(struct canvas *can, struct color *col, char *s,
       +ff_text_center(struct canvas *can, struct color *color, char *s,
                int x, int y)
        {
                x -= font_strlen(font, s) / 2;
       -        return ff_text_left(can, col, s, x, y);
       +        return ff_text_left(can, color, s, x, y);
        }
        
        /*
         * Draw a right aligned string without wrapping it.
         */
        static size_t
       -ff_text_right(struct canvas *can, struct color *col, char *s,
       +ff_text_right(struct canvas *can, struct color *color, char *s,
                int x, int y)
        {
                x -= font_strlen(font, s);
       -        return ff_text_left(can, col, s, x, y);
       +        return ff_text_left(can, color, s, x, y);
        }
        
        static void
       @@ -326,12 +223,16 @@ ff_print(struct canvas *can)
        static int
        ff_t2x(time_t t, time_t tmin, time_t tmax)
        {
       +        if (tmin == tmax)
       +                return PLOT_W;
                return (t - tmin) * PLOT_W / (tmax - tmin);
        }
        
        static int
        ff_v2y(double v, double vmin, double vmax)
        {
       +        if (vmin == vmax)
       +                return PLOT_H;
                return (v - vmin) * PLOT_H / (vmax - vmin);
        }
        
       @@ -394,7 +295,7 @@ ff_title(struct canvas *can,
        }
        
        static void
       -ff_plot(struct canvas *can, struct vlist *v,
       +ff_plot(struct canvas *can, struct vlist *vl, struct color *color,
                double vmin, double vmax,
                time_t tmin, time_t tmax)
        {
       @@ -403,12 +304,12 @@ ff_plot(struct canvas *can, struct vlist *v,
                int                x, y, n, ylast, xlast, first;
        
                first = 1;
       -        for (tp = v->t, vp = v->v, n = v->n; n > 0; n--, vp++, tp++) {
       +        for (tp = vl->t, vp = vl->v, n = vl->n; n > 0; n--, vp++, tp++) {
                        y = ff_v2y(*vp, vmin, vmax);
                        x = ff_t2x(*tp, tmin, tmax);
        
                        if (!first)
       -                        ff_line(can, &v->color, xlast, ylast, x, y);
       +                        ff_line(can, color, xlast, ylast, x, y);
        
                        ylast = y;
                        xlast = x;
       @@ -417,24 +318,24 @@ ff_plot(struct canvas *can, struct vlist *v,
        }
        
        static void
       -ff_values(struct canvas *can, struct vlist *v, int n,
       +ff_values(struct canvas *can, struct vlist *vl, struct color **cl, size_t ncol,
                time_t tmin, time_t tmax,
                double vmin, double vmax)
        {
       -        for (; n > 0; n--, v++)
       -                ff_plot(can, v, vmin, vmax, tmin, tmax);
       +        for (; ncol > 0; ncol--, vl++, cl++)
       +                ff_plot(can, vl, *cl, vmin, vmax, tmin, tmax);
        }
        
        static void
       -ff_legend(struct canvas *can, struct color *label_fg, struct vlist *v, int n)
       +ff_legend(struct canvas *can, struct color *fg, struct vlist *vl, struct color **cl, size_t ncol)
        {
       -        int i, x, y;
       +        size_t                i, x, y;
        
       -        for (i = 0; i < n; i++, v++) {
       -                x = MARGIN;
       -                x = ff_text_left(can, &v->color, "\1", x, y);
       -                x = ff_text_left(can, label_fg, v->label, x, y);
       -                y = LEGEND_H - i * (font->height + MARGIN) - font->height / 2;
       +        for (i = 0; i < ncol; i++, vl++, cl++) {
       +                x = MARGIN * 2;
       +                x = ff_text_left(can, *cl, "\1", x, y) + MARGIN;
       +                x = ff_text_left(can, fg, vl->label, x, y);
       +                y = LEGEND_H - i * (font->height + MARGIN);
                }
        }
        
       @@ -450,7 +351,7 @@ ff_legend(struct canvas *can, struct color *label_fg, struct vlist *v, int n)
         *                x label here        
         */
        static void
       -ff(struct vlist *v, int n, char *name, char *units)
       +ff(struct vlist *vl, struct color **cl, size_t ncol, char *name, char *units)
        {
                struct canvas        can = { IMAGE_W, IMAGE_H, 0, 0, NULL };
                struct color        plot_bg = { 0x2222, 0x2222, 0x2222, 0xffff };
       @@ -461,7 +362,7 @@ ff(struct vlist *v, int n, char *name, char *units)
                double                vmin, vmax, vstep;
                time_t                tmin, tmax, tstep;
        
       -        scale(v, n, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep);
       +        scale(vl, ncol, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep);
        
                assert(can.buf = calloc(IMAGE_H * IMAGE_W, sizeof *can.buf));
        
       @@ -487,108 +388,41 @@ ff(struct vlist *v, int n, char *name, char *units)
        
                can.x = PLOT_X;
                can.y = PLOT_Y;
       -        ff_values(&can, v, n, tmin, tmax, vmin, vmax);
       +        ff_values(&can, vl, cl, ncol, tmin, tmax, vmin, vmax);
        
                can.x = LEGEND_X;
                can.y = LEGEND_Y;
       -        ff_legend(&can, &label_fg, v, n);
       +        ff_legend(&can, &label_fg, vl, cl, ncol);
        
                ff_print(&can);
        }
       - 
       -static void
       -csv_labels(struct vlist *v, char **argv, char *buf)
       -{
       -        struct color        *color;
       -
       -        if (esfgets(buf, LINE_MAX, stdin) == NULL)
       -                err(1, "missing label line");
       - 
       -        if (strcmp(strsep(&buf, ","), "epoch") != 0)
       -                err(1, "first label must be \"epoch\"");
       - 
       -        for (; *argv != NULL; v++, argv++) {
       -                if ((v->label = strsep(&buf, ",")) == NULL)
       -                        err(1, "more arguments than columns");
       -                else if ((color = name_to_color(*argv)) == NULL)
       -                        err(1, "unknown color: %s", *argv);
       -                v->color = *color;
       -        }
       - 
       -        if (strsep(&buf, ",") != NULL)
       -                err(1, "more columns than arguments");
       -}
       -
       -static int
       -csv_addval(struct vlist *v, size_t sz, size_t nval, double field, time_t epoch)
       -{
       -        if (nval >= sz) {
       -                sz = sz * 2 + 1;
       -                if ((v->v = realloc(v->v, sz * sizeof(*v->v))) == NULL)
       -                        err(1, "reallocating values buffer");
       -                if ((v->t = realloc(v->t, sz * sizeof(*v->t))) == NULL)
       -                        err(1, "reallocating values buffer");
       -        }
       -        v->v[nval] = field;
       -        v->t[nval] = epoch;
       -        v->n = nval + 1;
       -
       -        return sz;
       -}
        
       -/*
       - * Add to each column the value on the current row.
       - */
       -static int
       -csv_addrow(struct vlist *v, size_t sz, size_t ncol, size_t nval, char *line)
       +static struct color *
       +name_to_color(char *name)
        {
       -        time_t                epoch;
       -        int                bs;
       -        char                *field, *dot;
       -
       -        if ((field = strsep(&line, ",")) == NULL)
       -                err(1, "%d: missing epoch", nval);
       -
       -        if ((dot = strchr(field, '.')) != NULL)
       -                *dot = '\0';
       -        epoch = eatol(field);
       -        for (; (field = strsep(&line, ",")) != NULL; ncol--, v++) {
       -                if (ncol <= 0)
       -                        err(1, "%d: too many fields", nval);
       -                bs = csv_addval(v, sz, nval, eatof(field), epoch);
       -        }
       -        if (ncol > 0)
       -                err(1, "%d: too few fields", ncol);
       +        struct cname        *cn;
        
       -        return bs;
       +        for (cn = cname; cn->name != NULL; cn++)
       +                if (strcmp(name, cn->name) == 0)
       +                        return &cn->color;
       +        return NULL;
        }
        
       -/*
       - *       < ncol >
       - * epoch,a1,b1,c1  ^
       - * epoch,a2,b2,c2 nval
       - * epoch,a3,b3,c3  v
       - */
        static void
       -csv_values(struct vlist *v, size_t ncol)
       +argv_to_color(struct color **cl, char **argv)
        {
       -        int                nval, sz;
       -        char                line[LINE_MAX];
       -
       -        sz = 0;
       -        for (nval = 0; esfgets(line, sizeof(line), stdin) != NULL; nval++)
       -                sz = csv_addrow(v, sz, ncol, nval, line);
       -        if (nval == 0)
       -                err(1, "no value could be read\n");
       +        for (; *argv != NULL; cl++, argv++)
       +                if ((*cl = name_to_color(*argv)) == NULL)
       +                        err(1, "unknown color name: %s", *argv);
        }
        
        static void
        usage(void)
        {
                fprintf(stderr, "usage: %s [-t title] [-u unit] {", arg0);
       -        fputs(clist->name, stderr);
       -        for (struct clist *c = clist + 1; c->name != NULL; c++)
       -                fprintf(stderr, ",%s", c->name);
       +        fputs(cname->name, stderr);
       +        for (struct cname *cn = cname + 1; cn->name != NULL; cn++)
       +                fprintf(stderr, ",%s", cn->name);
                fputs("}...\n", stderr);
                exit(1);
        }
       @@ -596,7 +430,8 @@ usage(void)
        int
        main(int argc, char **argv)
        {
       -        struct vlist        *v;
       +        struct vlist        *vl;
       +        struct color        **cl;
                char                labels[LINE_MAX];
        
                ARG_SWITCH(argc, argv) {
       @@ -610,15 +445,19 @@ main(int argc, char **argv)
                        usage();
                }
        
       -        fflush(stdout);
       +        if (argc == 0)
       +                usage();
        
       -        if ((v = calloc(argc, sizeof(*v))) == NULL)
       -                err(1, "calloc value list");
       +        assert(vl = calloc(argc, sizeof(*vl)));
       +        assert(cl = calloc(argc, sizeof(*cl)));
        
       -        csv_labels(v, argv, labels);
       -        csv_values(v, argc);
       +        csv_labels(vl, argv, labels);
       +        csv_values(vl, argc);
       +        argv_to_color(cl, argv);
        
       -        ff(v, argc, tflag, uflag);
       +        ff(vl, cl, argc, tflag, uflag);
        
       +        free(vl);
       +        free(cl);
                return 0;
        }
   DIR diff --git a/ploot-plot.c b/ploot-plot.c
       @@ -1,201 +0,0 @@
       -#include <assert.h>
       -#include <errno.h>
       -#include <stdint.h>
       -#include <stdio.h>
       -#include <stdlib.h>
       -#include <string.h>
       -#include <time.h>
       -#include <math.h>
       -
       -#include "def.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;
       -}
       -
       -/*
       - * Return the step between two values.
       - */
       -static int
       -plot_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,
       -                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
       -        };
       -
       -        for (time_t *s = scale; *s != 0; s++)
       -                if (*s >= 20 * step)
       -                        return *s;
       -        return 1;
       -}
       -
       -static size_t
       -plot_axis_x(char *buf, size_t sz, time_t step, time_t t2, 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);
       -        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 += interval - t % interval;
       -        for (; t < t2; t += interval) {
       -                strftime(tmp, sizeof tmp, fmt, localtime(&t));
       -                x = ((t - t2) / 2 + col * step) / step;
       -                prec = x - n + strlen(tmp);
       -                assert((n += snprintf(buf+n, sz-n, "%*s", prec, tmp)) <= sz);
       -        }
       -        assert((n += strlcpy(buf+n, "\n", sz-n)) < sz);
       -        return n;
       -}
       -
       -/*
       - * 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)
       -{
       -        size_t                i;
       -        char                tmp[10] = "", *s;
       -        double                val;
       -
       -        val = (max - min) * (rows - r) / rows + min;
       -        humanize(tmp, sizeof tmp, val);
       -        s = (r == 0) ? "┌" :
       -            (r == rows - 1) ? "└" :
       -            "├";
       -        i = snprintf(buf, sz, "%s%-6s ", s, tmp);
       -        return (i > sz) ? (sz) : (i);
       -}
       -
       -static char *
       -plot_render(struct drawille *drw, double min, double max, time_t step, time_t t2)
       -{
       -        char                *buf;
       -        size_t                sz;
       -        size_t                n;
       -
       -        /* 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);
       -        }
       -        plot_axis_x(buf+n, sz-n, step, t2, drw->col);
       -
       -        return buf;
       -err:
       -        errno = ENOBUFS;
       -        free(buf);
       -        return NULL;
       -}
       -
       -/*
       - * Plot the body as an histogram interpolating the gaps and include
       - * a vertical and horizontal axis.
       - */
       -static char *
       -plot_hist(struct timeserie *ts, time_t t2, struct drawille *drw)
       -{
       -        int                x, y, zero, shift;
       -        double                min, max, val;
       -        time_t                t1, t;
       -
       -        /* Adjust the y scale. */
       -        shift = min = max = 0;
       -        timeserie_stats(ts, &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 + ts->step - t2 % ts->step;
       -        t1 = t2 - ts->step * ts->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(ts, t);
       -                if (!isnan(val)) {
       -                        y = timeserie_ypos(val, min, max, drw->row*4) - shift;
       -                        drawille_dot_hist(drw, x, y, zero);
       -                }
       -                t -= ts->step;
       -        }
       -
       -        return plot_render(drw, min, max, ts->step, t2);
       -}
       -
       -static char *
       -plot(struct timeserie *ts, time_t t2, int row, int col)
       -{
       -        struct drawille        *drw;
       -        size_t                len;
       -        char                *buf;
       -
       -        len = 500;
       -        buf = NULL;
       -        drw = NULL;
       -        col -= 8;
       -
       -        if (timeserie_read(ts) == -1)
       -                goto err;
       -
       -        if ((drw = drawille_new(row, col)) == NULL)
       -                goto err;
       -
       -        buf = plot_hist(ts, t2, drw);
       -err:
       -        if (buf == NULL)
       -                timedb_close(&ts->db);
       -        free(drw);
       -        return buf;
       -}