rename ploot-ff to ploot-farbfeld, to make it obvious what -ff is - 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 ffb9fc9caeaf3a79f5ab4c7fcbbf4994c1037582
(DIR) parent 1c1a69494de95f4f5a3a439a16fac98026e2aa09
(HTM) 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;
-}