URI: 
       rename ploot-ff to ploot-farbfeld, to make it obvious what -ff is - 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 ffb9fc9caeaf3a79f5ab4c7fcbbf4994c1037582
   DIR parent 1c1a69494de95f4f5a3a439a16fac98026e2aa09
  HTML Author: Josuah Demangeon <me@josuah.net>
       Date:   Sat, 15 Feb 2020 14:52:07 +0100
       
       rename ploot-ff to ploot-farbfeld, to make it obvious what -ff is
       
       While very good, not everybody knows the farbfeld file format (as
       opposed to png), and if ploot support multiple output format, it
       will end-up hard to know what extension maps to what.
       
       Diffstat:
         M .gitignore                          |       2 +-
         M Makefile                            |       4 ++--
         M README                              |      10 +++++-----
         M ploot-csv.7                         |       2 +-
         A ploot-farbfeld.1                    |     103 +++++++++++++++++++++++++++++++
         A ploot-farbfeld.c                    |     467 +++++++++++++++++++++++++++++++
         M ploot-feed.1                        |       2 +-
         D ploot-ff.1                          |     103 -------------------------------
         D ploot-ff.c                          |     463 -------------------------------
       
       9 files changed, 580 insertions(+), 576 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       @@ -1,4 +1,4 @@
        *.o
        *.core
       -ploot-ff
       +ploot-farbfeld
        ploot-feed
   DIR diff --git a/Makefile b/Makefile
       @@ -1,7 +1,7 @@
        CFLAGS        = -Wall -Wextra -std=c99 -pedantic -fPIC \
                        -D_POSIX_C_SOURCE=200809L
        LFLAGS        = -static
       -BIN        = ploot-ff ploot-feed
       +BIN        = ploot-farbfeld ploot-feed ploot-braille
        LIB        = -lm
        MANDIR        = $(PREFIX)/share/man
        
       @@ -15,7 +15,7 @@ ${BIN}: ${SRC:.c=.o} ${BIN:=.o}
        install: $(BIN)
                mkdir -p ${PREFIX}/bin $(MANDIR)/man1 $(MANDIR)/man7
                cp $(BIN) ${PREFIX}/bin
       -        cp ploot-ff.1 ploot-feed.1 $(MANDIR)/man1
       +        cp ploot-farbfeld.1 ploot-feed.1 $(MANDIR)/man1
                cp ploot-csv.7 $(MANDIR)/man7
        
        clean:
   DIR diff --git a/README b/README
       @@ -1,11 +1,11 @@
        ploot
       -================================================================================
       +=====
        
        
       -ploot-ff
       ---------------------------------------------------------------------------------
       +ploot-farbfeld
       +--------------
        
       -*ploot-ff* reads collectd-style comma separated values (CSV) and produces a plot
       +*ploot-farbfeld* reads collectd-style comma separated values (CSV) and produces a plot
        in the farbfeld [1] image format (pipe it to ff2png). It is an alternative to
        RRDtool [2].
        
       @@ -18,7 +18,7 @@ name of the curves.
        
        
        ploot-feed
       ---------------------------------------------------------------------------------
       +----------
        
        *ploot-feed* also reads collectd-style comma separated values (CSV) but produces
        a plain text continuous waterfall chart for live monitoring in the terminal. it
   DIR diff --git a/ploot-csv.7 b/ploot-csv.7
       @@ -62,7 +62,7 @@ The remaining columns are values parsed as floating point numbers by
        .Sh SEE ALSO
        .
        .Xr ploot-feed 1 ,
       -.Xr ploot-ff 1
       +.Xr ploot-farbfeld 1
        .
        .Sh HISTORY
        .
   DIR diff --git a/ploot-farbfeld.1 b/ploot-farbfeld.1
       @@ -0,0 +1,103 @@
       +.Dd $Mdocdate: August 08 2018$
       +.Dt PLOOT-FF 1
       +.Os
       +.
       +.
       +.Sh NAME
       +.
       +.Nm ploot-farbfeld
       +.Nd produce a farbfeld image of csv input
       +.
       +.
       +.Sh SYNOPSIS
       +.
       +.Nm ploot-farbfeld
       +.Op Fl t Ar title
       +.Op Fl u Ar unit
       +.Ar colors...
       +.
       +.
       +.Sh DESCRIPTION
       +.
       +The
       +.Nm
       +utility plots an image in the farbfeld format out of csv values coming from stdin.
       +.
       +.Bl -tag -width 6n
       +.
       +.It Fl t
       +Set the title of the plot printed at the top left corner.
       +.
       +.It Fl u
       +Set the unit description printed at the top right corner.
       +.
       +.It Ar colors
       +List of argument that specify the color for each column.
       +If the input csv have 5 columns in addition of the timestamp, there must
       +be 5 maxval arguments.
       +color_ts available are red, orange, yellow, green, cyan and blue.
       +.
       +.El
       +.
       +.Pp
       +The input format is documented in the
       +.Xr ploot-csv 7
       +manual page.
       +.
       +.
       +.Sh EXIT STATUS
       +.Ex -std
       +.
       +.
       +.Sh EXAMPLES
       +.
       +.Bd -literal -offset indent
       +$ cat <<EOF >sample.txt
       +epoch,used_memory,free_memory
       +1533752053,160,401
       +1533752054,180,381
       +1533752055,301,260
       +1533752056,303,258
       +EOF
       +$ ploot-farbfeld -t demo -u MB red yellow <sample.txt
       +.Ed
       +.
       +.
       +.Sh SEE ALSO
       +.
       +.Xr ploot-farbfeld 1 ,
       +.Xr ploot-csv 7
       +.
       +.Pp
       +The
       +.Xr farbfeld 7
       +image format:
       +.Lk https://tools.suckless.org/farbfeld/
       +.
       +.
       +.Sh HISTORY
       +.
       +.Nm
       +earned its author a bitreich.org medal of misspelled program name.
       +.
       +.Pp
       +.Nm
       +was written at
       +.Lk gopher://bitreich.org/1/scm/ploot/ "Bitreich"
       +.
       +.
       +.Sh AUTHORS
       +.
       +.An Josuah Demangeon
       +.Aq Mt mail@josuah.net
       +.
       +.
       +.Sh BUGS
       +.
       +.Nm
       +does not make any math on the input: if the timestamps are not at regular
       +interval, ploot will still print one output line every 4 lines read,
       +regardless of the time interval.
       +.
       +.Pp
       +However, the timestamp printed on the left is always exact.
   DIR diff --git a/ploot-farbfeld.c b/ploot-farbfeld.c
       @@ -0,0 +1,467 @@
       +#include <assert.h>
       +#include <ctype.h>
       +#include <fcntl.h>
       +#include <limits.h>
       +#include <stdint.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <time.h>
       +
       +#include <arpa/inet.h>
       +
       +#include <math.h>
       +
       +#include "arg.h"
       +#include "log.h"
       +#include "def.h"
       +
       +#define MARGIN                4
       +
       +#define IMAGE_H                (TITLE_H + PLOT_H + XLABEL_H)
       +#define IMAGE_W                (YLABEL_W + PLOT_W + LEGEND_W)
       +
       +#define TITLE_X                (YLABEL_W)
       +#define TITLE_Y                (IMAGE_H - TITLE_H)
       +#define TITLE_H                ((font)->height * 2)
       +#define TITLE_W                (PLOT_W)
       +
       +#define YLABEL_X        (0)
       +#define YLABEL_Y        (PLOT_Y)
       +#define YLABEL_H        (PLOT_H)
       +#define YLABEL_W        (40 + MARGIN)
       +
       +#define XLABEL_X        (PLOT_X)
       +#define XLABEL_Y        (0)
       +#define XLABEL_H        ((font)->height * 2)
       +#define XLABEL_W        (PLOT_W)
       +
       +#define PLOT_X                (YLABEL_W)
       +#define PLOT_Y                (XLABEL_H)
       +#define PLOT_W                (700)
       +#define PLOT_H                (160)
       +
       +#define LEGEND_X        (IMAGE_W - LEGEND_W)
       +#define LEGEND_Y        (TITLE_H + PLOT_H - (font)->height)
       +#define LEGEND_W        (100)
       +#define LEGEND_H        (PLOT_H)
       +
       +struct color {
       +        uint16_t        red;
       +        uint16_t        green;
       +        uint16_t        blue;
       +        uint16_t        alpha;
       +};
       +
       +struct cname {
       +        char                *name;
       +        struct color        color;
       +};
       +
       +struct canvas {
       +        int                w;                /* width */
       +        int                h;                /* height */
       +        int                x;                /* x offset */
       +        int                y;                /* y offset */
       +        struct color        *buf;
       +};
       +
       +char const                *arg0;
       +static char                *tflag        = "";
       +static char                *uflag        = "";
       +static struct font        *font = &font13;
       +
       +static struct cname cname[] = {
       +        /* name       red     green   blue    alpha */
       +        { "red",    { 0xffff, 0x4444, 0x4444, 0xffff } },
       +        { "orange", { 0xffff, 0x9999, 0x4444, 0xffff } },
       +        { "yellow", { 0xffff, 0xffff, 0x4444, 0xffff } },
       +        { "green",  { 0x2222, 0xffff, 0x5555, 0xffff } },
       +        { "cyan",   { 0x0000, 0xffff, 0xdddd, 0xffff } },
       +        { "blue",   { 0x2222, 0x9999, 0xffff, 0xffff } },
       +        { NULL, { 0, 0, 0, 0 } }
       +};
       +
       +/*
       + * 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:
       + *        width * y + y.
       + * The coordinates are shifted by offx and offy to permit relative coordinates.
       + *
       + * The convention used:                                      y
       + * - (0,0) is at the lower left corner of the canvas.        |
       + * - (0,1) is above it.                                      +--x
       + */
       +static void
       +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, color, sizeof(*can->buf));
       +}
       +
       +static void
       +ff_rectangle(struct canvas *can, struct color *color,
       +        int y1, int x1,
       +        int y2, int x2)
       +{
       +        int                x, y, ymin, xmin, ymax, xmax;
       +
       +        ymin = MIN(y1, y2); ymax = MAX(y1, y2);
       +        xmin = MIN(x1, x2); xmax = MAX(x1, x2);
       +
       +        for (y = ymin; y <= ymax; y++)
       +                for (x = xmin; x <= xmax; x++)
       +                        ff_pixel(can, color, x, y);
       +}
       +
       +/*
       + * From Bresenham's line algorithm and dcat's tplot.
       + */
       +static void
       +ff_line(struct canvas *can, struct color *color,
       +        int x0, int y0,
       +        int x1, int y1)
       +{
       +        int                dy, dx, sy, sx, err, e;
       +
       +        sx = x0 < x1 ? 1 : -1;
       +        sy = y0 < y1 ? 1 : -1;
       +        dx = abs(x1 - x0);
       +        dy = abs(y1 - y0);
       +        err = (dy > dx ? dy : -dx) / 2;
       +
       +        for (;;) {
       +                ff_pixel(can, color, x0, y0);
       +
       +                if (y0 == y1 && x0 == x1)
       +                        break;
       +
       +                e = err;
       +                if (e > -dy) {
       +                        y0 += sy;
       +                        err -= dx;
       +                }
       +                if (e < dx) {
       +                        x0 += sx;
       +                        err += dy;
       +                }
       +        }
       +}
       +
       +/*
       + * Draw a coloured glyph from font f centered on y.
       + */
       +static int
       +ff_char(struct canvas *can, struct color *color, char c,
       +        int x, int y)
       +{
       +        int                yf, xf, wf;
       +
       +        if (c & 0x80)
       +                c = '\0';
       +        y -= font->height / 2;
       +        wf = font_width(font, 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, color, x + xf, y + yf);
       +        return wf + 1;
       +}
       +
       +/*
       + * Draw a left aligned string without wrapping it.
       + */
       +static size_t
       +ff_text_left(struct canvas *can, struct color *color, char *s,
       +        int x, int y)
       +{
       +        for (; *s != '\0'; s++)
       +                x += ff_char(can, color, *s, x, y);
       +        return x;
       +}
       +
       +/*
       + * Draw a center aligned string without wrapping it.
       + */
       +static size_t
       +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, color, s, x, y);
       +}
       +
       +/*
       + * Draw a right aligned string without wrapping it.
       + */
       +static size_t
       +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, color, s, x, y);
       +}
       +
       +static void
       +ff_print(struct canvas *can)
       +{
       +        uint32_t                w, h;
       +
       +        w = htonl(can->w);
       +        h = htonl(can->h);
       +
       +        fputs("farbfeld", stdout);
       +        fwrite(&w, sizeof(w), 1, stdout);
       +        fwrite(&h, sizeof(h), 1, stdout);
       +        fwrite(can->buf, can->w * can->h, sizeof(*can->buf), stdout);
       +}
       +
       +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);
       +}
       +
       +static void
       +ff_xaxis(struct canvas *can, struct color *label, struct color *grid,
       +        time_t tmin, time_t tmax, time_t tstep)
       +{
       +        time_t                t;
       +        int                x;
       +        char                str[sizeof("MM/DD HH/MM")], *fmt;
       +
       +        if (tstep < 3600 * 12)
       +                fmt = "%H:%M:%S";
       +        else if (tstep < 3600 * 24)
       +                fmt = "%m/%d %H:%M";
       +        else
       +                fmt = "%X/%m/%d";
       +
       +        for (t = tmax - tmax % tstep; t >= tmin; t -= tstep) {
       +                x = ff_t2x(t, tmin, tmax);
       +
       +                ff_line(can, grid,
       +                        x, XLABEL_H,
       +                        x, XLABEL_H + PLOT_H);
       +
       +                strftime(str, sizeof(str), fmt, localtime(&t));
       +                ff_text_center(can, label, str,
       +                        x, XLABEL_H / 2);
       +        }
       +}
       +
       +static void
       +ff_yaxis(struct canvas *can, struct color *label, struct color *grid,
       +        double vmin, double vmax, double vstep)
       +{
       +        double                v;
       +        int                y;
       +        char                str[8 + 1];
       +
       +        for (v = vmax - fmod(vmax, vstep); v >= vmin; v -= vstep) {
       +                y = ff_v2y(v, vmin, vmax);
       +
       +                ff_line(can, grid,
       +                        YLABEL_W, y,
       +                        YLABEL_W + PLOT_W, y);
       +
       +                humanize(str, v);
       +                ff_text_right(can, label, str,
       +                        YLABEL_W - MARGIN, y);
       +        }
       +}
       +
       +static void
       +ff_title(struct canvas *can,
       +        struct color *ct, char *title,
       +        struct color *cu, char *unit)
       +{
       +        ff_text_left(can, ct, title, TITLE_H / 2, 0);
       +        ff_text_right(can, cu, unit, TITLE_H / 2, TITLE_W);
       +}
       +
       +static void
       +ff_plot(struct canvas *can, struct vlist *vl, struct color *color,
       +        double vmin, double vmax,
       +        time_t tmin, time_t tmax)
       +{
       +        time_t                *tp;
       +        double                *vp;
       +        int                x, y, n, ylast, xlast, first;
       +
       +        first = 1;
       +        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, color, xlast, ylast, x, y);
       +
       +                ylast = y;
       +                xlast = x;
       +                first = 0;
       +        }
       +}
       +
       +static void
       +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 (; ncol > 0; ncol--, vl++, cl++)
       +                ff_plot(can, vl, *cl, vmin, vmax, tmin, tmax);
       +}
       +
       +static void
       +ff_legend(struct canvas *can, struct color *fg, struct vlist *vl, struct color **cl, size_t ncol)
       +{
       +        size_t                x, y;
       +
       +        for (; ncol > 0; ncol--, vl++, cl++) {
       +                y = -(ncol - 1) * (font->height + MARGIN);
       +                x = MARGIN * 2;
       +                x = ff_text_left(can, *cl, "-", x, y) + MARGIN;
       +                x = ff_text_left(can, fg, vl->label, x, y);
       +        }
       +}
       +
       +/*
       + * Plot the 'n' values list of the 'v' arrax with title 'name' and
       + * 'units' label.
       + *
       + *               Title       (units)
       + *             x ^                    Legend
       + *         label | - + - + - + - + -   ....
       + *          here | - + - + - + - + -   ....
       + *               +---+---+---+---+-->
       + *                x label here        
       + */
       +static void
       +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 };
       +        struct color        grid_bg = { 0x2929, 0x2929, 0x2929, 0xffff };
       +        struct color        grid_fg = { 0x3737, 0x3737, 0x3737, 0xffff };
       +        struct color        label_fg = { 0x8888, 0x8888, 0x8888, 0xffff };
       +        struct color        title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff };
       +        double                vmin, vmax, vstep;
       +        time_t                tmin, tmax, tstep;
       +
       +        scale(vl, ncol, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep);
       +
       +        assert(can.buf = calloc(IMAGE_H * IMAGE_W, sizeof *can.buf));
       +
       +        can.y = 0;
       +        can.x = 0;
       +        ff_rectangle(&can, &plot_bg, 0, 0, IMAGE_H - 1, IMAGE_W - 1);
       +
       +        can.x = PLOT_X;
       +        can.y = PLOT_Y;
       +        ff_rectangle(&can, &grid_bg, 0, 0, PLOT_H, PLOT_W);
       +
       +        can.x = XLABEL_X;
       +        can.y = XLABEL_Y;
       +        ff_xaxis(&can, &label_fg, &grid_fg, tmin, tmax, tstep);
       +
       +        can.x = YLABEL_X;
       +        can.y = YLABEL_Y;
       +        ff_yaxis(&can, &label_fg, &grid_fg, vmin, vmax, vstep);
       +
       +        can.x = TITLE_X;
       +        can.y = TITLE_Y;
       +        ff_title(&can, &title_fg, name, &label_fg, units);
       +
       +        can.x = PLOT_X;
       +        can.y = PLOT_Y;
       +        ff_values(&can, vl, cl, ncol, tmin, tmax, vmin, vmax);
       +
       +        can.x = LEGEND_X;
       +        can.y = LEGEND_Y;
       +        ff_legend(&can, &label_fg, vl, cl, ncol);
       +
       +        ff_print(&can);
       +}
       +
       +static struct color *
       +name_to_color(char *name)
       +{
       +        struct cname        *cn;
       +
       +        for (cn = cname; cn->name != NULL; cn++)
       +                if (strcmp(name, cn->name) == 0)
       +                        return &cn->color;
       +        return NULL;
       +}
       +
       +static void
       +argv_to_color(struct color **cl, char **argv)
       +{
       +        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(cname->name, stderr);
       +        for (struct cname *cn = cname + 1; cn->name != NULL; cn++)
       +                fprintf(stderr, ",%s", cn->name);
       +        fputs("}...\n", stderr);
       +        exit(1);
       +}
       +
       +int
       +main(int argc, char **argv)
       +{
       +        struct vlist        *vl;
       +        struct color        **cl;
       +        char                labels[LINE_MAX];
       +        size_t                ncol;
       +
       +        ARG_SWITCH(argc, argv) {
       +        case 't':
       +                tflag = ARG;
       +                break;
       +        case 'u':
       +                uflag = ARG;
       +                break;
       +        default:
       +                usage();
       +        }
       +
       +        if (argc == 0)
       +                usage();
       +
       +        assert(cl = calloc(argc, sizeof(*cl)));
       +
       +        csv_labels(stdin, labels, &vl, &ncol);
       +        if (ncol > (size_t)argc)
       +                err(1, "too many columns or not enough arguments");
       +        else if (ncol < (size_t)argc)
       +                err(1, "too many arguments or not enough columns");
       +        csv_values(stdin, vl, ncol);
       +        argv_to_color(cl, argv);
       +
       +        ff(vl, cl, argc, tflag, uflag);
       +
       +        free(vl);
       +        free(cl);
       +        return 0;
       +}
   DIR diff --git a/ploot-feed.1 b/ploot-feed.1
       @@ -60,7 +60,7 @@ $ ploot-feed -w 80 1 1 <sample.txt
        .
        .Sh SEE ALSO
        .
       -.Xr ploot-ff 1 ,
       +.Xr ploot-farbfeld 1 ,
        .Xr ploot-format 7
        .
        .
   DIR diff --git a/ploot-ff.1 b/ploot-ff.1
       @@ -1,103 +0,0 @@
       -.Dd $Mdocdate: August 08 2018$
       -.Dt PLOOT-FF 1
       -.Os
       -.
       -.
       -.Sh NAME
       -.
       -.Nm ploot-ff
       -.Nd produce a farbfeld image of csv input
       -.
       -.
       -.Sh SYNOPSIS
       -.
       -.Nm ploot-ff
       -.Op Fl t Ar title
       -.Op Fl u Ar unit
       -.Ar colors...
       -.
       -.
       -.Sh DESCRIPTION
       -.
       -The
       -.Nm
       -utility plots an image in the farbfeld format out of csv values coming from stdin.
       -.
       -.Bl -tag -width 6n
       -.
       -.It Fl t
       -Set the title of the plot printed at the top left corner.
       -.
       -.It Fl u
       -Set the unit description printed at the top right corner.
       -.
       -.It Ar colors
       -List of argument that specify the color for each column.
       -If the input csv have 5 columns in addition of the timestamp, there must
       -be 5 maxval arguments.
       -color_ts available are red, orange, yellow, green, cyan and blue.
       -.
       -.El
       -.
       -.Pp
       -The input format is documented in the
       -.Xr ploot-csv 7
       -manual page.
       -.
       -.
       -.Sh EXIT STATUS
       -.Ex -std
       -.
       -.
       -.Sh EXAMPLES
       -.
       -.Bd -literal -offset indent
       -$ cat <<EOF >sample.txt
       -epoch,used_memory,free_memory
       -1533752053,160,401
       -1533752054,180,381
       -1533752055,301,260
       -1533752056,303,258
       -EOF
       -$ ploot-ff -t demo -u MB red yellow <sample.txt
       -.Ed
       -.
       -.
       -.Sh SEE ALSO
       -.
       -.Xr ploot-ff 1 ,
       -.Xr ploot-csv 7
       -.
       -.Pp
       -The
       -.Xr farbfeld 7
       -image format:
       -.Lk https://tools.suckless.org/farbfeld/
       -.
       -.
       -.Sh HISTORY
       -.
       -.Nm
       -earned its author a bitreich.org medal of misspelled program name.
       -.
       -.Pp
       -.Nm
       -was written at
       -.Lk gopher://bitreich.org/1/scm/ploot/ "Bitreich"
       -.
       -.
       -.Sh AUTHORS
       -.
       -.An Josuah Demangeon
       -.Aq Mt mail@josuah.net
       -.
       -.
       -.Sh BUGS
       -.
       -.Nm
       -does not make any math on the input: if the timestamps are not at regular
       -interval, ploot will still print one output line every 4 lines read,
       -regardless of the time interval.
       -.
       -.Pp
       -However, the timestamp printed on the left is always exact.
   DIR diff --git a/ploot-ff.c b/ploot-ff.c
       @@ -1,463 +0,0 @@
       -#include <assert.h>
       -#include <ctype.h>
       -#include <fcntl.h>
       -#include <limits.h>
       -#include <stdint.h>
       -#include <stdio.h>
       -#include <stdlib.h>
       -#include <string.h>
       -#include <time.h>
       -
       -#include <arpa/inet.h>
       -
       -#include <math.h>
       -
       -#include "arg.h"
       -#include "log.h"
       -#include "def.h"
       -
       -#define MARGIN                4
       -
       -#define IMAGE_H                (TITLE_H + PLOT_H + XLABEL_H)
       -#define IMAGE_W                (YLABEL_W + PLOT_W + LEGEND_W)
       -
       -#define TITLE_X                (YLABEL_W)
       -#define TITLE_Y                (IMAGE_H - TITLE_H)
       -#define TITLE_H                ((font)->height * 2)
       -#define TITLE_W                (PLOT_W)
       -
       -#define YLABEL_X        (0)
       -#define YLABEL_Y        (PLOT_Y)
       -#define YLABEL_H        (PLOT_H)
       -#define YLABEL_W        (40 + MARGIN)
       -
       -#define XLABEL_X        (PLOT_X)
       -#define XLABEL_Y        (0)
       -#define XLABEL_H        ((font)->height * 2)
       -#define XLABEL_W        (PLOT_W)
       -
       -#define PLOT_X                (YLABEL_W)
       -#define PLOT_Y                (XLABEL_H)
       -#define PLOT_W                (700)
       -#define PLOT_H                (160)
       -
       -#define LEGEND_X        (IMAGE_W - LEGEND_W)
       -#define LEGEND_Y        (TITLE_H + PLOT_H - (font)->height)
       -#define LEGEND_W        (100)
       -#define LEGEND_H        (PLOT_H)
       -
       -struct color {
       -        uint16_t        red;
       -        uint16_t        green;
       -        uint16_t        blue;
       -        uint16_t        alpha;
       -};
       -
       -struct cname {
       -        char                *name;
       -        struct color        color;
       -};
       -
       -struct canvas {
       -        int                w;                /* width */
       -        int                h;                /* height */
       -        int                x;                /* x offset */
       -        int                y;                /* y offset */
       -        struct color        *buf;
       -};
       -
       -char const                *arg0;
       -static char                *tflag        = "";
       -static char                *uflag        = "";
       -static struct font        *font = &font13;
       -
       -static struct cname cname[] = {
       -        /* name       red     green   blue    alpha */
       -        { "red",    { 0xffff, 0x4444, 0x4444, 0xffff } },
       -        { "orange", { 0xffff, 0x9999, 0x4444, 0xffff } },
       -        { "yellow", { 0xffff, 0xffff, 0x4444, 0xffff } },
       -        { "green",  { 0x2222, 0xffff, 0x5555, 0xffff } },
       -        { "cyan",   { 0x0000, 0xffff, 0xdddd, 0xffff } },
       -        { "blue",   { 0x2222, 0x9999, 0xffff, 0xffff } },
       -        { NULL, { 0, 0, 0, 0 } }
       -};
       -
       -/*
       - * 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:
       - *        width * y + y.
       - * The coordinates are shifted by offx and offy to permit relative coordinates.
       - *
       - * The convention used:                                      y
       - * - (0,0) is at the lower left corner of the canvas.        |
       - * - (0,1) is above it.                                      +--x
       - */
       -static void
       -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, color, sizeof(*can->buf));
       -}
       -
       -static void
       -ff_rectangle(struct canvas *can, struct color *color,
       -        int y1, int x1,
       -        int y2, int x2)
       -{
       -        int                x, y, ymin, xmin, ymax, xmax;
       -
       -        ymin = MIN(y1, y2); ymax = MAX(y1, y2);
       -        xmin = MIN(x1, x2); xmax = MAX(x1, x2);
       -
       -        for (y = ymin; y <= ymax; y++)
       -                for (x = xmin; x <= xmax; x++)
       -                        ff_pixel(can, color, x, y);
       -}
       -
       -/*
       - * From Bresenham's line algorithm and dcat's tplot.
       - */
       -static void
       -ff_line(struct canvas *can, struct color *color,
       -        int x0, int y0,
       -        int x1, int y1)
       -{
       -        int                dy, dx, sy, sx, err, e;
       -
       -        sx = x0 < x1 ? 1 : -1;
       -        sy = y0 < y1 ? 1 : -1;
       -        dx = abs(x1 - x0);
       -        dy = abs(y1 - y0);
       -        err = (dy > dx ? dy : -dx) / 2;
       -
       -        for (;;) {
       -                ff_pixel(can, color, x0, y0);
       -
       -                if (y0 == y1 && x0 == x1)
       -                        break;
       -
       -                e = err;
       -                if (e > -dy) {
       -                        y0 += sy;
       -                        err -= dx;
       -                }
       -                if (e < dx) {
       -                        x0 += sx;
       -                        err += dy;
       -                }
       -        }
       -}
       -
       -/*
       - * Draw a coloured glyph from font f centered on y.
       - */
       -static int
       -ff_char(struct canvas *can, struct color *color, char c,
       -        int x, int y)
       -{
       -        int                yf, xf, wf;
       -
       -        if (c & 0x80)
       -                c = '\0';
       -        y -= font->height / 2;
       -        wf = font_width(font, 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, color, x + xf, y + yf);
       -        return wf + 1;
       -}
       -
       -/*
       - * Draw a left aligned string without wrapping it.
       - */
       -static size_t
       -ff_text_left(struct canvas *can, struct color *color, char *s,
       -        int x, int y)
       -{
       -        for (; *s != '\0'; s++)
       -                x += ff_char(can, color, *s, x, y);
       -        return x;
       -}
       -
       -/*
       - * Draw a center aligned string without wrapping it.
       - */
       -static size_t
       -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, color, s, x, y);
       -}
       -
       -/*
       - * Draw a right aligned string without wrapping it.
       - */
       -static size_t
       -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, color, s, x, y);
       -}
       -
       -static void
       -ff_print(struct canvas *can)
       -{
       -        uint32_t                w, h;
       -
       -        w = htonl(can->w);
       -        h = htonl(can->h);
       -
       -        fputs("farbfeld", stdout);
       -        fwrite(&w, sizeof(w), 1, stdout);
       -        fwrite(&h, sizeof(h), 1, stdout);
       -        fwrite(can->buf, can->w * can->h, sizeof(*can->buf), stdout);
       -}
       -
       -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);
       -}
       -
       -static void
       -ff_xaxis(struct canvas *can, struct color *label, struct color *grid,
       -        time_t tmin, time_t tmax, time_t tstep)
       -{
       -        time_t                t;
       -        int                x;
       -        char                str[sizeof("MM/DD HH/MM")], *fmt;
       -
       -        if (tstep < 3600 * 12)
       -                fmt = "%H:%M:%S";
       -        else if (tstep < 3600 * 24)
       -                fmt = "%m/%d %H:%M";
       -        else
       -                fmt = "%X/%m/%d";
       -
       -        for (t = tmax - tmax % tstep; t >= tmin; t -= tstep) {
       -                x = ff_t2x(t, tmin, tmax);
       -
       -                ff_line(can, grid,
       -                        x, XLABEL_H,
       -                        x, XLABEL_H + PLOT_H);
       -
       -                strftime(str, sizeof(str), fmt, localtime(&t));
       -                ff_text_center(can, label, str,
       -                        x, XLABEL_H / 2);
       -        }
       -}
       -
       -static void
       -ff_yaxis(struct canvas *can, struct color *label, struct color *grid,
       -        double vmin, double vmax, double vstep)
       -{
       -        double                v;
       -        int                y;
       -        char                str[8 + 1];
       -
       -        for (v = vmax - fmod(vmax, vstep); v >= vmin; v -= vstep) {
       -                y = ff_v2y(v, vmin, vmax);
       -
       -                ff_line(can, grid,
       -                        YLABEL_W, y,
       -                        YLABEL_W + PLOT_W, y);
       -
       -                humanize(str, v);
       -                ff_text_right(can, label, str,
       -                        YLABEL_W - MARGIN, y);
       -        }
       -}
       -
       -static void
       -ff_title(struct canvas *can,
       -        struct color *ct, char *title,
       -        struct color *cu, char *unit)
       -{
       -        ff_text_left(can, ct, title, TITLE_H / 2, 0);
       -        ff_text_right(can, cu, unit, TITLE_H / 2, TITLE_W);
       -}
       -
       -static void
       -ff_plot(struct canvas *can, struct vlist *vl, struct color *color,
       -        double vmin, double vmax,
       -        time_t tmin, time_t tmax)
       -{
       -        time_t                *tp;
       -        double                *vp;
       -        int                x, y, n, ylast, xlast, first;
       -
       -        first = 1;
       -        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, color, xlast, ylast, x, y);
       -
       -                ylast = y;
       -                xlast = x;
       -                first = 0;
       -        }
       -}
       -
       -static void
       -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 (; ncol > 0; ncol--, vl++, cl++)
       -                ff_plot(can, vl, *cl, vmin, vmax, tmin, tmax);
       -}
       -
       -static void
       -ff_legend(struct canvas *can, struct color *fg, struct vlist *vl, struct color **cl, size_t ncol)
       -{
       -        size_t                i, x, y;
       -
       -        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);
       -        }
       -}
       -
       -/*
       - * Plot the 'n' values list of the 'v' arrax with title 'name' and
       - * 'units' label.
       - *
       - *               Title       (units)
       - *             x ^                    Legend
       - *         label | - + - + - + - + -   ....
       - *          here | - + - + - + - + -   ....
       - *               +---+---+---+---+-->
       - *                x label here        
       - */
       -static void
       -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 };
       -        struct color        grid_bg = { 0x2929, 0x2929, 0x2929, 0xffff };
       -        struct color        grid_fg = { 0x3737, 0x3737, 0x3737, 0xffff };
       -        struct color        label_fg = { 0x8888, 0x8888, 0x8888, 0xffff };
       -        struct color        title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff };
       -        double                vmin, vmax, vstep;
       -        time_t                tmin, tmax, tstep;
       -
       -        scale(vl, ncol, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep);
       -
       -        assert(can.buf = calloc(IMAGE_H * IMAGE_W, sizeof *can.buf));
       -
       -        can.y = 0;
       -        can.x = 0;
       -        ff_rectangle(&can, &plot_bg, 0, 0, IMAGE_H - 1, IMAGE_W - 1);
       -
       -        can.x = PLOT_X;
       -        can.y = PLOT_Y;
       -        ff_rectangle(&can, &grid_bg, 0, 0, PLOT_H, PLOT_W);
       -
       -        can.x = XLABEL_X;
       -        can.y = XLABEL_Y;
       -        ff_xaxis(&can, &label_fg, &grid_fg, tmin, tmax, tstep);
       -
       -        can.x = YLABEL_X;
       -        can.y = YLABEL_Y;
       -        ff_yaxis(&can, &label_fg, &grid_fg, vmin, vmax, vstep);
       -
       -        can.x = TITLE_X;
       -        can.y = TITLE_Y;
       -        ff_title(&can, &title_fg, name, &label_fg, units);
       -
       -        can.x = PLOT_X;
       -        can.y = PLOT_Y;
       -        ff_values(&can, vl, cl, ncol, tmin, tmax, vmin, vmax);
       -
       -        can.x = LEGEND_X;
       -        can.y = LEGEND_Y;
       -        ff_legend(&can, &label_fg, vl, cl, ncol);
       -
       -        ff_print(&can);
       -}
       -
       -static struct color *
       -name_to_color(char *name)
       -{
       -        struct cname        *cn;
       -
       -        for (cn = cname; cn->name != NULL; cn++)
       -                if (strcmp(name, cn->name) == 0)
       -                        return &cn->color;
       -        return NULL;
       -}
       -
       -static void
       -argv_to_color(struct color **cl, char **argv)
       -{
       -        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(cname->name, stderr);
       -        for (struct cname *cn = cname + 1; cn->name != NULL; cn++)
       -                fprintf(stderr, ",%s", cn->name);
       -        fputs("}...\n", stderr);
       -        exit(1);
       -}
       -
       -int
       -main(int argc, char **argv)
       -{
       -        struct vlist        *vl;
       -        struct color        **cl;
       -        char                labels[LINE_MAX];
       -
       -        ARG_SWITCH(argc, argv) {
       -        case 't':
       -                tflag = ARG;
       -                break;
       -        case 'u':
       -                uflag = ARG;
       -                break;
       -        default:
       -                usage();
       -        }
       -
       -        if (argc == 0)
       -                usage();
       -
       -        assert(vl = calloc(argc, sizeof(*vl)));
       -        assert(cl = calloc(argc, sizeof(*cl)));
       -
       -        csv_labels(vl, argv, labels);
       -        csv_values(vl, argc);
       -        argv_to_color(cl, argv);
       -
       -        ff(vl, cl, argc, tflag, uflag);
       -
       -        free(vl);
       -        free(cl);
       -        return 0;
       -}