rename ploot-ff to ploot-farbfeld, to make it obvious what -ff is - ploot - sim… | |
git clone git://bitreich.org/ploot git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
LICENSE | |
--- | |
commit ffb9fc9caeaf3a79f5ab4c7fcbbf4994c1037582 | |
parent 1c1a69494de95f4f5a3a439a16fac98026e2aa09 | |
Author: Josuah Demangeon <[email protected]> | |
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(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -1,4 +1,4 @@ | |
*.o | |
*.core | |
-ploot-ff | |
+ploot-farbfeld | |
ploot-feed | |
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: | |
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 pl… | |
+*ploot-farbfeld* reads collectd-style comma separated values (CSV) and produce… | |
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 produc… | |
a plain text continuous waterfall chart for live monitoring in the terminal. it | |
diff --git a/ploot-csv.7 b/ploot-csv.7 | |
@@ -62,7 +62,7 @@ The remaining columns are values parsed as floating point num… | |
.Sh SEE ALSO | |
. | |
.Xr ploot-feed 1 , | |
-.Xr ploot-ff 1 | |
+.Xr ploot-farbfeld 1 | |
. | |
.Sh HISTORY | |
. | |
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 st… | |
+. | |
+.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 [email protected] | |
+. | |
+. | |
+.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. | |
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->b… | |
+} | |
+ | |
+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]… | |
+ 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… | |
+{ | |
+ 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; | |
+} | |
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 | |
. | |
. | |
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 st… | |
-. | |
-.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 [email protected] | |
-. | |
-. | |
-.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. | |
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->b… | |
-} | |
- | |
-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]… | |
- 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… | |
-{ | |
- 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; | |
-} |